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-merged.md
2026-03-07 07:53:08 +01:00

370 lines
14 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.

## 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 apps 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 `<Drawer.Screen name='DAEList'>` with label `Défibrillateurs`.
- Drawer icon: `MaterialCommunityIcons` name `heart-pulse`.
- Add `<Drawer.Screen name='DAEItem'>` 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 apps 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
```