myodo — User Flows (MVP Beta)

Last Updated: 2026-04-25
Purpose: Engineering handoff document. Each flow shows what the user does, what happens behind the scenes, and what data is created or changed.
Scope: Everything in this document is MVP beta unless explicitly marked “Post-MVP.”

Table of Contents
  1. System Overview
  2. Driver Journeys
    • 2.1 Driver Onboarding (Beta — Shop Invite)
    • 2.2 Add Vehicle (Claim)
    • 2.3 Pair OBD-II Device
    • 2.4 View Service History & Health
    • 2.5 Receive Notifications & Schedule Service
    • 2.6 Manage Shop Connections
    • 2.7 Relinquish / Transfer Vehicle
    • 2.8 Dispute a Record
  3. Shop Journeys
    • 3.1 Shop Onboarding (Beta — Invite Only)
    • 3.2 Internal Admin Dashboard (Beta)
    • 3.3 Upload Repair Orders
    • 3.4 Receive & Handle Service Requests
    • 3.5 Messaging & Appointments
  4. Entity Lifecycle Diagrams
  5. Data Entities Reference
  6. Third-Party Integrations
  7. Record Visibility Rules
  8. Driver App (Glovebox) Views
    • 8.1 App Shell & Navigation
    • 8.2 Vehicles (Home Screen)
    • 8.3 Vehicle Detail Page
    • 8.4 Driver Upload RO
  9. Scope Boundary

1. System Overview

Two products, two user types, one shared data layer.

graph LR subgraph Driver["Driver (Glovebox)"] D1[Add vehicles] D2[View service history] D3[Receive maintenance alerts] D4[Request service from shop] D5[Manage privacy & consent] end subgraph Shop["Shop (Dashboard)"] S1[Upload repair orders] S2[Receive service requests] S3[Message customers] S4[Appointment reminders] end subgraph System["myodo Platform"] P1[Record parsing pipeline] P2[Identity matching] P3[Vehicle health engine] P4[Notification engine] P5[Telemetry ingestion] end D1 --> P2 S1 --> P1 --> P2 --> P3 --> P4 --> D3 D4 --> S2 P5 --> P4

End-to-End Lifecycle

The full loop — how a driver and shop interact through myodo from first contact to recurring service.

flowchart TD subgraph Onboarding["Driver Onboarding (Beta)"] A1[Shop signs up + gets verified] --> A2[Driver visits shop for service] A2 --> A3[Shop tells driver about myodo] A3 --> A4[Shop uploads repair order] A4 --> A5[System parses RO: VIN, customer info, services] A5 --> A6[Provisioned account created] A6 --> A7[Shop sends invite from their email] A7 --> A8[Driver claims account + consent flow] end subgraph Record["Record Flow"] B1[Shop uploads repair order PDF] --> B2[OCR + AI parsing] B2 --> B3[VIN decode + customer info extraction] B3 --> B4[Records grouped under provisioned account] B4 --> B5[Vehicle health updated] end subgraph Loop["Recommendation-to-Revenue Loop"] C1[OBD-II reports mileage] --> C2{Service due?} C2 -->|Yes| C3[Notification sent to driver] C3 --> C4[Driver taps: Schedule with preferred shop] C4 --> C5[Service request lands in shop Dashboard] C5 --> C6[Shop accepts + schedules appointment] C6 --> C7[Service performed] C7 --> C8[Shop uploads new repair order] C8 --> B1 end A8 --> C1 A4 --> B1

The Core Loop

This is the business engine — the cycle that generates recurring revenue for shops and keeps drivers’ vehicles maintained. Everything else in the product exists to support this loop.

flowchart LR A["Service need detected (oil, tires, CEL)"] --> B["Driver notified (email, SMS, in-app)"] B --> C["Driver requests service from preferred shop"] C --> D["Shop receives request, schedules appointment"] D --> E["Service performed, RO uploaded"] E --> F["Record parsed, health updated"] F --> A

2. Driver Journeys

2.1 Driver Onboarding (Beta — Shop Invite)

During beta, driver onboarding is controlled through shop invites. The driver does not create their own account — the system provisions one from a parsed repair order, and the driver claims it.

At scale, drivers will also be able to sign up independently via the website. That flow is out of scope for beta.

Step 1: Shop uploads repair order

flowchart TD A[Driver visits shop for service] --> B[Shop tells driver about myodo] B --> C[Driver agrees to join] C --> D[Shop uploads repair order PDF/image] D --> E[OCR + AI parsing] E --> F{Customer email and phone parsed?} F -->|Yes| G[Provisioned account created] F -->|No| H[Shop notified: missing customer info] H --> I[Shop fills in email and/or phone from their records] I --> G G --> J{Existing provisioned account for this customer?} J -->|Yes: same email or phone| K[New record grouped under existing account] J -->|No| L[New provisioned account created] K --> M[Customer appears in shop invite list] L --> M

What gets parsed from the repair order:

Data created:

StepEntityOperationFields
Store filesource_documentsINSERTfile_url, shop_id, status=pending
Parse resultservice_eventsINSERTvehicle_id, shop_id, date, mileage, services, costs
VehiclevehiclesUPSERT on VINvin, year, make, model, engine, trim
Provisioned accountusersINSERTname, email, phone, type=driver, status=provisioned
Group recordsservice_eventsLink to provisioned user via VIN + identity match
Shop relationshipshop_driver_relationshipsINSERTshop_id, driver_id, is_preferred=true

Step 2: Shop sends invite

flowchart TD A[Shop sees customer in invite list on Dashboard] --> B[Shop clicks Send Invite] B --> C{Shop email configured in profile?} C -->|Yes| D[Invite email sent FROM shop email address] C -->|No| E[Prompt shop to configure email in settings] E --> F[Shop configures email relay/verification] F --> D D --> G[Driver receives invite email from shop]

The invite email is sent from the shop’s own email address, not from myodo. This requires the shop to configure their email in their profile settings. The system uses an email relay mechanism (engineer decides implementation — sender verification, SMTP relay, etc.) so the driver sees an email from a business they trust, not from an unknown platform.

