"""Next-stop recommendation scoring — ported from NomadCNA logic, adapted to nomadro destinations.""" from __future__ import annotations from typing import Any def _parse_nomads_count(value: str) -> int: digits = "".join(ch for ch in str(value) if ch.isdigit()) return int(digits) if digits else 0 def score_destination( dest: dict[str, Any], *, budget: int, internet: int, climate: str, tags: list[str], priority: str = "balanced", ) -> tuple[float, list[str]]: """Return (score, match_reasons) for a destination dict.""" cost = int(dest.get("cost") or 0) speed = int(dest.get("speed") or 0) temp = int(dest.get("temperature") or 20) rating = float(dest.get("rating") or 0) nomads = _parse_nomads_count(dest.get("nomads_count") or "0") highlights = dest.get("highlights") or [] tag = str(dest.get("tag") or "") reasons: list[str] = [] score = rating * 8 # Budget fit (monthly cost in CNY-ish units) if cost <= budget: score += 28 reasons.append("预算内") elif cost <= budget * 1.2: score += 14 reasons.append("略超预算") else: score += max(0, 10 - (cost - budget) / 800) # Internet if speed >= internet: score += 22 reasons.append("网络达标") else: score += max(0, 22 - (internet - speed) / 4) reasons.append("需确认住处网络") # Climate preference if climate == "warm": score += 18 if temp >= 24 else max(0, 18 - (24 - temp) * 2) if temp >= 24: reasons.append("气候偏暖") elif climate == "cool": score += 18 if temp <= 18 else max(0, 18 - (temp - 18) * 2) if temp <= 18: reasons.append("气候清爽") else: # mild score += 18 if 18 <= temp <= 26 else max(0, 18 - abs(temp - 22) * 2) if 18 <= temp <= 26: reasons.append("气候温和") # Tag overlap (NomadCNA-style lifestyle tags) dest_tags = set(highlights + [tag]) overlap = [t for t in tags if any(t in dt or dt in t for dt in dest_tags)] if overlap: score += min(15, len(overlap) * 5) reasons.append(f"标签匹配:{overlap[0]}") # Community density if nomads >= 8000: score += 12 reasons.append("社区活跃") elif nomads >= 3000: score += 6 # Priority boost if priority == "cost": score += (14000 - cost) / 120 elif priority == "speed": score += speed / 5 elif priority == "community": score += nomads / 400 if not reasons: reasons.append("综合评分推荐") return round(score, 1), reasons[:3] def recommend_destinations( destinations: list[dict[str, Any]], *, budget: int = 8000, internet: int = 50, climate: str = "mild", tags: list[str] | None = None, priority: str = "balanced", limit: int = 12, ) -> list[dict[str, Any]]: tags = tags or [] scored: list[tuple[float, dict[str, Any], list[str]]] = [] for dest in destinations: s, reasons = score_destination( dest, budget=budget, internet=internet, climate=climate, tags=tags, priority=priority, ) scored.append((s, dest, reasons)) scored.sort(key=lambda x: x[0], reverse=True) items = [] for s, dest, reasons in scored[:limit]: items.append({ **dest, "match_score": s, "match_reasons": reasons, }) return items