# DAE / Defibrillator integration plan (agent-splittable) ## Common plan prefix (include this at the top of every coding-agent prompt) ### Product goal Integrate defibrillator (DAE) discovery into the app using the embedded SQLite DB query helper [`getNearbyDefibs()`](src/data/getNearbyDefibs.js:37). Provide: - A new left-drawer link to a new view `DAEList`. - In an active alert `Situation` view, a button `Afficher les défibrillateurs` that: 1) enables DAE display around the **10km corridor around the segment** between user location and alert location, 2) then navigates to the alert map. - In alert map, render DAE markers; tapping a DAE opens `DAEItem`. - `DAEList` screen with bottom navigation: `Liste` (default) and `Carte`, showing defibs nearest→farthest around the user, **within 10km**. - `DAEItem` screen with bottom navigation: `Infos` (default) and a map/itinerary to reach the selected DAE (mimic alert routing). - During alert posting, if a cardiac-related keyword is detected in the alert subject, show a **persistent modal** (must remain on top even after redirect to the alert view, and must work offline) with two actions: - `Chercher un défibrillateur` → go to `DAEList` - `Non merci` → dismiss ### v1 decisions already made 1) Availability filter: **only** use `disponible_24h === 1` for now; no parsing of `horaires` string yet (later iteration). 2) Corridor filter: **within 10km of the user↔alert segment** (not union of circles). 3) Location permission denied: use last-known location; if none, show an explanatory empty state (no hard block). ### Known architecture + relevant anchors in codebase - Defib query wrapper: [`src/data/getNearbyDefibs.js`](src/data/getNearbyDefibs.js:1) exports [`getNearbyDefibs()`](src/data/getNearbyDefibs.js:37) which calls repo H3 query and falls back to bbox on error. - Repo query + distance rank: [`src/db/defibsRepo.js`](src/db/defibsRepo.js:1) provides [`getNearbyDefibs()`](src/db/defibsRepo.js:62) and [`getNearbyDefibsBbox()`](src/db/defibsRepo.js:159). - Embedded DB bootstrap: [`getDb()`](src/db/openDb.js:11) expects a bundled asset `require('../assets/db/geodae.db')` in [`src/db/openDb.js`](src/db/openDb.js:31). **Note:** current repo listing shows `src/assets/db/` empty, so packaging must be validated. - Drawer navigation screens declared in [`src/navigation/Drawer.js`](src/navigation/Drawer.js:85). - Root stack only defines `Main` and `ConnectivityError` in [`src/navigation/RootStack.js`](src/navigation/RootStack.js:142). Drawer contains “hidden” stack-like screens (e.g. `SendAlertConfirm`) already. - Alert current tabs: `Situation`, `Messages`, `Carte` in [`src/scenes/AlertCur/Tabs.js`](src/scenes/AlertCur/Tabs.js:25). - Alert map uses MapLibre and has route computation (OSRM) already in [`src/scenes/AlertCurMap/index.js`](src/scenes/AlertCurMap/index.js:170). - Map features clustering uses Supercluster in [`useFeatures()`](src/scenes/AlertCurMap/useFeatures.js:8) and map press routing in [`useOnPress()`](src/scenes/AlertCurMap/useOnPress.js:17). - Persistent top-layer UI is implemented elsewhere via `react-native-paper` [`Portal`](src/containers/SmsDisclaimerModel/index.js:37) + [`Modal`](src/containers/SmsDisclaimerModel/index.js:38). - Alert posting flow navigates to `AlertCurOverview` in [`onSubmit()`](src/scenes/SendAlertConfirm/useOnSubmit.js:32) after `alertActions.setNavAlertCur()` in [`src/scenes/SendAlertConfirm/useOnSubmit.js`](src/scenes/SendAlertConfirm/useOnSubmit.js:127). ### Data model (canonical Defib object for UI) Base DB row shape (from repo select) is: - `id: string` - `latitude: number` - `longitude: number` - `nom: string` - `adresse: string` - `horaires: string` (unused in v1) - `acces: string` - `disponible_24h: 0|1` - plus computed `distanceMeters: number` Represent coordinates consistently as: - Map coordinates arrays `[lon, lat]` to match existing map usage (see `alert.location.coordinates` in [`src/scenes/AlertCurMap/index.js`](src/scenes/AlertCurMap/index.js:157)). ### Filtering logic requirements **Near-user list (DAEList):** - Input: user location (current if available, else last-known) - Filter: distance ≤ 10_000m - Sort: nearest→farthest - Availability: if `disponible_24h === 1` keep; else exclude in v1 (until horaires parsing) **Alert-axis corridor overlay (Alert map + Situation button):** - Input: user location + alert location - Filter: points within `corridorMeters = 10_000` of the line segment user→alert - Also restrict to a sensible max radius around user to limit query size (see “Query strategy” below) Corridor math recommendation: - Use existing Turf dependency already present in map stack: `@turf/helpers` [`lineString()`](src/scenes/AlertCurMap/index.js:24), `@turf/nearest-point-on-line` [`nearestPointOnLine()`](src/scenes/AlertCurMap/index.js:25), and a distance function (`geolib` or Turf `distance`). ### Query strategy (performance + offline) Use the existing local SQLite query API (no network) via [`getNearbyDefibs()`](src/data/getNearbyDefibs.js:37). - For near-user list: query radiusMeters = `10_000`, limit = e.g. 200–500 (tune later). - For corridor overlay: - First query a radius around the **midpoint** or around **user** large enough to include the whole segment + corridor. - Practical v1 approach: compute `segmentLengthMeters` and query radius = `segmentLengthMeters/2 + corridorMeters` around the midpoint. - Then apply corridor filter in JS and cap results to a max marker count (e.g. 200) to keep map responsive. ### Navigation and UX conventions - Drawer items come from `` options in [`src/navigation/Drawer.js`](src/navigation/Drawer.js:100) and are rendered by [`menuItem()`](src/navigation/DrawerNav/menuItem.js:4). Hidden routes should set `options.hidden = true`. - Bottom tab patterns exist (see alert tabs in [`src/scenes/AlertCur/Tabs.js`](src/scenes/AlertCur/Tabs.js:25)). - For “persistent modal on top even after redirect”, implement modal at a global provider level (within [`LayoutProviders`](src/layout/LayoutProviders.js:30) tree) using `Portal` so it survives navigation. --- ## Split tasks (agent-ready prompts) Each task below is designed to be handed to a coding agent. Include the **Common plan prefix** above in every prompt. ### Task 1 — Validate embedded DB asset packaging and repo schema assumptions **Objective:** Ensure the bundled SQLite `geodae.db` is present and accessible on-device, and confirm schema columns used by [`defibsRepo`](src/db/defibsRepo.js:120) exist. **Implementation notes:** - [`initDb()`](src/db/openDb.js:18) copies `require('../assets/db/geodae.db')` to documents. - Current workspace shows `src/assets/db/` empty; find where DB is stored or add it. **Acceptance criteria:** - App can open DB without throwing at [`require('../assets/db/geodae.db')`](src/db/openDb.js:31). - Query `SELECT ... FROM defibs` works with columns: `id, latitude, longitude, nom, adresse, horaires, acces, disponible_24h` and `h3`. **Likely files touched:** - [`src/db/openDb.js`](src/db/openDb.js:1) - DB asset under [`src/assets/db/`](src/assets/db:1) - Possibly Expo config / bundling rules (if needed) --- ### Task 2 — Define and implement defib filtering utilities (10km near-user + 10km corridor) **Objective:** Create pure utility functions to: - compute query radius for corridor overlay - filter a list of defibs to those inside corridor - normalize coordinates and compute distances **Implementation notes:** - Prefer reusing Turf already used in map stack (see imports in [`src/scenes/AlertCurMap/index.js`](src/scenes/AlertCurMap/index.js:24)). - Keep utilities side-effect free and unit-testable. **Suggested exports:** - `computeCorridorQueryRadiusMeters({ userLonLat, alertLonLat, corridorMeters })` - `filterDefibsInCorridor({ defibs, userLonLat, alertLonLat, corridorMeters })` - `toLonLat({ latitude, longitude })` **Acceptance criteria:** - Given synthetic points, corridor filter behaves as “distance to segment ≤ 10km”. **Likely files touched:** - New: [`src/utils/geo/defibsCorridor.js`](src/utils/geo/defibsCorridor.js:1) - Possibly reuse existing [`haversine`](src/db/defibsRepo.js:5) logic as reference --- ### Task 3 — Add a Defibs store (zustand atom) to manage caching + overlay enablement + modal state **Objective:** Add centralized state to avoid repeated DB queries and to coordinate UI across screens. **State concerns:** - cached near-user defibs list - cached corridor defibs for current alert id - flag `showDefibsOnAlertMap` (or `defibsOverlayEnabledByAlertId`) - selected defib id for `DAEItem` - “DAE suggestion modal” visibility (global) **Integration points:** - Mirror patterns used by alert store in [`createAtom()`](src/stores/alert.js:6). **Acceptance criteria:** - Other tasks can simply call actions like `defibsActions.loadNearUser()` and `defibsActions.enableCorridorOverlay(alertId)`. **Likely files touched:** - New: [`src/stores/defibs.js`](src/stores/defibs.js:1) - Update exports/hooks in [`src/stores/index.js`](src/stores/index.js:1) --- ### Task 4 — Add navigation routes: `DAEList` in drawer + `DAEItem` as hidden route **Objective:** Add new screens to navigation so they can be opened from: - left drawer (DAEList) - map marker press (DAEItem) - modal CTA (DAEList) **Implementation notes:** - Drawer routes are defined in [`src/navigation/Drawer.js`](src/navigation/Drawer.js:85). - If `DAEItem` is a detail view, set `options.hidden = true` (see existing hidden screens around [`SendAlertConfirm`](src/navigation/Drawer.js:490)). - Decide where to place the DAE link in drawer sections: - Sections are sliced by indices in [`src/navigation/DrawerNav/DrawerItemList.js`](src/navigation/DrawerNav/DrawerItemList.js:4). Adding a new Drawer.Screen will shift indices; adjust `index1/index2` or reorder screens. **Acceptance criteria:** - A new drawer link navigates to `DAEList`. - `DAEItem` route can be navigated to programmatically. **Likely files touched:** - [`src/navigation/Drawer.js`](src/navigation/Drawer.js:85) - [`src/navigation/DrawerNav/DrawerItemList.js`](src/navigation/DrawerNav/DrawerItemList.js:1) --- ### Task 5 — Situation button on active alert: enable DAE overlay and navigate to alert map **Objective:** In `Situation` view for current alert, add button `Afficher les défibrillateurs`. **Behavior:** 1) Determine user coords (prefer current; fall back to last-known as in alert map’s `useLocation` usage in [`src/scenes/AlertCurMap/index.js`](src/scenes/AlertCurMap/index.js:83)). 2) Query defibs from DB with a computed radius around midpoint. 3) Filter to corridor (10km). 4) Store result + enable overlay flag. 5) Navigate to map tab: `Main → AlertCur → AlertCurTab → AlertCurMap` (same pattern used in [`AlertCurOverview`](src/scenes/AlertCurOverview/index.js:276)). **Acceptance criteria:** - Button exists only when alert has coordinates. - After tap, map opens and defib markers appear. **Likely files touched:** - [`src/scenes/AlertCurOverview/index.js`](src/scenes/AlertCurOverview/index.js:61) - Store from Task 3 - Utilities from Task 2 --- ### Task 6 — Alert map: render DAE markers and open `DAEItem` on tap **Objective:** Display defib markers as a separate feature layer on alert map. **Implementation notes:** - Existing feature system creates a GeoJSON FeatureCollection for alerts in [`useFeatures()`](src/scenes/AlertCurMap/useFeatures.js:42). - Add DAE features when overlay is enabled. - Add a new marker icon to map images by extending [`FeatureImages`](src/containers/Map/FeatureImages.js:13). - Update press handler in [`useOnPress()`](src/scenes/AlertCurMap/useOnPress.js:17) to recognize `properties.defib` and navigate to `DAEItem`. **Acceptance criteria:** - Markers appear/disappear based on overlay flag. - Tapping a marker navigates to `DAEItem`. - Clustering behavior is acceptable (either include in cluster or keep separate; v1 can skip clustering by rendering as a separate layer). **Likely files touched:** - [`src/scenes/AlertCurMap/useFeatures.js`](src/scenes/AlertCurMap/useFeatures.js:8) - [`src/scenes/AlertCurMap/useOnPress.js`](src/scenes/AlertCurMap/useOnPress.js:17) - [`src/containers/Map/FeatureImages.js`](src/containers/Map/FeatureImages.js:1) --- ### Task 7 — Implement `DAEList` screen with bottom tabs: List (default) + Map **Objective:** Create `DAEList` scene with bottom navigation and two tabs: - `Liste`: list view nearest→farthest - `Carte`: map view of same defibs **Behavior:** - Load near-user defibs within 10km using [`getNearbyDefibs()`](src/data/getNearbyDefibs.js:37). - Filter by availability: keep `disponible_24h === 1` only. - If no location available, show empty state with explanation. - Tapping list item navigates to `DAEItem`. **Implementation notes:** - Follow bottom tab pattern from [`createBottomTabNavigator()`](src/scenes/AlertCur/Tabs.js:3). **Acceptance criteria:** - Drawer link opens `DAEList`. - List is sorted by `distanceMeters`. - Map tab renders markers. **Likely files touched:** - New: [`src/scenes/DAEList/index.js`](src/scenes/DAEList/index.js:1) - New: [`src/scenes/DAEList/Tabs.js`](src/scenes/DAEList/Tabs.js:1) - Possibly shared list row component --- ### Task 8 — Implement `DAEItem` screen with bottom tabs: Infos (default) + Go-to map (itinerary) **Objective:** Create `DAEItem` detail view for a selected defib. **Tabs:** - `Infos`: name, address, access, availability badge - `Carte`: show route from user to DAE, mimicking alert routing implementation in [`AlertCurMap`](src/scenes/AlertCurMap/index.js:170) **Implementation notes:** - Reuse route calculation patterns (OSRM URL building, polyline decode, step list components) from alert map. - Route target is defib coordinates instead of alert coordinates. **Acceptance criteria:** - From any entrypoint (alert map marker or list), `DAEItem` shows correct defib. - Itinerary works when online; offline behavior is a clear message (route unavailable offline). **Likely files touched:** - New: [`src/scenes/DAEItem/index.js`](src/scenes/DAEItem/index.js:1) - New: [`src/scenes/DAEItem/Tabs.js`](src/scenes/DAEItem/Tabs.js:1) - New: [`src/scenes/DAEItem/Map.js`](src/scenes/DAEItem/Map.js:1) --- ### Task 9 — Keyword detection while posting an alert + persistent DAE suggestion modal **Objective:** Detect cardiac-related terms during alert posting and display a global modal that remains visible even after navigation. **Detection requirements:** - Keywords include: `cardiaque`, `cardiac` (typos), `coeur`, `malaise`, `inconscient`, `évanoui` etc. - Should run locally/offline. **Implementation approach (recommended):** - Use fuzzy matching with `Fuse` (already used in [`findAlertTitle()`](src/finders/alertTitle.js:50)) or implement a lightweight normalization + substring/levenshtein. - Trigger detection in the confirm submit flow, before/around navigation in [`onSubmit()`](src/scenes/SendAlertConfirm/useOnSubmit.js:32). - Render modal at app root using `react-native-paper` [`Portal`](src/containers/SmsDisclaimerModel/index.js:37) inside [`LayoutProviders`](src/layout/LayoutProviders.js:51) so it persists across navigation. **Modal UI:** - Title/text: explain quickly why looking for a DAE matters. - Two buttons: - `Chercher un défibrillateur` → navigate to `DAEList` - `Non merci` → dismiss **Acceptance criteria:** - Modal shows for matching terms even with no internet. - Modal stays visible after redirect to current alert view. **Likely files touched:** - [`src/scenes/SendAlertConfirm/useOnSubmit.js`](src/scenes/SendAlertConfirm/useOnSubmit.js:1) - New: [`src/containers/DAESuggestModal/index.js`](src/containers/DAESuggestModal/index.js:1) - [`src/layout/LayoutProviders.js`](src/layout/LayoutProviders.js:30) - Store from Task 3 --- ### Task 10 — Verification checklist (manual) + minimal automated coverage **Objective:** Provide a deterministic checklist and (where feasible) simple automated tests. **Manual verification checklist:** 1) **Drawer**: DAE link visible, opens list. 2) **DAEList**: - permission granted → list populated, sorted - permission denied + last-known available → list uses last-known - permission denied + no last-known → empty state 3) **Alert Situation button**: enables overlay and opens alert map. 4) **Alert map**: DAE markers render; tap → DAEItem. 5) **DAEItem routing**: online route works; offline shows message. 6) **Keyword modal**: - trigger term in subject → modal shows - redirect to alert occurs and modal remains on top - CTA navigates to DAEList **Acceptance criteria:** - Checklist documented and reproducible. **Likely files touched:** - New: [`plans/DAE-manual-test-checklist.md`](plans/DAE-manual-test-checklist.md:1) - Optional: e2e tests under [`e2e/`](e2e:1) --- ## 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 ```