feat: Vehicle association and record creation (#149) #158

Closed
opened 2026-02-13 03:52:22 +00:00 by egullickson · 1 comment
Owner

Relates to #149

Scope

Handle vehicle association logic and create fuel log or maintenance records from OCR results.

Vehicle Association

  • Single vehicle: Auto-associate with the user's only vehicle via VehiclesService.getUserVehicles(userId). If exactly 1 vehicle, use it.
  • Multiple vehicles: Cannot auto-associate. Insert into pending_vehicle_associations table with extracted_data JSONB and document_id. Create in-app notification prompting user to select vehicle.
  • No vehicles: Send error email - user must add a vehicle first.

Record Creation

  • Fuel log: Map OCR ReceiptExtractionResponse fields to CreateFuelLogRequest:
    • merchantName -> stationName
    • transactionDate -> date
    • totalAmount -> totalCost
    • fuelQuantity -> gallons
    • pricePerUnit -> pricePerGallon
    • fuelGrade -> grade
  • Maintenance record: Map OCR ReceiptExtractionResponse fields to CreateMaintenanceRecordRequest:
    • merchantName -> shopName
    • transactionDate -> date
    • totalAmount -> cost
    • Service name -> category + subtypes
    • odometer -> odometerReading
    • Set receiptDocumentId to stored document ID

Files

  • Logic within backend/src/features/email-ingestion/domain/email-ingestion.service.ts (extends service from sub-issue #3)

Acceptance Criteria

  • Single-vehicle users have records auto-created
  • Multi-vehicle users get pending association + in-app notification
  • No-vehicle users receive error email
  • Fuel log created with correct field mapping
  • Maintenance record created with correct field mapping and receipt document linked
Relates to #149 ## Scope Handle vehicle association logic and create fuel log or maintenance records from OCR results. ### Vehicle Association - **Single vehicle**: Auto-associate with the user's only vehicle via `VehiclesService.getUserVehicles(userId)`. If exactly 1 vehicle, use it. - **Multiple vehicles**: Cannot auto-associate. Insert into `pending_vehicle_associations` table with extracted_data JSONB and document_id. Create in-app notification prompting user to select vehicle. - **No vehicles**: Send error email - user must add a vehicle first. ### Record Creation - **Fuel log**: Map OCR `ReceiptExtractionResponse` fields to `CreateFuelLogRequest`: - merchantName -> stationName - transactionDate -> date - totalAmount -> totalCost - fuelQuantity -> gallons - pricePerUnit -> pricePerGallon - fuelGrade -> grade - **Maintenance record**: Map OCR `ReceiptExtractionResponse` fields to `CreateMaintenanceRecordRequest`: - merchantName -> shopName - transactionDate -> date - totalAmount -> cost - Service name -> category + subtypes - odometer -> odometerReading - Set `receiptDocumentId` to stored document ID ### Files - Logic within `backend/src/features/email-ingestion/domain/email-ingestion.service.ts` (extends service from sub-issue #3) ## Acceptance Criteria - [ ] Single-vehicle users have records auto-created - [ ] Multi-vehicle users get pending association + in-app notification - [ ] No-vehicle users receive error email - [ ] Fuel log created with correct field mapping - [ ] Maintenance record created with correct field mapping and receipt document linked
egullickson added the
status
backlog
type
feature
labels 2026-02-13 03:52:49 +00:00
egullickson added this to the Sprint 2026-02-02 milestone 2026-02-13 03:52:59 +00:00
egullickson added
status
in-progress
and removed
status
backlog
labels 2026-02-13 14:48:41 +00:00
Author
Owner

Milestone: Vehicle Association and Record Creation

Phase: Execution | Agent: Feature Agent | Status: PASS

Changes

Updated backend/src/features/email-ingestion/domain/email-ingestion.service.ts with:

Vehicle Association (3-way branching)

  • No vehicles (0): Sends error email via receipt_failed template telling user to add a vehicle first
  • Single vehicle (1): Auto-associates and creates the actual fuel log or maintenance record
  • Multiple vehicles (2+): Creates pending association with in-app notification (existing behavior)

Fuel Log Creation

  • Maps ExtractedReceiptData to EnhancedCreateFuelLogRequest
  • vendor -> locationData.stationName
  • date -> dateTime (falls back to current date)
  • gallons -> fuelUnits
  • pricePerGallon -> costPerUnit (derives from total/gallons if missing)
  • fuelType -> mapped to FuelType enum (gasoline/diesel/electric)
  • odometerReading -> odometerReading

Maintenance Record Creation

  • Maps ExtractedReceiptData to CreateMaintenanceRecordRequest
  • shopName or vendor -> shopName
  • date -> date
  • total -> cost
  • category -> mapped to MaintenanceCategory (routine_maintenance/repair/performance_upgrade)
  • subtypes -> validated against category with case-insensitive matching and fallback to first valid subtype
  • odometerReading -> odometerReading
  • description -> notes
  • Sets receiptDocumentId when available

Error Handling

  • Record creation failure is graceful: logs error, sends "partially processed" notification
  • Notification includes referenceId pointing to the created record for navigation

Validation

  • TypeScript: 0 errors
  • ESLint: 0 errors (0 new warnings)
  • Commit: fce6075 on issue-16-maintenance-receipt-upload-ocr

Verdict: PASS | Next: Quality review

## Milestone: Vehicle Association and Record Creation **Phase**: Execution | **Agent**: Feature Agent | **Status**: PASS ### Changes Updated `backend/src/features/email-ingestion/domain/email-ingestion.service.ts` with: **Vehicle Association (3-way branching)** - **No vehicles (0)**: Sends error email via `receipt_failed` template telling user to add a vehicle first - **Single vehicle (1)**: Auto-associates and creates the actual fuel log or maintenance record - **Multiple vehicles (2+)**: Creates pending association with in-app notification (existing behavior) **Fuel Log Creation** - Maps `ExtractedReceiptData` to `EnhancedCreateFuelLogRequest` - `vendor` -> `locationData.stationName` - `date` -> `dateTime` (falls back to current date) - `gallons` -> `fuelUnits` - `pricePerGallon` -> `costPerUnit` (derives from total/gallons if missing) - `fuelType` -> mapped to `FuelType` enum (gasoline/diesel/electric) - `odometerReading` -> `odometerReading` **Maintenance Record Creation** - Maps `ExtractedReceiptData` to `CreateMaintenanceRecordRequest` - `shopName` or `vendor` -> `shopName` - `date` -> `date` - `total` -> `cost` - `category` -> mapped to `MaintenanceCategory` (routine_maintenance/repair/performance_upgrade) - `subtypes` -> validated against category with case-insensitive matching and fallback to first valid subtype - `odometerReading` -> `odometerReading` - `description` -> `notes` - Sets `receiptDocumentId` when available **Error Handling** - Record creation failure is graceful: logs error, sends "partially processed" notification - Notification includes `referenceId` pointing to the created record for navigation ### Validation - TypeScript: 0 errors - ESLint: 0 errors (0 new warnings) - Commit: `fce6075` on `issue-16-maintenance-receipt-upload-ocr` *Verdict*: PASS | *Next*: Quality review
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#158