370 lines
14 KiB
Markdown
370 lines
14 KiB
Markdown
## 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 `<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 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
|
||
```
|
||
|