nomadweb/backend/app/data/city_details_data.py
eric bdf0516197 Fix dating match gate and expand seed data for usable demos.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 06:18:25 -05:00

344 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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",
},
]