Step 3: Driver claims account

flowchart TD A[Driver opens invite email] --> B{Driver decision} B -->|Claim| C[Verify identity: confirm email + phone] B -->|Reject| R1[Driver clicks reject / request deletion] R1 --> R2[PII scrubbed from provisioned account] R2 --> R3[Anonymized service records retained on VIN] C --> D[Consent flow] D --> D1[Terms of Service] D1 --> D2[Privacy Policy] D2 --> D3[Data Processing consent] D3 --> D4[Communication preferences] D4 --> E[Set up authentication] E --> E1{Auth method?} E1 -->|Google OAuth| F1[Sign in with Google] E1 -->|Email + password| F2[Create password] F1 --> G[Account activated] F2 --> G G --> H[Glovebox populated: vehicle, service records, shop connection]

Data created/modified during claim:

StepEntityOperationFields
Identity confirmedusersUPDATEemail_verified=true, phone_verified=true
Each consent stepconsent_eventsINSERTuser_id, type (tos/privacy/data_processing/comms), method, timestamp
Comms preferencesnotification_preferencesINSERTuser_id, service_reminders (bool), promotional (bool)
Auth setupusersUPDATEauth_provider (google/email), status=active
Account activatedusersUPDATEstatus=active, claimed_at=now()

Data modified on rejection:

StepEntityOperationFields
Reject inviteusersUPDATEstatus=rejected
Scrub PIIusersUPDATEname=NULL, email=NULL, phone=NULL
Retain recordsservice_eventsNo change — records stay, anonymized (no PII link)

Unclaimed accounts

If a provisioned account is never claimed, PII is automatically deleted after a configurable retention period (engineers decide timeline). Anonymized service records are retained on the VIN — the vehicle’s service history has value regardless of whether a specific owner claims it.

stateDiagram-v2 [*] --> provisioned: RO parsed, account created provisioned --> active: Driver claims account provisioned --> rejected: Driver rejects invite provisioned --> expired: PII retention period exceeded rejected --> [*]: PII scrubbed, anonymized records retained expired --> [*]: PII auto-deleted, anonymized records retained active --> active: Normal operation

Error states:


2.2 Add Vehicle (Claim)

Two paths to the same result: driver provides a VIN, system decodes and creates a pending vehicle record.

flowchart TD A[Driver taps Add Vehicle] --> B{Input method?} B -->|License plate| C[Enter plate number + state] C --> C1{Plate-to-VIN lookup} C1 -->|Success| D[Display decoded: year, make, model] C1 -->|Lookup failed| C2[Prompt manual VIN entry] C2 --> E B -->|Manual VIN| E[Enter 17-character VIN] E --> E1{VIN format valid?} E1 -->|No| E2[Show format error, retry] E2 --> E E1 -->|Yes| F[NHTSA vPIC decode] F --> D D --> G{VIN already in drivers garage?} G -->|Yes| G1[Show duplicate warning, stop] G -->|No| H{VIN exists in system?} H -->|No| I[Create vehicle record, ownership=pending] H -->|Yes, unclaimed| I2[Link driver as owner, ownership=pending] H -->|Yes, claimed by another| J[Vehicle claimed by another driver] J --> J1[Offer transfer request option] I --> K[Vehicle appears in Glovebox garage] I2 --> K
StepEntityOperationFields
Plate decode(external API call)plate, state → VIN
NHTSA decode(external API call)VIN → year, make, model, engine, trim
New vehiclevehiclesINSERT (UPSERT on VIN)vin, year, make, model, engine, trim, ownership_status=pending
Ownership linkownership_periodsINSERTvehicle_id, customer_id, started_at=now(), ended_at=NULL
Key rule: Ownership stays pending until a service record uploads that matches BOTH the VIN AND the driver’s identity info (name, email, or phone). Then it flips to verified.

2.3 Pair OBD-II Device

flowchart TD A[Driver taps Add Device] --> B{IMEI input method?} B -->|Photo of label| C[Camera capture, OCR extracts IMEI] B -->|Manual entry| D[Type IMEI] C --> E{IMEI recognized in telemetry system?} D --> E E -->|No| E1[Error: device not found. Contact support.] E -->|Yes| F{Driver has vehicles?} F -->|Yes| G[Select vehicle from garage list] F -->|No| H[Add vehicle first] H --> G G --> I[Optional: enter current odometer for baseline] I --> J[Confirm device + vehicle pairing] J --> K[Device provisioned, telemetry flowing]
StepEntityOperationFields
Pair devicedevicesINSERT/UPDATEimei, vehicle_id, owner_id, status=active, nickname
Baseline mileagemileage_readingsINSERTvehicle_id, reading, source=app, timestamp
Audit trailvehicle_eventsINSERTvehicle_id, event_type=device_coupled, device_id

Telemetry feeds after pairing:


2.4 View Service History & Health

This is the core Glovebox experience. The data assembly rules are critical for engineers.

flowchart TD A[Driver opens vehicle detail] --> B[Fetch service records for VIN] B --> C{For each record: does ownership_period overlap record date?} C -->|Yes| D{Does record PII match driver identity?} D -->|Yes: identity matched| E[Show FULL detail: pricing, parts, labor, shop, findings] D -->|No match| F[Show ANONYMIZED: date, mileage, services — NO pricing] C -->|No: outside ownership| F E --> G[Render unified timeline across all shops] F --> G G --> H[Vehicle dashboard] H --> H1[Service history timeline] H --> H2[Upcoming services: oil, tire, CEL countdowns] H --> H3[Quick action: Schedule with preferred shop]

See Section 7: Record Visibility Rules for the full decision matrix.


2.5 Receive Notifications & Schedule Service

MVP notifications are mileage-based and require an OBD-II device.

