395 lines
17 KiB
Markdown
395 lines
17 KiB
Markdown
# 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 `<Drawer.Screen>` 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
|
||
```
|
||
|