Livrable 12
Live, fraîcheur, confiance
13 — Freshness Engine
14 — Trust, conflits, City Pulse
(Un seul document pour que les trois définitions ne divergent pas.)
Principe
« Live » qualifie une fenêtre de validité et un statut de vérification, pas un tuyau websocket. Le transport (poll, SSE) est indépendant. Si le canal n'est pas poussé, on n'écrit pas « temps réel » dans l'UI : on écrit « actualisé à HH:MM ».
Statuts — trois axes
Calcul à la lecture, horloge serveur UTC, timezone ville. On n'affiche jamais un seul badge qui mélange les axes.
| Axe | Valeurs |
|---|---|
source_type | BUSINESS_VERIFIED (membre d'un claim approuvé), OFFICIAL_SOURCE, AUTHORIZED_API, CITY_EDITOR, USER_REPORT, OPEN_DATA, PARTNER, UNKNOWN |
verification_status | DECLARED_BY_BUSINESS (déclaration, pas un tiers), VERIFIED (last_verified_at dans verified_days), UNVERIFIED |
temporal_status | voir tableau |
temporal_status | Définition |
|---|---|
EXPIRED | valid_until non nul et now >= valid_until, ou event now >= ends_at. Pas un closed_until. |
CLOSED_OVERRIDE | venue : closed_until non nul et now < closed_until. L'horaire théorique est ignoré. L'enregistrement du lieu n'expire pas. |
LIVE | non expiré, non CLOSED_OVERRIDE, et (fenêtre d'un live_update ou d'une offre), ou event starts_at <= now < ends_at, ou lieu ouvert selon horaires et fraîcheur au-dessus du seuil héros |
RECENT | non expiré, updated_at dans recent_hours, hors créneau « maintenant » |
STALE | non expiré, last_verified_at plus vieux que stale_after, ou horaire importé plus vieux que hours_stale_after |
UNKNOWN | pas assez de champs pour trancher. Jamais remplacé par une supposition. |
Phrases UI (exemples, pas un score) :
- « L'établissement indique être ouvert jusqu'à 02:00 · il y a 20 min » =
BUSINESS_VERIFIED+DECLARED_BY_BUSINESS+LIVE - « Horaire importé · non revérifié depuis … » =
OPEN_DATAouOFFICIAL_SOURCE+UNVERIFIED - « Relevé par la rédaction · le … » =
CITY_EDITOR+VERIFIED - « Fermé jusqu'au … selon l'établissement » =
CLOSED_OVERRIDE
USER_REPORT et UNKNOWN ne entrent pas dans Maintenant. Un signalement OPEN n'ajoute pas de badge public. Un badge « signalé » n'existe qu'après une décision RESOLVED qui choisit une mention publique.
EXPIRED sort de Maintenant, Ce soir (si la fin est avant la fenêtre), la carte « en cours », la reco « maintenant ». Il peut rester en archive.
STALE peut rester en liste avec label, pas dans le héros Maintenant.
CLOSED_OVERRIDE exclut le lieu de « ouvert maintenant » et exclut ses événements qui tombent dans la fermeture, sans les marquer EXPIRED.
Freshness — fonction pure
Entier 0–100. Paramètres dans freshness_policy (globale + override ville). Défauts proposés, non validés :
| Situation | Score |
|---|---|
Live update d'un pro approuvé, âge ≤ t_business_high (12 h) | 100 |
Même update, âge ≤ stale_after | 70 |
Source officielle ou open data licence OK, âge ≤ t_official_high (24 h) | 80 |
Même source, âge ≤ t_hours_mid (30 j) | 60 |
Horaire importé, âge ≤ t_hours_mid (30 j) | 55 |
Horaire importé, âge > 30 j et ≤ t_unverified_low (90 j) | 40 |
| Non vérifié, âge > 90 j | 15 |
EXPIRED | 0 |
Le cas « horaire importé de moins de 30 jours = moyenne » est donc 55. Seuil d'entrée du héros Now : hero_freshness_min (50) ou déclaration pro dans t_business_high. Un horaire importé récent peut apparaître, avec la phrase de provenance. Au-delà de 30 jours (score 40) : fiche oui, héros non.
PARTNER et AUTHORIZED_API : ligne créée seulement si un contrat existe. Sinon on ne crée pas la source.
Autres défauts éditables, non validés : stale_after = 30 j, recent_hours = 48 h, verified_days = 30 j. Le City Manager peut les changer ; l'audit enregistre.
Priorité : déclaration d'un claim approuvé > import plus ancien, sans effacer l'historique.
Trust
confidence_score interne, jamais affiché :
- base par
source_type(table) ; - + si
entity_matchescorroborent ; - − si signalement non résolu (effet interne seulement) ;
- − si
STALE; - entier plafonné.
UI = phrase de provenance. Pas de « 92 % fiable ».
Conflits
| Cas | Gagnant |
|---|---|
Import dit ouvert, pro BUSINESS_VERIFIED dit fermé (closed_until ou update) | Pro → CLOSED_OVERRIDE ou la déclaration, historique conservé |
| Deux imports en désaccord | human_review. Hors héros. La fiche déjà publique reste. La candidate n'est pas publiée. |
Signalement closed_business | ne ferme pas. Pas de badge public tant que OPEN. |
| Match sous le seuil | pas de fusion auto. merged seulement par acte humain. |
match_score : somme pondérée nom (trigram ou ratio), proximité (mètres), téléphone normalisé, host du site. Seuils en politique. Pas de fusion silencieuse au-dessus d'un doute.
City Pulse
Interdit : « 500 personnes », « bondé », heatmap de foule.
Autorisé seulement si le flag city_pulse est on et qu'on affiche la phrase exacte « activité numérique » définie par un comptage :
ouvertures publiées + events actifs + offres actives + publications pro + recherches anonymisées + clics + favoris + demandes d'itinéraire, sur une fenêtre (ex. 60 min), comparée à la médiane de la même zone sur les semaines précédentes s'il y a assez d'historique.
S'il n'y a pas d'historique : on n'affiche pas « forte ». On affiche le compte brut (« 4 événements en cours ») ou rien.
Défaut : flag off.
Now engine
Entrées : now, city_id, optionnel zone_id ou bbox, optionnel origine (lat/lng non stockée).
Inclut si :
- venue ouverte maintenant selon règles horaires et non
closed_until, et pasSTALEpour le héros ; ou - event actif ou
starting_soon; ou - offer / live_update dans la fenêtre.
Exclut EXPIRED, STALE (du héros), CLOSED_OVERRIDE, USER_REPORT, UNKNOWN, human_review, brouillons, alcohol_related si legal_gates.alcohol_ads est off, autres villes. Le plan payant n'est pas un critère.
Rang : voir reco. Distance si origine fournie, sinon pas de distance (pas de centroïde inventé). Si le city manager a publié un centroïde, « distance au centre configuré » est un mode explicite, pas le défaut silencieux.
Cache et jobs
- Job : passer
expiredles events/offres/updates dont la fin est dépassée (cohérence des filtres). La lecture filtre aussi sur le temps, pour ne pas dépendre du job. - Cible prod : Redis pour la page Now chaude, TTL court (ordre de grandeur : une minute — politique, pas un SLO).
- Sandbox / Vercel sans Redis : cache HTTP court ou recalcul. Ne pas prétendre un cache distribué.
Décision
- Choix : statuts définis + fonction pure + exclusion mécanique des expirés.
- Alternative rejetée : « live =
updated_atrécent ». - Risque : héros vide. C'est préférable à un mensonge.
- Multi-ville : politiques overridables, définitions identiques.