This repository has been archived on 2026-03-09. You can view files and clone it, but cannot push or open issues or pull requests.
as-app/plans/PLAN_DAE-gpt.md
2026-03-07 07:53:08 +01:00

395 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. 200500 (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 maps `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
```