nomadweb/backend/app/data/city_details_data.py
eric 9b3f210bd2 Add city detail depth, enriched routes, and meetup auth flows.
Port NomadCNA city_details/content/routes seeding into PocketBase and surface full destination pages with related meetups and discussions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 11:54:10 -05:00

238 lines
8.9 KiB
Python
Raw 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": "🚄",
},
]
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",
},
]