"""City detail depth for destinations — NomadCNA city_details adapted to nomadro slugs.""" from __future__ import annotations from typing import Any from app.data.mock_data import DESTINATIONS def _city_detail(dest: dict[str, Any], index: int) -> dict[str, Any]: slug = dest["slug"] name = dest["name"] cost = int(dest.get("cost") or 5000) speed = int(dest.get("speed") or 50) temp = int(dest.get("temperature") or 22) rating = float(dest.get("rating") or 8.5) nomads_raw = str(dest.get("nomads_count") or "1000") nomads = int("".join(c for c in nomads_raw if c.isdigit()) or "1000") tags = dest.get("highlights") or [] tag_short = [t.replace("🏄 ", "").replace("🧘 ", "").replace("💰 ", "").replace("🌴 ", "")[:12] for t in tags][:4] rent = max(1200, round(cost * 0.42)) food = max(800, round(cost * 0.28)) cowork = max(200, round(cost * 0.12)) transport = max(200, round(cost * 0.08)) leisure = max(300, cost - rent - food - cowork - transport) hue = int(dest.get("hue") or 170) return { "citySlug": slug, "guide": { "summary": f"{name}适合想把远程工作和生活质量同时拉高的人。建议先用 2–4 周短住验证网络、社群和生活节奏,再决定是否长期停留。{dest.get('description', '')[:80]}", "bestFor": tag_short or ["远程办公", "旅居试住"], "workSetup": f"建议选择靠近核心生活区的住处,搭配 1 个固定共享办公点和 2 个备用咖啡馆。当前网速参考值为 {speed} Mbps。", "arrivalChecklist": ["确认 4G/5G 覆盖", "预约第一周住宿", "加入本地社群", "收藏 2 个办公点", "报名一场线下活动"], "linkedContent": ["digital-nomad-guide", "visa-playbook"], }, "pros": { "pros": [ f"月均生活成本约 ¥{cost:,},适合控制预算。", f"社区活跃度参考 {nomads_raw},容易找到同频伙伴。", f"综合评分 {rating}/10,亮点:{'、'.join(tag_short[:3]) or name}。", ], "cons": [ "热门区域旺季住宿价格波动较大。", "长期停留前仍需实测具体住处网络。", "社交圈集中,需要主动参加活动融入。", ], }, "reviews": { "rating": rating, "items": [ {"author": "小林", "role": "前端开发", "text": f"{name}最适合有稳定远程收入的人,白天专注工作,晚上参加小规模聚会。", "score": 5}, {"author": "Marco", "role": "UI 设计师", "text": "如果你需要高强度商务会面,建议先看交通和航班;如果是深度工作,这里很舒服。", "score": 4}, {"author": "阿静", "role": "内容创作者", "text": "生活素材很多,但要避免把旅行感当成生产力,最好保持固定作息。", "score": 4}, ], }, "cost": { "monthlyTotal": cost, "currency": "CNY", "breakdown": [ {"label": "住宿", "amount": rent}, {"label": "餐饮", "amount": food}, {"label": "共享办公", "amount": cowork}, {"label": "交通", "amount": transport}, {"label": "社交/休闲", "amount": leisure}, ], "tip": "预算表与城市卡片成本字段关联,可按个人消费校准。", }, "people": { "nomadsNow": nomads, "personas": [ {"name": "远程全职", "percent": 36}, {"name": "自由职业", "percent": 28}, {"name": "独立开发者", "percent": 18}, {"name": "内容创作者", "percent": 18}, ], "featuredProfiles": ["小林", "Marco", "阿静"], }, "chat": { "channels": [ {"name": f"{name}落地互助群", "members": min(999, nomads), "status": "open"}, {"name": f"{name}共享办公情报", "members": round(nomads * 0.42), "status": "open"}, {"name": "周末活动约局", "members": round(nomads * 0.28), "status": "members"}, ], "latestTopics": ["本周线下咖啡局", "哪里适合视频会议", "短租避坑清单"], }, "photos": { "items": [ f"https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=1200&h={hue}", "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=1200", "https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200", ] }, "weather": { "temperature": temp, "humidity": 55 + (index * 3) % 30, "bestMonths": ["3月", "4月", "10月", "11月"], "note": f"当前气候估算适合中长期试住,体感温度需结合季节和住宿条件判断。", }, "trends": { "growth": [ {"month": "1月", "value": max(50, nomads - 160)}, {"month": "2月", "value": max(70, nomads - 120)}, {"month": "3月", "value": max(90, nomads - 80)}, {"month": "4月", "value": max(120, nomads - 40)}, {"month": "5月", "value": nomads}, ], "insight": f"{name}的社群热度正在上升,新增用户主要来自远程全职和自由职业人群。", }, "demographics": { "age": [ {"label": "22-27", "value": 24}, {"label": "28-34", "value": 46}, {"label": "35-44", "value": 22}, {"label": "45+", "value": 8}, ], "work": [ {"label": "技术/产品", "value": 34}, {"label": "内容/设计", "value": 26}, {"label": "运营/市场", "value": 18}, {"label": "创业/自由职业", "value": 22}, ], }, "relatedContent": ["digital-nomad-guide", "visa-playbook"], } CITY_DETAILS: list[dict[str, Any]] = [ _city_detail(d, i) for i, d in enumerate(DESTINATIONS) ] CITY_DETAILS_BY_SLUG: dict[str, dict[str, Any]] = { d["citySlug"]: d for d in CITY_DETAILS } # Extra nomad routes aligned with destination slugs EXTRA_ROUTES: list[dict[str, Any]] = [ { "slug": "sea-slow-trail", "title": "东南亚慢旅三角", "titleEn": "SEA Slow Trail", "citySlugs": ["chiangmai", "bali"], "durationDays": 90, "budget": 18000, "description": "清迈 → 巴厘岛,低成本 + 高速网络 + 成熟游民社区。", "descriptionEn": "Chiang Mai to Bali for cost and community.", "stops": ["清迈 45 天", "巴厘岛 45 天"], "status": "published", "emoji": "🌴", }, { "slug": "euro-culture-loop", "title": "欧洲文化环线", "titleEn": "Euro Culture Loop", "citySlugs": ["lisbon", "barcelona"], "durationDays": 60, "budget": 35000, "description": "里斯本 → 巴塞罗那,签证友好、文化丰富。", "descriptionEn": "Lisbon to Barcelona for visas and culture.", "stops": ["里斯本 30 天", "巴塞罗那 30 天"], "status": "published", "emoji": "🏰", }, { "slug": "latam-budget-run", "title": "拉美性价比冲刺", "titleEn": "LATAM Budget Run", "citySlugs": ["mexico"], "durationDays": 45, "budget": 12000, "description": "墨西哥城深度试住,贴近北美时区、生活成本可控。", "descriptionEn": "Mexico City deep stay near North America timezones.", "stops": ["墨西哥城 45 天"], "status": "published", "emoji": "🌮", }, { "slug": "asia-metro-sprint", "title": "亚洲都市效率线", "titleEn": "Asia Metro Sprint", "citySlugs": ["tokyo", "chiangmai"], "durationDays": 40, "budget": 28000, "description": "东京高效率冲刺 + 清迈成本回调,适合产品与工程团队。", "descriptionEn": "Tokyo sprint then Chiang Mai cooldown.", "stops": ["东京 14 天", "清迈 26 天"], "status": "published", "emoji": "🚄", }, { "slug": "tax-base-loop", "title": "税务友好试住线", "titleEn": "Tax Base Loop", "citySlugs": ["tbilisi", "dubai"], "durationDays": 75, "budget": 22000, "description": "第比利斯试住 + 迪拜枢纽月,适合评估税务与中转策略。", "descriptionEn": "Tbilisi base trial plus Dubai hub month.", "stops": ["第比利斯 45 天", "迪拜 30 天"], "status": "published", "emoji": "🧾", }, { "slug": "latam-spring-trail", "title": "拉美春城双城", "titleEn": "LATAM Spring Trail", "citySlugs": ["medellin", "mexico"], "durationDays": 60, "budget": 15000, "description": "麦德林 → 墨西哥城,北美时区远程友好。", "descriptionEn": "Medellín to Mexico City for NA timezones.", "stops": ["麦德林 30 天", "墨西哥城 30 天"], "status": "published", "emoji": "🌺", }, { "slug": "create-slow-china", "title": "国内慢创作线", "titleEn": "China Slow Create", "citySlugs": ["dali"], "durationDays": 45, "budget": 9000, "description": "大理深度创作,适合内容创作者与独立开发者。", "descriptionEn": "Dali deep create stay.", "stops": ["大理 45 天"], "status": "published", "emoji": "📷", }, { "slug": "euro-oss-sprint", "title": "欧洲开源冲刺线", "titleEn": "Euro OSS Sprint", "citySlugs": ["berlin", "lisbon"], "durationDays": 50, "budget": 32000, "description": "柏林技术社群 + 里斯本阳光回调。", "descriptionEn": "Berlin tech then Lisbon sun.", "stops": ["柏林 25 天", "里斯本 25 天"], "status": "published", "emoji": "🖥️", }, { "slug": "east-asia-net", "title": "东亚网速双城", "titleEn": "East Asia Net Duo", "citySlugs": ["seoul", "tokyo"], "durationDays": 35, "budget": 30000, "description": "首尔与东京高强度协作月,适合产品迭代。", "descriptionEn": "Seoul and Tokyo sprint months.", "stops": ["首尔 18 天", "东京 17 天"], "status": "published", "emoji": "📶", }, ] CONTENT_ITEMS: list[dict[str, Any]] = [ { "slug": "digital-nomad-guide", "type": "guide", "title": "数字游民落地手册", "titleEn": "Digital Nomad Landing Guide", "subtitle": "签证、网络、住宿、社群一次看懂", "description": "覆盖东南亚与欧洲热门城市的 30 天落地清单。", "coverImage": "", "mediaUrl": "", "targetUrl": "/digital", "ctaLabel": "打开手册", "citySlugs": ["bali", "chiangmai", "lisbon"], "sortOrder": 1, "status": "published", "authorName": "nomadro", }, { "slug": "visa-playbook", "type": "guide", "title": "签证选址速查", "titleEn": "Visa Playbook", "subtitle": "D7 / Nomad Visa / LTR 对照", "description": "按收入门槛与审批周期对比主流签证路径。", "targetUrl": "/#visa", "ctaLabel": "看签证", "citySlugs": ["lisbon", "barcelona", "tokyo"], "sortOrder": 2, "status": "published", "authorName": "nomadro", }, { "slug": "bali-cowork-week", "type": "video", "title": "巴厘岛一周联合办公实录", "subtitle": "Canggu 节奏与咖啡馆踩点", "description": "适合第一次去巴厘岛远程工作的人。", "targetUrl": "/videos", "ctaLabel": "看视频", "citySlugs": ["bali"], "sortOrder": 3, "status": "published", "authorName": "nomadro", }, { "slug": "medellin-trial", "type": "guide", "title": "麦德林试住手册", "titleEn": "Medellín Trial Guide", "subtitle": "安全区、西语与北美时区", "description": "30 天试住清单与预算模板。", "targetUrl": "/destinations/medellin", "ctaLabel": "看城市", "citySlugs": ["medellin"], "sortOrder": 4, "status": "published", "authorName": "Sofia", }, { "slug": "tbilisi-tax-note", "type": "guide", "title": "第比利斯税务笔记", "titleEn": "Tbilisi Tax Notes", "subtitle": "远程签证与开户提醒", "description": "非正式法律意见,帮助你先做试住决策。", "targetUrl": "/destinations/tbilisi", "ctaLabel": "打开笔记", "citySlugs": ["tbilisi"], "sortOrder": 5, "status": "published", "authorName": "Devon", }, { "slug": "first-month-video", "type": "video", "title": "旅居第一月稳定输出", "subtitle": "SIM、短租、社群五步法", "description": "新手最容易踩坑的第一周怎么过。", "targetUrl": "/videos/first-month", "ctaLabel": "看视频", "citySlugs": ["chiangmai", "lisbon", "bali"], "sortOrder": 6, "status": "published", "authorName": "nomadro", }, ]