flowchart TD A[Telemetry system reports current mileage] --> B{Compare against last known service} B -->|Oil change| C{Mileage delta >= threshold?} B -->|Tire rotation| D{Mileage delta >= threshold?} B -->|CEL detected| E[Device reports check engine light + fault code] C -->|Due| F[Create notification: Oil change due in X miles] D -->|Due| F2[Create notification: Tire rotation due in X miles] E --> F3[Create notification: fault code + plain-language description] F --> G[Deliver via email + SMS + in-app] F2 --> G F3 --> G G --> H[Driver taps notification, deep link to vehicle detail] H --> I[Driver sees Schedule with preferred shop button] I --> J{Preferred shop has Dashboard account?} J -->|Yes| K[Driver types free-text service description] K --> L[Service request sent to shop Dashboard] J -->|No| M[Show business card modal: name, address, phone] M --> N[Driver calls shop directly]
StepEntityOperationFields
Detect needservice_needsINSERTvehicle_id, type (oil/tire/cel), detected_at, current_mileage, last_service_mileage
Send notificationnotificationsINSERTuser_id, service_need_id, channel (email/sms/in_app), status=sent, sent_at
Driver readsnotificationsUPDATEstatus=read, read_at
Submit requestservice_requestsINSERTdriver_id, shop_id, vehicle_id, description (free text), status=pending

Post-MVP: The free-text description becomes a natural language interface that parses into structured line items (like a cart).


2.6 Manage Shop Connections

flowchart TD A[Driver opens Shop Connections settings] --> B[View list of connected shops] B --> C[See which shop is preferred — star icon] B --> D{Action?} D -->|Change preferred shop| E[Select new preferred shop] E --> E1{Revoke cross-shop history from old preferred?} E1 -->|Yes| E2[Old shop loses cross-shop access — keeps own uploads] E1 -->|No| E3[Old shop retains cross-shop access] D -->|Edit permissions for a shop| F[Per-shop toggles] F --> F1[Toggle: allow cross-shop history access — anonymized] F --> F2[Toggle: allow promotional messages] D -->|Revoke shop access| G[Confirm revocation] G --> G1[Shop can no longer see cross-shop records] G --> G2[Shop ALWAYS retains records they uploaded — cannot be revoked]
StepEntityOperationFields
Change preferredshop_driver_relationshipsUPDATEis_preferred=false on old, is_preferred=true on new
Toggle permissionshop_driver_relationshipsUPDATEcross_shop_history_visible (bool), promotional_messages (bool)

2.7 Relinquish / Transfer Vehicle

Owner relinquishes (selling/giving away vehicle)

flowchart TD A["Owner taps 'I no longer own this vehicle'"] --> B[Confirmation dialog] B --> B1{Driver confirms?} B1 -->|Confirm| C[Backend processing] C --> C1[Clear vehicle owner] C --> C2[Close ownership period] C --> C3{OBD-II device paired?} C3 -->|Yes| C4[Uncouple device from vehicle] C3 -->|No| C5[Skip] C4 --> C6[Purge telemetry data] C5 --> D[Vehicle moves to Archived Vehicles — read-only] B1 -->|Cancel| E[No changes]
Relinquish confirmation tells the driver:
  • You will lose live health record, telemetry, and notifications for this vehicle
  • Your historical service records from your ownership period become read-only in “Archived Vehicles”
  • Any paired OBD-II device is uncoupled (device stays on your account)

New owner claims transferred vehicle

flowchart TD A[New owner enters VIN] --> B{VIN status in system?} B -->|Unclaimed — old owner relinquished| C[Claim vehicle normally] B -->|Still claimed by someone| D[Vehicle currently claimed by another driver] D --> E[Offer Submit transfer request] E --> F[New owner provides reason: purchased, inherited, etc.] F --> G[Internal ticket created for human review] G --> H{Human decision} H -->|Approve| I[Force-relinquish old owner + new owner claims] H -->|Reject| J[Notify new owner — cannot transfer]

What new owner sees for pre-ownership records: Anonymized — date, mileage, shop name, services performed, findings. NO pricing, NO previous owner identity. Living Health Record transfers fully (follows VIN).


2.8 Dispute a Record

flowchart TD A[Driver views a parsed service record] --> B[Driver believes record is inaccurate] B --> C[Tap Dispute this record] C --> D[Driver describes what is wrong — free text] D --> E[Dispute submitted] E --> F[Internal ticket created] F --> G[Human reviews: parsed output vs. source document] G --> H{Accurate?} H -->|Parsing was wrong| I[Human corrects record, closes ticket] H -->|Parsing was correct| J[Close ticket, notify driver]
StepEntityOperation
Submit disputerecord_disputesINSERT: record_id, user_id, description, status=open
Resolverecord_disputesUPDATE: status=resolved, resolution_notes
Correct recordservice_eventsUPDATE: corrected fields
Important: Drivers cannot manually edit parsed records. All corrections go through human review. This protects data integrity and serves as a feedback loop for improving the parsing pipeline.

3. Shop Journeys

3.1 Shop Onboarding (Beta — Invite Only)

During beta, shops join by invite only. There are two paths into the system — both converge at the same onboarding flow.

How shops get invited

flowchart TD subgraph Path1["Path A: We invite them (pre-vetted)"] A1[myodo team identifies shop through outreach] --> A2[Team sends invite email with unique sign-up link] end subgraph Path2["Path B: Shop finds us (request access)"] B1[Shop discovers myodo through materials/word-of-mouth] --> B2[Shop fills request form: name, email, phone] B2 --> B3[myodo team receives notification] B3 --> B4[Brief interview to determine beta fit] B4 --> B5{Decision} B5 -->|Approve| B6[Send invite email with unique sign-up link] B5 -->|Not yet| B7[Add to waitlist — follow up later] B5 -->|Not a fit| B8[Polite decline] end A2 --> C[Shop clicks invite link → onboarding flow] B6 --> C

Pre-vetted shops (Path A) have already been screened — no review step after they accept. Request-access shops (Path B) go through the interview before receiving an invite.

Onboarding flow

