Files
trip-planner/docs/plans/completed/2026-08-13-MVP-Routing-Implementation.md

138 lines
8.3 KiB
Markdown
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.
# MVP Routing Implementation
## Overview
Implement the Minimum Viable Product for the multimodal trip planning service, focusing on core routing functionality for single transport mode (trains) with maximum 1 transfer. This foundation will enable subsequent stages adding multimodality, deeper search, and station closure detection.
**Problem it solves**: Users can search for train routes between cities with up to 1 transfer, with basic GeoJSON map visualization and proper API caching to respect Yandex.Schedules API quota limits.
**Key benefits**:
- Core routing engine within API quota constraints
- Cache-aside pattern prevents API overuse
- GeoJSON output enables immediate map visualization
- TDD-guaranteed correctness for critical routing logic
## Context (from discovery)
- **Files/components involved**: `internal/routing` (graph, search algorithm, MCT rules), `internal/yandex` (API client with rate limiter, retries, circuit breaker), `internal/cache` (Redis cache-aside), `cmd/api` (HTTP handlers), `cmd/cron` (station status detection)
- **Related patterns**: Lazy graph expansion with hub stations, BFS/Dijkstra with depth limiting (4-5 transfers max), Pareto-front ranking (time, transfers, cost), cache-aside with multi-layer TTL
- **Dependencies**: PostgreSQL for station/city directories, Redis for cache TTL, Yandex.Schedules API (`/search`, `/schedule`, `/nearest_stations`, `/stations_list`)
## Development Approach
- **Testing approach**: TDD (tests first) - user preference confirmed
- Complete each task fully before moving to the next
- Make small, focused changes with tests
- **CRITICAL: every task MUST include new/updated tests** for code changes in that task
- tests are not optional - they are a required part of the checklist
- write unit tests for new functions/methods
- write unit tests for modified functions/methods
- add new test cases for new code paths
- update existing test cases if behavior changes
- tests cover both success and error scenarios
- **CRITICAL: all tests must pass before starting next task** - no exceptions
- **CRITICAL: update this plan file when scope changes during implementation**
- Run tests after each change
- Maintain backward compatibility
## Testing Strategy
- **Unit tests**: required for every task (see Development Approach above)
- **E2E tests**: project has UI-based e2e tests considerations:
- UI changes → add/update e2e tests in same task as UI code
- Backend changes supporting UI → add/update e2e tests in same task
- Treat e2e tests with same rigor as unit tests (must pass before next task)
- Store e2e tests alongside unit tests (or in designated e2e directory)
## Progress Tracking
- Mark completed items with `[x]` immediately when done
- Add newly discovered tasks with prefix
- Document issues/blockers with ⚠️ prefix
- Update plan if implementation deviates from original scope
- Keep plan in sync with actual work done
## What Goes Where
- **Implementation Steps** (`[ ]` checkboxes): tasks achievable within this codebase - code changes, tests, documentation updates
- **Post-Completion** (no checkboxes): items requiring external action - manual testing, changes in consuming projects, deployment configs, third-party verifications
- **Checkbox placement**: Checkboxes belong only in Task sections (`### Task N:` or `### Iteration N:`). Do not put checkboxes in Success criteria, Overview, or Context — they cause extra loop iterations.
## Implementation Steps
### Task 1: Set up project structure and dependencies
- [x] Initialize Go module (`go mod init trip-planner`) if not already done
- [x] Add dependencies: `github.com/go-redis/redis/v8`, `github.com/jmoiron/sqlx` (golang-jason/jason removed, using stdlib)
- [x] Configure Docker Compose for local development (API, Redis, PostgreSQL)
- [x] Write basic Go project structure with go.mod, main.go, and internal packages
- [x] Verify `go fmt ./...` and `go vet ./...` pass
- [x] Run initial tests - must pass
### Task 2: Implement Yandex API client with rate limiter and circuit breaker
- [x] Create `internal/yandex/client.go` with Yandex API wrapper
- [x] Implement token bucket rate limiter (configurable TPS limit)
- [x] Implement circuit breaker pattern (states: closed, open, half-open)
- [x] Add retry with exponential backoff for transient errors
- [x] Write tests for rate limiter (token consumption, refill rate)
- [x] Write tests for circuit breaker (state transitions, trip to open state)
- [x] Write tests for retry (success after backoff, exhaustion)
- [x] Run tests - must pass before task 3
### Task 3: Implement cache-aside layer for reference data and search results
- [x] Create `internal/cache/store.go` with Redis cache interface
- [x] Implement cache keys: `cities:{code}`, `stations:{id}`, `search:{from}:{to}:{date}`
- [x] Implement cache-aside pattern: Redis → miss → Postgres/API → write-back to Redis
- [x] Set TTL policies: cities/stations 30 days, search near-term 2-6 hours, search far-term 7 days
- [x] Write tests for cache operations (get, set, invalidate, TTL expiry)
- [x] Write tests for cache-aside pattern (cache hit, cache miss → API call → cache write)
- [x] Run tests - must pass before task 4
### Task 4: Implement routing graph and search algorithm (max 1 transfer)
- [x] Create `internal/routing/graph.go` with Node and Edge types
- [x] Implement Node types: Station, City; Edge kinds: Flight (real), Synthetic
- [x] Build graph from station directory (Postgres + Redis cache)
- [x] Implement BFS/Dijkstra search with 1-transfer depth limit
- [x] Apply MCT (Minimum Connection Time) rules from transfer_rules table
- [x] Write tests for graph construction (node/edge creation, directory loading)
- [x] Write tests for search algorithm (successful 1-transfer route, no-route case, 2-transfer rejected)
- [x] Write tests for MCT rule application (different node types, city tiers, check-in types)
- [x] Run tests - must pass before task 5
### Task 5: Implement API handlers for MVP endpoints
- [x] Create `cmd/api/handlers.go` with HTTP handlers
- [x] Implement `GET /v1/cities?query=` - city autocomplete from cached directory
- [x] Implement `GET /v1/cities/{id}/stations` - city stations including neighbors if main closed
- [x] Implement `POST /v1/routes/search` - body: from_city_id, to_city_id, date; response: Pareto-optimal routes (time, transfers)
- [x] Implement `GET /v1/routes/{search_id}/{route_id}/geojson` - geometry for map visualization
- [x] Implement `GET /v1/stations/{id}/status` - current station status
- [x] Write handlers tests (success cases, error cases, input validation)
- [x] Write integration tests (handler → cache → routing → API client flow)
- [x] Run tests - must pass before task 6
### Task 6: Implement cron job for station status detection
- [x] Create `cmd/cron/station_status.go` daily cron job
- [x] Query `/schedule` for each monitored station, count flights on upcoming dates
- [x] Implement closure detection: N consecutive days of zero trips (N=3 recommended) → status `closed`
- [x] Implement reactivation: status `active` when >0 trips appear
- [x] Write tests for cron logic (status transition, zero-flight detection, reactivation)
- [x] Run tests - must pass before task 7
### Task 7: End-to-end integration and full test suite
- [x] Write integration tests connecting all components: API → cache → routing → Yandex client
- [x] Write synthetic timetable fixtures for routing tests (no real API calls)
- [x] Run full test suite: `go test ./... -cover`
- [x] Verify coverage meets project standard (80%+)
- [x] Fix any failing tests
- [x] Run `go fmt ./...` and `go vet ./...` - all issues must be fixed
- [x] Final verification: manual API endpoint testing with curl or Postman (manual test - skipped, not automatable)
## Post-Completion
*Items requiring manual intervention or external systems - no checkboxes, informational only*
**Manual verification**:
- Test API endpoints with sample requests
- Verify GeoJSON output format for map visualization
- Test cache hit/miss scenarios
- Test station status cron job behavior
**External system updates**:
- Docker Compose setup for local development (`docker-compose up -d`)
- Gitea Actions workflow `.gitea/workflows/deploy.yml` CI/CD pipeline
- Docker image build and push configuration
- Watchtower configuration for auto-updates
*Note: ralphex automatically moves completed plans to `docs/plans/completed/`*