Margi
The network failed. The golden hour didn't.
Margi Master Team Brief
The network failed. The golden hour didn't.
Margi โ Complete Replication Bible
This is the master engineering record for Margi. It captures the real shipped architecture, every subsystem, the exact tech stack and versions, environment + deployment, and a full problem-to-solution engineering log so the entire project can be rebuilt from zero by a human or an AI model. The original aspirational planning brief (PWA vision) is preserved verbatim in Appendix A for history.
Project: Margi โ offline-first Golden Hour road-safety prototype
Team: Team NovaDrive ยท IIT Madras National Road Safety Hackathon 2026 (RoadSoS track, CoERS & RBG Labs / MoRTH)
Repository: github.com/Stormynubee/Margi
Primary client: novadrive-mobile/ โ native Expo SDK 54 Android app (com.margi.app, margi://)
Optional cloud: novadrive/ โ Next.js 14 Sarthi AI BFF + web relay (Vercel)
Brief site (this page): static HTML built from this file โ roadsafetyhackathon-six.vercel.app
Tagline: When signal drops, the path still holds. The network failed โ the golden hour didn't.
Table of Contents
- What Margi Really Is
- The Two Safety Lanes
- Repository Topology
- Complete Tech Stack And Versions
- Mobile App Architecture
- Expo Router Screen Map
- Core Library Reference
- START Triage FSM
- Golden Hour Packet GHP
- Facilities SQLite And Trauma Tier Ranking
- Emergency Orchestrator
- Hold SOS Quick SOS And Incident Tracker
- Trauma Response HUD
- Naari Shakti Womens Safety Portal
- Crash And Distress Voice Engines
- Sarthi Assistant Offline And Cloud
- Sarthi BFF Internals
- Supabase Backend
- Web Mirror And Bystander Relay
- Brief Site Build Pipeline
- Environment Variables
- Local Development Setup
- Deployment Mobile APK
- Deployment Sarthi BFF
- Testing Strategy
- Engineering Problem And Solution Log
- Replicate From Scratch Playbook
- Honesty Boundaries And Rejected Scope
- Glossary
- Resources And Links
- Multilingual Support and Searchable Tactile Grid Selector
1. What Margi Really Is
Margi is a client-heavy, offline-first emergency system for Indian highway corridors. All medical and routing decisions run on-device; the network is an optional enhancement (SMS to 108, Supabase auth, Gemini LLM chat).
| Layer | Reality |
|---|---|
| Primary client | novadrive-mobile/ โ Expo SDK 54, package com.margi.app, version 2.0.0 |
| Offline core | START triage FSM โ SQLite facility ranking (~50 demo POIs) โ Golden Hour Packet โ QR (ND1: envelope) / SMS composer |
| Optional online | Sarthi Gemini BFF (novadrive/), Supabase auth + profile + NGO + dispatch audit, OSRM trip routing, HTTP dispatch hooks |
| Web mirror | Bystander relay at /relay, plus a full web emergency wizard in novadrive/ |
| Brief site | Static HTML at docs/site/ (this page) deployed as a separate Vercel project |
| Tests | 225 Jest unit tests (73 suites) over lib / FSM / encoders / orchestrator / KB |
| Judge APK | GitHub Actions builds margi-debug.apk |
What Margi is NOT (honesty boundaries โ keep these in every pitch):
- Not production emergency medical software โ no physician-certified triage, no verified national POI registry, no EMS dispatch guarantee.
- Not a PWA as the primary client โ early docs describe a Next.js PWA with sql.js; the shipped app is native Expo. (The PWA vision survives as Appendix A.)
- Not always-on crash detection โ accelerometer heuristics + experimental voice; native crash adapter is stubbed unless a custom dev build is used.
- Not auto-dial โ SOS opens the SMS composer / dialer intent; the user taps Send / Call (platform policy).
Margi provides decision support only. It is not a medical diagnosis. In an emergency, always call 108/112 when possible.
2. The Two Safety Lanes
Margi ships two parallel emergency experiences that share the offline core.
Lane A โ Golden Hour (road accident SOS)
flowchart LR SOS[Hold SOS 3s / Quick SOS / header SOS] --> Pick[Incident Tracker] Pick --> Act[Activation ~6s splash] Act --> Orch[runEmergencyOrchestrator] Orch --> ICE[ICE SMS intent] Orch --> S108[108 SMS intent] Orch --> Maps[Google Maps -> nearest trauma POI lat/lng] Orch --> Trauma[Trauma Response HUD] Trauma --> Triage[START triage FSM] Triage --> Route[Facility ranking] Route --> GHP[GHP + QR relay]
Lane B โ Naari Shakti (women's safety portal)
Gender-gated (self-reported on device) saffron + navy portal: SMS nearest police station, ICE alert, helpline 181, hold-to-activate (2s) distress HUD with optional recording, 112 national fallback when far from the demo seed.
| Lane | What the user gets |
|---|---|
| Golden Hour | Incident type โ activation โ automated orchestration (ICE SMS, 108 SMS, nearest trauma POI Maps) โ trauma HUD with live incident timer, rear torch, Call ICE FAB, hospital navigation to facility coords โ START triage โ GHP + bystander QR |
| Naari Shakti | Gender-gated portal โ SMS nearest police station, ICE alert, helpline 181, 2s hold distress with recording, 112 fallback |
3. Repository Topology
roadsafetyhackathon/ # monorepo root (git: Stormynubee/Margi)
โโโ novadrive-mobile/ # PRIMARY โ Expo SDK 54 Android app (ship this)
โ โโโ app/ # expo-router screens (tabs, emergency, naari, sarthi...)
โ โโโ src/ # lib, components, hooks, context, theme
โ โโโ scripts/ # APK build, sarthi check, seed gen, gradle patches
โ โโโ assets/ # icon/splash docs + models/
โ โโโ app.json / app.config.js # Expo config
โ โโโ babel.config.js # babel-preset-expo + reanimated plugin
โ โโโ jest.config.js # ts-jest, node env, src roots, *.test.ts
โ โโโ tsconfig.json # extends expo base, @/* -> ./src/*
โ โโโ package.json # version 2.0.0
โโโ novadrive/ # Next.js 14 Sarthi BFF + web relay (Vercel)
โ โโโ src/app/ # web pages + api/sarthi/{health,chat}
โ โโโ src/lib/sarthi/aiConfig.ts # Gemini provider + probe
โ โโโ vercel.json # separate Vercel project
โ โโโ package.json
โโโ docs/ # judge docs, specs, brief site source
โ โโโ CANON.md # single source of truth for scope
โ โโโ ARCHITECTURE.md # technical architecture
โ โโโ PHASE3_SETUP.md # Supabase + Sarthi BFF deploy guide
โ โโโ MARGI_MASTER_BRIEF.md # THIS FILE -> builds the brief site
โ โโโ site/ # build-docs.js -> index.html (deployed)
โโโ supabase/migrations/ # profiles, volunteer_providers, dispatch_events + RLS
โโโ data/corridors/ # NH-48 POI verification CSV/JSON inputs
โโโ scripts/ # Python Overpass ingest + pytest (build-time only)
โโโ .github/workflows/ # ci.yml, android-apk.yml, poi-ingest.yml
โโโ vercel.json # ROOT project -> static brief site (docs/site)
โโโ README.md / JUDGE_START_HERE.md / CHANGELOG.md / CONTRIBUTING.md / SECURITY.md
Critical deployment split (the single biggest gotcha):
| Vercel project | vercel.json | Serves | Root directory |
|---|---|---|---|
root (roadsafetyhackathon) | repo-root vercel.json | static brief site from docs/site | repo root |
| novadrive (Sarthi BFF) | novadrive/vercel.json | Next.js app with /api/sarthi/* and /relay | novadrive |
The brief-site hosts (roadsafetyhackathon-six.vercel.app, margi-tau.vercel.app) return 404 for /api/sarthi/health. Sarthi must point at the separate novadrive project (e.g. novadrive-eta.vercel.app).
4. Complete Tech Stack And Versions
Mobile (novadrive-mobile/package.json, version 2.0.0)
| Concern | Package | Version |
|---|---|---|
| Runtime | expo | ~54.0.0 |
| React | react | 19.1.0 |
| React Native | react-native | 0.81.5 |
| Router | expo-router | ~6.0.23 |
| Animations | react-native-reanimated | ~4.1.1 |
| Worklets | react-native-worklets | 0.5.1 |
| Local DB | expo-sqlite | ~16.0.10 |
| Camera / torch | expo-camera | ~17.0.10 |
| Location | expo-location | ~19.0.8 |
| Sensors (crash) | expo-sensors | ~15.0.8 |
| Audio (voice) | expo-audio | ~1.1.1 |
| Speech (TTS) | expo-speech | ~14.0.8 |
| Haptics | expo-haptics | ~15.0.8 |
| Crypto (hash) | expo-crypto | ~15.0.9 |
| Secure store | expo-secure-store | ~15.0.8 |
| Storage | @react-native-async-storage/async-storage | 2.2.0 |
| Network state | @react-native-community/netinfo | ^11.4.1 |
| Backend SDK | @supabase/supabase-js | ^2.106.2 |
| QR | react-native-qrcode-svg | ^6.3.21 |
| SVG | react-native-svg | 15.12.1 |
| Compression | lz-string | ^1.5.0 |
| Icons | @expo/vector-icons | ^15.0.2 |
| Fonts | @expo-google-fonts/hanken-grotesk, @expo-google-fonts/public-sans | โ |
| Localization | src/lib/translations/ | 15-Language engine (modular registry) |
| Tests | jest ~29.7.0 + ts-jest ^29.4.11 | TypeScript ~5.9.2 |
Cloud BFF (novadrive/package.json, version 0.1.0)
| Concern | Package | Version |
|---|---|---|
| Framework | next | 14.2.35 |
| React | react / react-dom | ^18 |
| AI SDK | ai | ^6.0.191 |
| Gemini provider | @ai-sdk/google | ^3.0.79 |
| OpenAI provider (spare) | @ai-sdk/openai | ^3.0.65 |
| QR (web) | qrcode.react | ^4.2.0 |
| Compression | lz-string | ^1.5.0 |
| Icons | lucide-react | ^1.16.0 |
| Styling | tailwindcss | ^3.4.1 |
Build-time data pipeline (scripts/)
Python 3 + Overpass API โ emergency_seed.db; validated by pytest in CI (poi-ingest.yml). Not bundled into the app at runtime โ the mobile app uses an inline seed.
5. Mobile App Architecture
novadrive-mobile is a client-heavy app: the offline core never requires a server.
flowchart TB
subgraph mobile [novadrive-mobile]
Plan[Trip + OSRM when online]
J[Journey HUD + HoldSOS]
Orch[emergencyOrchestrator]
S[START Triage FSM]
DB[(SQLite trauma POI)]
G[GHP + QR + SMS 108]
N[Naari Shakti engine]
Sarthi[Sarthi KB + optional BFF]
Plan --> J
J --> Orch
J --> S
Orch --> DB
S --> DB
DB --> G
N -.->|parallel| G
Sarthi -.-> J
end
subgraph cloud [optional online]
SB[(Supabase)]
BFF[Sarthi BFF on Vercel]
end
mobile -.-> SB
mobile -.-> BFFConventions
- Path alias:
@/โ./src/(tsconfig + babel). - State: React Context providers wrap the app in
app/_layout.tsx:AppProvider(journey, triage FSM, GHP, crash, facilities),SarthiProvider,NaariShaktiProvider, plusQuickMenuProviderin(tabs)/_layout.tsx. - Data boundaries: auth tokens + relay GHP in SecureStore; profile + accessibility in AsyncStorage; POI in the SQLite file seeded on first launch.
- TDD discipline: every
src/libmodule that holds logic ships with a colocated.test.ts.
6. Expo Router Screen Map
app/
_layout.tsx # Root: fonts, providers, Stack
index.tsx / splash.tsx # Boot + splash
home.tsx # Standalone home (also see (tabs)/explore)
auth.tsx # Supabase sign-in (Guest mode bypasses)
settings.tsx # notifyEmergencyContacts toggle, voice sensitivity
accessibility.tsx medical.tsx emergency-contacts.tsx
sarthi.tsx # full-screen Sarthi chat
scan.tsx # bystander QR scanner
naari-shakti.tsx # women's portal
journey.tsx
(tabs)/
_layout.tsx # MargiTabBar + SarthiOverlayBridge + QuickMenuProvider
explore.tsx # Home tab (Drive mode, Quick SOS, Naari, brief, Sarthi FAB)
drive.tsx # Journey HUD โ Hold SOS lives here
history.tsx # Community hazards (~5 km), leaderboard
profile.tsx # Medical ICE, voice toggle, a11y
emergency/
selection.tsx # incident type picker + cancel-SOS countdown
activation.tsx # 6s activation splash + runEmergencyOrchestrator
locate.tsx # GPS capture + reverse geocode
triage.tsx # START triage FSM UI
route.tsx # facility ranking list + Maps navigate
response.tsx # trauma HUD: timer, torch, ICE, QR, first aid
packet.tsx # GHP QR + relay URL
relay.tsx # bystander relay steps + RahVeer claim
journey/ depart.tsx complete.tsx feedback.tsx
settings/ journey-history.tsx
trip/ _layout.tsx plan.tsx discover.tsx
brief/ [slug].tsx
rahveer/ index.tsx claim.tsx
ngo/ index.tsx register.tsx
Canonical emergency flow: selection โ activation โ locate โ triage โ route โ response โ packet โ relay.
7. Core Library Reference
novadrive-mobile/src/lib/ โ the brain of the app.
| Module | Responsibility |
|---|---|
startTriageFSM.ts | START protocol deterministic FSM โ initialState, getQuestion, applyAnswer |
parseEmergencyText.ts | offline keyword โ FSM slot prefill |
facilitiesDb.ts | expo-sqlite seed (emergency_nodes) + rankFacilities(triage, lat, lng) (Haversine + tier filter) |
ghp.ts | buildPacket, hashPayload, formatSms, encodeQrPayload, encodeQrRelayUrl, decodeQrPayload, verifyQrDecodedIntegrity |
emergencySms.ts | ICE + 108 SMS intents, coords resolver (sos_hold, crash_detected templates) |
emergency/emergencyOrchestratorPlan.ts | planEmergencyOrchestrator, buildMargiIceSmsBody, notifyEmergencyContacts gate |
emergency/emergencyOrchestrator.ts | runEmergencyOrchestrator โ runs ICE + 108 + Maps in sequence |
emergency/holdSosReleaseGrace.ts | debounce so a quick release does not skip incident picker |
emergency/hospitalNavTarget.ts | resolve Maps destination to the facility lat/lng |
emergency/dispatchOrchestrator.ts | optional Supabase dispatch_events insert |
emergency/incidentCatalog.ts incidentElapsed.ts | incident types + live timer math |
emergency/traumaSession.ts traumaAssistantOffline.ts | trauma HUD session + offline first-aid copy |
crashEngine.ts + crash/crashOrchestrator.ts crash/nativeCrashAdapter.ts | accel + speed fusion (journey-active only); native adapter stubbed |
sarthiEngine.ts | createThread, sendMessage โ offline-first; cloud when online + BFF healthy + not a KB match |
sarthiOffline.ts | offline reply wrapper โ knowledge base |
sarthi/sarthiKnowledgeBase.ts | SARTHI_KB_ENTRIES pattern-matched offline answers (en/hi/ta) |
sarthi/sarthiHealth.ts | checkSarthiBffHealth() โ GET /api/sarthi/health, requires geminiReachable |
sarthi/sarthiStrings.ts sarthiStatusCopy.ts | welcome/fallback strings, status chip + connection banner copy |
supabase/client.ts authSession.ts profileSync.ts | Supabase client (SecureStore auth), session, profiles upsert |
naariShakti/ | women's portal engine, stations, SMS, linking actions |
voice/ | distress voice classifier (YAMNet path), default OFF |
routing/ | OSRM + Nominatim trip routing |
tts/narrator.ts | spoken FSM prompts |
storage.ts types.ts brand.ts relayChain.ts journeyDb.ts | persistence, types, brand tokens, relay log, journey store |
8. START Triage FSM
Deterministic finite state machine implementing the international START (Simple Triage And Rapid Treatment) protocol. The FSM, not the LLM, makes every medical decision.
States
AMBULATORY โ BREATHING_CHECK โ AIRWAY_REPOSITION โ RESPIRATORY_RATE โ PERFUSION_CHECK โ MENTAL_STATUS โ TAGGED
Transition rules (do not simplify incorrectly)
| Condition | Result |
|---|---|
| Can walk | GREEN โ done |
| Not breathing โ airway maneuver fails | BLACK โ done |
| Not breathing โ airway maneuver succeeds | โ RESPIRATORY_RATE (NOT immediate RED) |
| Respiratory rate > 30/min | RED |
| No radial pulse OR capillary refill โฅ 2s | RED |
| Cannot follow simple commands | RED |
| All checks pass, non-ambulatory | YELLOW |
Interface
export type TriageState =
| 'AMBULATORY' | 'BREATHING_CHECK' | 'AIRWAY_REPOSITION'
| 'RESPIRATORY_RATE' | 'PERFUSION_CHECK' | 'MENTAL_STATUS' | 'TAGGED';
export interface TriageContext {
count: number;
canWalk: boolean;
breathing: boolean;
respiratoryRateOver30: boolean;
capillaryRefillOk: boolean;
followsCommands: boolean;
severeBleeding: boolean;
triageResult: 'RED' | 'YELLOW' | 'GREEN' | 'BLACK';
stateLog: string[];
}
UI: app/emergency/triage.tsx drives the FSM via AppContext.answerTriage, renders AnswerChips, and speaks prompts through tts/narrator.ts. Triage color feeds facility ranking.
9. Golden Hour Packet GHP
A structured, dispatch-ready brief (not raw chat logs). Built in src/lib/ghp.ts.
Schema
export interface GoldenHourPacket {
id: string; // UUID-ish
createdAt: string; // ISO8601
triage: 'RED' | 'YELLOW' | 'GREEN' | 'BLACK';
location: { lat: number; lng: number; landmark?: string; nhCode?: string; nhKm?: number };
victims: {
count: number; canWalk: boolean; breathing: boolean;
severeBleeding: boolean; capillaryRefillOk: boolean; followsCommands: boolean;
};
routing: { facilityName: string; facilityType: 'trauma' | 'hospital' | 'clinic';
phone: string; etaMinutes: number; distanceKm: number };
emergency: { dial: string; state: string; language: 'en' | 'hi' | 'ta' };
relayChain: Array<{ at: string; deviceHint: string }>;
integrity: string; // SHA-256 hex digest (corruption check, not crypto signing)
}
Encoding and relay
| Step | Method |
|---|---|
| 1 | Build minimal payload |
| 2 | Compress with lz-string |
| 3 | Wrap in the ND1: envelope for relay URLs (encodeQrRelayUrl) |
| 4 | Integrity via SHA-256 (hashPayload); verifyQrDecodedIntegrity rejects tampered/corrupt packets |
| 5 | If the payload is too large for a reliable QR, the QR carries the minimal { id, triage, lat, lng, integrity } and the full GHP stays on screen + in SecureStore |
SMS template (English)
ROAD EMERGENCY - Margi
Triage: RED | Victims: 1
Location: NH48 km 87 (13.082700, 80.270700)
Injuries: Not walking, breathing, severe bleeding suspected
TAKE TO: Apollo Trauma Center (12 min / 8.4 km)
Call: 108 (Tamil Nadu)
Generated offline via bystander relay.
The GHP screen (app/emergency/packet.tsx) renders the QR; a bystander scans it (in-app scan.tsx or the web /relay?p=ND1...), the packet is verified, the relay chain is appended, and an SMS to 108 is composed when signal returns.
10. Facilities SQLite And Trauma Tier Ranking
src/lib/facilitiesDb.ts opens an expo-sqlite database (emergency_seed.db, table emergency_nodes), seeds it on first launch from an inline SEED[] (~50 Chennai-corridor POIs) plus a POI_COORDS map, then ranks.
Schema
CREATE TABLE emergency_nodes (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
lat REAL NOT NULL,
lng REAL NOT NULL,
type TEXT NOT NULL, -- hospital | clinic | police | ambulance
trauma_tier INTEGER NOT NULL, -- 1 trauma center, 2 hospital ER, 3 clinic
emergency_phone TEXT,
nh_corridor TEXT,
state TEXT,
verified INTEGER DEFAULT 0,
verified_at TEXT
);
CREATE INDEX idx_coords ON emergency_nodes(lat, lng);
CREATE INDEX idx_tier ON emergency_nodes(trauma_tier);
rankFacilities(triage, lat, lng)
- Filter by triage-appropriate
trauma_tier(RED โ 1,2 / YELLOW โ 2,3 / GREEN โ 3). - Compute Haversine distance to each candidate.
- Sort ascending, take top 6, mark the first as
recommended. - Offline ETA fallback:
etaMinutes = (distanceKm / 40) * 60(40 km/h ambulance average). Online uses OSRM duration.
scripts/generate-facilities-seed.mjs.
11. Emergency Orchestrator
The orchestrator turns one SOS into a coordinated burst of actions. Plan is pure/testable; the runner performs side effects.
- Plan:
src/lib/emergency/emergencyOrchestratorPlan.tsโplanEmergencyOrchestrator()+buildMargiIceSmsBody(). - Runner:
src/lib/emergency/emergencyOrchestrator.tsโrunEmergencyOrchestrator().
flowchart LR
start[Activation done] --> coords[Resolve GPS coords]
coords --> rank[rankFacilities -> nearest trauma POI]
rank --> plan[planEmergencyOrchestrator]
plan --> ice{notifyEmergencyContacts?}
ice -->|yes & ICE set| iceSms[Open ICE SMS intent]
ice -->|no| skip[skip ICE]
iceSms --> s108[Open 108 SMS intent]
skip --> s108
s108 --> maps[Open Google Maps to facility lat/lng]
maps --> trauma[Navigate to Trauma Response HUD]notifyEmergencyContactsis a boolean inUserProfile.settings(defaulttrueinstorage.ts, toggled inapp/settings.tsx); the plan gates ICE SMS on it.- Maps navigation targets the facility coordinates (
hospitalNavTarget.ts), not the user's pin โ a deliberate fix (see ยง26). - Triggered from
app/emergency/activation.tsxafter the countdown.
12. Hold SOS Quick SOS And Incident Tracker
All SOS entry points share one intentional path โ no premature SMS before an incident type is chosen.
| Entry | Behavior |
|---|---|
| Hold SOS (3s) on Drive HUD | HoldSOSButton.tsx (haptics + progress ring) opens the Incident Tracker; holdSosReleaseGrace.ts prevents accidental skips on quick release |
| Quick SOS (Home) | src/lib/home/quickSos.ts โ Incident Tracker |
| Header SOS | same path |
app/emergency/selection.tsx is the Incident Tracker (road accident / natural calamity) and includes a cancel-SOS countdown. After selection โ activation.tsx (โ6s splash) โ orchestrator runs.
13. Trauma Response HUD
app/emergency/response.tsx with src/components/emergency/TraumaResponseActionBar.tsx.
- Live incident timer since SOS (
incidentElapsed.ts). - Rear-flash torch via
useTorch.ts(camera permission +torchOn) rendered throughTorchCameraLayer.tsx(a hidden 1ร1CameraViewwithenableTorch). The torch logic and the camera layer are deliberately split (see ยง26). - Call ICE + Call Center FABs (
contactActions.ts). - First-aid board (
FirstAidBoard.tsx) + offline trauma copy (traumaAssistantOffline.ts). - Sarthi assistant available inline.
14. Naari Shakti Womens Safety Portal
Gender-gated (self-reported in Medical) women's safety lane with a saturated saffron + navy identity.
- Routes:
app/naari-shakti.tsx; components undersrc/components/naari/; logic insrc/lib/naariShakti/. - Actions: SMS nearest police station, ICE alert, helpline 181, 2s hold-to-activate distress HUD with optional recording.
- 112 national fallback when the user is far from the demo police seed.
- Entry: Medical โ set Female โ Home NAARI SHAKTI card โ enable portal โ Safety Mode โ hold Emergency Help 2s.
15. Crash And Distress Voice Engines
CrashEngine (crashEngine.ts)
Active only when journey.status === ACTIVE. Heuristic fusion:
- Peak accelerometer above threshold within a window.
- Speed before impact > 25 km/h.
- Speed after < 5 km/h.
CrashCandidateModal.tsx). No automatic triage or 108 at timer zero โ the user confirms. Native OS crash APIs are stubbed (crash/nativeCrashAdapter.ts) unless a custom dev build supplies them.
Distress voice (voice/)
Optional YAMNet-based distress classifier, off by default (sensitivity setting in Profile). Even when triggered, the countdown does not auto-open the 108 SMS โ fail-safe against false positives. Evaluation: docs/VOICE_CLASSIFIER_EVAL.md.
16. Sarthi Assistant Offline And Cloud
Sarthi is the in-app AI helper. It is offline-first: a deterministic knowledge base answers without any network, and the cloud Gemini BFF is an enhancement.
Decision flow (src/lib/sarthiEngine.ts)
flowchart TD
msg[User message] --> kb{KB match?}
kb -->|yes, offline-first| offline[Knowledge base reply]
kb -->|no| online{Online AND BFF healthy?}
online -->|yes| cloud[POST /api/sarthi/chat -> Gemini]
online -->|no| offlineFb[Offline reply + cloud-unavailable prefix]
cloud -->|error| offlineFbmatchKnowledgeBase()overSARTHI_KB_ENTRIES(crash, fire, trapped, bleeding, not-breathing, SOS, corridor, medical, greeting, help_general, guest_identity...) in en/hi/ta.shouldUseOfflineFirst()keeps emergency/help intents fully offline even when online.- When the cloud call fails,
wrapCloudUnavailableReply()prefixes the offline answer with "Cloud Sarthi (Gemini) is temporarily unavailable. On-device guidance:" so it never silently degrades to a generic boilerplate. - Health gate:
checkSarthiBffHealth()requiresok === trueandgeminiReachable !== false. The status chip ("Gemini online" vs "Offline KB") is driven by this real probe, not by "is an API key present".
17. Sarthi BFF Internals
novadrive/ is a Next.js 14 app. Only two API routes matter.
src/lib/sarthi/aiConfig.ts
- Reads
GOOGLE_GENERATIVE_AI_API_KEYand sanitizes a UTF-8 BOM (\uFEFF) and whitespace that CLI env uploads can prepend (sanitizeGoogleApiKey). - Scrubs
process.envon load so libraries reading the raw var get a clean key. - Builds the provider explicitly:
createGoogleGenerativeAI({ apiKey })rather than relying on implicit env reads. - Model:
process.env.SARTHI_GEMINI_MODEL || 'gemini-2.5-flash'. probeSarthiGemini()performs a real generation (system: 'You are Sarthi health check. Reply in one short word.',prompt: 'ping',maxOutputTokens: 64) and returns{ ok, error? }based on whether non-empty text comes back.resolveSarthiModel()prefers direct Google when a key exists, else falls back to the AI Gateway string model.
GET /api/sarthi/health (src/app/api/sarthi/health/route.ts)
Returns { ok, service, model, geminiConfigured, geminiReachable, provider, probeError? }, status 200 only when configured and the probe actually reaches Gemini. CORS enabled.
POST /api/sarthi/chat (src/app/api/sarthi/chat/route.ts)
- 503 if not configured.
- Builds a system prompt from the context (journey phase, language en/hi/ta, display name, guest vs auth, medical summary, ICE presence, regional protocols) and calls
generateText({ model: resolveSarthiModel(), system, messages, maxOutputTokens: 256 }). - Returns
{ reply, actionCard? }(action card appears when corridor/route is mentioned). CORS enabled.
novadrive-mobile/scripts/check-sarthi-bff.cjs hits both endpoints to validate a deployment from the dev machine.
18. Supabase Backend
Project Projectmargi (yllcmksndrhlektbjvcu). Migrations under supabase/migrations/.
| Migration | Contents |
|---|---|
20260528_phase3_core.sql | Tables profiles, volunteer_providers, dispatch_events; RLS policies; handle_new_user() trigger on auth.users |
20260528_phase3_security_fix.sql | Revokes public RPC execution on handle_new_user() |
Mobile integration:
src/lib/supabase/client.tsโcreateClientwithexpo-secure-storeauth persistence; readsEXPO_PUBLIC_SUPABASE_URL+EXPO_PUBLIC_SUPABASE_ANON_KEY(use the publishablesb_publishable_...key, never the secret).profileSync.tsโfrom('profiles')select/upsert;authSession.tsโ session helpers.ngo/volunteerProviders.tsโfrom('volunteer_providers'); setverified = truefor a demo NGO row.emergency/dispatchOrchestrator.tsโfrom('dispatch_events').insert(...)audit.- Guest mode bypasses auth entirely so judges never need to sign in.
19. Web Mirror And Bystander Relay
novadrive/src/app/ mirrors the emergency flow for laptop demos and, crucially, hosts the bystander relay:
/relay(relay/page.tsx) โ decodes a?p=query (ND1:+ lz-string), shows triage + location, and offers Google Maps + SMS-to-108 links. This is the URL embedded in the mobile GHP QR.- *
/emergency/โ a full web wizard (locate โ triage โ route โ packet โ relay) backed bylib/session-store.ts, reusing web copies ofstartTriageFSM.ts,ghp.ts, andfacilities.ts.
20. Brief Site Build Pipeline
This very page is generated by docs/site/build-docs.js:
- Reads
docs/MARGI_MASTER_BRIEF.md(this file). - Splits out fenced
mermaidblocks, converts the rest of the markdown to HTML with a small custom parser (headers, tables, lists, code fences, blockquotes, inline code, bold/italic, links, horizontal rules). - Generates the sidebar TOC from lines matching
N. Label. - Writes
docs/site/index.htmland copiesMARGI_MASTER_BRIEF.md(+ PDF if present) for download. - Mermaid renders client-side; a print stylesheet produces a clean PDF via "Save as PDF".
node docs/site/build-docs.js. Deployed by the root Vercel project (vercel.json: buildCommand: node docs/site/build-docs.js, outputDirectory: docs/site).
Anchor rule: a heading ## N. Title becomes id n-title (lowercased, punctuation stripped, spaces โ hyphens). Keep headings ASCII and punctuation-free so the TOC links resolve.
21. Environment Variables
Never commit real values. Names only:
novadrive-mobile/.env
| Name | Purpose |
|---|---|
EXPO_PUBLIC_SARTHI_API_URL | Sarthi BFF origin (the novadrive Vercel project, not the brief site) |
EXPO_PUBLIC_SUPABASE_URL | Supabase project URL |
EXPO_PUBLIC_SUPABASE_ANON_KEY | Supabase publishable key |
EXPO_PUBLIC_TRAUMA_DISPATCH_URL | optional HTTP dispatch endpoint (trauma) |
EXPO_PUBLIC_POLICE_DISPATCH_URL | optional HTTP dispatch endpoint (police) |
novadrive/.env
| Name | Purpose |
|---|---|
GOOGLE_GENERATIVE_AI_API_KEY | Google AI Studio key for Gemini (server-side only) |
SARTHI_GEMINI_MODEL | optional model override (default gemini-2.5-flash) |
AI_GATEWAY_API_KEY / VERCEL_OIDC_TOKEN | optional alternative providers via AI Gateway |
After changing mobile .env, restart Metro with npx expo start --clear (env is inlined at bundle time).
22. Local Development Setup
# Mobile (primary)
cd novadrive-mobile
npm install --legacy-peer-deps
npx expo install react-native-worklets babel-preset-expo
cp .env.example .env # add Sarthi BFF + Supabase keys (optional)
npm test # Jest unit tests
npm run typecheck # tsc --noEmit
npm run android # dev build + Metro (Android Studio JBR on Windows)
# or Expo Go on same Wi-Fi:
npm run start:lan
# Cloud BFF
cd ../novadrive
npm install
npm run dev # http://localhost:3000
npm run dev:lan # -H 0.0.0.0 -p 3000 for device testing
# Brief site
node docs/site/build-docs.js # regenerate docs/site/index.html
Recommended SOS demo: Guest โ Trip โ Start Driving โ calibration โ hold SOS 3s โ pick incident โ activation โ trauma response (verify ICE FAB, timer, torch, Maps opens toward the hospital, not the user pin).
23. Deployment Mobile APK
GitHub Actions .github/workflows/android-apk.yml:
- Triggers on
workflow_dispatchand publishedrelease. - Node 20 + Java 17 + Android SDK โ
cd novadrive-mobile && npm installโexpo prebuild --platform android --cleanโ./gradlew assembleDebug. - Uploads artifact
margi-debug.apk; attaches it to the GitHub Release when triggered by a release.
npm run android:apk (needs JDK 17+; scripts patch the Foojay Gradle resolver and resolve the JDK). Judges install the APK and tap Continue as Guest.
24. Deployment Sarthi BFF
Deploy novadrive/ as a
From GitHub (recommended)
- Import
Stormynubee/Margiat vercel.com/new.
novadrive (not repo root).
npm install, Build npm run build, docs/site value โ that belongs to the brief-site project).
GOOGLE_GENERATIVE_AI_API_KEY (Production + Preview).From CLI
cd novadrive
npm install
npx vercel --prod
# add GOOGLE_GENERATIVE_AI_API_KEY in the dashboard, then redeploy
Verify
curl https://YOUR-BFF.vercel.app/api/sarthi/health
# expect: { "ok": true, "geminiConfigured": true, "geminiReachable": true, "model": "gemini-2.5-flash" }
node novadrive-mobile/scripts/check-sarthi-bff.cjs https://YOUR-BFF.vercel.app
Then set EXPO_PUBLIC_SARTHI_API_URL to that URL (no trailing slash) and restart Expo.
25. Testing Strategy
testEnvironment: node, roots in src/, pattern /.test.ts (logic only โ no .tsx rendering tests). 73 test files / 225 cases. Notable clusters: startTriageFSM, ghp, facilitiesDb, emergencyOrchestrator, sarthiEngine, sarthiKnowledgeBase, voice/distress, naariShakti, home/safety brief.
src/lib logic module; keep side effects (intents, navigation, SQLite) behind pure plan functions so they are testable.npm run verify:docs, npm run verify:branding.ci.yml): Job 1 mobile typecheck + test + verify gates; Job 2 novadrive next build; Job 3 build the brief site.poi-ingest.yml): Python pytest on scripts/tests for any scripts/ or data/ change.novadrive-mobile/docs/DEVICE_SMOKE_MATRIX.md (device matrix), optional Maestro flows.26. Engineering Problem And Solution Log
This is the institutional memory โ every non-trivial bug solved while building Margi, with the real root cause and fix. Read this before re-touching the relevant subsystem.
26.1 Metro bundling failed on useTorch.ts (JSX in a .ts file)
- Symptom: Android Metro bundling error; torch hook would not compile.
- Root cause: JSX (the hidden
CameraView) lived in a.tsfile; only.tsxmay contain JSX. - Fix: split responsibilities โ
src/hooks/useTorch.tsholds permission +torchOnlogic only; the JSX moved tosrc/components/emergency/TorchCameraLayer.tsx.TraumaResponseActionBar.tsxrenders the layer. Lesson: keep hooks JSX-free, or use.tsx.
26.2 SOS stuck on "ADVANCING NOW..." at timer 0
- Symptom: Activation countdown reached 0 and froze on "ADVANCING NOWโฆ".
- Root cause: navigation was attempted before the router was ready, and the orchestrator ran before navigation, blocking the transition.
- Fix: in
app/emergency/activation.tsx, navigate first, then run the orchestrator, and guard navigation withuseRootNavigationState(). Also corrected the triage type passed to the orchestrator.
26.3 Trauma screen crash โ FirstAidBoard missing
- Symptom: opening the trauma response screen crashed.
- Root cause:
app/emergency/response.tsxreferencedFirstAidBoardwithout importing it. - Fix: import
FirstAidBoardfromsrc/components/emergency/FirstAidBoard.tsx.
26.4 Sarthi returned the same canned reply for every message
- Symptom: every message ("how are u", "I need sleep") returned the identical intro/fallback line, while the chip claimed the BFF was online.
- Root causes (stacked):
ok: true when the API key merely existed, without ever calling Gemini.
2. Chat failed silently โ the mobile send path caught BFF errors and fell back to getFallbackMessage() (the same boilerplate every time).
3. Wrong BFF URL โ the app was pointed at the static brief site (roadsafetyhackathon-six / margi-tau), which 404s on /api/sarthi/.
- Fixes:
probeSarthiGemini) and returns geminiReachable; the mobile health check requires geminiReachable !== false.
- On cloud failure, the offline answer is prefixed with a visible "Cloud Sarthi unavailable" banner instead of silently repeating boilerplate; KB intents stay offline-first by design.
- EXPO_PUBLIC_SARTHI_API_URL points at the dedicated novadrive project (e.g. novadrive-eta.vercel.app).
26.5 Fake "GEMINI BFF ONLINE" chip
- Symptom: chip showed online even when Gemini never responded.
- Root cause: status was derived from "is a key configured", not from reachability.
- Fix: status copy (
sarthiStatusCopy.ts,app/sarthi.tsx) is driven by the realgeminiReachableprobe; chip reads "Gemini online" only when the probe truly succeeds, otherwise "Offline KB".
26.6 UTF-8 BOM in the Vercel env key (character at index 0 has value 65279)
- Symptom: health/chat errored with
... character at index 0 has a value of 65279 .... - Root cause: piping the key via PowerShell into
vercel env addprepended a UTF-8 BOM (\uFEFF);@ai-sdk/googlesent it verbatim in the auth header. - Fix:
sanitizeGoogleApiKey()strips BOM + whitespace,process.envis scrubbed on load, and the provider is built explicitly withcreateGoogleGenerativeAI({ apiKey: clean }). Re-added the env without BOM and redeployed.
26.7 Gemini quota exceeded on gemini-2.0-flash
- Symptom:
You exceeded your current quota ... free_tier ... limit: 0, model: gemini-2.0-flash. - Root cause: the key had no free-tier quota for
gemini-2.0-flash. - Fix: switched the default model to
gemini-2.5-flash(overridable viaSARTHI_GEMINI_MODEL), which had availability for the key. Chat then returned real, contextual replies.
26.8 Health probe false-negative ("Empty model response" / "Unexpected probe reply: i")
- Symptom: chat worked but health reported
geminiReachable: false. - Root cause: the probe demanded the model echo an exact word with a tiny token budget;
gemini-2.5-flashreturned empty/partial text for the over-constrained prompt. - Fix: relaxed the probe to a short system +
prompt: 'ping'withmaxOutputTokens: 64, accepting any non-empty reply as healthy. Health then matched chat reality.
26.9 Maps opened to the user's pin instead of the hospital
- Symptom: orchestrator's Maps step navigated to the victim location, not the destination hospital.
- Root cause: the navigate target used current coords.
- Fix:
hospitalNavTarget.tsresolves the ranked facility lat/lng (added lat/lng to theFacilitytype) and Maps opens toward it.
26.10 Premature SMS before incident selection / false alerts
- Symptom: SMS could fire before the user chose an incident type; voice/crash countdowns risked auto-SMS.
- Fix: unified all SOS entries through the Incident Tracker first; added
holdSosReleaseGraceso a quick release doesn't skip the picker; disabled voice detection by default; removed the countdown-0 auto-SMS so the user always confirms.
26.11 Documentation drift (PWA vision vs shipped native app)
- Symptom: docs described a Next.js PWA + sql.js that was never the shipped client.
- Fix: created
docs/CANON.mdas the single source of truth; marked the old brief as historical (now Appendix A here); README + this bible describe the real Expo app.
26.12 Windows parallel Gradle and C++ compiler OOM crash
- Symptom: Android CLI build (
npx expo run:android) crash withCreateProcess error=1455, The paging file is too small for this operation to completeor Gradle Daemon compiler exhaustion. - Root cause: Default parallel execution inside
gradle.properties(org.gradle.parallel=true) coupled with unconstrained C++ compilation threads (e.g. building Reanimated or custom modules) overcommited host system memory on Windows machines. - Fix: Disabled parallel builds and workers in
gradle.properties(org.gradle.parallel=false,org.gradle.workers.max=1) and restricted concurrent C++ compiler processes by launching the build withCMAKE_BUILD_PARALLEL_LEVEL=1set. This bounds paging size requirements perfectly, enabling stable builds under 1.5GB total JVM memory footprint.
26.13 Prominent 15-language selector reactive translation sync
- Symptom: When translating the core login screen, text fragments did not reactively update when regional (Hindi/Tamil) chips were selected; selector UI felt generic.
- Fix: Designed a robust full-width Expandable Tactile Grid Language Selector containing 15 global locales (with individual flag glyphs, native, and English labels). Built the translation registry (
src/lib/translations/- mapping English, Hindi, Tamil, Spanish, German, Mandarin, etc.) into the reactive state context (useApp()). Selecting a chip reactively re-evaluates all active text keys (getAuthString()) instantly while triggering pleasant haptics (Haptics.notificationAsync).
27. Replicate From Scratch Playbook
To rebuild Margi from zero:
- Monorepo: create
novadrive-mobile/(Expo),novadrive/(Next.js),docs/,supabase/migrations/,scripts/,.github/workflows/. Add the twovercel.jsonfiles (root โ brief site;novadrive/โ Next.js). - Mobile scaffold:
npx create-expo-app(SDK 54), add expo-router, the dependency set in ยง4, the@/alias, andreact-native-reanimated/pluginin babel. Configureapp.json(com.margi.app,margi://, camera/location/SMS permissions, plugins). - Offline core first (TDD): build
startTriageFSM.ts(ยง8) โfacilitiesDb.ts+ seed (ยง10) โghp.tsencoder + integrity (ยง9), each with.test.ts. This is the demo-critical path; it must work in airplane mode. - SOS path:
HoldSOSButtonโ Incident Tracker (emergency/selection) โactivation(navigate-then-orchestrate, router guard) โemergencyOrchestratorPlan/emergencyOrchestrator(ICE SMS โ 108 SMS โ Maps to facility) โ trauma HUD (response.tsx+TraumaResponseActionBar+useTorch/TorchCameraLayer) โpacket(QR) โrelay. - Sarthi: offline KB (
sarthiKnowledgeBase.ts) + engine (sarthiEngine.ts) offline-first; health gate (sarthiHealth.ts) requiringgeminiReachable. Then the BFF (ยง17):aiConfig.ts(BOM-safe key, explicit provider,gemini-2.5-flash),/api/sarthi/health(real probe),/api/sarthi/chat. - Naari Shakti (ยง14), crash + voice off-by-default (ยง15).
- Supabase (ยง18): apply migrations, wire client with SecureStore, keep Guest mode.
- CI/CD (ยง23โยง25):
ci.yml,android-apk.yml,poi-ingest.yml. - Brief site (ยง20): write this file;
node docs/site/build-docs.js; deploy root project. - Wire env (ยง21) and verify health endpoints before claiming "online".
.ts; navigate before side effects and guard the router; never report a service "online" without probing it; sanitize CLI-uploaded secrets (BOM); pick a model the key actually has quota for; route Maps to the destination, not the origin; never auto-SMS without explicit user confirmation.
28. Honesty Boundaries And Rejected Scope
| Topic | Demo truth |
|---|---|
| POI database | NH48 verified pack (50 OSM nodes, 40 phone-verified) in the Chennai bbox; baseline 108 + GPS + triage elsewhere |
| Naari police | Chennai demo stations; 112 national fallback beyond the seed |
| "production" tag | v2.0.0-production = integration milestone, not clinical production |
| Sarthi | 31+ offline KB entries; cloud LLM optional |
| Gender | self-reported on device, unverified in P0 |
| Crash/voice | sensor heuristics + experimental voice; no OS crash API in P0; no auto-SMS |
Deliberately rejected (do not reintroduce without a team vote)
| Rejected idea | Why killed |
|---|---|
| Ultrasonic V2V | browsers block mic without gesture; road noise; unreliable demo |
| BLE / DTN mesh | browsers can't advertise BLE; iOS has no Web Bluetooth |
| Wi-Fi Direct beacons | not available in web/RN-managed APIs |
| Always-on crash audio ML | false positives, privacy, battery, research-grade scope |
| Curvature/anxiety physics engine | wrong problem statement; OSM geometry too sparse |
| Background volunteer GPS | unreliable background tracking; needs gov integration |
| Auto-dial 108 | platform policy; user must tap Send/Call |
Strategic lesson: judges score reliable offline triage with valid POI data โ not machines chirping at each other. Margi's differentiator is information surviving dead zones (GHP + human QR relay).
29. Glossary
| Term | Meaning |
|---|---|
| START | Simple Triage And Rapid Treatment โ accident-scene triage protocol |
| FSM | Finite State Machine โ deterministic step logic |
| GHP | Golden Hour Packet โ structured 108-ready emergency brief |
| ND1: | Margi relay payload envelope (lz-string compressed) carried in QR / /relay?p= |
| trauma_tier | hospital capability: 1 trauma center, 2 hospital ER, 3 clinic |
| Haversine | great-circle distance between two GPS points |
| ICE | In Case of Emergency contact |
| 108 / 112 / 181 | India ambulance / unified emergency / women's helpline |
| Naari Shakti | Margi's women's safety lane |
| BFF | Backend-For-Frontend โ the novadrive Next.js server fronting Gemini |
| Sarthi | Margi's in-app AI assistant (offline KB + optional Gemini) |
| OSRM | Open Source Routing Machine โ road ETA |
| RLS | Row Level Security (Supabase/Postgres) |
| Golden hour | first ~60 minutes after trauma โ survival-critical |
| RoadSoS | the hackathon problem statement โ emergency services access |
| CoERS | Centre of Excellence for Road Safety, IIT Madras |
30. Resources And Links
| Resource | Link |
|---|---|
| GitHub repo | https://github.com/Stormynubee/Margi |
| Live brief site (this page) | https://roadsafetyhackathon-six.vercel.app |
| Android APK workflow | https://github.com/Stormynubee/Margi/actions/workflows/android-apk.yml |
| Google AI Studio (Gemini key) | https://aistudio.google.com/apikey |
| CoERS event | https://coers.iitm.ac.in/event/national-road-safety-hackathon-2026/ |
| Unstop (Indian track) | https://unstop.com/hackathons/road-safety-hackathon-2026-iit-madras-1680515 |
| OpenStreetMap / Overpass | https://www.openstreetmap.org ยท https://overpass-api.de |
| OSRM demo | http://router.project-osrm.org |
| START reference | https://en.wikipedia.org/wiki/Simple_triage_and_rapid_treatment |
In-repo: docs/CANON.md (scope truth), docs/ARCHITECTURE.md, docs/PHASE3_SETUP.md, JUDGE_START_HERE.md, docs/SUBMISSION.md, novadrive-mobile/README.md, novadrive-mobile/docs/DEVICE_SMOKE_MATRIX.md.
31. Multilingual Support and Searchable Tactile Grid Selector
To ensure extreme accessibility across diverse Indian highway demographics and global locales, Margi features a robust, fully localized 15-Language Engine. This system empowers users and first responders to switch the interface language reactively on the fly.
Shipped Languages
| Locale | Native Name | English Name | Region/Country |
|---|---|---|---|
| en | English | English | Global |
| hi | เคนเคฟเคจเฅเคฆเฅ | Hindi | India (National) |
| ta | เฎคเฎฎเฎฟเฎดเฏ | Tamil | India (Tamil Nadu) |
| es | Espaรฑol | Spanish | Global |
| fr | Franรงais | French | Global |
| de | Deutsch | German | Global |
| zh | ไธญๆ | Mandarin | Global |
| ja | ๆฅๆฌ่ช | Japanese | Global |
| ar | ุงูุนุฑุจูุฉ | Arabic | Global |
| pt | Portuguรชs | Portuguese | Global |
| ru | ะ ัััะบะธะน | Russian | Global |
| bn | เฆฌเฆพเฆเฆฒเฆพ | Bengali | India / Bangladesh |
| pa | เจชเฉฐเจเจพเจฌเฉ | Punjabi | India / Pakistan |
| mr | เคฎเคฐเคพเค เฅ | Marathi | India (Maharashtra) |
| te | เฐคเฑเฐฒเฑเฐเฑ | Telugu | India (Andhra/Telangana) |
Key Capabilities
- Searchable Tactile Grid Selector: Built as a full-width, expandable UI component in
app/auth.tsx. It displays each language in a distinct, high-impact tile featuring native labels, English translations, and country flags. An embedded real-time search input filters the grid instantly. - Haptic Touch Integration: Utilizes native haptics (
expo-haptics) to trigger medium and success haptic bumps on interaction, providing physical confirmation of language changes to the user. - Modular Translation Registry: The dictionaries are separated cleanly into
src/lib/translations/(en.ts,hi.ts,ta.ts,global_dicts.ts) and unified inindex.ts. - Reactive App-Wide Translation: The selected locale is managed in the app-wide settings state (
useApp()). Core auth screens and subsequent pages re-render reactively using thegetAuthString()helper, keeping the active language synchronized across the entire user experience without requiring an app reload.
Appendix A โ Original Planning Brief (Historical PWA Vision)
The content below is the original aspirational brief. Margi shipped as a native Expo Android app, not the Next.js PWA + sql.js described here. Sections 1โ30 above are canonical. This appendix is retained for learning and provenance โ it documents the architecture we explored, the reasoning, and the rejected sensor-mesh path.
A.1 Core innovation (three pillars)
flowchart LR P1["1. START TRIAGE CHATBOT
(AI + FSM)"] --> P2["2. TRAUMA-TIER ROUTING
(not nearest pin)"] --> P3["3. GOLDEN HOUR PACKET (GHP)
+ QR HUMAN RELAY"]
The three pillars (offline START triage, capability-based trauma-tier routing, and the GHP carried through dead zones by a human QR relay) survived from this vision into the shipped app โ only the delivery platform changed from PWA to native Expo.
A.2 Original PWA system architecture
flowchart TB
subgraph Client["Margi PWA (Next.js 14)"]
Chat[Triage Chat UI]
FSM[START Triage FSM]
Router[Offline Spatial Router]
GHP[GHP Builder + Compressor]
QR[QR Generator / Scanner]
IDB[(IndexedDB Relay Store)]
SW[Service Worker / Serwist]
SQL[(sql.js + emergency_seed.db)]
end
Chat --> FSM --> Router --> SQL
FSM --> GHP --> QR --> IDB
SW --> SQLIn the shipped app: Next.js PWA โ Expo Router native; sql.js โ expo-sqlite; IndexedDB relay โ SecureStore + /relay; Serwist service worker โ native offline (assets bundled in the APK); Web Speech API โ expo-speech + optional YAMNet voice.
A.3 Original build-time data pipeline
OpenStreetMap -> Overpass API (nwr: hospitals, clinics, police, ambulance)
-> ingestCorridors.py (parse names/phones/types, assign trauma_tier, tag corridor/state, manual verify CSV)
-> emergency_seed.db -> bundled + submitted
This pipeline still exists under scripts/ (validated by poi-ingest.yml) and produces the verified NH48 pack, but the runtime app uses an inline seed in facilitiesDb.ts rather than loading the built DB file.
A.4 Original full runtime sequence
sequenceDiagram
participant V as Victim Phone (Offline)
participant FSM as START FSM
participant DB as SQLite
participant GHP as GHP Builder
participant B as Bystander Phone
participant SMS as 108 SMS
V->>FSM: Start Emergency + GPS
FSM->>FSM: Triage questions
FSM->>DB: Query trauma-tier POIs
DB-->>FSM: Ranked facilities
FSM->>GHP: Build packet + hash
GHP-->>V: Display QR + brief
B->>V: Scan QR
Note over B: Drives until signal returns
B->>SMS: Auto-compose 108 messageEnd of Margi โ Complete Replication Bible ยท Team NovaDrive ยท CoERS Road Safety Hackathon 2026.
Maintainer note: docs/CANON.md wins on any scope dispute. Update the Problem and Solution Log (Section 26) whenever a non-trivial bug is fixed, and rebuild the brief site with node docs/site/build-docs.js.