Onboarding must be completed in a single session — no partial saves. If a shop doesn’t want to lose progress, they need to finish. If onboarding is not completed within 48 hours of clicking the invite link, the myodo team is notified so they can follow up manually.

flowchart TD A[Shop clicks invite link] --> B[Step 1: Shop Profile] B --> B1[Contact name] B1 --> B2[Shop name] B2 --> B3[Phone number] B3 --> B4[Contact email address] B4 --> B5[Business address] B5 --> B6[Hours of operation — structured input] B6 --> B7[Website domain] B7 --> C[Step 2: Legal Agreements] C --> C1[Terms of Service] C1 --> C2[Privacy Policy] C2 --> C3[Data Processing / Service Provider Agreement] C3 --> C4["Checkbox: 'I agree on behalf of [Shop Name]'"] C4 --> C5[Signer name + title — for audit trail] C5 --> D[Step 3: Email Configuration] D --> D1["Explanation: customer invites will be sent from YOUR email address"] D1 --> D2[Shop configures email relay / verification] D2 --> D3["Clear messaging: drivers see an invite from a business they trust, not from an unknown platform"] D3 --> E[Step 4: Set Up Authentication] E --> E1[Engineers determine auth methods — email+password, Google OAuth, etc.] E1 --> F[Step 5: First Repair Order Upload — MANDATORY] F --> F1["Guided prompt: Pick your best customer — someone you know will want to sign up for this"] F1 --> F2["Save a PDF of a service record for this customer"] F2 --> F3[Drag-and-drop or file select uploader] F3 --> F4[System parses RO: customer profile populates in invite list] F4 --> F5[Shop sends first invite to that customer] F5 --> F6["Note: We're happy to come by in person to see how your system works so we can figure out the easiest way to add PDF uploads to your standard workflow"] F5 --> G["Onboarding complete — status: active"] G --> G1[myodo team receives email notification: shop signed up] G --> G2[Shop lands on Dashboard — ready to operate]
Why the first upload is mandatory: We want shops to experience the full loop — upload a record, see the customer appear in their invite list, and send the invite — so they’re confident using the system going forward. This is training, not busywork.
Why no partial saves: If a shop pauses halfway through, they forget to come back. One session keeps momentum and ensures they’re fully set up before they touch the Dashboard.

Hours of operation input

The hours input avoids duplicate effort for days with the same schedule:

flowchart TD A[Do all days have the same hours?] --> B{Response} B -->|Yes| C[Enter hours range] C --> C1{Midday closure?} C1 -->|Yes| C2[Add additional range — e.g. 8am-12pm, 1pm-6pm] C1 -->|No| C3[Single range — e.g. 8am-6pm] B -->|No| D[Show list of days with checkboxes] D --> D1[Select days with same hours — e.g. Mon-Fri] D1 --> D2[Set hours for that batch] D2 --> D3{More days to configure?} D3 -->|Yes| D4[Select next batch — e.g. Sat] D4 --> D5[Set hours for that batch] D5 --> D3 D3 -->|No| D6[Done — unchecked days are closed]

The stored format should be structured (per-day open/close ranges) so it can be displayed consistently across the platform.

Shop user roles

Shops have multiple user accounts with different privilege levels. The person who completes onboarding gets the admin role.

RoleUpload ROsSend customer invitesMessage customersEdit business info / hours / emailAdd/remove shop usersTransfer admin
AdminYesYesYesYesYesCan receive
Service AdvisorYesYesYesNoNoCan give away

Adding service advisors:

flowchart TD A[Admin opens shop user management] --> B[Clicks Add User] B --> C[Enter advisor name + email] C --> D[System sends invite email with link to accept role] D --> E[Advisor clicks link → sets up auth → accepts role] E --> F[Advisor now has Dashboard access with limited privileges]

Admin transfer: If a service advisor signed up first and the shop owner/manager wants to take over, the advisor can transfer admin privileges to the owner. The owner receives and confirms the transfer.

Upload attribution: Every upload is tied to the specific shop user who uploaded it, not just the shop. This provides accountability and audit trail.

Shop statuses (state machine)

stateDiagram-v2 [*] --> invited: myodo sends invite (pre-vetted or approved request) invited --> active: Shop completes onboarding invited --> onboarding_stale: 48hrs without completion onboarding_stale --> active: Shop completes onboarding (late) active --> suspended: Policy / billing issue suspended --> active: Issue resolved active --> churned: Deactivated / terminated

Beta request statuses (separate from shop statuses):

stateDiagram-v2 [*] --> pending: Shop submits request form pending --> interviewing: myodo team reaches out interviewing --> approved: Good fit — send invite interviewing --> waitlisted: Not ready yet interviewing --> declined: Not a fit waitlisted --> approved: Later approved

Data created

StepEntityOperationKey Fields
Request access (Path B)shop_beta_requestsINSERTname, email, phone, status=pending, requested_at
Interview outcomeshop_beta_requestsUPDATEstatus (approved/waitlisted/declined), reviewed_by, reviewed_at
Send inviteshopsINSERTname (from request or manual), status=invited, invite_token, invited_at
Profile setupshopsUPDATEcontact_name, name, phone, email, address, hours (JSON), website, status=invited
Legal agreementsshop_agreementsINSERTshop_id, signer_name, signer_title, signed_at, ip_address, agreement_version
Email configshopsUPDATEemail_relay_config (engineer decides structure)
Auth setupshop_usersINSERTshop_id, user_id, role=admin
First RO uploadsource_documentsINSERTfile_url, shop_id, uploaded_by (shop_user_id), status=pending
Onboarding completeshopsUPDATEstatus=active, activated_at
Team notification(internal email)Shop name, contact info, link to admin dashboard
Stale alert (48hrs)(internal email)Shop name, contact info, invite sent timestamp
Why the processing agreement matters: myodo is a data processor. Without a formal agreement there is no legal basis to receive/store the shop’s customer data. CCPA/CPRA requires a service provider agreement when a business shares consumer PII with a processor. Agreement must be reviewed by a privacy attorney before launch.
Why invite emails come from the shop, not myodo: The invite is sent from the shop’s own email address so the driver receives a message from a business they recognize and trust, not from an unknown platform. The shop verbally confirmed the driver’s interest in-person — the email is the follow-through on that conversation.

