## 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: 1) A new left-drawer link to a new view `DAEList`. 2) In an active alert `Situation` view, a button `Afficher les défibrillateurs` that: - enables DAE display inside a 10km corridor around the segment between user location and alert location - navigates to the alert map tab 3) On alert map, render DAE markers; tapping a DAE opens `DAEItem`. 4) `DAEList` screen with bottom navigation: `Liste` (default) and `Carte`, showing defibs nearest→farthest around the user, within 10km. 5) `DAEItem` screen with bottom navigation: `Infos` (default) and `Carte` (map + itinerary to the selected DAE; reuse alert routing patterns). 6) 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 to `DAEList` - `Non merci` → dismiss ### v1 decisions (explicit) 1) Availability display: use `horaires_std` (structured JSON) to show open/closed/unknown status and schedule details. - `horaires_std` shape: `{ 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), `null` if unknown. - `slots`: `[{open:"HH:MM", close:"HH:MM"}]`, `null` if 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"` - `days` includes today + current time falls within a `slot` → `"open"` - `days` includes today + no slots + `businessHours` → approximate Mon-Fri 8h-18h → `"open"` or `"closed"` - `days` does 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). 2) Corridor filter: points are kept if distance to the user→alert line segment is ≤ `corridorMeters = 10_000`. 3) Alert-map DAE markers: v1 uses a separate, non-clustered layer (do not mix into alert clusters). 4) Location permission denied: use last-known location; if none, show an explanatory empty state (no hard block). 5) DB open failure: the app must not crash; DAE UI shows an error empty state and overlay remains disabled. 6) Keyword detection: normalized-text + regex list (no fuzzy search library) in v1. ### Relevant anchors in codebase - Defib query wrapper: `src/data/getNearbyDefibs.js` exports `getNearbyDefibs()`. - Repo query: `src/db/defibsRepo.js` — SELECTs and JSON-parses `horaires_std`. - Embedded DB bootstrap: `src/db/openDb.js` requires `../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.js` uses Paper `Portal` + `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.js` calls `require('../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 defibs` works with columns: `id, latitude, longitude, nom, adresse, horaires, horaires_std, acces, disponible_24h` and `h3`. - `horaires_std` is a JSON string parseable by `JSON.parse()` (already handled in `defibsRepo.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): 1. `disponible_24h === 1` → `"open"`, label `"24h/24 7j/7"` 2. `is24h === true` and `days` includes current ISO day → `"open"`, label `"24h/24"` 3. `days !== null` and does not include current ISO day → `"closed"`, label day range from `days` 4. `days` includes today and `slots` is non-empty → check if current time falls within any slot → `"open"` or `"closed"` with next open/close time as label 5. `businessHours === true` (no explicit slots) → approximate Mon-Fri 08:00-18:00 → `"open"` or `"closed"` 6. `nightHours === true` → approximate 20:00-08:00 → `"open"` or `"closed"` 7. `events === true` → `"unknown"`, label `"Selon événements"` 8. Fallback → `"unknown"`, label from `notes` or `"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` + fixed `now`, 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: false` - `selectedDefib: null` - `showDaeSuggestModal: 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` 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 `distanceMeters` ascending. - 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.notes` is 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 `selectedDefib` in store and navigate to `DAEItem`. Acceptance criteria: - Drawer link opens `DAEList` and 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 - Fallback: show raw `horaires` string if `horaires_std` is null/empty. - Add an `Itinéraire` button that switches to the `Carte` tab. 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, `DAEItem` shows 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 `` with label `Défibrillateurs`. - Drawer icon: `MaterialCommunityIcons` name `heart-pulse`. - Add `` hidden (not shown in menu) and only navigated programmatically. - Adjust section indices in `DrawerItemList.js` if adding a screen shifts boundaries. - Add header title cases for `DAEList` and `DAEItem`. Acceptance criteria: - Drawer link appears under the intended section and opens `DAEList`. - `DAEItem` can 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: `MaterialCommunityIcons` name `heart-pulse`. - Position: next to/after existing main actions (align with current layout conventions). Behavior: 1) Get user coords. 2) Get alert coords. 3) Load corridor defibs using midpoint query radius + corridor filter. 4) Store results and set overlay enabled. 5) 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` + `Modal` mounted high in the tree so it persists across navigations. - CTA: - `Chercher un défibrillateur` → dismiss + navigate to `DAEList` - `Non 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: 1) Drawer: link visible, opens list. 2) 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 3) Situation button: enables overlay and opens alert map. 4) Alert map: DAE markers render with availability-colored icons; tap → DAEItem. 5) 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. 6) Keyword modal: - trigger terms → modal shows - modal persists across navigation - CTA navigates to DAEList 7) 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 `events` flag → shows "unknown" / "Selon événements" --- ## Mermaid overview ```mermaid 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 ```