Skip to content

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.

Tooling

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.