3.2 Internal Admin Dashboard (Beta)

An internal-facing tool for the myodo team to manage shops and monitor platform health during beta.

Core views (MVP)

ViewContents
Active ShopsShop name, contact name, email, phone, website, activated_at
Waitlisted ShopsName, email, phone, waitlisted_at, notes from interview
Deletion RequestsUser name (if available), request date, status (pending/completed), associated records

Extra credit (engineer discretion)

ViewContents
Requested ShopsName, email, phone, requested_at, status
Per-Shop MetricsROs uploaded (count), customers invited (count), invites accepted vs. rejected

Deletion request tracking: When a driver rejects an invite and requests data deletion (see Section 2.1 Step 3), or when an unclaimed account expires, the deletion must be logged. The admin dashboard shows these so the team can verify PII was scrubbed and anonymized records were retained correctly.


3.3 Upload Repair Orders

Uploading a repair order is the primary action in the system. It triggers record parsing, vehicle creation, account provisioning, and health updates.

flowchart TD A[Shop uploads PDF/image of repair order] --> B[File stored in cloud storage] B --> C[OCR extracts raw text] C --> D[AI parses text into structured data] D --> E{VIN extracted?} E -->|No| E1[Flag for manual review] E -->|Yes| F[NHTSA vPIC decode: year, make, model] F --> G{Vehicle exists in system?} G -->|No| G1[Create vehicle record] G -->|Yes| G2[Link to existing vehicle] G1 --> H{Customer info parsed? email + phone} G2 --> H H -->|Yes| I{Existing account matches this customer?} H -->|No| H1[Shop notified: missing customer info needed] I -->|Yes: active account| J[Record added to existing account] I -->|Yes: provisioned account| K[Record grouped under provisioned account] I -->|No match| L[New provisioned account created] J --> M[Update Living Health Record] K --> M L --> M M --> N[Check for service notifications — oil change, tire rotation] N --> O[Record visible in shop Dashboard] O --> P{Account active or provisioned?} P -->|Active| Q[Record also visible in driver Glovebox] P -->|Provisioned| R[Record held until account is claimed]

What gets extracted from a repair order:

StepEntityOperation
Store filesource_documentsINSERT: file_url, shop_id, status=pending
OCR completesource_documentsUPDATE: ocr_text, status=parsed
VehiclevehiclesUPSERT on VIN: vin, year, make, model, engine, trim
Parse resultservice_eventsINSERT: vehicle_id, shop_id, date, mileage, services (JSON), costs, dtcs, findings
Account (if new customer)usersINSERT: name, email, phone, type=driver, status=provisioned
Account (if existing)service_eventsLink to existing user
Shop relationshipshop_driver_relationshipsINSERT (if new): shop_id, driver_id, is_preferred=true
Health updateliving_health_docsUPSERT: vehicle_id, per-item status updates
Notificationsservice_needsINSERT (if triggered, only for active accounts)

3.4 Receive & Handle Service Requests

flowchart TD A[Driver submits service request from Glovebox] --> B[Request appears in shop Dashboard] B --> C[Shop sees: customer name, vehicle, free-text description] C --> D{Shop action?} D -->|Accept| E[Shop sends message to customer — can propose date/time] D -->|Decline| F[Shop sends decline message with reason] D -->|Need more info| G[Shop messages customer with questions] E --> H[Customer receives notification] F --> H G --> H H --> I[Conversation continues via messaging thread]

3.5 Messaging & Appointments

flowchart TD A[Shop Dashboard] --> B{Action?} B -->|Send message| C[Select customer from customer list] C --> D[Compose message in thread] D --> E[Customer gets notification — email/SMS/in-app] B -->|Create appointment| F[Select customer + vehicle] F --> G[Set date, time, service type, notes] G --> H[Appointment created] H --> I[Reminder sent to customer before appointment] B -->|View customers| J[Customer list — all customers linked to shop] J --> K[Click customer: see vehicles + service history from this shop]
ActionEntityOperation
Send messagemessagesINSERT: thread_id, sender_id, sender_type, content, sent_at
New threadmessage_threadsINSERT: shop_id, driver_id, subject
Create appointmentappointmentsINSERT: shop_id, driver_id, vehicle_id, scheduled_at, service_type, notes, status=scheduled
Send remindernotificationsINSERT: type=appointment_reminder

4. Entity Lifecycle Diagrams

Driver Account

stateDiagram-v2 [*] --> provisioned: RO parsed, account created provisioned --> active: Driver claims account provisioned --> rejected: Driver rejects invite provisioned --> expired: PII retention period exceeded rejected --> [*]: PII scrubbed, anonymized records retained expired --> [*]: PII auto-deleted, anonymized records retained active --> active: Normal operation

Vehicle Ownership

stateDiagram-v2 [*] --> pending: Driver adds vehicle (plate or VIN) pending --> verified: Identity-matched record uploaded verified --> verified: Normal operation verified --> relinquished: Owner taps I sold this vehicle relinquished --> pending: New owner claims VIN pending --> contested: VIN already claimed by another contested --> pending: Transfer approved by human review contested --> [*]: Transfer rejected

Source Document (Repair Order)

stateDiagram-v2 [*] --> uploaded: File received uploaded --> processing_ocr: Queued for OCR processing_ocr --> processing_parse: OCR complete, LLM parsing processing_parse --> parsed: Structured data extracted processing_parse --> error: Parse failed error --> processing_ocr: Retry / reparse parsed --> disputed: Driver disputes accuracy disputed --> parsed: Human resolves dispute

Service Request

stateDiagram-v2 [*] --> pending: Driver submits request pending --> accepted: Shop accepts pending --> declined: Shop declines pending --> info_requested: Shop needs more info info_requested --> pending: Driver provides info accepted --> scheduled: Appointment created scheduled --> completed: Service performed

