14 KiB
Common plan prefix (include at the top of every coding-agent prompt)
Product goal (v1)
Integrate defibrillator (DAE) discovery into the app using the embedded SQLite DB query helper getNearbyDefibs().
Deliver:
- A new left-drawer link to a new view
DAEList. - In an active alert
Situationview, a buttonAfficher les défibrillateursthat:- enables DAE display inside a 10km corridor around the segment between user location and alert location
- navigates to the alert map tab
- On alert map, render DAE markers; tapping a DAE opens
DAEItem. DAEListscreen with bottom navigation:Liste(default) andCarte, showing defibs nearest→farthest around the user, within 10km.DAEItemscreen with bottom navigation:Infos(default) andCarte(map + itinerary to the selected DAE; reuse alert routing patterns).- During alert posting, if cardiac-related keywords are detected in the alert subject, show a persistent modal (must remain visible even after navigation, and must work offline) with:
Chercher un défibrillateur→ go toDAEListNon merci→ dismiss
v1 decisions (explicit)
- Availability display: use
horaires_std(structured JSON) to show open/closed/unknown status and schedule details.horaires_stdshape:{ days: number[]|null, slots: {open,close}[]|null, is24h: boolean, businessHours: boolean, nightHours: boolean, events: boolean, notes: string }days: ISO 8601 day numbers (1=Mon … 7=Sun),nullif unknown.slots:[{open:"HH:MM", close:"HH:MM"}],nullif no specific times.- Availability logic (pure function
getDefibAvailability(horaires_std, disponible_24h)→{ status: "open"|"closed"|"unknown", label: string }):disponible_24h === 1→ always"open", label"24h/24 7j/7"is24h && days includes today→"open"daysincludes today + current time falls within aslot→"open"daysincludes today + no slots +businessHours→ approximate Mon-Fri 8h-18h →"open"or"closed"daysdoes not include today →"closed"events === true→"unknown", label"Selon événements"- Else →
"unknown"
- No hard filter: show all defibs regardless of availability, but display status visually (green/red/grey dot or icon).
- Corridor filter: points are kept if distance to the user→alert line segment is ≤
corridorMeters = 10_000. - Alert-map DAE markers: v1 uses a separate, non-clustered layer (do not mix into alert clusters).
- Location permission denied: use last-known location; if none, show an explanatory empty state (no hard block).
- DB open failure: the app must not crash; DAE UI shows an error empty state and overlay remains disabled.
- Keyword detection: normalized-text + regex list (no fuzzy search library) in v1.
Relevant anchors in codebase
- Defib query wrapper:
src/data/getNearbyDefibs.jsexportsgetNearbyDefibs(). - Repo query:
src/db/defibsRepo.js— SELECTs and JSON-parseshoraires_std. - Embedded DB bootstrap:
src/db/openDb.jsrequires../assets/db/geodae.db. - Drawer navigation:
src/navigation/Drawer.js. - Drawer sections:
src/navigation/DrawerNav/DrawerItemList.js. - Header titles:
src/navigation/RootStack.js. - Alert tabs:
src/scenes/AlertCur/Tabs.js. - Alert map feature pipeline:
src/scenes/AlertCurMap/useFeatures.js+src/scenes/AlertCurMap/useOnPress.js. - Persistent modal pattern:
src/containers/SmsDisclaimerModel/index.jsuses PaperPortal+Modal. - Alert posting flow:
src/scenes/SendAlertConfirm/useOnSubmit.js. - Preprocessing pipeline:
scripts/dae/(geodae-to-csv.js → csv-to-sqlite.mjs).
Split tasks (agent-ready)
Task 1 — Validate embedded DB asset packaging and schema assumptions
Objective: ensure the bundled SQLite geodae.db is present and accessible on-device, and confirm expected schema columns exist.
Notes:
src/db/openDb.jscallsrequire('../assets/db/geodae.db').- In the current workspace,
src/assets/db/may be missing; find the DB source and ensure it is bundled for Expo.
Acceptance criteria:
- App can open the DB without throwing at the asset require.
- Query
SELECT ... FROM defibsworks with columns:id, latitude, longitude, nom, adresse, horaires, horaires_std, acces, disponible_24handh3. horaires_stdis a JSON string parseable byJSON.parse()(already handled indefibsRepo.js).- If DB open fails, DAE screens show a non-blocking error empty state and overlay stays disabled.
Task 2 — Implement geo utilities for corridor filtering + availability helper (pure, unit-testable)
Objective: create pure utilities to compute the query radius for the corridor overlay, filter defibs to those inside the corridor, and determine real-time availability from horaires_std.
2a — Geo / corridor utilities
Suggested exports (e.g. src/utils/geo/corridor.js):
toLonLat({ latitude, longitude })computeCorridorQueryRadiusMeters({ userLonLat, alertLonLat, corridorMeters })filterDefibsInCorridor({ defibs, userLonLat, alertLonLat, corridorMeters })
Implementation guidance:
- Use the existing Turf usage patterns already present in the alert map stack.
- Corridor definition: keep point if distance to the line segment ≤
corridorMeters. - Query strategy: query around midpoint with radius
segmentLengthMeters / 2 + corridorMeters, then apply corridor filter in JS and cap markers (e.g. 200) to keep map responsive.
2b — Availability helper
Suggested export (e.g. src/utils/dae/getDefibAvailability.js):
getDefibAvailability(horaires_std, disponible_24h, now?)→{ status: "open"|"closed"|"unknown", label: string }
The horaires_std object has shape:
{ days: number[]|null, slots: {open,close}[]|null, is24h, businessHours, nightHours, events, notes }
Logic (in priority order):
disponible_24h === 1→"open", label"24h/24 7j/7"is24h === trueanddaysincludes current ISO day →"open", label"24h/24"days !== nulland does not include current ISO day →"closed", label day range fromdaysdaysincludes today andslotsis non-empty → check if current time falls within any slot →"open"or"closed"with next open/close time as labelbusinessHours === true(no explicit slots) → approximate Mon-Fri 08:00-18:00 →"open"or"closed"nightHours === true→ approximate 20:00-08:00 →"open"or"closed"events === true→"unknown", label"Selon événements"- Fallback →
"unknown", label fromnotesor"Horaires non renseignés"
The now parameter (defaults to new Date()) enables deterministic testing.
Acceptance criteria:
- Given synthetic points, corridor filter is correct and deterministic.
- Given synthetic
horaires_std+ fixednow, availability status is correct.
Task 3 — Add a Defibs store (zustand atom) for caching + overlay + selection + modal state
Objective: add centralized state to avoid repeated DB queries and coordinate UI across screens.
State:
nearUserDefibs: []corridorDefibs: [](or keyed by current alert id if needed)showDefibsOnAlertMap: falseselectedDefib: nullshowDaeSuggestModal: false
Actions (suggested):
loadNearUser({ userLonLat })loadCorridor({ userLonLat, alertLonLat })setShowDefibsOnAlertMap(bool)setSelectedDefib(defib)setShowDaeSuggestModal(bool)reset()
Acceptance criteria:
- Other tasks can call actions without duplicating query/filter logic.
Task 4 — Create DAEList scene with bottom tabs: Liste + Carte
Objective: add DAEList screen with bottom navigation and two tabs.
UI specifics (from existing patterns):
- Follow the app’s bottom-tab pattern.
- Tab icons:
- Liste:
format-list-bulleted - Carte:
map-marker-outline
- Liste:
Behavior:
- Get user location (current if possible; else last-known).
- Query within 10km using
getNearbyDefibs({ radiusMeters: 10_000 }). - No hard availability filter: show all defibs. Each row shows real-time availability status via
getDefibAvailability(horaires_std, disponible_24h). - Sort by
distanceMetersascending. - Empty states:
- no location → explain how to enable permission
- DB error → explain that DAE database is unavailable
List row content:
- Name (
nom), distance, address summary. - Availability status indicator: green dot + "Ouvert" / red dot + "Fermé" / grey dot + "Inconnu" (or label from
getDefibAvailability). - If
horaires_std.notesis non-empty, show it as a secondary line.
Carte tab:
- Map markers colored by availability status (green/red/grey).
Interaction:
- Tap list row or map marker → set
selectedDefibin store and navigate toDAEItem.
Acceptance criteria:
- Drawer link opens
DAEListand list is sorted nearest→farthest. - Availability status is displayed per row/marker.
Task 5 — Create DAEItem scene with bottom tabs: Infos + Carte
Objective: create DAE detail view for a selected defib.
Tab icons:
- Infos:
information-outline - Carte:
map-marker-outline
Infos tab content:
- Name (
nom), address (adresse), access (acces), distance. - Availability section:
- Current status via
getDefibAvailability()with colored indicator. - Schedule details from
horaires_std:- If
is24h: "Disponible 24h/24" - If
days: show day range (e.g. "Lun-Ven") - If
slots: show time slots (e.g. "08:00 - 18:00") - If
businessHours: "Heures ouvrables" - If
nightHours: "Heures de nuit" - If
events: "Selon événements" - If
notes: show notes text
- If
- Fallback: show raw
horairesstring ifhoraires_stdis null/empty.
- Current status via
- Add an
Itinérairebutton that switches to theCartetab.
Carte tab:
- Map + itinerary to the defib coordinates.
- Reuse alert routing implementation patterns (OSRM fetch etc.).
- Offline behavior: show a clear message that routing is unavailable offline.
Acceptance criteria:
- From list or alert map marker,
DAEItemshows the correct selected defib.
Task 6 — Navigation wiring: DAEList in drawer + DAEItem hidden
Objective: register new screens and keep drawer sections consistent.
Implementation notes:
- Add
<Drawer.Screen name='DAEList'>with labelDéfibrillateurs. - Drawer icon:
MaterialCommunityIconsnameheart-pulse. - Add
<Drawer.Screen name='DAEItem'>hidden (not shown in menu) and only navigated programmatically. - Adjust section indices in
DrawerItemList.jsif adding a screen shifts boundaries. - Add header title cases for
DAEListandDAEItem.
Acceptance criteria:
- Drawer link appears under the intended section and opens
DAEList. DAEItemcan be navigated to programmatically.
Task 7 — Add Situation button: enable corridor overlay and navigate to alert map
Objective: in active alert Situation view, add Afficher les défibrillateurs.
UI specifics:
- Use the app’s existing action button pattern.
- Icon:
MaterialCommunityIconsnameheart-pulse. - Position: next to/after existing main actions (align with current layout conventions).
Behavior:
- Get user coords.
- Get alert coords.
- Load corridor defibs using midpoint query radius + corridor filter.
- Store results and set overlay enabled.
- Navigate to map tab using existing nested navigation pattern.
Acceptance criteria:
- Tap leads to alert map and DAE markers appear.
Task 8 — Alert map overlay: render DAE markers (separate layer) and open DAEItem on tap
Objective: add a DAE marker layer to the alert map.
Implementation notes:
- Extend the existing feature pipeline to include DAE features when overlay is enabled.
- Add DAE icon images to map images (3 variants: green/red/grey for open/closed/unknown).
- Compute availability via
getDefibAvailability()to select marker color. - Update press handling to detect DAE features and navigate to
DAEItem. - v1 does not cluster DAE markers.
Acceptance criteria:
- Markers appear/disappear based on overlay flag.
- Tapping a marker opens
DAEItem.
Task 9 — Keyword detection during alert posting + persistent DAE suggestion modal
Objective: detect cardiac-related terms in alert subject and display a global modal that remains visible even after navigation.
Detection (v1):
- Normalize text: lowercase + remove diacritics.
- Regex list covering common terms and typos, e.g.:
- cardiaque, cardiac, cardique
- coeur, cœur
- malaise, mailaise, mallaise
- inconscient
- evanoui, évanoui (variants)
- arret, arrêt (especially arrêt cardiaque)
- defibrillateur, défibrillateur
- reanimation, réanimation
- massage cardiaque
- ne respire plus
Modal:
- Use Paper
Portal+Modalmounted high in the tree so it persists across navigations. - CTA:
Chercher un défibrillateur→ dismiss + navigate toDAEListNon merci→ dismiss
Acceptance criteria:
- Modal shows offline.
- Modal remains visible after redirect to current alert view.
Task 10 — Verification checklist (manual)
Objective: provide a deterministic checklist.
Checklist:
- Drawer: link visible, opens list.
- DAEList:
- permission granted → list populated and sorted
- permission denied + last-known available → list uses last-known
- permission denied + no last-known → empty state
- DB missing/unavailable → non-blocking error empty state
- each row shows availability status (open/closed/unknown) with colored indicator
- Situation button: enables overlay and opens alert map.
- Alert map: DAE markers render with availability-colored icons; tap → DAEItem.
- DAEItem:
- Infos tab shows schedule details from
horaires_std(days, slots, 24h, notes). - Availability status is prominently displayed.
- Routing: online route works; offline shows message.
- Infos tab shows schedule details from
- Keyword modal:
- trigger terms → modal shows
- modal persists across navigation
- CTA navigates to DAEList
- Availability logic:
- 24h/24 defib → shows "open" at any time
- Defib with Lun-Ven slots → shows "open" during those hours, "closed" on weekends
- Defib with
eventsflag → shows "unknown" / "Selon événements"
Mermaid overview
flowchart TD
A[User posts alert] --> B[Keyword detection]
B -->|match| M[Show persistent DAE modal]
B -->|no match| C[Navigate to AlertCur Situation]
M --> C
M -->|Chercher un defibrillateur| L[DAEList]
M -->|Non merci| C
C --> S[Button Afficher les defibrillateurs]
S --> E[Enable overlay in store]
E --> MAP[AlertCurMap]
MAP -->|tap DAE marker| ITEM[DAEItem]
L -->|tap list item| ITEM