# Backend Platform Service Update - Agent 3 ## Task: Update VehicleDataService to use string-based signatures **Status**: Ready for Implementation **Dependencies**: Agent 2 (Platform Repository) must be complete **Estimated Time**: 1 hour **Assigned To**: Agent 3 (Platform Service) --- ## Overview Update the service layer to match the new repository signatures. Change from ID-based parameters to string-based parameters, and update return types from objects to string arrays. --- ## Prerequisites ### Verify Agent 2 Completed ```bash # Verify repository compiles cd backend && npm run build # Should see no errors in vehicle-data.repository.ts ``` ### Files to Modify ``` backend/src/features/platform/domain/vehicle-data.service.ts backend/src/features/platform/domain/platform-cache.service.ts (cache keys) backend/src/features/platform/models/responses.ts (if not already updated) ``` --- ## Current Implementation Analysis **File**: `backend/src/features/platform/domain/vehicle-data.service.ts` **Current Methods**: ```typescript getYears(pool: Pool): Promise getMakes(pool: Pool, year: number): Promise getModels(pool: Pool, year: number, makeId: number): Promise getTrims(pool: Pool, year: number, modelId: number): Promise getEngines(pool: Pool, year: number, modelId: number, trimId: number): Promise ``` **Pattern**: Each method has: 1. Cache lookup 2. Repository call if cache miss 3. Cache storage 4. Logging --- ## Step 1: Update Imports **Current** (line 8): ```typescript import { MakeItem, ModelItem, TrimItem, EngineItem } from '../models/responses'; ``` **New**: ```typescript import { VINDecodeResult } from '../models/responses'; // MakeItem, ModelItem, TrimItem, EngineItem removed - using string[] now ``` --- ## Step 2: Update getMakes() Method **Current** (lines 44-60): ```typescript async getMakes(pool: Pool, year: number): Promise { try { const cached = await this.cache.getMakes(year); if (cached) { logger.debug('Makes retrieved from cache', { year }); return cached; } const makes = await this.repository.getMakes(pool, year); await this.cache.setMakes(year, makes); logger.debug('Makes retrieved from database and cached', { year, count: makes.length }); return makes; } catch (error) { logger.error('Service error: getMakes', { error, year }); throw error; } } ``` **New**: ```typescript async getMakes(pool: Pool, year: number): Promise { try { const cached = await this.cache.getMakes(year); if (cached) { logger.debug('Makes retrieved from cache', { year }); return cached; } const makes = await this.repository.getMakes(pool, year); await this.cache.setMakes(year, makes); logger.debug('Makes retrieved from database and cached', { year, count: makes.length }); return makes; } catch (error) { logger.error('Service error: getMakes', { error, year }); throw error; } } ``` **Changes**: - Line 44: Return type `Promise` → `Promise` - Logic unchanged (cache still works the same way) **Note**: Cache service will need updates (Step 7) --- ## Step 3: Update getModels() Method **Current** (lines 65-81): ```typescript async getModels(pool: Pool, year: number, makeId: number): Promise { try { const cached = await this.cache.getModels(year, makeId); if (cached) { logger.debug('Models retrieved from cache', { year, makeId }); return cached; } const models = await this.repository.getModels(pool, year, makeId); await this.cache.setModels(year, makeId, models); logger.debug('Models retrieved from database and cached', { year, makeId, count: models.length }); return models; } catch (error) { logger.error('Service error: getModels', { error, year, makeId }); throw error; } } ``` **New**: ```typescript async getModels(pool: Pool, year: number, make: string): Promise { try { const cached = await this.cache.getModels(year, make); if (cached) { logger.debug('Models retrieved from cache', { year, make }); return cached; } const models = await this.repository.getModels(pool, year, make); await this.cache.setModels(year, make, models); logger.debug('Models retrieved from database and cached', { year, make, count: models.length }); return models; } catch (error) { logger.error('Service error: getModels', { error, year, make }); throw error; } } ``` **Changes**: - Line 65: Parameter `makeId: number` → `make: string` - Line 65: Return type `Promise` → `Promise` - Line 67: Cache call uses `make` instead of `makeId` - Line 69: Logger uses `make` instead of `makeId` - Line 73: Repository call uses `make` instead of `makeId` - Line 74: Cache set uses `make` instead of `makeId` - Line 75: Logger uses `make` instead of `makeId` - Line 78: Logger uses `make` instead of `makeId` --- ## Step 4: Update getTrims() Method **Current** (lines 86-102): ```typescript async getTrims(pool: Pool, year: number, modelId: number): Promise { try { const cached = await this.cache.getTrims(year, modelId); if (cached) { logger.debug('Trims retrieved from cache', { year, modelId }); return cached; } const trims = await this.repository.getTrims(pool, year, modelId); await this.cache.setTrims(year, modelId, trims); logger.debug('Trims retrieved from database and cached', { year, modelId, count: trims.length }); return trims; } catch (error) { logger.error('Service error: getTrims', { error, year, modelId }); throw error; } } ``` **New**: ```typescript async getTrims(pool: Pool, year: number, make: string, model: string): Promise { try { const cached = await this.cache.getTrims(year, make, model); if (cached) { logger.debug('Trims retrieved from cache', { year, make, model }); return cached; } const trims = await this.repository.getTrims(pool, year, make, model); await this.cache.setTrims(year, make, model, trims); logger.debug('Trims retrieved from database and cached', { year, make, model, count: trims.length }); return trims; } catch (error) { logger.error('Service error: getTrims', { error, year, make, model }); throw error; } } ``` **Changes**: - Line 86: Parameters changed from `modelId: number` to `make: string, model: string` - Line 86: Return type `Promise` → `Promise` - All cache and logger calls updated to use `make, model` instead of `modelId` --- ## Step 5: Update getEngines() Method **Current** (lines 107-123): ```typescript async getEngines(pool: Pool, year: number, modelId: number, trimId: number): Promise { try { const cached = await this.cache.getEngines(year, modelId, trimId); if (cached) { logger.debug('Engines retrieved from cache', { year, modelId, trimId }); return cached; } const engines = await this.repository.getEngines(pool, year, modelId, trimId); await this.cache.setEngines(year, modelId, trimId, engines); logger.debug('Engines retrieved from database and cached', { year, modelId, trimId, count: engines.length }); return engines; } catch (error) { logger.error('Service error: getEngines', { error, year, modelId, trimId }); throw error; } } ``` **New**: ```typescript async getEngines(pool: Pool, year: number, make: string, model: string, trim: string): Promise { try { const cached = await this.cache.getEngines(year, make, model, trim); if (cached) { logger.debug('Engines retrieved from cache', { year, make, model, trim }); return cached; } const engines = await this.repository.getEngines(pool, year, make, model, trim); await this.cache.setEngines(year, make, model, trim, engines); logger.debug('Engines retrieved from database and cached', { year, make, model, trim, count: engines.length }); return engines; } catch (error) { logger.error('Service error: getEngines', { error, year, make, model, trim }); throw error; } } ``` **Changes**: - Line 107: Parameters changed from `modelId: number, trimId: number` to `make: string, model: string, trim: string` - Line 107: Return type `Promise` → `Promise` - All cache and logger calls updated to use `make, model, trim` instead of `modelId, trimId` --- ## Step 6: Add getTransmissions() Method (NEW) Add this new method after `getEngines()`: ```typescript /** * Get transmissions for a year, make, and model with caching */ async getTransmissions(pool: Pool, year: number, make: string, model: string): Promise { try { const cached = await this.cache.getTransmissions(year, make, model); if (cached) { logger.debug('Transmissions retrieved from cache', { year, make, model }); return cached; } const transmissions = await this.repository.getTransmissions(pool, year, make, model); await this.cache.setTransmissions(year, make, model, transmissions); logger.debug('Transmissions retrieved from database and cached', { year, make, model, count: transmissions.length }); return transmissions; } catch (error) { logger.error('Service error: getTransmissions', { error, year, make, model }); throw error; } } ``` **Why This is New**: - Repository added `getTransmissions()` method (real data, not hardcoded) - Service needs to expose this with caching --- ## Step 7: Update Cache Service **File**: `backend/src/features/platform/domain/platform-cache.service.ts` The cache service needs updates to handle string-based cache keys instead of ID-based keys. ### Cache Key Changes **Current cache keys**: ```typescript `years` `makes:${year}` `models:${year}:${makeId}` `trims:${year}:${modelId}` `engines:${year}:${modelId}:${trimId}` ``` **New cache keys**: ```typescript `years` `makes:${year}` `models:${year}:${make}` `trims:${year}:${make}:${model}` `engines:${year}:${make}:${model}:${trim}` `transmissions:${year}:${make}:${model}` // NEW ``` ### Update Cache Method Signatures **Find and update these methods**: #### getMakes / setMakes ```typescript // Signature unchanged (still takes year: number) async getMakes(year: number): Promise async setMakes(year: number, makes: string[]): Promise ``` #### getModels / setModels ```typescript // OLD async getModels(year: number, makeId: number): Promise async setModels(year: number, makeId: number, models: ModelItem[]): Promise // NEW async getModels(year: number, make: string): Promise async setModels(year: number, make: string, models: string[]): Promise // Implementation change: const key = `models:${year}:${make}`; // was makeId ``` #### getTrims / setTrims ```typescript // OLD async getTrims(year: number, modelId: number): Promise async setTrims(year: number, modelId: number, trims: TrimItem[]): Promise // NEW async getTrims(year: number, make: string, model: string): Promise async setTrims(year: number, make: string, model: string, trims: string[]): Promise // Implementation change: const key = `trims:${year}:${make}:${model}`; // was modelId only ``` #### getEngines / setEngines ```typescript // OLD async getEngines(year: number, modelId: number, trimId: number): Promise async setEngines(year: number, modelId: number, trimId: number, engines: EngineItem[]): Promise // NEW async getEngines(year: number, make: string, model: string, trim: string): Promise async setEngines(year: number, make: string, model: string, trim: string, engines: string[]): Promise // Implementation change: const key = `engines:${year}:${make}:${model}:${trim}`; // was modelId:trimId ``` #### getTransmissions / setTransmissions (NEW) ```typescript async getTransmissions(year: number, make: string, model: string): Promise { const key = `transmissions:${year}:${make}:${model}`; const cached = await this.redisClient.get(key); if (cached) { return JSON.parse(cached); } return null; } async setTransmissions(year: number, make: string, model: string, transmissions: string[]): Promise { const key = `transmissions:${year}:${make}:${model}`; await this.redisClient.setex(key, this.TTL, JSON.stringify(transmissions)); } ``` --- ## Step 8: Update Documentation Comments Update the file header: **Old** (lines 1-4): ```typescript /** * @ai-summary Vehicle data service with caching * @ai-context Business logic for hierarchical vehicle data queries */ ``` **New**: ```typescript /** * @ai-summary Vehicle data service with caching for dropdown queries * @ai-context String-based cascade queries with Redis caching * @ai-migration Updated to use string parameters (not IDs) */ ``` --- ## Complete Updated Service Structure After all changes: ```typescript /** * @ai-summary Vehicle data service with caching for dropdown queries * @ai-context String-based cascade queries with Redis caching * @ai-migration Updated to use string parameters (not IDs) */ import { Pool } from 'pg'; import { VehicleDataRepository } from '../data/vehicle-data.repository'; import { PlatformCacheService } from './platform-cache.service'; import { VINDecodeResult } from '../models/responses'; import { logger } from '../../../core/logging/logger'; export class VehicleDataService { private repository: VehicleDataRepository; private cache: PlatformCacheService; constructor(cache: PlatformCacheService, repository?: VehicleDataRepository) { this.cache = cache; this.repository = repository || new VehicleDataRepository(); } async getYears(pool: Pool): Promise { ... } // Unchanged async getMakes(pool: Pool, year: number): Promise { ... } async getModels(pool: Pool, year: number, make: string): Promise { ... } async getTrims(pool: Pool, year: number, make: string, model: string): Promise { ... } async getEngines(pool: Pool, year: number, make: string, model: string, trim: string): Promise { ... } async getTransmissions(pool: Pool, year: number, make: string, model: string): Promise { ... } // NEW } ``` --- ## Testing & Verification ### Unit Tests Update existing tests: **File**: `backend/src/features/platform/tests/unit/vehicle-data.service.test.ts` ```typescript import { VehicleDataService } from '../../domain/vehicle-data.service'; import { VehicleDataRepository } from '../../data/vehicle-data.repository'; import { PlatformCacheService } from '../../domain/platform-cache.service'; describe('VehicleDataService', () => { let service: VehicleDataService; let mockRepository: jest.Mocked; let mockCache: jest.Mocked; let mockPool: any; beforeEach(() => { mockRepository = { getMakes: jest.fn(), getModels: jest.fn(), getTrims: jest.fn(), getEngines: jest.fn(), getTransmissions: jest.fn(), } as any; mockCache = { getMakes: jest.fn(), setMakes: jest.fn(), getModels: jest.fn(), setModels: jest.fn(), // ... etc } as any; service = new VehicleDataService(mockCache, mockRepository); mockPool = {} as any; }); describe('getMakes', () => { it('should return string array from cache', async () => { mockCache.getMakes.mockResolvedValue(['Ford', 'Honda']); const result = await service.getMakes(mockPool, 2024); expect(result).toEqual(['Ford', 'Honda']); expect(mockRepository.getMakes).not.toHaveBeenCalled(); }); it('should fetch from repository on cache miss', async () => { mockCache.getMakes.mockResolvedValue(null); mockRepository.getMakes.mockResolvedValue(['Ford', 'Honda']); const result = await service.getMakes(mockPool, 2024); expect(result).toEqual(['Ford', 'Honda']); expect(mockRepository.getMakes).toHaveBeenCalledWith(mockPool, 2024); expect(mockCache.setMakes).toHaveBeenCalledWith(2024, ['Ford', 'Honda']); }); }); describe('getModels', () => { it('should accept make string parameter', async () => { mockCache.getModels.mockResolvedValue(null); mockRepository.getModels.mockResolvedValue(['F-150', 'Mustang']); const result = await service.getModels(mockPool, 2024, 'Ford'); expect(result).toEqual(['F-150', 'Mustang']); expect(mockRepository.getModels).toHaveBeenCalledWith(mockPool, 2024, 'Ford'); }); }); describe('getTransmissions', () => { it('should return transmission data', async () => { mockCache.getTransmissions.mockResolvedValue(null); mockRepository.getTransmissions.mockResolvedValue(['10-Speed Automatic']); const result = await service.getTransmissions(mockPool, 2024, 'Ford', 'F-150'); expect(result).toEqual(['10-Speed Automatic']); }); }); }); ``` Run tests: ```bash cd backend npm test -- vehicle-data.service.test.ts ``` ### Integration Test Test the full flow with real cache: ```bash # Start backend container make start # Access backend container docker exec -it mvp-backend node # In Node REPL: const { Pool } = require('pg'); const { VehicleDataService } = require('./src/features/platform/domain/vehicle-data.service'); const { VehicleDataRepository } = require('./src/features/platform/data/vehicle-data.repository'); const { PlatformCacheService } = require('./src/features/platform/domain/platform-cache.service'); const Redis = require('ioredis'); const pool = new Pool({ host: 'postgres', database: 'motovaultpro', user: 'postgres', password: process.env.POSTGRES_PASSWORD }); const redis = new Redis({ host: 'redis' }); const cache = new PlatformCacheService(redis); const repo = new VehicleDataRepository(); const service = new VehicleDataService(cache, repo); // Test getMakes await service.getMakes(pool, 2024); // First call: fetches from DB, caches result // Second call: returns from cache await service.getMakes(pool, 2024); // Test getModels with string parameter await service.getModels(pool, 2024, 'Ford'); // Test getTrims await service.getTrims(pool, 2024, 'Ford', 'F-150'); // Test getEngines await service.getEngines(pool, 2024, 'Ford', 'F-150', 'XLT'); // Test getTransmissions (NEW) await service.getTransmissions(pool, 2024, 'Ford', 'F-150'); ``` --- ## Completion Checklist Before signaling completion: - [ ] All method signatures updated (string parameters, not IDs) - [ ] Return types changed to string[] (removed object types) - [ ] getMakes() updated - [ ] getModels() updated with make parameter - [ ] getTrims() updated with make and model parameters - [ ] getEngines() updated with make, model, trim parameters - [ ] getTransmissions() method added (new) - [ ] Cache service signatures updated - [ ] Cache keys use strings (not IDs) - [ ] Unit tests pass - [ ] Integration tests verify caching works - [ ] TypeScript compiles with no errors - [ ] File documentation updated --- ## Common Issues ### Issue: TypeScript error "Type 'string[]' is not assignable to type 'MakeItem[]'" **Cause**: Cache service still using old types **Solution**: - Update cache service method signatures (Step 7) - Ensure cache methods return `string[] | null` not `MakeItem[] | null` ### Issue: Cache keys collision **Cause**: String-based keys might collide with old ID-based keys if Redis not cleared **Solution**: ```bash # Clear Redis cache docker exec mvp-redis redis-cli FLUSHDB # Or clear specific pattern docker exec mvp-redis redis-cli KEYS "makes:*" | xargs docker exec mvp-redis redis-cli DEL ``` ### Issue: Service tests fail with parameter mismatch **Cause**: Tests still passing old ID parameters **Solution**: - Update test calls to use strings: `service.getModels(pool, 2024, 'Ford')` not `service.getModels(pool, 2024, 1)` --- ## Handoff to Agent 4 Once complete, provide this information: ### Updated Service Contract **Methods**: ```typescript getYears(pool: Pool): Promise getMakes(pool: Pool, year: number): Promise getModels(pool: Pool, year: number, make: string): Promise getTrims(pool: Pool, year: number, make: string, model: string): Promise getEngines(pool: Pool, year: number, make: string, model: string, trim: string): Promise getTransmissions(pool: Pool, year: number, make: string, model: string): Promise ``` **Key Changes for Agent 4**: - All dropdown methods return `string[]` - Parameters use strings (make, model, trim) not IDs - `getTransmissions()` is a new method - Caching layer handles string-based keys ### Verification Command ```bash # Agent 4 can verify service is ready: cd backend && npm run build # Should compile with no errors in vehicle-data.service.ts ``` --- ## Completion Message Template ``` Agent 3 (Platform Service): COMPLETE Files Modified: - backend/src/features/platform/domain/vehicle-data.service.ts - backend/src/features/platform/domain/platform-cache.service.ts Changes Made: - Updated all method signatures to accept strings (not IDs) - Changed return types to string[] (removed object types) - Updated cache keys to use string-based parameters - Added getTransmissions() method with caching - All methods still use Redis caching (TTL unchanged) Verification: ✓ TypeScript compiles successfully ✓ Unit tests pass ✓ Integration tests confirm caching works ✓ Cache keys use string parameters Agent 4 (Vehicles API) can now update controllers to use new service signatures. Breaking Changes for Agent 4: - Service methods accept strings: make, model, trim (not makeId, modelId, trimId) - Service methods return string[] (not {id, name}[]) - New method: getTransmissions() ``` --- **Document Version**: 1.0 **Last Updated**: 2025-11-10 **Status**: Ready for Implementation