景点/学校推荐 + 多场景内部导航平台 MVP。
当前仓库处于 Stage 44 Frontend Product Polish 阶段:已建立 FastAPI / Vue / AMap / Docker Compose 骨架,加入 SQLAlchemy 核心表模型、确定性 seed/reset 数据,并把校园地图浏览、北邮沙河校区内部路线规划、室内导航、附近设施、景点/学校目的地搜索、目的地推荐、OSM/高德数据导入、游记社区、美食推荐、AIGC Agent 和后台数据看板接入数据库数据。近期阶段补齐了高德坐标漂移修正、用户兴趣编辑、高德 Web Service 真实 POI 导入、设施数据清洗、地点选择路线输入、游记媒体/索引检索/兴趣推荐、用户注册登录/收藏评分/行为日志闭环、按目的地范围过滤的美食推荐、后台内容管理、真实优先地图图层、北邮沙河参考校园拓扑导入、双 POI 数据集、游记管理/交流的讲义要求对齐、管理员/普通用户角色登录与后台权限保护、AIGC 可解释轻量 Agent 工作流、北京颐和园内部导航场景、讲义要求下的美食 Top-K 推荐/模糊查询/路线距离排序、颐和园周边真实高德餐饮 POI 导入、中国科学技术馆主展厅 B1-5F 室内导航示意图、应用级登录入口/服务总览页信息架构调整、景区游记 seed、游记浏览查询页与发布/AIGC 创作页拆分、游记社区卡片式列表排版、美食推荐两段式布局和本地菜品图片封面展示、北邮/颐和园内部导航数据清洗、课程数据规模要求检查脚本、答辩演示页、首页数据驾驶舱、ECharts 关系图、algorithm_trace 前端可视化、可配置 LLM 用户画像提取,以及参考 TripStar 后形成的产品级前端外壳、工作台布局和核心页面视觉打磨。
Scope clarification:
- 推荐模块面向不同景点和学校,按热度、评分、个人兴趣和行为反馈输出 Top 10 目的地。
- 导航模块按
scene_key隔离内部地图,默认面向北京邮电大学沙河校区,新增场景面向北京颐和园。 - 校园建筑和设施用于地图、搜索、场所查询和路线输入,默认不作为旅游推荐候选。
- Frontend: Vue 3 + Vite + TypeScript + Element Plus
- Map UI: 高德地图 JS API +
@amap/amap-jsapi-loader - Map data/routing topology: OpenStreetMap + OSMnx / Overpass
- POI enrichment: 高德 Web Service Place Around API
- Backend: FastAPI
- Database: PostgreSQL + PostGIS
- Cache: Redis
- Deploy: Docker Compose + Nginx
backend/ FastAPI backend
frontend/ Vue frontend
docs/ planning, matrix, acceptance docs
infra/nginx/ reverse proxy config
media/ local uploaded files
scripts/ bash check/seed/reset scripts
tests/fixtures/ deterministic small test fixtures
data/reference/ committed reference source data for campus navigation imports
data/external/ downloaded OSM/AMap source payloads before cleaning/import
cp .env.example .env.localDo not commit .env.local.
Set the AMap Web JS API key in .env.local:
VITE_AMAP_KEY=your_amap_web_js_api_keySet the AMap Web Service key only when importing real POIs:
AMAP_WEB_API_KEY=your_amap_web_service_api_keySet an OpenAI-compatible LLM provider only when you want real AI user-profile extraction:
LLM_API_KEY=your_openai_compatible_api_key
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4o-mini
LLM_TIMEOUT_SECONDS=20If LLM_API_KEY is missing or the provider fails, POST /api/v1/users/{id}/profile/llm-extract automatically uses deterministic local behavior/tag aggregation and marks fallback_used=true in algorithm_trace.
The frontend VITE_AMAP_KEY is used only for browser rendering. Backend map layers now prefer real imported data: OSMnx/OpenStreetMap for roads and building polygons, AMap Web Service for dense POIs, and OSM amenities as an additional POI source. The deterministic seed graph remains hidden by default and is retained for offline tests and local Dijkstra fallback.
Manually supplied campus map source files should be placed under:
data/reference/bupt-shahe/raw_wgs84/
data/reference/bupt-shahe/topology/
Use raw_wgs84/ for original WGS84 JSON/GeoJSON layers and topology/ for the WGS84 road graph. Do not put these files in backend/cache/, frontend/public/, or the repository root. Future import scripts should validate and import them into the database before APIs use them.
Default campus:
北京邮电大学沙河校区
Center: [116.28333, 40.15608]
Optional backend conda workflow:
conda activate travel-agent
pip install -r backend/requirements.txt
BACKEND_PYTHON_CMD="conda run -n travel-agent python" \
BACKEND_PYTEST_CMD="conda run -n travel-agent pytest" \
bash scripts/check_backend.shbash scripts/check_backend.sh
bash scripts/check_frontend.sh
bash scripts/check_data_requirements.sh
bash scripts/check_all.sh
bash scripts/check_map_frontend_optional.shcheck_map_frontend_optional.sh is intentionally guarded: it skips when VITE_AMAP_KEY, a running backend, or local Playwright is unavailable. Use it only when browser screenshot proof of the AMap page is needed.
Backend:
PYTHONPATH=backend uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadFrontend:
cd frontend
npm install
npm run dev -- --host 0.0.0.0When Docker infrastructure is introduced:
docker compose up --buildbash scripts/seed_all.sh
bash scripts/reset_dev_db.sh
bash scripts/restore_campus_map.sh
bash scripts/restore_summer_palace_map.sh
bash scripts/restore_summer_palace_foods.sh
bash scripts/smoke_features.sh
bash scripts/clean_demo_map_layers.shThese scripts default to DEV_DATABASE_URL=sqlite:///./smart_tour_dev.db and currently seed 11 users, including user01 as a normal user and admin01 as an admin, 207 real China attraction/university destinations, 180 map nodes, 641 map edges, 60 buildings, 120 facilities, 10 facility categories, 76 indoor nodes, 94 indoor edges, 12 restaurants, 72 foods, 20 diaries, and sample user feedback rows.
bash scripts/reset_dev_db.sh resets the SQLite demo database and then calls bash scripts/restore_campus_map.sh, bash scripts/restore_summer_palace_map.sh, and bash scripts/restore_summer_palace_foods.sh when saved payloads exist, so BUPT Shahe, Summer Palace navigation, and Summer Palace nearby food recommendations survive reset.
bash scripts/seed_all.sh is incremental once a dev DB already exists: it creates missing tables/columns, upgrades old 北邮沙河导览点 destination rows to real attraction/university rows, assigns existing restaurants to nearby destinations, and backfills sample favorites/ratings/behavior logs without deleting real imported AMap facilities.
The deterministic seed is an offline demo fallback, not a claim of real campus POI density. To enrich facilities from real AMap POI data after seeding:
PYTHONPATH=backend python backend/scripts/import_amap_pois.py --radius 1800 --max-pages 3 --request-interval 0.5Use --reset-facilities when you want AMap POIs to replace seeded facility points while keeping the existing road graph:
PYTHONPATH=backend python backend/scripts/import_amap_pois.py --radius 3000 --max-pages 3 --request-interval 0.8 --reset-facilitiesTwo POI datasets are supported:
PYTHONPATH=backend python backend/scripts/import_amap_pois.py --dataset nearby_facilities --radius 3000 --max-pages 3 --request-interval 0.8 --reset-facilities
PYTHONPATH=backend python backend/scripts/import_amap_pois.py --dataset campus_navigation --radius 1500 --max-pages 3 --request-interval 0.8 --campus-only --reset-dataset
PYTHONPATH=backend python backend/scripts/import_amap_pois.py --dataset campus_navigation --radius 1500 --campus-only --reset-dataset --load-raw data/external/bupt-shahe/amap_gcj02/campus_navigation_raw.jsonDestination-nearby restaurant POIs are imported separately into restaurants and foods. The current saved Summer Palace food payload imports 202 real nearby restaurants:
PYTHONPATH=backend python backend/scripts/import_amap_foods.py --destination-id 103 --radius 3000 --max-pages 2 --request-interval 0.5 --reset-destination --save-raw data/external/summer-palace/amap_gcj02/food_pois_raw.json
bash scripts/restore_summer_palace_foods.shTo capture external source payloads without changing the database:
PYTHONPATH=backend python backend/scripts/import_osm_campus.py --source osmnx --dist 900 --download-only --save-payload data/external/bupt-shahe/osm/osmnx_campus_payload.json
PYTHONPATH=backend python backend/scripts/import_amap_pois.py --dataset campus_navigation --campus-only --download-only --save-raw data/external/bupt-shahe/amap_gcj02/campus_navigation_raw.json
conda run -n travel-agent python backend/scripts/import_osm_campus.py --source osmnx --scene-key summer_palace --place-name "Summer Palace, Beijing, China" --center-lng 116.2755 --center-lat 39.9996 --dist 1800 --download-only --save-payload data/external/summer-palace/osm/osmnx_summer_palace_payload.json
PYTHONPATH=backend python backend/scripts/import_amap_foods.py --destination-id 103 --radius 3000 --max-pages 2 --download-only --save-raw data/external/summer-palace/amap_gcj02/food_pois_raw.jsonTo remove old offline square building/facility layers:
bash scripts/clean_demo_map_layers.shTo merge real OSM road graph, OSM building polygons, and OSM amenities while preserving AMap POIs:
python backend/scripts/import_osm_campus.py --source osmnx --graph-only --dist 1800
python backend/scripts/import_osm_campus.py --source osmnx --features-only --dist 1800To import the manually supplied BUPT Shahe WGS84 campus topology:
bash scripts/restore_campus_map.sh
bash scripts/restore_summer_palace_map.shThis restores the reference campus graph, offline BUPT OSM building/POI layers, and the saved real Summer Palace OSM graph/features. The deterministic seed graph stays hidden as fallback for BUPT, and both internal-navigation scenes remain visible after a database reset.
The public map API hides seed/fallback layers by default. Use include_demo=true only for fallback inspection:
curl 'http://127.0.0.1:8000/api/v1/map/geojson'
curl 'http://127.0.0.1:8000/api/v1/map/geojson?include_demo=true'The local backend reads API data from API_DATABASE_URL. For the SQLite demo path, run the backend from the repository root so the SQLite relative path matches scripts/reset_dev_db.sh:
bash scripts/reset_dev_db.sh
bash scripts/smoke_features.sh
PYTHONPATH=backend uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadFor a cleaned real-facility demo, replace the fallback facility seed after reset:
bash scripts/reset_dev_db.sh
python backend/scripts/import_amap_pois.py --radius 3000 --max-pages 3 --request-interval 0.8 --reset-facilities
bash scripts/smoke_features.sh
PYTHONPATH=backend uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadThen check:
curl http://127.0.0.1:8000/api/v1/map/stats
curl -X POST http://127.0.0.1:8000/api/v1/routes/plan \
-H 'Content-Type: application/json' \
-d '{"start_lng":116.28333,"start_lat":40.15608,"end_lng":116.2862,"end_lat":40.1582,"route_source":"local_graph"}'
curl -X POST http://127.0.0.1:8000/api/v1/routes/plan \
-H 'Content-Type: application/json' \
-d '{"start_place_id":"facility-1","end_place_id":"facility-2","strategy":"shortest_distance","mode":"walk","route_source":"local_graph"}'
curl -X POST http://127.0.0.1:8000/api/v1/routes/multi-point \
-H 'Content-Type: application/json' \
-d '{"start_lng":116.28333,"start_lat":40.15608,"points":[{"name":"教学楼","lng":116.2842,"lat":40.1567},{"name":"图书馆","lng":116.2862,"lat":40.1582}],"return_to_start":true}'
curl 'http://127.0.0.1:8000/api/v1/facilities/nearby?current_lng=116.28333¤t_lat=40.15608&category=water&radius=5000&limit=3'
curl 'http://127.0.0.1:8000/api/v1/destinations?category=school&q=大学&limit=5'
curl 'http://127.0.0.1:8000/api/v1/search/places?keyword=厕所&scope=campus&limit=5'
curl 'http://127.0.0.1:8000/api/v1/search/places?keyword=大学&scope=destinations&limit=5'
curl 'http://127.0.0.1:8000/api/v1/recommendations?user_id=1&strategy=composite&limit=10'
curl -X POST http://127.0.0.1:8000/api/v1/users/login \
-H 'Content-Type: application/json' \
-d '{"username_or_email":"user01","password":"demo123456"}'
ADMIN_TOKEN=$(curl -s -X POST http://127.0.0.1:8000/api/v1/users/login \
-H 'Content-Type: application/json' \
-d '{"username_or_email":"admin01","password":"admin123456"}' \
| python -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')
curl -X POST http://127.0.0.1:8000/api/v1/users/1/favorites \
-H 'Content-Type: application/json' \
-d '{"target_type":"destination","target_id":120,"note":"demo favorite"}'
curl -X POST http://127.0.0.1:8000/api/v1/users/1/ratings \
-H 'Content-Type: application/json' \
-d '{"target_type":"destination","target_id":120,"rating":5}'
curl -X POST http://127.0.0.1:8000/api/v1/users/1/behavior \
-H 'Content-Type: application/json' \
-d '{"target_type":"destination","target_id":120,"action":"view","metadata_text":"demo browse"}'
curl 'http://127.0.0.1:8000/api/v1/recommendations?user_id=1&strategy=behavior&limit=10'
curl -X POST http://127.0.0.1:8000/api/v1/users/1/profile/llm-extract \
-H 'Content-Type: application/json' \
-d '{}'
curl 'http://127.0.0.1:8000/api/v1/users/1/profile/analysis'
curl -X POST http://127.0.0.1:8000/api/v1/admin/map/import \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"source":"fixture","reset_existing":true}'
curl -X POST http://127.0.0.1:8000/api/v1/admin/map/import \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"source":"amap_poi","dist":1800,"max_pages":3,"request_interval":0.5,"reset_existing":false}'
curl -X POST http://127.0.0.1:8000/api/v1/admin/map/import \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"source":"reference_campus","reset_existing":true}'
curl 'http://127.0.0.1:8000/api/v1/diaries?limit=5'
curl 'http://127.0.0.1:8000/api/v1/foods/recommend?destination_id=1&limit=5'
curl -X POST http://127.0.0.1:8000/api/v1/aigc/diary-draft \
-H 'Content-Type: application/json' \
-d '{"topic":"颐和园半日游记","keywords":["昆明湖","佛香阁"],"tone":"自然"}'
curl -X POST http://127.0.0.1:8000/api/v1/aigc/agent/run \
-H 'Content-Type: application/json' \
-d '{"task":"diary_animation","text":"从东宫门进入颐和园,沿昆明湖步道游览,再到佛香阁附近拍摄湖面和古建。","destination_name":"颐和园","media_urls":["/media/demo/scenic-photo.jpg"],"scene_count":4}'
curl -H "Authorization: Bearer $ADMIN_TOKEN" 'http://127.0.0.1:8000/api/v1/admin/stats'
curl -X PATCH http://127.0.0.1:8000/api/v1/admin/destinations/1 \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"name":"后台更新目的地","popularity":999,"tags":["food","study"]}'
curl -H "Authorization: Bearer $ADMIN_TOKEN" 'http://127.0.0.1:8000/api/v1/admin/diaries?limit=5'OSMnx import CLI:
PYTHONPATH=backend python backend/scripts/import_osm_campus.py --source osmnxIf the configured OSM_DEFAULT_PLACE is not found by Nominatim, the importer falls back to OSM_FALLBACK_LAT / OSM_FALLBACK_LNG / OSM_FALLBACK_DIST.
AMap POI import CLI:
PYTHONPATH=backend python backend/scripts/import_amap_pois.py --radius 1800 --max-pages 3 --request-interval 0.5AMap POI import uses the official Place Around Web Service endpoint, converts returned GCJ-02 coordinates back to WGS84 for storage, classifies campus facilities, de-duplicates repeated keyword hits, binds each POI to the nearest local graph node, and backs off when AMap returns QPS limit code 10021.
AMap walking route smoke:
python backend/scripts/smoke_amap_route.pyAGENTS.md: rules for Codex and future agents.docs/feature_matrix.md: feature/API/page/table/test status.docs/course_requirement_gap_analysis.md: current gaps against要求.md.docs/acceptance_checklist.md: acceptance requirements.docs/amap_map_plan.md: AMap frontend rendering plan and OSM backend boundary.docs/stage_1_foundation.md: project skeleton delivery notes and known gaps.docs/stage_2_data_foundation.md: data model and seed delivery notes.docs/stage_3_map_data_api.md: database-backed map API delivery notes.docs/stage_4_route_planning.md: database-backed Dijkstra route planning notes.docs/stage_5_nearby_facilities.md: graph-distance nearby facility query notes.docs/stage_6_destinations_search_recommend.md: destination list, search, and recommendation notes.docs/stage_7_osm_import.md: OSMnx import pipeline and admin import notes.docs/stage_8_diaries.md: diary community, search, rating, comments, and compression notes.docs/stage_9_food_aigc_admin.md: food recommendation, AIGC placeholders, and admin dashboard notes.docs/stage_10_final_docs.md: final documentation and demo-readiness notes.docs/stage_11_mainline_hardening.md: smoke, merge-marker, and frontend error-handling hardening notes.docs/stage_12_multi_point_routes.md: multi-point route planning notes.docs/stage_13_campus_data_density.md: campus data density and AMap coordinate drift notes.docs/stage_14_route_strategies.md: congestion and transport-mode route strategy notes.docs/stage_15_indoor_navigation.md: indoor graph and cross-floor route notes.docs/stage_16_user_preferences.md: editable interests and recommendation refresh notes.docs/stage_18_query_polish.md: facility category lookup and food search sorting notes.docs/stage_19_real_data_enrichment.md: AMap POI real-data enrichment notes.docs/stage_20_data_cleaning_route_inputs.md: data cleaning and route place-input notes.docs/stage_21_real_route_planning.md: AMap walking route and local Dijkstra fallback notes.docs/stage_22_diary_media_search.md: diary media, exact title index, inverted index, and interest recommendation notes.docs/stage_23_user_feedback_loop.md: registration/login, token auth, favorites, ratings, behavior logs, and recommendation feedback notes.docs/stage_24_destination_food_scope.md: restaurant destination linkage and scoped food API notes.docs/stage_25_admin_moderation.md: admin edit endpoints and diary moderation notes.docs/stage_26_optional_map_smoke.md: optional AMap browser screenshot smoke notes.docs/stage_27_real_map_layers.md: real-priority OSM + AMap POI map layer cleanup and import notes.docs/campus_navigation_data_plan.md: reference campus data placement, validation, and import plan.docs/stage_28_reference_campus_navigation.md: supplied BUPT reference topology import and verification notes.docs/stage_29_dual_poi_sources.md: separate nearby-facility and campus-navigation POI source workflows.docs/stage_30_diary_requirement_alignment.md: diary management/community requirement mapping and next implementation focus.docs/stage_31_admin_user_auth.md: role-aware user/admin login-state implementation notes and acceptance criteria.docs/stage_32_aigc_agent.md: AIGC Agent workflow implementation, API contract, trace schema, and acceptance criteria.docs/stage_33_campus_map_restore.md: reset-safe BUPT Shahe campus map restore notes.docs/stage_34_nearby_facility_origin.md: nearby-facility origin selection plan and acceptance criteria.docs/stage_35_multi_scene_scenic_navigation.md: multi-scene navigation and Summer Palace scenic navigation plan.docs/stage_36_food_recommendation.md: food Top-K recommendation, fuzzy search, sort modes, and data boundary notes.docs/stage_37_real_food_poi.md: real destination-nearby restaurant POI import and Summer Palace food data notes.docs/stage_38_indoor_navigation_plan.md: China Science and Technology Museum indoor navigation upgrade plan.docs/stage_39_diary_community_ui.md: diary browse/search UI split and card-style community layout notes.docs/stage_40_food_recommendation_ui.md: food recommendation card layout, cuisine cover visuals, and restaurant specialty cuisine notes.docs/stage_41_navigation_data_cleaning.md: BUPT/Summer Palace internal navigation endpoint and graph data cleaning notes.docs/stage_42_data_requirement_coverage.md: course data-volume requirement counts and check script notes.docs/stage_43_demo_llm_profile.md: defense demo dashboard, ECharts trace visualization, and LLM user profile extraction notes.README_DEPLOY.md: local and Docker deployment commands.tests/fixtures/README.md: shared test fixture notes.
- Pick a row from
docs/feature_matrix.md. - Implement backend API, frontend page, data model, and tests.
- Update feature status.
- Run
bash scripts/check_all.sh. - Update acceptance checklist when criteria change.