# Vehicle Dropdown Database Migration - Master Overview ## Status: READY FOR IMPLEMENTATION This document coordinates the migration from normalized ID-based vehicle dropdowns to a denormalized string-based system using ETL-generated data (1.1M+ vehicle options). --- ## Quick Context ### What Changed - **Old System**: Normalized database (vehicles.make, vehicles.model, etc.) with ID-based API responses - **New System**: Denormalized vehicle_options table with string-based API responses - **Impact**: Complete replacement of vehicle dropdown system (database + backend + frontend) ### Why This Change - **Better Data**: ETL pipeline provides 1.1M+ comprehensive vehicle configurations from 1980-2026 - **Simpler Queries**: Single denormalized table vs complex JOINs across 6+ tables - **Real Transmissions**: 828 actual transmission types vs hardcoded ["Automatic", "Manual"] - **Performance**: Composite indexes enable sub-50ms query times --- ## Implementation Strategy ### Parallel Work Units This migration is broken into **8 independent work units** that can be executed by separate agents in parallel (where dependencies allow). | Agent | Task | File | Dependencies | Can Start | |-------|------|------|--------------|-----------| | Agent 1 | Database Migration | `database-migration.md` | None | Immediately | | Agent 2 | Platform Repository | `backend-platform-repository.md` | Agent 1 done | After DB | | Agent 3 | Platform Service | `backend-platform-service.md` | Agent 2 done | After Repo | | Agent 4 | Vehicles API | `backend-vehicles-api.md` | Agent 3 done | After Service | | Agent 5 | Frontend API Client | `frontend-api-client.md` | Agent 4 API contract defined | After API contract | | Agent 6 | Frontend Forms | `frontend-vehicle-form.md` | Agent 5 done | After API client | | Agent 7 | Testing | `testing-validation.md` | Agents 1-6 done | After all | | Agent 8 | VIN Decode (if needed) | Manual investigation | Agent 1 done | After DB | ### Critical Path ``` Database (Agent 1) ↓ Platform Repository (Agent 2) ↓ Platform Service (Agent 3) ↓ Vehicles API (Agent 4) ↓ Frontend API Client (Agent 5) ↓ Frontend Forms (Agent 6) ↓ Testing (Agent 7) ``` **Parallel opportunities:** - Agent 8 can investigate VIN decode while Agents 2-6 work - Agent 5 can start after Agent 4 defines API contract (doesn't need full implementation) --- ## Key Design Decisions (FINAL) ### 1. API Format: String-Based **Decision**: Switch from `{id: number, name: string}[]` to `string[]` **Rationale**: Aligns with denormalized database design, simpler code **Example:** ```typescript // OLD GET /api/vehicles/dropdown/makes?year=2024 Response: [{id: 1, name: "Ford"}, {id: 2, name: "Honda"}] // NEW GET /api/vehicles/dropdown/makes?year=2024 Response: ["Ford", "Honda"] ``` ### 2. Migration Strategy: Complete Replacement **Decision**: Replace vehicles.* schema entirely (not parallel schemas) **Rationale**: Clean architecture, no data duplication, simpler maintenance **Impact**: Must verify VIN decode still works (Agent 8) ### 3. NULL Handling: Show with 'N/A' **Decision**: Include electric vehicles, display 'N/A' for missing engine/transmission **Rationale**: Ensures all vehicles available, handles 1.1% of records with NULL engine_id **Example:** ```typescript // Electric vehicle in dropdown { engine: "N/A (Electric)", transmission: "Single-Speed Automatic" } ``` ### 4. Implementation Approach: Single Update **Decision**: No feature flags, complete migration in one cycle **Rationale**: Faster delivery, cleaner code, no dual-system complexity **Requirement**: Thorough testing before deployment --- ## Breaking Changes ### API Changes **Endpoint Response Format:** ```typescript // All dropdown endpoints now return string[] GET /api/vehicles/dropdown/years → number[] (unchanged) GET /api/vehicles/dropdown/makes → string[] (was {id, name}[]) GET /api/vehicles/dropdown/models → string[] (was {id, name}[]) GET /api/vehicles/dropdown/trims → string[] (was {id, name}[]) GET /api/vehicles/dropdown/engines → string[] (was {id, name}[]) GET /api/vehicles/dropdown/transmissions → string[] (was {id, name}[]) ``` **Query Parameters:** ```typescript // Parameters now use string values, not IDs GET /api/vehicles/dropdown/models?year=2024&make=Ford (was make_id=1) GET /api/vehicles/dropdown/trims?year=2024&make=Ford&model=F-150 (was make_id=1&model_id=42) ``` ### Database Schema Changes **Removed Tables:** ```sql vehicles.make vehicles.model vehicles.model_year vehicles.trim vehicles.engine vehicles.trim_engine vehicles.transmission vehicles.trim_transmission ``` **New Tables:** ```sql public.engines -- 30,066 records public.transmissions -- 828 records public.vehicle_options -- 1,122,644 records ``` **New Database Functions:** ```sql get_makes_for_year(year INT) get_models_for_year_make(year INT, make VARCHAR) get_trims_for_year_make_model(year INT, make VARCHAR, model VARCHAR) get_options_for_vehicle(year INT, make VARCHAR, model VARCHAR, trim VARCHAR) ``` ### Frontend Type Changes **Old Type:** ```typescript interface DropdownOption { id: number; name: string; } ``` **New Type:** ```typescript type DropdownOption = string; // Or simply use string[] directly ``` --- ## New Database Schema Details ### Tables **engines** ```sql CREATE TABLE engines ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL -- e.g., "V8 5.0L", "L4 2.0L Turbo" ); -- 30,066 records ``` **transmissions** ```sql CREATE TABLE transmissions ( id SERIAL PRIMARY KEY, type VARCHAR(255) NOT NULL -- e.g., "8-Speed Automatic", "6-Speed Manual" ); -- 828 records ``` **vehicle_options** ```sql CREATE TABLE vehicle_options ( id SERIAL PRIMARY KEY, year INTEGER NOT NULL, make VARCHAR(100) NOT NULL, -- "Ford", "Honda" (Title Case) model VARCHAR(100) NOT NULL, -- "F-150", "Civic" trim VARCHAR(255), -- "XLT SuperCrew", "Sport Touring" engine_id INTEGER REFERENCES engines(id), transmission_id INTEGER REFERENCES transmissions(id) ); -- 1,122,644 records -- 1.1% have NULL engine_id (electric vehicles) -- Composite indexes for cascade queries CREATE INDEX idx_vehicle_year_make ON vehicle_options(year, make); CREATE INDEX idx_vehicle_year_make_model ON vehicle_options(year, make, model); CREATE INDEX idx_vehicle_year_make_model_trim ON vehicle_options(year, make, model, trim); ``` ### Database Functions These functions optimize common queries: ```sql -- Returns distinct makes for a given year get_makes_for_year(year INT) RETURNS TABLE(make VARCHAR) -- Returns distinct models for year/make get_models_for_year_make(year INT, make VARCHAR) RETURNS TABLE(model VARCHAR) -- Returns distinct trims for year/make/model get_trims_for_year_make_model(year INT, make VARCHAR, model VARCHAR) RETURNS TABLE(trim_name VARCHAR) -- Returns engine/transmission options for specific vehicle get_options_for_vehicle(year INT, make VARCHAR, model VARCHAR, trim VARCHAR) RETURNS TABLE(engine_name VARCHAR, transmission_type VARCHAR, ...) ``` --- ## Data Flow Changes ### Old Flow (ID-Based Cascade) ``` 1. User selects Year (2024) → GET /dropdown/makes?year=2024 → Returns: [{id: 1, name: "Ford"}, {id: 2, name: "Honda"}] 2. User selects Make (clicks "Ford", id=1) → GET /dropdown/models?year=2024&make_id=1 → Returns: [{id: 42, name: "F-150"}, {id: 43, name: "Mustang"}] 3. User selects Model (clicks "F-150", id=42) → GET /dropdown/trims?year=2024&make_id=1&model_id=42 → Returns: [{id: 301, name: "XLT"}, {id: 302, name: "Lariat"}] 4. Form stores: make="Ford", model="F-150", trim="XLT" (name strings) ``` ### New Flow (String-Based Cascade) ``` 1. User selects Year (2024) → GET /dropdown/makes?year=2024 → Returns: ["Ford", "Honda"] 2. User selects Make ("Ford") → GET /dropdown/models?year=2024&make=Ford → Returns: ["F-150", "Mustang"] 3. User selects Model ("F-150") → GET /dropdown/trims?year=2024&make=Ford&model=F-150 → Returns: ["XLT", "Lariat"] 4. Form stores: make="Ford", model="F-150", trim="XLT" (same strings) ``` **Benefit**: Simpler frontend logic - no ID tracking, direct string values --- ## File Locations Reference ### Database Files ``` data/make-model-import/migrations/001_create_vehicle_database.sql data/make-model-import/output/01_engines.sql data/make-model-import/output/02_transmissions.sql data/make-model-import/output/03_vehicle_options.sql data/make-model-import/IMPLEMENTATION_SUMMARY.md (reference doc) ``` ### Backend Platform Feature ``` backend/src/features/platform/domain/vehicle-data.repository.ts backend/src/features/platform/domain/vehicle-data.service.ts backend/src/features/platform/types/index.ts backend/src/features/platform/cache/platform-cache.service.ts (may need updates) ``` ### Backend Vehicles Feature ``` backend/src/features/vehicles/api/vehicles.controller.ts backend/src/features/vehicles/api/vehicles.routes.ts (minimal changes) backend/src/features/vehicles/domain/vehicles.service.ts backend/src/features/vehicles/types/index.ts ``` ### Frontend ``` frontend/src/features/vehicles/api/vehicles.api.ts frontend/src/features/vehicles/types/vehicles.types.ts frontend/src/features/vehicles/components/VehicleForm.tsx ``` --- ## Success Criteria ### Technical Requirements - [ ] Database migration completes successfully (3 SQL files imported) - [ ] All dropdown endpoints return string[] format - [ ] Query parameters use strings (not IDs) - [ ] NULL values display as 'N/A' or appropriate label - [ ] Transmissions show real data (not hardcoded) - [ ] Composite indexes perform sub-50ms queries - [ ] All backend tests pass - [ ] All frontend tests pass - [ ] Mobile responsiveness verified ### Functional Requirements - [ ] Create vehicle form works end-to-end - [ ] Edit vehicle form loads and displays correctly - [ ] Cascading dropdowns work: Year → Make → Model → Trim → Engine/Trans - [ ] Electric vehicles appear in results with 'N/A' engine display - [ ] Saved vehicles store correct string values - [ ] VIN decode feature still operational (if dependent on schema) ### Quality Requirements (per CLAUDE.md) - [ ] All linters pass with zero issues - [ ] All automated checks green - [ ] No formatting errors - [ ] Code follows project conventions - [ ] Old code deleted (not commented out) --- ## Testing Checklist ### Backend API Tests - [ ] GET /dropdown/years returns number[] - [ ] GET /dropdown/makes?year=2024 returns string[] - [ ] GET /dropdown/models?year=2024&make=Ford returns string[] - [ ] GET /dropdown/trims with all params returns string[] - [ ] GET /dropdown/engines returns string[] with 'N/A' for electric - [ ] GET /dropdown/transmissions returns real data (not hardcoded) - [ ] Invalid parameters return 400 errors - [ ] Database errors return 500 with appropriate message ### Frontend Form Tests - [ ] Create form: Year dropdown loads on mount - [ ] Create form: Make dropdown loads after year selected - [ ] Create form: Model dropdown loads after make selected - [ ] Create form: Trim dropdown loads after model selected - [ ] Create form: Engine dropdown loads after trim selected - [ ] Create form: Submission saves correct string values - [ ] Edit form: Loads existing vehicle data correctly - [ ] Edit form: Pre-populates all dropdowns in correct order - [ ] Edit form: Allows changing selections and cascades properly ### Mobile Testing (REQUIRED per CLAUDE.md) - [ ] Dropdowns render correctly on mobile viewport - [ ] Touch interactions work smoothly - [ ] Form is usable on small screens - [ ] No horizontal scrolling issues ### Integration Tests - [ ] Complete flow: Select all fields → Save → Verify in DB - [ ] Edit existing vehicle → Change year → Cascades reset correctly - [ ] Electric vehicle selection → Engine shows 'N/A' → Saves correctly --- ## Rollback Plan If critical issues discovered: 1. **Database Rollback** ```bash # Drop new tables DROP TABLE vehicle_options CASCADE; DROP TABLE transmissions CASCADE; DROP TABLE engines CASCADE; # Restore vehicles.* schema from backup # (ensure backup created before migration) ``` 2. **Code Rollback** ```bash git revert # Revert all changes make rebuild # Rebuild containers ``` 3. **Partial Rollback (if only frontend broken)** - Revert frontend changes only - Backend can stay with new system - Fix frontend issues and redeploy --- ## Common Issues & Solutions ### Issue: VIN Decode Broken **Symptom**: VIN decode endpoint returns errors after migration **Solution**: - Check if VIN decode queries vehicles.* tables - If yes, need to create separate VIN decode data solution - May need to keep minimal vehicles.* for VIN decode only ### Issue: Electric Vehicles Show No Data **Symptom**: Electric vehicles missing from dropdowns **Solution**: - Verify NULL handling in repository queries - Check that 'N/A' label generation works correctly - Ensure frontend displays 'N/A' properly ### Issue: Slow Query Performance **Symptom**: Dropdown queries take > 100ms **Solution**: - Verify composite indexes created: `\d vehicle_options` - Check query plans: `EXPLAIN ANALYZE ` - Ensure using database functions (not raw queries) - Consider adding Redis caching ### Issue: Make Names in Wrong Case **Symptom**: Makes show as "FORD" instead of "Ford" **Solution**: - Verify ETL conversion to Title Case worked - Check database: `SELECT DISTINCT make FROM vehicle_options LIMIT 10;` - If wrong, re-run ETL with case conversion fix --- ## Agent Communication Protocol ### Completion Signals Each agent should: 1. Update their assigned documentation file with actual changes made 2. Post completion status in project communication channel 3. List any deviations from plan 4. Note any blockers for dependent agents ### Handoff Information When completing work, agents should document: - **What changed**: Specific files and functions modified - **How to verify**: Command or test to confirm work complete - **Breaking changes**: Any API changes affecting downstream agents - **Blockers resolved**: Issues fixed that were blocking others ### Example Completion Message ``` Agent 2 (Platform Repository): COMPLETE Files modified: - backend/src/features/platform/domain/vehicle-data.repository.ts Changes: - getMakes() now returns string[] using get_makes_for_year() - All 5 dropdown methods updated to query vehicle_options table - NULL engine_id returns 'N/A (Electric)' label Verification: - Run: npm test -- vehicle-data.repository.test.ts - All tests passing API Contract for Agent 3: - getMakes(pool, year) → Promise - getModels(pool, year, make) → Promise - getTrims(pool, year, make, model) → Promise - getEngines(pool, year, make, model, trim) → Promise - getTransmissions(pool, year, make, model) → Promise Agent 3 can now proceed with service layer updates. ``` --- ## Detailed Task Documents Each task has a dedicated document with implementation details: 1. **[Database Migration](./database-migration.md)** - Agent 1 2. **[Platform Repository Updates](./backend-platform-repository.md)** - Agent 2 3. **[Platform Service Updates](./backend-platform-service.md)** - Agent 3 4. **[Vehicles API Updates](./backend-vehicles-api.md)** - Agent 4 5. **[Frontend API Client Updates](./frontend-api-client.md)** - Agent 5 6. **[Frontend Form Updates](./frontend-vehicle-form.md)** - Agent 6 7. **[Testing & Validation](./testing-validation.md)** - Agent 7 --- ## Questions or Issues If you encounter issues: 1. Check this master doc for common issues 2. Review your task-specific document 3. Check database state: `docker exec mvp-postgres psql -U postgres -d motovaultpro` 4. Check logs: `make logs` 5. Consult original data docs: `data/make-model-import/IMPLEMENTATION_SUMMARY.md` --- ## Timeline Estimate - **Agent 1** (Database): 30 minutes - **Agent 2** (Platform Repo): 1-2 hours - **Agent 3** (Platform Service): 1 hour - **Agent 4** (Vehicles API): 1-2 hours - **Agent 5** (Frontend API): 1 hour - **Agent 6** (Frontend Forms): 2-3 hours - **Agent 7** (Testing): 2-3 hours **Total Sequential**: ~10-14 hours **With Parallelization**: ~6-8 hours (agents work simultaneously where possible) --- **Document Version**: 1.0 **Last Updated**: 2025-11-10 **Status**: Ready for Implementation