add plan: full-implementation

This commit is contained in:
2026-08-15 01:22:50 +03:00
parent d10dbf37f4
commit c92baeca02

View File

@@ -0,0 +1,269 @@
# Полная реализация согласно спецификации `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 [ ]
- [ ] Remove `Population` field from `HubStation` struct in `internal/routing/graph.go`
- [ ] Simplify `SelectHubStations` to use only `minOutgoingFlights` criterion
- [ ] Update all test criteria to match new hub selection logic (remove `minPopulation`)
- [ ] **Write tests:** TestSelectHubStations with various minOutgoingFlights values
- [ ] Run tests - must pass before task 2
### Task 2: Implement synthetic edge fallback in FindRoute [ ]
- [ ] Add synthetic edge fallback when lazy expansion fails in `FindRoute` method
- [ ] Create `addSyntheticEdgesForNode` function
- - [ ] Write tests: TestFindRouteWithSyntheticFallback
- [ ] Run tests - must pass before task 3
### Task 3: Add ResetCircuitBreaker helper [ ]
- [ ] Add `ResetCircuitBreaker` function to `internal/yandex/client.go`
- [ ] Update tests to use the new reset function
- [ ] **Write tests:** TestResetCircuitBreaker
- [ ] 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