# Полная реализация согласно спецификации `docs/specification.md` ## Overview Implement the complete multimodal trip planning service as specified in `docs/specification.md`, covering all four development stages from MVP through polish. The implementation follows the lazy graph expansion architecture due to Yandex.Schedules API limitations (no full timetable dump). **Problem solved:** Users can find multimodal routes combining planes, trains, and buses with arbitrary transfer depth, automatic fallback to neighboring stations when main stations are closed, and map visualization — all within API quota constraints. **Key architectural decisions:** - Lazy graph expansion with hub stations (instead of full RAPTOR, which would exhaust API quota) - BFS/Dijkstra with depth limiting (4-5 transfers max) - On-demand `/search` requests only for relevant station pairs - Multi-layer TTL caching strategy - Pareto-front ranking (time, transfers, cost) rather than single "optimal" route - Station closure detection with automatic fallback ## Context (from discovery) - **Current state:** Lazy graph expansion partially implemented (commit edfc567): hub station selection, on-demand `/search`, transfer depth limiting, synthetic edge fallback, `ResetCircuitBreaker` helper - **Files involved:** `internal/routing/graph.go`, `internal/routing/graph_test.go`, `internal/yandex/client.go`, `internal/yandex/client_test.go`, `internal/cache/`, `internal/storage/`, `cmd/api/`, `cmd/cron/` - **Related patterns:** cache-aside, circuit breaker, transfer rules, MCT calculation, GeoJSON assembly - **Dependencies:** PostgreSQL with PostGIS (optional), Redis with TTL, Yandex.Schedules API ## Development Approach - **Testing approach:** TDD (tests first) — all new code must have corresponding tests; tests are a required deliverable of every task, not optional - All tests must pass before starting the next task — no exceptions - Update plan file when scope changes during implementation - Run tests after each change - Maintain backward compatibility ## Testing Strategy - **Unit tests:** Required for every task — write tests for all new/modified functions, including success and error scenarios - **Synthetic timetable fixtures:** Test routing algorithm on synthetic data without real API calls - Mock external API calls in all tests - Test cache-aside patterns thoroughly - Validate MCT (Minimum Connection Time) calculations ## Progress Tracking - Mark completed items with `[x]` immediately when done - Add newly discovered tasks with ➕ prefix - Document issues/blockers with ⚠️ prefix - Keep plan in sync with actual work done ## What Goes Where - **Implementation Steps** (`[ ]` checkboxes): tasks achievable within this codebase - **Post-Completion** (no checkboxes): items requiring external action - **Checkbox placement:** Checkboxes belong only in Task sections (`### Task N:`). Do not put checkboxes in Success criteria, Overview, or Context --- # Этап 1 — MVP (Minimum Viable Product) *Already partially implemented: lazy graph expansion, basic routing with single transport mode, basic caching* ## Implementation Steps ### Task 1: Refactor hub station selection [x] - [x] Remove `Population` field from `HubStation` struct in `internal/routing/graph.go` - [x] Simplify `SelectHubStations` to use only `minOutgoingFlights` criterion - [x] Update all test criteria to match new hub selection logic (remove `minPopulation`) - [x] **Write tests:** TestSelectHubStations with various minOutgoingFlights values - [x] Run tests - must pass before task 2 ### Task 2: Implement synthetic edge fallback in FindRoute [x] - [x] Add synthetic edge fallback when lazy expansion fails in `FindRoute` method - [x] Create `addSyntheticEdgesForNode` function - [x] Write tests: TestFindRouteWithSyntheticFallback - [x] Run tests - must pass before task 3 ### Task 3: Add ResetCircuitBreaker helper [x] - [x] Add `ResetCircuitBreaker` function to `internal/yandex/client.go` - [x] Update tests to use the new reset function - [x] **Write tests:** TestResetCircuitBreaker - [x] Run tests - must pass before task 4 ### Task 4: Implement on-demand /search integration [ ] - [ ] Integrate on-demand `/search` calls in lazy graph expansion - [ ] Implement cache key generation and TTL policies - [ ] Write tests: TestSearchRoutes_onDemand with circuit breaker reset - [ ] Run tests - must pass before task 5 ### Task 5: Transfer depth limiting [ ] - [ ] Implement depth limiting in BFS/Dijkstra (max 4-5 transfers) - [ ] Add transfer depth tracking in search options - [ ] Write tests: TestFindRouteWithDepthLimiting - [ ] Run tests - must pass before task 6 ### Task 6: Pareto-front ranking [ ] - [ ] Implement multi-criteria ranking (time, transfers, cost if available) - [ ] Return set of non-dominated routes instead of single "optimal" - [ ] Write tests: TestRouteParetoRanking - [ ] Run tests - must pass before task 7 ### Task 7: Basic caching layer [ ] - [ ] Implement cache-aside pattern for `/search` results - [ ] Add TTL policies: 2-6 hours for near-term dates, 7 days for far-term - [ ] Write tests: TestCacheAsideSearch - [ ] Run tests - must pass before task 8 ### Task 8: Station status endpoint [ ] - [ ] Implement `GET /v1/stations/{id}/status` endpoint - [ ] Write tests: TestStationStatusEndpoint - [ ] Run tests - must pass before task 9 **✅ Stage 1 Complete — MVP ready (basic single-mode routing with lazy expansion)** --- # Этап 2 — Мультимодальность и MCT (Minimum Connection Time) *Add planes and buses, synthetic edges with MCT rules, manual neighboring airports* ## Implementation Steps ### Task 9: Add bus transport type [ ] - [ ] Add `TransportType` enum with values: `plane`, `train`, `bus` - [ ] Update `Edge` struct to include `TransportType` - [ ] Update routing algorithm to handle all three transport types - [ ] **Write tests:** TestTransportTypesInGraph - [ ] Run tests - must pass before task 10 ### Task 10: Synthetic edges "город↔аэропорт" [ ] - [ ] Implement synthetic edges for airport-city transfers - [ ] Add constants for transfer time estimation (section 7.4) - [ ] Mark synthetic edges in GeoJSON output (dashed line) - [ ] **Write tests:** TestSyntheticAirportCityEdges - [ ] Run tests - must pass before task 11 ### Task 11: MCT rules implementation [ ] - [ ] Create `transfer_rules` table migration - [ ] Seed default MCT values (Section 7.4): - airport_internal/through → 30 min - airport_internal/separate → 60 min - station_internal → 30 min - airport_to_city/small → 60 min - airport_to_city/million_plus → 90 min - [ ] Implement `MinTransferTime` function reading from transfer rules - [ ] Use MCT in routing algorithm for transfer validation - [ ] **Write tests:** TestMCTCalculation, TestTransferRules - [ ] Run tests - must pass before task 12 ### Task 12: Manual neighboring stations [ ] - [ ] Add `station_neighbors` table support - [ ] Implement `internal/airports` package with geo + manual override - [ ] Add `source` field (geo/manual) and `is_excluded` flag - [ ] Update `cities/{id}/stations` endpoint to include neighbors when main station closed - [ ] **Write tests:** TestNeighboringStations, TestStationNeighbors - [ ] Run tests - must pass before task 13 ### Task 13: Admin station status override [ ] - [ ] Implement `POST /internal/admin/stations/{id}/status` endpoint - [ ] Add authentication protection - [ ] Allow manual status setting with `source: manual` - [ ] Write tests: TestAdminStationStatus, TestAdminAuth - [ ] Run tests - must pass before task 14 **✅ Stage 2 Complete — Multimodality + MCT operational** --- # Этап 3 — Глубокий поиск и автодетект (Deep Search + Closure Detection) *Lazy hub-based expansion to depth 4-5, Pareto ranking, auto-closure detection* ## Implementation Steps ### Task 14: Lazy hub expansion depth 4-5 [ ] - [ ] Implement BFS/Dijkstra with explicit depth limiting - [ ] Track transfer count at each step; stop when depth > 5 - [ ] On expansion failure, add synthetic edges as fallback - [ ] Write tests: TestLazyExpansionDepthLimit, TestFindRouteMaxTransfers - [ ] Run tests - must pass before task 15 ### Task 15: Pareto-front ranking integration [ ] - [ ] Integrate multi-criteria ranking into route search results - [ ] Sort by default "быстрее всего" (fastest) - [ ] Add UI controls to switch to "меньше пересадок" / "дешевле" - [ ] Write tests: TestParetoFrontGeneration - [ ] Run tests - must pass before task 16 ### Task 16: Auto station closure detection [ ] - [ ] Implement daily cron job checking `/schedule` for monitored stations - [ ] Track `zero_since` timestamp; if 0 flights for N=3 consecutive days → status `closed` - [ ] Update `station_status` table with `zero_since`, `last_seen_flight` - [ ] When station closed, automatically substitute neighboring stations - [ ] Write tests: TestStationClosureDetection, TestAutoClosureChronology - [ ] Run tests - must pass before task 17 ### Task 17: Neighbor substitution in routing [ ] - [ ] When station is closed, route automatically uses neighboring stations - [ ] Update `GET /v1/cities/{id}/stations` to reflect closure status - [ ] Write tests: TestRouteWithClosedStationSubstitution - [ ] Run tests - must pass before task 18 ### Task 18: GeoJSON route visualization [ ] - [ ] Implement route-to-GeoJSON conversion - [ ] Real segments: solid lines, color by transport type - [ ] Synthetic segments: dashed lines - [ ] Transfer point markers with popup info (connection time, type) - [ ] Write tests: TestRouteGeoJSON, TestGeoJSONVisualization - [ ] Run tests - must pass before task 19 **✅ Stage 3 Complete — Deep search + closure detection operational** --- # Этап 4 — Полировка (Polish) *Price consideration, flight change notifications, personalization* ## Implementation Steps ### Task 19: Price as routing criterion [ ] - [ ] Investigate price source data from Yandex.Schedules - [ ] If price data available, add as 4th routing criterion - [ ] If not available, add marker "цена не указана" in UI - [ ] Write tests: TestPriceInRouting (if applicable) - [ ] Run tests - must pass before task 20 ### Task 20: Flight change notifications [ ] - [ ] Track already-built routes for status changes - [ ] Implement re-search on significant changes (cancellation, major delay) - [ ] Write tests: TestRouteReSearchOnChange - [ ] Run tests - must pass before task 21 ### Task 21: Personalization [ ] - [ ] Add user preferences (saved cities, history of searches) - [ ] Store preferences in Redis or Postgres - [ ] Write tests: TestUserPreferences - [ ] Run tests - must pass before task 22 ### Task 22: Observability and metrics [ ] - [ ] Add metrics: cache hit-rate per layer, API quota remaining, circuit breaker trips, average search time - [ ] Add Prometheus metrics endpoints or logging structured - [ ] Write tests: TestMetricsEndpoints - [ ] Run tests - must pass before task 23 ### Task 23: Full test suite and linter [ ] - [ ] Run entire test suite: `go test ./...` - [ ] Fix all linter issues: `go vet ./...` - [ ] Verify test coverage meets standard (80%+) - [ ] Fix any remaining issues - [ ] **Final verification:** all checkboxes marked `[x]`, all tests passing **✅ Stage 4 Complete — Polish finished** --- # Post-Completion ## Manual verification (if applicable) - Manual UI/UX testing scenarios across all transport mode combinations - Performance testing under load (simulate cold cache, warm cache scenarios) - Security review considerations for admin endpoints ## External system updates - Consuming projects that may need updates after this library change - Configuration changes in deployment systems (docker-compose, cron schedules) - Third-party service integrations to verify (Yandex API access, Redis/PG connectivity) ## Migration path from MVP to full 1. Stage 1 (MVP) → functional single-mode routing 2. Stage 2 → add planes/buses + MCT + manual neighbors 3. Stage 3 → lazy hub expansion + auto-closure + GeoJSON 4. Stage 4 → price, notifications, personalization, observability **Notes for ralphex:** - Auto-move completed plan to `docs/plans/completed/` upon full task completion - Each task MUST include tests as checklist items — they are not optional - If tests cannot pass until a later task: write tests with TODO comment noting dependency, mark test checkbox as `[x] write tests ... (fails until Task X)`, do NOT skip test writing - Update plan file when scope changes during implementation