Shop Account

stateDiagram-v2 [*] --> invited: myodo sends invite (pre-vetted or approved request) invited --> active: Shop completes onboarding invited --> onboarding_stale: 48hrs without completion onboarding_stale --> active: Shop completes onboarding (late) active --> suspended: Policy / billing issue suspended --> active: Issue resolved active --> churned: Deactivated / terminated

5. Data Entities Reference

This is not a schema — the engineer owns the data model. These are the entities the flows above require, with the relationships and key fields that must exist.

Entity Relationship Diagram

erDiagram users ||--o{ vehicles : "owns (via ownership_periods)" users ||--o{ consent_events : "logs consent" users ||--o{ notification_preferences : "sets preferences" users ||--o{ devices : "owns" users ||--o{ service_requests : "submits" users ||--o{ notifications : "receives" vehicles ||--o{ ownership_periods : "tracks owners" vehicles ||--o{ service_events : "has service history" vehicles ||--o{ source_documents : "has uploaded records" vehicles ||--o{ living_health_docs : "has health record" vehicles ||--o{ health_observations : "has findings" vehicles ||--o{ mileage_readings : "has odometer data" vehicles ||--o{ trips : "has trip data" vehicles ||--o{ vehicle_events : "has audit trail" vehicles ||--o{ service_needs : "has detected needs" vehicles ||--o{ devices : "paired with" shops ||--o{ shop_users : "has staff" shops ||--o{ source_documents : "uploads" shops ||--o{ service_events : "associated with" shops ||--o{ shop_agreements : "signs" shops ||--o{ shop_assets : "receives" shops ||--o{ appointments : "schedules" shops ||--o{ message_threads : "participates in" shop_users }o--|| users : "is a user" shop_driver_relationships }o--|| shops : "connects to" shop_driver_relationships }o--|| users : "connects to" source_documents ||--o| service_events : "parsed into" service_events ||--o{ health_observations : "generates" service_events ||--o{ record_disputes : "can be disputed" service_requests }o--|| shops : "sent to" service_requests }o--|| vehicles : "for vehicle" message_threads ||--o{ messages : "contains" message_threads }o--|| shops : "involves" message_threads }o--|| users : "involves" appointments }o--|| shops : "at shop" appointments }o--|| users : "for driver" appointments }o--|| vehicles : "for vehicle" users { string id PK string name string email string phone enum type "driver | shop_admin" enum status "provisioned | active | rejected | expired" string auth_provider "google | email" datetime claimed_at "NULL until claimed" } vehicles { string id PK string vin UK "UNIQUE — universal key" string year string make string model string engine string trim string customer_id FK enum ownership_status "pending | verified" } shops { string id PK string name string contact_name string email string phone string address json hours "structured per-day ranges" string website enum status "invited | onboarding_stale | active | suspended | churned" datetime invited_at datetime activated_at } shop_users { string id PK string shop_id FK string user_id FK enum role "admin | advisor" enum status "invited | active | disabled" datetime invited_at datetime accepted_at } shop_beta_requests { string id PK string name string email string phone enum status "pending | interviewing | waitlisted | approved | declined" datetime requested_at } deletion_requests { string id PK string user_id FK enum source "invite_rejection | account_expiry | user_request" enum status "pending | completed" datetime requested_at datetime completed_at } ownership_periods { string id PK string vehicle_id FK string customer_id FK datetime started_at datetime ended_at "NULL if current owner" } source_documents { string id PK string file_url string shop_id FK "nullable — NULL for driver uploads" string driver_id FK "nullable — NULL for shop uploads" string uploaded_by FK "shop_user_id for shop uploads" text ocr_text enum status "uploaded | processing_ocr | processing_parse | parsed | error" } service_events { string id PK string vehicle_id FK string shop_id FK string source_document_id FK date event_date int mileage json services text summary "AI-generated one-liner, stored at parse time" decimal total_cost json dtcs text findings } service_requests { string id PK string driver_id FK string shop_id FK string vehicle_id FK text description enum status "pending | accepted | declined | scheduled | completed" } shop_driver_relationships { string shop_id FK string driver_id FK bool is_preferred bool cross_shop_history_visible bool promotional_messages } devices { string imei PK string vehicle_id FK string owner_id FK enum status "active | disabled" string nickname }

Users & Auth

EntityKey FieldsNotes
usersid, name, email, phone, type (driver/shop_admin), status (provisioned/active/rejected/expired), auth_provider, claimed_atDrivers can be provisioned (from RO parse) or active (claimed). See 2.1 for lifecycle.
consent_eventsuser_id, type, method, timestampImmutable log — every consent action recorded
notification_preferencesuser_id, service_reminders, promotionalPer-user delivery preferences

Vehicles & Ownership

EntityKey FieldsNotes
vehiclesvin (UNIQUE), year, make, model, engine, trim, customer_id, ownership_statusVIN is the universal key
ownership_periodsvehicle_id, customer_id, started_at, ended_atTracks who owned when — drives record visibility
vehicle_authorized_usersvehicle_id, user_id, relationshipSpouse, family, etc. — requires verification

Service Records & Health

EntityKey FieldsNotes
source_documentsid, file_url, shop_id, uploaded_by (shop_user_id), ocr_text, status, uploaded_atThe raw uploaded file + OCR output. Attributed to specific shop user.
service_eventsid, vehicle_id, shop_id, source_document_id, event_date, mileage, services (JSON), total_cost, dtcs, findingsThe parsed, structured record
living_health_docsvehicle_id, items (JSON: per-item status)Running health status per vehicle — follows VIN, not owner
health_observationsid, vehicle_id, service_event_id, item_id, status, measurement, notesIndividual inspection findings
record_disputesid, service_event_id, user_id, description, status, resolutionDriver-submitted accuracy disputes

Shops, Users & Relationships

EntityKey FieldsNotes
shopsid, name, contact_name, email, phone, address, hours (JSON), website, status, invited_at, activated_atStatus drives what the shop can do. Hours stored as structured per-day ranges.
shop_usersid, shop_id, user_id, role (admin/advisor), status (invited/active/disabled), invited_at, accepted_atMulti-user shops. First onboarder gets admin. Uploads attributed via this.
shop_beta_requestsid, name, email, phone, status (pending/interviewing/waitlisted/approved/declined), requested_atRequest-to-join form submissions. Separate from shop records.
shop_agreementsshop_id, signer_name, signer_title, signed_at, ip_address, agreement_versionImmutable audit record
shop_assetsshop_id, qr_code_url, invite_linkGenerated on activation
shop_driver_relationshipsshop_id, driver_id, is_preferred, cross_shop_history_visible, promotional_messagesPer-shop permissions controlled by driver
deletion_requestsid, user_id, source (invite_rejection/account_expiry/user_request), status (pending/completed), requested_at, completed_atTracks all PII deletion events for compliance

Devices & Telemetry

EntityKey FieldsNotes
devicesimei, vehicle_id, owner_id, status, nickname, last_seenOBD-II hardware units
mileage_readingsvehicle_id, reading, source (device/app/repair_order), timestampOdometer snapshots from multiple sources
tripsvehicle_id, device_id, started_at, ended_at, distanceIgnition on → off events
vehicle_eventsvehicle_id, event_type, device_id, timestampAudit trail: couple/uncouple/disable

Notifications & Requests

EntityKey FieldsNotes
service_needsvehicle_id, type (oil/tire/cel), detected_at, current_mileage, last_service_mileageDetected maintenance needs
notificationsuser_id, service_need_id, channel, status (sent/read/acted_on), sent_atDelivery tracking
service_requestsdriver_id, shop_id, vehicle_id, description, statusFree-text requests (MVP)
appointmentsshop_id, driver_id, vehicle_id, scheduled_at, service_type, notes, statusScheduled service visits

Messaging

EntityKey FieldsNotes
message_threadsid, shop_id, driver_id, subject, created_atOne thread per shop-driver pair per topic
messagesid, thread_id, sender_id, sender_type, content, sent_atIndividual messages in a thread

6. Third-Party Integrations

IntegrationUsed InPurposeMVP?
Plate-to-VIN APIVehicle claiming (2.2)Decode license plate → VINYes
NHTSA vPICVehicle claiming (2.2)Decode VIN → year, make, model, engine, trimYes
Google Document AIRecord upload (3.3)OCR — extract text from PDF/imageYes
OpenAI GPT-4oRecord upload (3.3)Parse OCR text into structured service dataYes
Email serviceNotifications (2.5), onboardingTransactional email deliveryYes
SMS serviceNotifications (2.5)SMS delivery for opted-in usersYes
Cloudflare WorkersTelemetry (2.3)OBD-II data ingestion — firm infrastructure decisionYes

7. Record Visibility Rules

This is the core privacy model. Every record access must evaluate these rules.

flowchart TD A[Request to view a service record] --> B{Who is requesting?} B -->|Driver| C{Ownership period overlaps record date?} C -->|No overlap| D[ANONYMIZED] C -->|Yes| E{Record PII matches driver identity?} E -->|Yes| F[FULL VIEW] E -->|No| D B -->|Shop| G{Shop uploaded this record?} G -->|Yes| H[FULL VIEW] G -->|No| I{Driver granted cross-shop access?} I -->|No| J[NO ACCESS] I -->|Yes| K[ANONYMIZED — no pricing, labor, or shop names]

What each view includes

ViewShowsHides
FULLDate, mileage, shop, services, parts, labor, pricing, customer name, findings, DTCsNothing
ANONYMIZED (driver)Date, mileage, shop name, services performed, findingsPricing, parts cost, labor cost, customer identity
ANONYMIZED (cross-shop)Date, mileage, services performed, findingsPricing, labor times, other shop’s name, other shop’s contact info
NO ACCESSNothingEverything

Key invariants

  1. A shop always sees records they uploaded — this cannot be revoked
  2. Cross-shop records shown to shops are anonymized to protect competitive data (no pricing, no shop names)
  3. After vehicle transfer, the new owner sees pre-ownership records as anonymized
  4. The Living Health Record (inspection statuses) is vehicle-scoped — always shows full history regardless of ownership
  5. OBD-II telemetry (location, trips) is never transferred to a new owner — purged on relinquish

8. Driver App (Glovebox) Views

Sections 1–7 describe what happens behind the scenes. This section describes what the driver actually sees and interacts with.

8.1 App Shell & Navigation

Entry: Driver receives a business card with a QR code linking to the myodo website, or navigates to the URL manually. QR code / URL → login screen → home.

Layout: Main content area with a persistent side navigation bar.

Side nav items:

Nav ItemDestination
VehiclesHome screen — vehicle card grid (default)
DevicesOBD-II device management
Request ServiceService request form (to preferred shop)
Upload RODriver-uploaded repair orders
SettingsAccount, privacy, shop connections, notification preferences

On mobile, side nav collapses to a hamburger menu.


8.2 Vehicles (Home Screen)

The home screen is a grid of vehicle cards — one card per vehicle in the driver’s garage.

Vehicle card contents

┌─────────────────────────────────────────┐
│  2019 Honda Accord EX-L                 │
│                                         │
│  Next oil change due at 67,500 mi       │
│  Due in 1,230 mi              (device)  │
│                                         │
└─────────────────────────────────────────┘
ElementSourceNotes
Year, make, modelvehicles (from VIN decode)Always shown
Next oil change due at X miCalculated: last oil change mileage + 5,000Shown if oil change record exists
Due in X miCalculated: due mileage − current device odometerOnly for vehicles with active OBD-II device

If no oil change record exists for a vehicle, the card shows a prompt instead of the oil change status (see 8.3).

Click a card → navigate to vehicle detail page (8.3).


8.3 Vehicle Detail Page

Two-column layout. On mobile, the right column stacks on top — upcoming services and actionable items are prioritized above service history.

Header

ElementWith deviceWithout device
Year, make, modelAlwaysAlways
OdometerCurrent reading from device (live)Mileage from last service record, labeled “Last service: X mi”

Service records list

Each record appears as a row. Records are ordered by date, most recent first.

Owned record row (identity matched, within ownership period):

FieldSource
Dateservice_events.event_date
Mileageservice_events.mileage
Priceservice_events.total_cost
AI summaryservice_events.summary (generated at parse time, stored)

Anonymized record row (no identity match, or outside ownership period):

FieldSourceNotes
Dateservice_events.event_dateShown
Mileageservice_events.mileageShown
PriceHidden
AI summaryservice_events.summaryShown (summary describes services, not pricing)
Shop nameHidden
Visual indicatorBadge or icon indicating “anonymized record”

No “View Original” link on anonymized records. Only the record owner can access the source PDF/image. Exposing the source document would defeat the purpose of anonymization.

Record drill-down (accordion)

Three levels of detail, each expanding on click:

flowchart TD A["Record row: date, mileage, price, AI summary"] -->|Click| B["Expanded: list of jobs performed"] B --> B1["Job 1: Oil & Filter Change"] B --> B2["Job 2: Tire Rotation"] B --> B3["Job 3: Multi-Point Inspection"] B1 -->|Click| C1["Parts & labor for this job"] C1 --> P1["Part: Mobil 1 5W-30 Full Synthetic - 5 qt - $42.99"] C1 --> P2["Part: OEM Oil Filter #15400-PLM-A02 - 1 - $8.49"] C1 --> L1["Labor: Oil & filter change - 0.5 hr - $35.00"] B2 -->|Click| C2["Parts & labor for this job"] C2 --> L2["Labor: Tire rotation - 0.3 hr - $25.00"]

Parts detail fields (as much as parsed):

FieldExampleNotes
Part description“Mobil 1 5W-30 Full Synthetic”Always shown if parsed
Part number#15400-PLM-A02Shown if parsed
BrandMobil 1Shown if parsed
Quantity5 qtShown if parsed
Price$42.99Only on owned records

Labor detail fields:

FieldExampleNotes
Description“Oil & filter change”Always shown
Time0.5 hrOnly on owned records
Rate/price$35.00Only on owned records

View Original Document: Button or link at the bottom of the expanded record. Opens the source PDF/image in a new tab. Only visible on owned records.

Upcoming services panel (right column)

For beta, this panel shows one thing: the next oil change.

flowchart TD A{Oil change record exists for this vehicle?} A -->|Yes| B[Calculate: last oil change mileage + 5,000] B --> C{Vehicle has active device?} C -->|Yes| D["Display: Next oil change due at X mi + Due in X mi"] C -->|No| E["Display: Next oil change due at X mi"] A -->|No| F["Prompt: No oil change on record"] F --> F1["Upload a record or ask your shop to upload your most recent oil change"] F1 --> F2["Upload Record button -> Upload RO page"] F1 --> F3["Request from Shop button -> Request Service page"]

Oil change detection: The parsing pipeline must identify oil changes in service records. When detected, the system stores the mileage and calculates the next due point.

ScenarioDisplay
Oil change on record, device installed“Next oil change due at 67,500 mi” + “Due in 1,230 mi” + [Schedule Service]
Oil change on record, no device“Next oil change due at 67,500 mi” + [Schedule Service]
No oil change record“No oil change on record” + prompt to upload or request

The “Schedule Service” button links to the Request Service flow with the preferred shop pre-selected and “oil change” pre-filled.

Post-MVP: This panel expands to show tire rotation, brake inspection, and other service reminders powered by the Living Health Record and manufacturer intervals.

8.4 Driver Upload RO

Drivers can add their own repair records from any shop — even shops that aren’t on the myodo platform.

Use cases:

Upload methods

flowchart TD A[Driver wants to add a record] --> B{Method} B -->|File upload| C[Drag-and-drop or file select] C --> D[PDF or image accepted] B -->|Camera capture| E[Take photo of paper record] E --> D B -->|Email forward| F["Driver forwards shop email to their myodo ingest address"] F --> G[System extracts PDF attachment] G --> D D --> H[Same parsing pipeline as shop uploads] H --> I[Record attributed to driver, linked to vehicle via VIN]

Email forwarding: Each driver gets a unique ingest email address (e.g., records+{token}@myodo.co or similar — engineer decides format). The driver forwards an email from their shop that contains a PDF attachment. The system extracts the attachment and runs it through the standard parsing pipeline. The record is attributed to the driver.

Key differences from shop uploads:

StepEntityOperationKey Fields
Upload filesource_documentsINSERTfile_url, driver_id, shop_id=NULL (unless parseable), status=pending
Parseservice_eventsINSERTvehicle_id (matched by VIN), summary, services, costs
Vehicle linkservice_eventsLink to vehicle in driver’s garage via VIN match

9. Scope Boundary

What’s NOT in MVP Beta

Listed here so engineers can design with awareness but not build them yet.

FeatureWhy DeferredDesign Consideration
Living Health Record UINext major feature after launchData model and extraction logic should be designed now — the 40-item taxonomy across 8 categories. Just don’t build the driver-facing UI yet.
Manufacturer service intervals (Motor)Requires Motor API integrationMVP uses simple mileage deltas for oil/tire. Data model should accommodate OEM-specific schedules later.
Fleet managementSeparate user categoryDriver data model should support multi-vehicle from day one. Fleet-specific reporting/bulk management is deferred.
NLP service request interfaceComplex NL parsingMVP uses free-text description. Structured line-item parsing is post-MVP.
CCPA/CPRA self-serviceNeeds legal reviewBuild the consent logging and data export infrastructure. Self-service UI is post-MVP but the data to support it should exist.
Forensic audit logScope too large for betaDesign immutable event logging from the start — retrofitting is painful.

What IS in MVP Beta (Checklist)