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

14 KiB
Raw Blame History

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

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