Repository navigation
Description
📋 Description
Détecte, sur l'ensemble du périmètre couvert par BioWatch (pas seulement les zones suivies par un utilisateur en particulier), les zones dont le score écologique se dégrade et qui sont classées à risque — puis écrit un événement dans Firestore pour alimenter à la fois le badge de notification temps réel (ticket frontend 4.3.3) et le feed d'activité historique du dashboard (ticket frontend 5.1.3). Une seule collection Firestore sert les deux besoins : onSnapshot côté frontend pour le temps réel, requêtes paginées par curseur pour l'historique.
Placement confirmé : apps/jobs, exécuté sur le VPS — pas une Cloud Function. PostGIS n'est jamais exposée hors du VPS ; une Cloud Function sur GCP ne peut pas la lire sans ouvrir un accès entrant (interdit) ou un tunnel VPN (hors de portée pour ce projet). Ce job lit stress_score_by_zone localement, puis fait un appel sortant vers Firebase (Admin SDK) — symétrique à ce que fait une Cloud Function quand elle appelle Stripe, aucune exposition supplémentaire requise.
Scheduling confirmé : timer systemd quotidien à 10h00.
Où est stockée la notification (Firestore)
Collection partagée (pas nichée sous users/{uid}, l'événement concerne une zone, pas un utilisateur) :
risk_alerts/{zone_id}_{bucket_id}
zone_id: string
bucket_id: string
score_before: number
score_after: number
detected_at: timestamp
- ID de document déterministe (
{zone_id}_{bucket_id}) : unset()sur cet ID est idempotent nativement — rejouer le job sur le même bucket écrase le même document au lieu de dupliquer. firestore.rules:allow read: if request.auth != null; allow write: if false;— tout utilisateur connecté peut lire, seul l'Admin SDK (ce job) peut écrire.- Le marquage "vu/non vu" n'est pas stocké ici (par-événement-par-utilisateur, trop lourd) — géré via
users/{uid}.last_seen_alert_atcôté frontend (tickets 4.3.3/5.1.3).
uv run apps.jobs.risk_alerts --bucket 2026-08{ "event": "job_completed", "job": "risk_alerts", "bucket_id": "2026-08", "events_written": 3 }Document Firestore écrit (id 881f1d4a3fffffff_2026-08) :
{ "zone_id": "881f1d4a3fffffff", "bucket_id": "2026-08", "score_before": 68.4, "score_after": 58.1, "detected_at": "2026-09-25T10:00:00Z" }✅ Acceptance Criteria
- Job idempotent : englobé dans
job_run(...), clé déterministe, pas de doublon d'événement pour un même bucket/zone - Timer systemd déclenche le job tous les jours à 10h00
- Compare
score_globalentre le bucket courant et le précédent, par zone, sur tout le périmètre - N'écrit un document Firestore QUE si delta négatif ET nouveau score à risque (pas toute variation) —
set()sur l'ID déterministe - Erreurs partielles tracées via
job_run_zone_errors, comme les autres jobs - Tests : baisse + à risque (écrit), baisse sans être à risque (n'écrit pas), hausse (n'écrit pas), absence de bucket précédent (première mesure), rejeu du job sur le même bucket (pas de doublon)
🔗 Prerequisite Issues
- 6. Industrialisation du calcul des scores #46 (Industrialisation du calcul des scores) —
stress_score_by_zonedoit être peuplée avant que ce job ait quoi que ce soit à comparer - intégrer SQLAlchemy + Alembic et centraliser l'accès DB (surcouche CRUD) #59 (intégrer SQLAlchemy + Alembic et centraliser l'accès DB) — closed, fournit
session_scope() - Idempotence & Exécution #21 (Idempotence & Exécution) — closed, fournit
job_run(...)et la gestionjob_run_zone_errors - Mise en place des jobs avec systemd timers #27 (Mise en place des jobs avec systemd timers) — pattern de service/timer systemd à suivre, pas un blocage strict
- Seuil "à risque" à confirmer par le PO (voir DoR)
- Crédentials Firebase Admin SDK disponibles côté VPS (service account, hors dépôt)
Source : Notion « Job détection dégradation à risque (→ Firestore) » (Jobs Utilisateur & Notifications).
Activity