541 lines
16 KiB
Markdown
541 lines
16 KiB
Markdown
# 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 <commit-hash> # 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 <query>`
|
|
- 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<string[]>
|
|
- getModels(pool, year, make) → Promise<string[]>
|
|
- getTrims(pool, year, make, model) → Promise<string[]>
|
|
- getEngines(pool, year, make, model, trim) → Promise<string[]>
|
|
- getTransmissions(pool, year, make, model) → Promise<string[]>
|
|
|
|
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
|