FleetPilot — Architectural Decisions
Every significant decision made during the build, with reasoning.
Add new decisions here when they're made.
Infrastructure
| Date |
Decision |
Reason |
| 2026-04-06 |
Separate repos over monorepo |
Professional standard, independent deployment lifecycles, easier onboarding |
| 2026-04-06 |
GCP over AWS |
Cloud Run + BigQuery combo better suited; familiar with both |
| 2026-04-06 |
FastAPI over Django |
API-first, lighter, better for async |
| 2026-04-06 |
Shared DB + RLS over schema-per-tenant |
Solo dev, simplicity wins at this stage |
| 2026-04-06 |
Vercel for frontend |
Next.js first-class support, zero-config deploys, preview per PR |
| 2026-04-06 |
Supabase over Cloud SQL |
Auth + DB + RLS + Storage in one place; no separate auth service needed |
| 2026-04-26 |
Cloud Tasks over Celery + Redis |
Cloud Run scales to zero — persistent Celery workers are wasteful. Cloud Tasks HTTP callbacks work natively with Cloud Run at zero idle cost. Production-ready at scale, no migration needed. |
| 2026-04-06 |
GCS for file storage |
GCP native, presigned URLs, private bucket, europe-west2 for UK data residency |
Architecture
| Date |
Decision |
Reason |
| 2026-04-25 |
Supabase JS direct for standard CRUD |
FastAPI adds a network hop for simple queries. Supabase JS with RLS is simpler, tested, working. |
| 2026-04-25 |
FastAPI only for GCS/Claude API/external services |
FastAPI earns its place when server-side secrets or external APIs are needed. Not for simple CRUD. |
| 2026-04-12 |
Presigned URL upload flow (Option B) |
Frontend uploads direct to GCS. FastAPI only generates the signed URL and registers the document. Cloud Run doesn't proxy file bytes. Cheaper, scales better. |
| 2026-04-25 |
Retired vehicle/customer/booking FastAPI routers |
Frontend never called them — pure dead code. Codified the correct split: FastAPI for GCS/AI/external only. |
Data model
| Date |
Decision |
Reason |
| 2026-04-06 |
Polymorphic documents table |
One table handles all doc types (licences, MOTs, PCNs, agreements) via entity_type + entity_id. Avoids separate tables per entity. |
| 2026-04-10 |
first_name + last_name not full_name |
Personalised comms ("Hi Sam"), sort by last name, formal documents. |
| 2026-04-11 |
Postgres trigger for vehicle status |
Booking → active: vehicle becomes rented. Booking → completed/cancelled: vehicle becomes available. Atomic, impossible to forget, can't be bypassed. |
| 2026-04-11 |
Backend conflict check + Postgres exclusion constraint |
Belt and braces. Backend gives readable 409 error. Constraint catches race conditions. |
| 2026-04-11 |
deposit_gbp added to bookings |
Operators take deposits. payment_status enum already accounted for it. |
| 2026-04-12 |
licence_number nullable on customers |
Existing customers have no licence data. No backfill needed. |
| 2026-04-25 |
pcn_scan_jobs table |
Persistent queue for AI processing jobs. Survives Cloud Run restarts. retry_count enables zombie recovery. |
| 2026-04-26 |
violation_time separate from violation_date |
A vehicle can have two bookings on the same day. Time needed for accurate matching. |
| 2026-04-25 |
total_due_gbp computed not stored |
amount_gbp + admin_fee_gbp computed in application layer. Never stored. No sync issues. |
Frontend
| Date |
Decision |
Reason |
| 2026-04-07 |
Geist font (not Inter) |
Next.js default, clean and modern, kept as-is. |
| 2026-04-08 |
Mobile-first from day one |
Operators are mobile-heavy — handing over keys in a car park. Every screen tested at 390px. |
| 2026-04-08 |
PWA Phase 2, native only if data justifies |
PWA gets 80% of native for 10% of effort. Build native only after real mobile usage data proves the gap. |
| 2026-04-11 |
Bookings expandable rows (not split panel) |
Operators scan — they need all bookings visible at once. Map only takes space when needed. |
| 2026-04-17 |
No tabs on dashboard |
Everything urgent surfaces automatically. Tabs would require hunting for critical information. |
| 2026-04-25 |
Design tokens in @theme |
Eliminates hard-coded hex values. All components consume semantic tokens. Consistent dark mode path. |
| 2026-04-25 |
vaul for mobile bottom sheet |
Radix-compatible, proper drag-to-snap, small bundle. Better than CSS toggle for Live Tracking mobile UX. |
| 2026-05-03 |
DerivedStatus pattern for bookings |
Single source of truth for booking display status. active/upcoming/overdue/returned/cancelled from raw DB status + date logic. |
AI / Claude API
| Date |
Decision |
Reason |
| 2026-04-06 |
Claude API for AI layer |
Document parsing, DM intent reading, location inference — Python AI ecosystem. |
| 2026-04-25 |
Extract-only endpoint separate from create |
POST /violations/scan/extract returns structured data, no DB writes. Form then calls POST /violations. Clean separation of concerns. |
| 2026-04-26 |
Always lead to pre-filled form, never dead end |
Even partial extraction is useful. Operator fills gaps. Better UX than stopping on missing fields. |
| 2026-04-26 |
SHA256 file hash deduplication |
Catches re-uploads before Claude runs. Saves API tokens. Layer 2: reference number check after extraction. |
Security / Compliance
| Date |
Decision |
Reason |
| 2026-04-06 |
Signed URLs with short expiry |
Documents never publicly accessible. 1-hour expiry. Regenerated fresh on every API response. |
| 2026-04-06 |
GCS bucket in europe-west2 |
UK customer data stays in UK. Post-Brexit data residency. |
| 2026-04-12 |
Warn on duplicates, never silently block |
Operator may have legitimate reason to re-process. Always show warning, always give the choice. |
| 2026-04-12 |
Soft deletes everywhere |
archived_at column. No hard deletes. UK GDPR right to erasure is a separate explicit flow. |
| Date |
Decision |
Reason |
| 2026-04-06 |
Linear for project management |
Clean, fast, great GitHub integration. Linear auto-comments on PRs when branch names match ticket IDs. |
| 2026-04-06 |
Miro for product brain |
Visual product thinking, UX decisions, ideas board. Separate from implementation tracking. |
| 2026-04-25 |
Claude Design for UI specification |
Reads codebase, generates on-brand designs, produces dev handoff docs. Sits between parent Claude and VS Code Claude. |
| 2026-05-07 |
fleetpilot-docs repo |
Persistent knowledge base for Claude sessions. CLAUDE.md is the master context file. Eliminates context loss between sessions. |