Version: 1.2
Date: June 2026
Status: Draft for Review
This document is the authoritative specification for ElaRide MVP. It defines what the system does and how well it must do it. Implementation details — field names, API schemas, database column types — belong in the System Design Document (SDD).
The client's wish list is large. We launch only the features that are core to the brand promise: a passenger can be safely booked, a driver can accept and complete the ride, and operations can manage everything in between. Everything else is Phase 2. This maximises speed-to-market, reduces initial cost, and lets real user feedback guide what gets built next.
In scope:
Explicitly out of MVP:
| Term | Definition |
|---|---|
| Guardian | Adult who books and pays for rides — parent, caregiver, or self-booking adult |
| Dependent | The passenger managed under a guardian account (typically a minor or care recipient) |
| Leg | One direction of a journey. Round trip = 2 legs = 2 rides |
| ElaAbo | Monthly subscription with a fixed leg quota |
| ElaRide+ | Advance-reservation single trip (not on-demand) |
| Safe Handover | Structured pickup/dropoff for minors, included in every minor ride |
| Pickup PIN | 4-digit code set by guardian; validated by driver at boarding |
| Guardian Mode | Real-time tracking, SOS, and safety tools active during a ride |
| Trusted Circle | Up to 4 emergency contacts per dependent set by the guardian |
| Ops Dashboard | Internal web application for admin, dispatcher, and support roles |
| PBefG | Personenbeförderungsgesetz (German passenger transport law) |
| HGB | Handelsgesetzbuch (German commercial code) |
| ArbZG | Arbeitszeitgesetz (German Working Hours Act) |
Recurring monthly subscription. Fixed routes, fixed times, same driver whenever possible. Designed for school runs, therapy, sports, and regular routines.
| Plan | Legs / Month | Base price ≤8 km | Monthly total | Extra per km >8 km |
|---|---|---|---|---|
| ElaAbo 12 | 12 | TBC ⚠️ | TBC ⚠️ | €2.20 |
| ElaAbo 24 | 24 | TBC ⚠️ | TBC ⚠️ | €2.20 |
| ElaAbo 40 | 40 | TBC ⚠️ | TBC ⚠️ | €2.20 |
Legs consumed beyond the monthly quota are billed at a configurable overage rate. The system supports configurable per-plan pricing; no values are hard-coded.
Advance-reservation only. Not on-demand. The UI must never present it as instant mobility.
| Component | Rate |
|---|---|
| Base fare (≤8 km) | €24.00 |
| Extra km (>8 km) | €2.20 / km |
| Waiting time | €0.50 / min |
Booking windows:
Add-ons are independent selections — there is no combined type. If multiple add-ons are selected, the billing engine handles time-window interactions between them.
| Add-on | Free tolerance | Billable rate |
|---|---|---|
| Safe Handover (minors only) | 5 minutes | Included — never charged |
| Wait+ | None beyond Safe Handover tolerance | TBC ⚠️ / 15-min block |
| Begleitung+ | 10 minutes | €12.00 flat, then TBC ⚠️ / 15-min block |
| Return+ | — | Standard leg price for return |
Safe Handover for minors includes: arrival confirmation, short handover at entrance or reception, confirmation of entry, app checkout, guardian notification, and PIN validation. The 5-minute tolerance is always included and never charged.
| Channel | Flow |
|---|---|
| Rider app (mobile) | Primary channel for all advance bookings |
| Rider web app (browser) | Secondary channel; same booking flow via browser |
| Phone | Guardian calls ops; operator creates booking in ops dashboard |
| Click-to-chat deep link opens WhatsApp to ops number; operator creates booking manually |
| Time before pickup | Guardian-initiated fee |
|---|---|
| ≥24 hours | Free |
| 3–24 hours | 50% of ride price |
| <3 hours or no-show | 100% |
If ElaRide cancels: full refund plus free rebooking to next available slot.
The system has two distinct user populations: passengers and operators. Within passengers, there are three access levels. Within operators, there are three functional responsibilities. Six roles is the correct minimum — fewer would require ad-hoc permission flags; more would be over-engineering for a small team.
| Role | Who | Access surface |
|---|---|---|
guardian |
Parents, caregivers, self-booking adults. Billing owner. | Rider app, rider web app, limited ops actions on own rides |
dependent |
Passengers aged 12+ with guardian-granted limited login | Rider app (restricted views only) |
driver |
Female employee. Sees minimum necessary ride data. | Driver app only |
dispatcher |
Assigns drivers, manages live board, route optimization | Ops dashboard |
support |
Handles incidents, communicates with guardians, escalates | Ops dashboard |
admin |
Full system access — rides, drivers, billing, config, KPIs | Ops dashboard |
Account types vs. roles: "Parent", "caregiver", and "self-booking adult" are profile metadata (account_type field), not separate roles. All three use the guardian role and are treated identically by the system.
No database permission tables are needed. Storing roles, permissions, and role-permission mappings in database tables is the correct pattern when an admin UI must configure permissions dynamically (multi-tenant SaaS, enterprise platforms). ElaRide's permissions are fixed business rules that do not change at runtime — they are encoded in the application. A role enum on the User record is sufficient for role storage.
Access control is enforced in three complementary layers:
Layer 1 — Route guards (NestJS, zero DB cost)
At login the JWT payload carries {userId, profileId, role, familyId}. Custom @Roles() decorators on controllers declare the permitted roles. A global RolesGuard validates the JWT claim before any database interaction. This handles coarse-grained access — a driver cannot reach a payment endpoint regardless of any other factor.
Layer 2 — Resource-level authorization (CASL)
Route guards answer "can a driver reach this endpoint?" but not "can this guardian access this specific ride?" That requires attribute-based checks against resource ownership. The @casl/ability library is used via a CaslAbilityFactory that, given a user object, builds an ability rule set encoding conditions such as can('read', 'Ride', { familyId: user.familyId }). A PoliciesGuard in NestJS evaluates these rules in service-layer methods before any data is returned. This is the industry-standard approach for NestJS applications with family-scoped or user-scoped data.
Layer 3 — PostgreSQL Row Level Security (database baseline)
For the most sensitive tables — ride_live_locations, payments, driver_documents — RLS policies enforce access at the database engine level using the current_setting('app.current_user_id') pattern set per query. No application-layer bug can bypass this.
UI-level guards are supplementary only. They improve UX but are not relied upon for security.
| Resource | Guardian | Dependent | Driver | Dispatcher | Support | Admin |
|---|---|---|---|---|---|---|
| Own profile | RW | RW | RW | — | R | RW |
| Dependent profiles | RW (own family) | own row | — | R | R | RW |
| Rides | RW (own family) | R (own) | R (assigned) | RW | R | RW |
| Ride stops | R (own family) | R (own) | R (assigned, labels only) | RW | R | RW |
| Live locations | R (active, family) | R (active, own) | W (active, assigned) | R | R | R |
| Incidents | RW (own family) | R (own) | R (assigned) | R | RW | RW |
| Ride events (audit log) | R (own family) | R (own) | R (assigned) | R | R | R |
| Driver documents | — | — | RW (own) | — | — | RW |
| Payments / subscriptions | RW | R (limited†) | — | R | R | RW |
| Driver shift data | — | — | RW (own) | R | — | RW |
| System config | — | — | — | — | — | RW |
† Dependent payment view: amount, status, ride_id, date only. No Stripe identifiers or billing details.
Driver ride view exposes only: ride_id, stop_type, address_label (not full address), scheduled_at, passenger_first_name, pin_required (boolean), special_needs_label, seat_requirement. Never date of birth, full address, guardian PII, or payment data.
Format: [DOMAIN]-[NNN] Priority — Description
Priorities: P1 = MVP blocker · P2 = MVP important · P3 = MVP nice-to-have
IAM-001 P1 — The system shall support six roles with distinct data access scopes enforced at application and database layers: guardian, dependent, driver, dispatcher, support, admin.
IAM-002 P1 — Guardians and drivers shall register with email and password. Phone number verification via one-time code is mandatory for both before the account is activated.
IAM-003 P1 — Guardians shall be able to create, edit, and remove up to 3 dependent profiles under their account. Each profile stores: name, date of birth, special needs notes, saved places, and authorised handover persons.
IAM-004 P1 — Guardians may store named recurring locations per dependent (e.g. "School", "Therapy centre"). These saved places pre-populate the booking form and are encrypted at rest. They are part of the dependent profile, not a separate entity.
IAM-005 P1 — A dependent aged 12 or older may be granted a limited login by their guardian via an allow_login toggle. The system verifies the dependent's date of birth. Dependents with allow_login = false or age under 12 cannot log in.
IAM-006 P1 — Guardians may revoke dependent login access at any time. The dependent's active session is invalidated immediately upon revocation.
IAM-007 P1 — Driver onboarding follows a sequential approval flow before activation:
This is a fully manual workflow: the driver obtains documents and uploads scans; admin reviews in the dashboard. No automated third-party verification API.
IAM-008 P1 — TOTP-based multi-factor authentication (authenticator app) shall be mandatory for admin, dispatcher, and support roles. Optional for guardians.
IAM-009 P1 — Guardians shall configure a Trusted Circle per dependent: up to 4 emergency contacts with name, phone number, and relationship. These contacts receive SMS alerts when SOS is triggered.
IAM-010 P1 — The system shall support password reset via email link, and phone number change via OTP verification.
BK-001 P1 — Guardians and eligible dependents shall book rides via two products: ElaAbo (subscription) and ElaRide+ (single trip, advance-reservation).
BK-002 P1 — When a dependent initiates a booking, the guardian always receives a push notification immediately.
BK-003 P1 — The booking form shall capture: passenger selection, pickup location, dropoff location, date and time, time flexibility window (exact / ±15 min / ±30 min), ride direction (one-way / return), and add-on selection.
BK-004 P1 — ElaRide+ booking rules:
BK-005 P1 — ElaAbo subscription configuration: guardian sets days of week, pickup time, time flexibility window, whether a return leg is included, and add-on selection. The system generates recurring reservation records for the billing cycle.
BK-006 P1 — For any passenger under 18, the booking flow shall automatically apply the Minor Safety Layer: Guardian Mode, Pickup PIN, Live Tracking, and Safe Handover. These cannot be disabled for minors.
BK-007 P1 — A transparent fare estimate showing the full breakdown (base, distance, add-ons, total) shall be presented before the guardian confirms any booking. Prices are fixed; no surge pricing.
BK-008 P1 — Operators shall be able to create bookings on behalf of a guardian from the ops dashboard, to support phone and WhatsApp channel requests.
BK-009 P2 — Group rides across multiple families sharing one vehicle are [PHASE 2]. At MVP, each booking covers one family's passengers only.
RL-001 P1 — All rides follow a single state machine. Entry points differ by product:
ElaRide+ path:
pending_review → confirmed → assigned → accepted → en_route
→ arrived → in_progress → completed
ElaAbo path:
scheduled → confirmed → assigned → accepted → en_route
→ arrived → in_progress → completed
Terminal states (reachable from most active states):
cancelled
no_show (reachable from: arrived, in_progress)
State meanings:
pending_review: ElaRide+ booking submitted, awaiting ops approvalscheduled: ElaAbo ride generated for a future date, not yet dispatchedconfirmed: approved and ready for driver assignmentassigned: driver offer sent; awaiting driver responseaccepted: driver has accepted the rideen_route: driver is travelling to the pickup locationarrived: driver is at the pickup locationin_progress: passenger onboard; trip is active (entered after PIN validation)completed: ride finished; passenger deliveredRL-002 P1 — Every state transition shall be recorded as an immutable entry in the ride event log: transition type, actor, actor role, timestamp, and context metadata. This log is the authoritative audit trail and cannot be modified or deleted.
RL-003 P1 — Ops approval for ElaRide+: when a booking is in pending_review, an ops team member reviews it (payment method validity, passenger profile completeness) and either confirms or rejects it. Short-notice bookings (12–24 h) go through the same flow with a time-sensitive flag.
RL-004 P1 — Driver offer: the dispatcher assigns a driver, creating a time-limited offer (configurable window, default 5 minutes). The driver receives a push notification. On expiry or rejection the ride returns to confirmed for re-dispatch, and the dispatcher is alerted.
RL-005 P1 — If a driver cancels an accepted ride, the system immediately alerts the dispatcher, flags the ride as critical priority, and initiates the re-dispatch protocol:
| Step | Target time | Action |
|---|---|---|
| Ops alert | Immediate | Dashboard high-priority flag |
| Guardian first notification | ≤5 minutes | Push + SMS: arranging replacement |
| Replacement search | ≤10 minutes | From available driver pool |
| Final resolution | ≤15 minutes | Replacement confirmed or no-replacement protocol |
No-replacement message to guardian: direct apology, full refund offered, support contact. Driver cancellation is logged for reliability tracking.
RL-006 P1 — The Pickup PIN must be validated before the ride transitions from arrived to in_progress. The driver enters the PIN; the system validates against the stored hash server-side. The ride cannot start without a match or an approved override.
RL-007 P1 — PIN forgotten flow:
RL-008 P1 — Safe Handover for minors at destination (in_progress → completed):
RL-009 P2 — After completing a ride, the driver submits a brief status report: OK / delay / incident / extra wait. For incidents, a report is auto-opened.
GT-001 P1 — Guardian Mode shall be the default and non-disableable setting for all rides involving a dependent. It is opt-in for adult self-bookings.
GT-002 P1 — Guardian Mode provides during an active ride: real-time driver location on a map, an SOS button, and Trusted Circle contact access.
GT-003 P1 — The driver app shall transmit GPS coordinates at an adaptive interval based on ride state:
| State | Interval | Rationale |
|---|---|---|
en_route |
Every 5 seconds | Driver moving; position updates needed for guardian ETA |
arrived |
Every 10 seconds | Driver stationary; lower update rate acceptable |
in_progress |
Every 4 seconds | Passenger onboard; highest safety criticality |
These are configured targets. The device may adapt further based on speed and battery conditions. The backend accepts updates at any frequency.
GT-004 P1 — GPS data flow: driver app transmits location → server validates (JWT and active assignment) → latest position cached for fast reads → location stored for anomaly detection → guardian and dependent apps receive real-time map update via WebSocket.
GT-005 P1 — Live location broadcast shall automatically cease when the ride reaches a terminal state (completed, cancelled, no_show). No further location events are emitted after this point.
GT-006 P1 — Live location data is not persisted beyond the active ride session unless an open incident investigation requires it.
SF-001 P1 — An SOS button shall be accessible to dependents and guardians throughout any active ride (arrived or in_progress state). It is not available outside active rides.
SF-002 P1 — Pressing SOS triggers all of the following simultaneously:
SF-003 P1 — If support does not acknowledge an SOS alert within a configurable window (default: 3 minutes), the system escalates: SMS and email alert sent to the operations lead.
SF-004 P1 — Route deviation detection (background job comparing real-time GPS against planned route):
| Level | Trigger | Response |
|---|---|---|
| 1 | >1.5 km from route OR ETA increased >5 min | Ops dashboard alert. No passenger notification. |
| 2 | Level 1 sustained + driver contacted but reason unclear | Driver must confirm status in app or by phone. |
| 3 | >3 km deviation OR >10 min delay OR driver unresponsive 2+ min | Guardian receives calm informational message. Ops monitors closely. |
| 4 | Driver unreachable OR continued divergence after Level 3 | Ride flagged Critical. SOS escalation path activated. 112 call button shown to guardian. |
SF-005 P1 — Any ride participant (guardian, dependent, driver) shall be able to file an incident report during or after a ride, with severity (1–5), type, and free-text description.
SF-006 P1 — Incident workflow: open → in_review → resolved | escalated. Escalated incidents require admin acknowledgement and trigger an email to the operations lead.
SF-007 P2 — Periodic passenger check-in prompts during active rides — [PHASE 2]. At configurable intervals the dependent receives an automated "Are you okay?" prompt. No response within a timeout triggers a guardian alert, followed by a support alert. GPS tracking and the SOS button cover the MVP safety promise.
DM-001 P1 — Driver onboarding status progression: pending_documents → documents_submitted → under_review → approved | action_required. Each status change triggers push and email notifications to the driver and admin.
DM-002 P1 — Required documents for activation: driving licence, Führungszeugnis (manual workflow — driver obtains, uploads scan), vehicle insurance certificate, training completion certificate. Admin approves each document individually.
DM-003 P1 — The system shall issue automated reminders to drivers whose Führungszeugnis is approaching its 1-year expiry (30 days before). If not renewed by expiry, driver status transitions to action_required and they are removed from the dispatch pool until renewed.
DM-004 P1 — Drivers shall set and manage availability blocks, including recurring weekly patterns. Availability blocks are visible to dispatchers on the driver schedule board.
DM-005 P1 — Shift management: drivers check in at the start of each shift and check out at the end. The system records shift start, end, and pause events. Checking in requires confirming vehicle safety, personal health readiness, and fitness to work. If a driver marks themselves unfit, the shift is blocked and ops receives an alert.
DM-006 P1 — German labour law compliance (ArbZG): before any ride offer is sent to a driver, the system shall verify the driver is not in violation of mandatory rest or break requirements. If a legal break is overdue or the 10-hour shift limit would be exceeded, the ride offer is blocked and the dispatcher is notified with the reason. Checks enforced: required break compliance, shift duration limit (10 hours), and minimum 11-hour rest between shifts.
DM-007 P2 — Same-driver preference for recurring ElaAbo routes: the system notes the last assigned driver per route and prioritises them when available. This is a preference, not a guarantee. [PHASE 2]
DM-008 P2 — Advanced fatigue scoring, break health engine, and in-app earnings summary. [PHASE 2]
NT-001 P1 — Notification channels: push notifications (Expo Push Notification Service, wrapping FCM and APNs), SMS (provider to be selected — see Open Items), and email (Brevo).
NT-002 P1 — Guardian notification triggers: ride requested by dependent, all ride status changes, driver assigned and accepted, SOS triggered, incident created and updated, and all payment events.
NT-003 P1 — Dependent notification triggers (when allow_login = true): driver assigned, driver en route, driver arrived, boarding confirmed, ride completed or cancelled.
NT-004 P1 — Driver notification triggers: new ride offer, ride status updates relevant to their assignment, ops messages. Drivers never receive family PII, guardian contact details, or payment data in notifications.
NT-005 P1 — Safety-critical notifications (SOS, incident escalation) shall be delivered via push and SMS simultaneously. For non-SOS notifications, SMS is a fallback if push is unacknowledged within 60 seconds.
NT-006 P1 — Notification content shall not include full minor names or full street addresses in preview text, in compliance with GDPR privacy requirements.
NT-007 P1 — SOS alerts cannot be disabled by any user. All other notification preferences are user-configurable.
PY-001 P1 — The billing entity is always the guardian's Stripe customer account, regardless of who initiated the booking.
PY-002 P1 — ElaAbo billing: monthly recurring charge via Stripe Subscriptions. Legs consumed are tracked against the monthly quota. Overage legs are billed at a configurable per-leg rate at cycle end.
PY-003 P1 — ElaRide+ billing: Stripe PaymentIntent created and authorised at booking confirmation. Captured on ride completion. Released on free-window cancellation.
PY-004 P1 — Apple Pay and Google Pay shall be supported via Stripe Payment Element.
PY-005 P1 — No raw card data shall be stored or logged in the ElaRide system. All card handling is delegated exclusively to Stripe.
PY-006 P1 — Every completed ride shall have a pricing record capturing: pricing mode (subscription or single), base amount, distance component, add-on charges, and total. This record is immutable after creation.
PY-007 P1 — Payment failure: guardian receives high-priority push and email. A configurable grace period (default: 48 hours) applies before new rides are blocked. Existing confirmed bookings are not cancelled during the grace period.
PY-008 P1 — All payment-related emails are sent via Brevo (not Stripe's built-in delivery — this is disabled in Stripe Dashboard settings to ensure full German-language brand control). After each completed ElaRide+ ride: auto-receipt email. After each ElaAbo billing cycle: monthly invoice email.
PY-009 P1 — Dependent payment view: dependents with login access see a restricted summary (amount, status, ride reference, date) with no Stripe identifiers or full billing details.
CA-001 P1 — Cancellation policy (guardian-initiated):
| Time before pickup | Fee |
|---|---|
| ≥24 hours | Free |
| 3–24 hours | 50% of ride price |
| <3 hours or no-show | 100% |
CA-002 P1 — When ElaRide cancels (driver unavailable, no replacement found): guardian receives full refund and is offered a free rebooking to the next available slot.
CA-003 P1 — Cancellation fees are processed via Stripe: for ElaRide+ via PaymentIntent partial capture; for ElaAbo as a deduction applied in the next billing cycle.
CA-004 P1 — All cancellations are logged in the ride event log with actor attribution and timestamp.
AO-001 P1 — Three add-on types: wait_plus, begleitung_plus, return_plus. Each is an independent boolean selection on a booking. If multiple are selected, the billing engine handles their interactions independently — there is no combined type.
AO-002 P1 — Billing rules:
AO-003 P1 — For ElaAbo subscriptions, add-ons are configured per subscription (boolean flags on the subscription record), not selected per ride.
AO-004 P2 — Dispatchers may waive add-on charges on individual rides with a mandatory reason note.
RO-001 P1 — Route optimization is dispatcher-triggered (semi-manual). There is no auto-dispatch at MVP.
RO-002 P1 — Dispatcher workflow: selects a time window and confirmed rides, selects available drivers, sets constraints, triggers an optimization run, reviews the proposed plan (map and list view), modifies if needed, then approves. Driver offers are only created after dispatcher approval.
RO-003 P1 — The optimization engine must respect: vehicle capacity, child seat requirements, driver availability blocks, and maximum detour percentage. It returns a proposed assignment plan which the dispatcher can modify before approving.
RO-004 P1 — Graceful degradation: if the optimization service is unavailable, dispatchers can continue with fully manual ride assignment without interruption.
The ops dashboard is a Next.js web application, designed for desktop and tablet use (1024 px and above). It serves three roles: admin, dispatcher, and support.
OD-001 P1 — Role-based dashboard views:
OD-002 P1 — Reservation Board: shows all bookings grouped by status (pending review, confirmed, today, next 7 days, no driver assigned). Dispatcher and admin can approve, assign a driver, or reject bookings from this view.
OD-003 P1 — Live Ride Board: real-time view of all active rides (en_route, arrived, in_progress) with status, passenger name, driver name, and ETA. Dispatcher can intervene: contact driver, notify guardian, or flag as incident.
OD-004 P1 — Driver Schedule Board: per-driver view of assigned rides, availability blocks, open time windows, and planned breaks. Dispatcher can insert ride offers into open windows.
OD-005 P1 — Safety & Incidents Board: all open incidents grouped by status (open, in review, escalated). Support handles incidents and escalates when required. Escalated incidents alert the operations lead via email.
OD-006 P1 — Driver Management (Admin): full driver pipeline — application review, document status, per-document approve/reject, training status, shift records, suspension and activation.
OD-007 P1 — System Configuration (Admin): cancellation windows, PIN rules, ElaAbo plan prices and quota, overage rates, add-on rates, notification templates, grace period settings.
OD-008 P1 — Booking on behalf of user: any ops user can create a booking in the dashboard on behalf of a guardian, to handle phone and WhatsApp channel requests.
OD-009 P2 — KPI Analytics (Admin): on-time rate, cancellation rate, incident rate, driver reliability score, re-booking rate, revenue by plan type. Queries run against a read replica. [PHASE 2 — basic stats only at MVP]
NFR-SEC-001 P1 — Row Level Security enforced at the database layer on sensitive tables. Application-layer guards are supplementary.
NFR-SEC-002 P1 — All data in transit: TLS 1.3 minimum. Pickup PINs stored as bcrypt hashes (cost factor ≥12). Never in plaintext.
NFR-SEC-003 P1 — Dependent PII (date of birth, special needs notes, saved places) encrypted at field level in the database. Führungszeugnis documents stored in a private encrypted bucket with admin-only access.
NFR-SEC-004 P1 — No raw card data stored or logged. All payment handling delegated to Stripe.
NFR-SEC-005 P1 — JWT access tokens: 15-minute lifetime. Refresh tokens: 7-day TTL, rotated on use, revocable server-side via Redis.
NFR-SEC-006 P1 — All secrets (database credentials, API keys, JWT signing key) managed via environment secret management. Never committed to version control.
NFR-SEC-007 P1 — Driver-facing data views expose only the minimum necessary data (see Section 3.3). No dependent date of birth, full address, guardian PII, or payment data ever reaches the driver.
NFR-SEC-008 P2 — Penetration test before public launch. Particularly important given handling of minor data and GDPR obligations.
NFR-PERF-001 P1 — Live location update latency (driver transmit to guardian map update): ≤2 seconds P95.
NFR-PERF-002 P1 — Core API responses (booking creation, status update): ≤500 ms P95 under normal load.
NFR-PERF-003 P1 — Push notifications delivered within 5 seconds of the triggering event (P90).
NFR-PERF-004 P1 — High-frequency GPS writes are cached first for fast reads and persisted asynchronously, preventing write saturation on the primary database.
NFR-PERF-005 P1 — Analytics and KPI queries run against a read replica, never the primary database.
NFR-PERF-006 P2 — Architecture shall support scaling from the Berlin pilot (approximately 50 concurrent rides) to 500+ concurrent rides without structural changes to the backend.
NFR-AVL-001 P1 — Core ride operations (booking, live tracking, SOS): target 99.9% uptime.
NFR-AVL-002 P1 — If route optimization is unavailable, manual dispatch continues uninterrupted.
NFR-AVL-003 P1 — SOS notification paths have no single point of failure. Push and SMS are sent in parallel; failure of one does not prevent the other.
NFR-AVL-004 P1 — Background job failures retry with exponential backoff (max 5 attempts). Permanently failed jobs alert on-call via dead-letter queue.
NFR-AVL-005 P1 — Database: daily automated backups with point-in-time recovery. RPO ≤1 hour; RTO ≤4 hours.
NFR-ACC-001 P1 — Rider and guardian interfaces shall meet WCAG 2.1 AA standards.
NFR-ACC-002 P1 — Mobile apps support iOS 16+ and Android 9+.
NFR-ACC-003 P1 — German and English at launch. Internationalisation architecture ready for additional locales without code changes.
NFR-ACC-004 P1 — All API endpoints documented via OpenAPI 3.0.
NFR-COM-001 P1 — GDPR: privacy by design, explicit guardian consent for minor data, configurable data retention, right to erasure for non-mandatory records, data portability export. Data Processing Agreements required with all sub-processors before launch.
NFR-COM-002 P1 — PBefG §49: all ride operations comply with Mietwagenunternehmen rules. Single-family rides only at MVP.
NFR-COM-003 P1 — Minor data handling reviewed with legal counsel before launch (Jugendschutz).
NFR-COM-004 P1 — HGB §257: billing and accounting records retained for minimum 10 years.
NFR-COM-005 P1 — ArbZG: driver shift engine enforces mandatory break requirements and the 10-hour shift limit.
| Data type | Retention | Action at expiry |
|---|---|---|
| Live GPS (active ride) | Real-time only | Purged on ride end |
| Detailed GPS route traces | 90 days | Anonymised |
| Aggregated route data | 12 months | Location detail removed |
| Trip metadata and billing records | 10 years | Archive; delete after (HGB §257) |
| Incident reports (general) | 3–5 years | Configurable |
| Incident reports (accident/insurance) | Up to 10 years | Manual admin confirmation required |
| Driving licence verification | Contract + 3 years | Admin must confirm deletion |
| Criminal record (Führungszeugnis) | Contract + 3 years | Admin must confirm deletion |
| Training records | Contract + 5 years | Admin must confirm deletion |
| Dependent profiles | Until guardian deletion or erasure request | GDPR erasure applies |
Automated retention jobs run as weekly scheduled background tasks. All executions are logged to a separate audit table.
| Surface | Framework | Rationale |
|---|---|---|
| Rider mobile app | Expo (React Native) — iOS and Android | Cross-platform, background GPS via expo-location, push notifications, app store distribution |
| Rider web app | Next.js 14 (App Router) | Proper desktop-first web experience; sharing shadcn/ui and TailwindCSS with the ops dashboard; booking and schedule management |
| Driver mobile app | Expo (React Native) — separate app from rider | Persistent background GPS, different OS permissions, different UX, independent release cadence |
| Ops dashboard | Next.js 14 (App Router) | Complex desktop UI: drag-and-drop boards, dense data tables, multi-panel layouts |
| Backend API | NestJS (TypeScript) | Modular monolith, strong DI, built-in WebSocket, OpenAPI generation |
Why two separate Expo apps (rider and driver):
Industry standard — Uber, Bolt, Lyft, and Grab all maintain separate apps. The driver app requires always-on background GPS (expo-task-manager), a different OS permission model, a completely different navigation structure, and an independent release cadence. Combining into one app with role switching increases the security surface area, bundle size, and operational complexity.
Why Next.js for the rider web app and not Expo Router web:
The user's primary web context is a guardian booking from a laptop or desktop browser. The desktop web experience requires a fundamentally different UI than a mobile screen: multi-column layouts, sidebar navigation, proper web form patterns, hover states, and keyboard interactions. Expo Router's web output is built around React Native components rendered via react-native-web, which produces a mobile-app-like experience that does not translate naturally to desktop — it accumulates Platform.OS === 'web' hacks as the UI grows. Next.js 14 is already in the stack for the ops dashboard; the team knows it, the design system (shadcn/ui, TailwindCSS) is shared, and types and API clients are shared via monorepo packages. There is no meaningful code saving from using Expo web for this surface, and the UX compromise is significant. Next.js is the correct choice.
Why Next.js for the ops dashboard and not Expo:
The ops dashboard is a complex desktop management tool with drag-and-drop kanban boards, dense data tables, multi-panel layouts, and complex filter UIs. This use case is far outside what React Native for Web is designed for.
Why modular monolith over microservices for NestJS:
At MVP scale (~50 concurrent rides in Berlin), microservices add network latency, distributed tracing overhead, multiple deployment pipelines, and significant operational complexity with no meaningful benefit. NestJS module boundaries (RideModule, AuthModule, TrackingModule, SafetyModule, PaymentModule, NotificationModule, DriverModule, DispatchModule) provide clean separation that can be extracted into independent services when load actually demands it.
┌────────────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ ┌─────────┐ │
│ │ Rider │ │ Rider │ │ Driver │ │ Ops │ │
│ │ Mobile App │ │ Web App │ │ Mobile App │ │ Dash- │ │
│ │ Expo │ │ Next.js 14 │ │ Expo │ │ board │ │
│ │ iOS/Android │ │ Desktop/Web │ │ iOS/Android│ │ Next.js │ │
│ └──────┬───────┘ └──────┬───────┘ └─────┬──────┘ └────┬────┘ │
└─────────┼────────────────┼────────────────┼────────────────┼───────┘
│ HTTPS + WSS (TLS 1.3) │ │
└────────────────┬────────────────┘────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────┐
│ BACKEND — NestJS (Modular Monolith) │
│ │
│ REST Endpoints WebSocket Gateway (Socket.io) │
│ /auth /rides /drivers Ride tracking rooms │
│ /dispatch /safety Ops live board │
│ /payments /docs Safety alert room │
│ │
│ Middleware: JWT → Role guard (Layer 1) → │
│ CASL policy check (Layer 2) → Zod validation │
│ │
│ Background Workers (BullMQ): │
│ GPS anomaly · billing webhooks · data retention │
│ driver eligibility · document renewal reminders │
└───────┬──────────────────────────────────┬──────────────────────┘
│ │
┌───────▼─────────────┐ ┌───────────▼────────┐
│ PostgreSQL 16 │ │ Redis (Upstash) │
│ + PostGIS │ │ │
│ (Neon serverless) │ │ GPS position cache │
│ │ │ JWT token store │
│ Primary: writes │ │ BullMQ queues │
│ Read replica: KPIs │ │ Rate limiting │
└─────────────────────┘ └────────────────────┘
GPS tracking:
Driver app → POST /rides/:id/location (adaptive interval)
→ JWT + assignment validation
→ Redis SET ride:{id}:loc (latest, fast read for dashboard)
→ Async insert to ride_live_locations (anomaly detection)
→ Socket.io emit to room ride:{id}
(guardian and dependent subscribers receive real-time map update)
SOS:
Guardian or dependent taps SOS
→ POST /safety/sos → validate active ride membership
→ Create incident (severity=5, type=sos)
→ Log ride event (sos_triggered)
→ Parallel:
Socket.io → ops safety room (acknowledgement required)
Expo push → guardian (HIGH PRIORITY)
SMS provider → all Trusted Circle contacts
→ App shows 112 call button (one tap, native dialler)
→ Timer: if unacknowledged within T minutes → SMS + email to ops lead
Booking → Confirmed → Dispatch:
Guardian books ElaRide+
→ POST /bookings → validation → ride status: pending_review
→ Stripe PaymentIntent authorised
→ Ops dashboard: new reservation for review
Ops approves
→ ride status: confirmed
→ Push to guardian: "Booking confirmed"
Dispatcher assigns
→ Route optimization (Vroom) or manual selection
→ Ride assignment offer created → ride status: assigned
→ Push to driver: ride offer
Driver accepts
→ ride status: accepted → push to guardian and dependent
→ en_route → arrived → in_progress → completed
→ Each transition: ride event logged, notifications sent
→ On completion: Stripe charge captured, receipt email via Brevo
| Layer | Choice | Rationale |
|---|---|---|
| Rider mobile app | Expo SDK 51+ (React Native) | Cross-platform iOS/Android. expo-location for GPS, expo-task-manager for background, expo-notifications for push. |
| Rider web app | Next.js 14 (App Router, TypeScript) | Proper desktop-first web UI. Shares TailwindCSS, shadcn/ui, and design system with ops dashboard. Types and API client shared via monorepo packages. |
| Driver app | Expo SDK 51+ — separate app | Persistent background GPS via expo-task-manager. Separate app store listing, permissions, and UX. |
| Ops dashboard | Next.js 14 (App Router, TypeScript) | Complex desktop UI: full web capabilities, TailwindCSS + shadcn/ui. |
| Backend | NestJS 10 (TypeScript) | Modular, strong DI, built-in WebSocket, Passport guards, auto-generated OpenAPI. |
| Authorization | CASL (@casl/ability) |
Resource-level attribute-based authorization in NestJS service layer. Handles ownership and family scoping conditions. Paired with custom @Roles() guard for route-level checks. No database permission tables — permissions are fixed business rules coded in the application. |
| ORM | Prisma | Type-safe queries, excellent migration tooling, auto-generated types shared via monorepo. |
| Database | PostgreSQL 16 + PostGIS (Neon) | Industry standard for ride-hailing (used by Uber, Lyft, Grab). PostGIS for geo queries. Neon: serverless, scales-to-zero for MVP cost, PITR, per-environment branching, migrates to AWS RDS as volume grows with no code changes. |
| Cache & Queue | Redis via Upstash | Serverless Redis. GPS position cache, JWT revocation store, BullMQ backing. |
| Job queue | BullMQ | Redis-backed, reliable retries, dead-letter queues, cron scheduling. |
| Real-time | Socket.io (@nestjs/platform-socket.io) | Room-based broadcasting per ride, handles reconnection, compatible with both Expo and Next.js clients. |
| File storage | Cloudflare R2 | S3-compatible API, zero egress fees (significant saving vs S3), private buckets with signed URLs for driver documents. |
| Maps | Google Maps Platform | Maps SDK for React Native (mobile apps), Maps JavaScript API (Next.js web apps). Directions API, Geocoding API, Distance Matrix API. |
| Route optimization | Vroom (self-hosted Docker) | Open-source VRPTW solver, REST API, handles fleet routing constraints. Self-hosted alongside the API. |
| Payments | Stripe | PaymentIntents (ElaRide+), Subscriptions (ElaAbo), Payment Element (Apple/Google Pay), webhooks. |
| Push notifications | Expo Push Notification Service | Free, wraps FCM (Android) and APNs (iOS), delivery receipts API. |
| SMS | To be selected — shortlist: seven.io (German company, GDPR-native), Plivo (30–40% cheaper than Twilio), GatewayAPI (Danish, EU-hosted) | All provide GDPR-compliant EU coverage. Twilio excluded due to US jurisdiction and GDPR complexity for Germany. Final selection after pricing benchmarks against Germany rates. |
| Brevo | French/EU company, EU data hosting by default, GDPR and ISO 27001 certified. 300 emails/day free (sufficient for Berlin MVP). German and multilingual template support. Stripe's automatic email delivery is disabled; all ElaRide emails go through Brevo. | |
| Authentication | Custom (Passport.js + JWT + TOTP via otplib) | No external auth vendor. JWT (access 15 min, refresh 7 days rotating), Redis token store with revocation. TOTP for ops MFA. |
| Monorepo | Turborepo | Single repo: apps/rider-mobile, apps/rider-web, apps/driver, apps/admin, packages/api-client, packages/shared-types, packages/ui. Shared types prevent API/client drift; shared UI primitives between the two Next.js apps. |
| Error monitoring | Sentry | SDK for NestJS, Next.js, and Expo. Free tier covers MVP. |
| CI/CD | GitHub Actions | Lint, type-check, unit and integration tests on every PR. Staging on develop merge, production on main merge. |
| Service | Purpose |
|---|---|
| Stripe | Subscriptions (ElaAbo), PaymentIntents (ElaRide+), Payment Element (Apple/Google Pay), webhook events |
| SMS provider (TBD) | Phone OTP at registration; SOS alert SMS to Trusted Circle; re-dispatch SMS to guardian |
| Expo Push Notification Service | Mobile push notifications (wraps FCM and APNs) |
| Google Maps Platform | Maps SDK (React Native mobile), Maps JS API (Next.js web), Directions, Geocoding, Distance Matrix |
| Brevo | All transactional email — receipts, invoices, onboarding, incident alerts, password reset, document status |
| Vroom | Route optimisation (VRPTW), self-hosted |
| Cloudflare R2 | Driver document storage (private bucket, signed URLs) |
| Bundesamt für Justiz | Führungszeugnis: manual document workflow only. No API. Driver obtains and uploads scan. |
| Sentry | Error tracking and performance monitoring |
| WhatsApp (click-to-chat) | wa.me deep link; no automation. Ops manually handles messages. |
This section names key entities and their relationships. Field-level design (column names, types, indices) belongs in the System Design Document.
User
One-to-one with: Guardian | Dependent (12+) | Driver
Has: role (enum), account_type (profile metadata)
Guardian
Has many: Dependents (max 3)
Has many: TrustedCircle entries (per dependent)
Has one: Stripe customer
Has many: Subscriptions
Has many: Payments
Dependent
Belongs to: Guardian
Has: optional login (User)
Has many: SavedPlaces (encrypted addresses)
Has many: AuthorizedHandoverPersons
Driver
Has one: User
Has many: DriverDocuments
Has many: AvailabilityBlocks
Has many: DriverShifts
Ride
Belongs to: Guardian (billing)
Belongs to: Dependent or self (passenger)
Has many: RideStops (pickup, dropoff, waypoints)
Has one: RideAssignment (current)
Has one: RidePricing (immutable after creation)
Has many: RideEvents (immutable audit log)
Has many: RideLiveLocations (purged post-ride)
Has many: AddonUsages
Has zero-or-one: Incident
RideAssignment
Belongs to: Ride, Driver
Tracks: offer status, acceptance, expiry
RideLiveLocation
Belongs to: Ride, Driver
Purged when ride reaches a terminal state
RideEvent (immutable)
Belongs to: Ride
Records: event type, actor, actor role, timestamp, metadata
Incident
Belongs to: Ride
Has many: IncidentEvents (status transitions)
Subscription
Belongs to: Guardian, Dependent
Links to: Stripe subscription
Generates: Rides on recurring schedule
Payment
Belongs to: Guardian
Links to: Ride or Subscription
Links to: Stripe payment intent or invoice
RoutePlan
Created by: Dispatcher
Has many: RoutePlanItems (driver-ride assignments, proposed)
Status: proposed → approved | discarded
Everything in Section 4 marked P1 or P2, excluding items explicitly marked [PHASE 2].
MVP includes: booking (ElaAbo + ElaRide+), ride lifecycle and state machine, live GPS tracking, Guardian Mode, SOS, pickup PIN and forgotten PIN flow, route deviation detection (all levels), Safe Handover, add-ons (Wait+, Begleitung+, Return+), driver onboarding and document management, shift management, German labour law compliance enforcement, ops dashboard (Reservation Board, Live Ride Board, Driver Schedule Board, Safety & Incidents Board, Driver Management, System Configuration), all notification channels, and payments.
| Feature | Reason for deferral |
|---|---|
| Multi-family seat pooling | PBefG compliance review required; complex billing split |
| WhatsApp Business API automation | Volume must justify the cost |
| B2B partner portal | Separate product surface; not needed for consumer launch |
| Driver benefits management | Internal HR feature; can be tracked externally at MVP |
| Periodic passenger check-in prompts | Complex UX; GPS and SOS cover the MVP safety promise |
| Trusted Circle live tracking access | Additional feature; guardian tracking covers MVP |
| Demand signal (last-minute availability) | Only needed when last-minute rides are offered |
| Full KPI analytics dashboard | Basic ops statistics sufficient at MVP |
| Same-driver preference for ElaAbo | Preference feature; dispatch works without it |
| Goodwill credit ledger | Can be handled manually at MVP |
| Advanced fatigue scoring | Basic ArbZG enforcement covers compliance |
| Psychological follow-up flag | Support team can track externally at MVP |
| In-vehicle dashcam or audio recording | Requires GDPR consent flow design first |
| Face ID and biometric login | Later UX enhancement |
| ID | Item | Status | Notes |
|---|---|---|---|
| OI-001 | ElaAbo per-plan monthly prices | Open ⚠️ | Use placeholder prices for development; confirm before billing module goes live |
| OI-002 | Wait+ per-15-min-block rate | Open ⚠️ | Same as above |
| OI-003 | ElaAbo overage rate per leg | Open ⚠️ | Same as above |
| OI-004 | Begleitung+ extra-time rate | Open ⚠️ | Same as above |
| OI-005 | Languages beyond German/English | Resolved | German and English only at launch |
| OI-006 | Payment failure grace period | Resolved | 48 hours default; configurable |
| OI-007 | Ops support hours | Resolved | Normal German business hours |
| OI-008 | Jugendschutz and GDPR consent flows for minor data | Open ⚠️ | Legal review required before launch |
| OI-009 | Data Processing Agreements with sub-processors | Open ⚠️ | Must be completed before launch: Stripe, SMS provider, Brevo, Google, Cloudflare |
| OI-010 | SMS provider final selection | Open ⚠️ | Benchmark seven.io, Plivo, and GatewayAPI on Germany pricing and GDPR terms; select before notification module is built |
| OI-011 | Exact German copy for all safety notifications | Open ⚠️ | Required for notification templates before go-live |
End of document — ElaRide SRS v1.2