DMV — Synthèse d'architecture : le Ranking Engine et l'organisation du Context Engine
Introduction
Ce document consolide l'ensemble des réflexions menées à travers plusieurs documents et échanges successifs. Il ne remplace pas les documents précédents (vision, principes de pertinence, pipeline de décision) — il les organise, tranche les points restés ouverts, et ajoute la pièce qui manquait depuis le début : le mécanisme concret de classement, appelé ici Ranking Engine.
Il reste un document métier. Aucune technologie n'y est mentionnée.
0. Objectifs fondateurs
Trois règles, prises ensemble, définissent l'application et cadrent l'ensemble des décisions de ce document — en particulier les principes de neutralité (3.4, 6.3, 7.3, 7.5) et les mécanismes de gamification producteur (7.5) :
- Les personnes trouvent l'information pertinente immédiatement. C'est l'objet du Ranking Engine dans son ensemble (section 4).
- Les personnes viennent, et reviennent régulièrement. Assumé explicitement comme objectif : sans visite, aucune application n'a de sens. Ceci ne contredit pas le principe de neutralité vis-à-vis de l'engagement posé en section 5 — ce principe vise à exclure toute manipulation du contenu affiché pour maximiser le temps passé ou le scroll (logique de réseau social). Viser une fréquence de visite régulière, par la valeur réelle du service ou par des mécaniques positives côté producteur, est un objectif de régularité, pas de durée ni de manipulation du classement.
- Les acteurs locaux ont une visibilité communale égale, tous au même niveau. Justifie la gratuité du socle (mairie comme acteurs standards) et l'ensemble des garde-fous de neutralité du Ranking Engine.
Le mécanisme d'engrenage. L'adoption de DMV dépend de deux versants qui s'alimentent mutuellement : la qualité et le volume du contenu publié par les acteurs, et la régularité des visiteurs qui viennent le consulter. Sans l'un, l'autre s'effondre — un problème classique d'amorçage de plateforme à deux versants. La gamification producteur (boosts gagnés à la régularité de publication, jamais à la popularité auprès des visiteurs — section 7.5) est un levier pensé pour agir sur le premier versant indépendamment du second, afin de ne pas dépendre d'une masse critique de visiteurs pour démarrer la mécanique.
1. Les trois surfaces du Context Engine
La réflexion a longtemps traité DMV comme un moteur unique. Il apparaît en réalité que le Context Engine sert trois usages distincts, qui partagent une base commune (pipeline, filtrage, exclusion des contenus invalides) mais qui se déclenchent différemment et ne composent pas leurs résultats de la même façon.
1.1 Le MurVille (mur public territorial)
- Contexte maîtrisé : une commune, un flux public.
- Publications empilées par date, filtrées par validité.
- Ce sujet est considéré comme réglé et n'est pas l'objet de ce document.
1.2 Le mur personnel (privé, mode passif)
- Alimenté sans requête explicite de l'utilisateur.
- Doit refléter ce qui le caractérise : ses relations, ses centres d'intérêt, sa géographie.
- C'est le sujet principal de ce document (sections 2 à 5).
1.3 La recherche mixte (mode actif, intention explicite)
- Déclenchée par une requête en langage naturel, potentiellement multi-intentions (ex : logement + activités + sorties + événements).
- Décomposée en sous-recherches spécialisées par type d'objet, chacune avec ses propres filtres durs et son propre classement local.
- La composition du résultat final suit directement la structure des intentions détectées dans la requête — pas de calcul de densité territoriale nécessaire ici.
- Un LLM a un rôle légitime à ce niveau : extraction d'intention et structuration de la requête, jamais pour le ranking final, qui reste déterministe.
2. Le mur personnel : deux flux de nature différente
Le mur personnel n'est pas un flux unique filtré par plusieurs critères combinés. Ce sont deux flux indépendants, fusionnés ensuite dans une seule liste, mais dont les règles d'inclusion n'ont rien à voir.
2.1 Flux relationnel
- Alimenté par les acteurs explicitement suivis par l'utilisateur.
- Inconditionnel : aucun filtre géographique ne s'applique. Un acteur suivi dans une ville jamais visitée par ailleurs reste dans le mur, parce qu'il caractérise l'utilisateur.
- Principe fondateur : ce qui définit l'utilisateur (ses relations explicites) prime toujours sur ce qui décrit son environnement (sa position, ses villes).
2.2 Flux territorial (découverte)
- Alimenté par la géographie active de l'utilisateur : ville favorite (résidence), ville GPS (position actuelle), villes suivies (famille, vacances, attaches occasionnelles).
- Sert la sérendipité : sans lui, l'utilisateur ne verrait jamais rien de nouveau, juste ce qu'il suit déjà.
- Règle d'inclusion asymétrique selon le rôle de la ville :
- Ville favorite et ville GPS : la géographie seule suffit à inclure une information.
- Villes suivies (non favorite, non GPS) : la géographie seule ne suffit pas. Il faut en plus au moins un signal d'affinité (catégorie suivie, acteur suivi même non localisé dans cette ville, importance intrinsèque de l'information).
2.3 Fusion et étiquetage
Les deux flux sont fusionnés dans une seule liste, mais chaque élément conserve un tag d'origine et de justification (ex : « pertinent car acteur suivi », « pertinent car ville favorite + fraîcheur », « pertinent car catégorie voyage suivie »). Ceci répond à un double besoin : transparence pour l'utilisateur, et auditabilité pour la conception (pas de score caché).
Décision tranchée : le flux relationnel n'a pas de priorité de principe sur le flux territorial. Les deux sont fusionnés puis triés uniquement par le score du Ranking Engine — la relation directe (acteur suivi) est un facteur fort dans ce score, mais pas un passe-droit absolu. Imposer une priorité de structure reproduirait l'erreur des quotas déjà écartée (document 4) : une contrainte rigide plutôt qu'un reflet de l'importance réelle. Seule la criticité reste un court-circuit absolu, et elle s'applique indifféremment aux deux flux.
3. La portée géographique : abandonner le rayon fixe
Le rayon unique paramétré par utilisateur ne fonctionne pas, parce qu'il applique la même contrainte spatiale à des informations dont l'importance n'a rien à voir avec la distance. Deux exemples extrêmes l'illustrent :
- Un repas associatif à 5 km ne doit pas apparaître, malgré la faible distance, en l'absence de tout signal d'affinité.
- Un concert d'envergure nationale à 20 km doit apparaître, malgré la distance, parce que son importance intrinsèque dépasse le rayon habituel.
3.1 Principe retenu
La portée n'est jamais une propriété fixe de l'utilisateur seul. C'est une fonction de trois éléments combinés :
- Catégorie de l'information (alerte, événement, publication municipale, commerce…)
- Affinité de l'utilisateur pour cette catégorie (dérivée des acteurs suivis, des centres d'intérêt déclarés, des « ça m'intéresse »)
- Envergure intrinsèque de l'information (locale / intercommunale / départementale / nationale)
3.2 Portée par catégorie (règle de base, avant affinité)
- Alertes de sécurité / criticité : portée par rayon géographique réel (km), indépendante du découpage administratif. Une alerte incendie se propage selon un rayon de danger réel, pas selon les limites de la commune.
- Informations municipales officielles : portée naturellement communale.
- Événements / vie associative / loisirs : portée par rayon + affinité déclarée obligatoire au-delà du rayon de base.
- Commerces / annonces : portée resserrée par défaut, élargie seulement par un signal d'intérêt fort.
3.3 Envergure : comment la connaître sans imposer de saisie au producteur
Contrainte UX-first : on ne demande pas au producteur de classifier lui-même l'envergure de sa publication. On la déduit d'une cascade de signaux structurels, chaque signal ne pouvant que faire monter le niveau, jamais le descendre :
- Niveau de départ hérité du type d'acteur : un EHPAD, une association de quartier → hyper-local par défaut. Une mairie, un comité des fêtes → communal par défaut. Un acteur rattaché à un réseau national connu (franchise, fédération) → intercommunal ou régional selon le réseau.
- Ajustement par volume d'intérêt agrégé : si le nombre de « ça m'intéresse » toutes villes confondues dépasse un seuil (à définir empiriquement) sur les premières heures/jours suivant la publication, le niveau monte d'un cran — signal sur l'objet et son engouement réel, pas un comportement individuel, donc compatible avec le déterminisme de la V1. C'est un ajustement a posteriori, qui corrige le niveau en cours de route si l'intérêt réel dépasse ce que le type d'acteur laissait présager.
Signal explicitement écarté : le nombre de communes distinctes relayant une même information n'est pas retenu comme signal d'envergure. Une commune ne communique généralement pas sur les affaires d'une autre ; le nombre de communes qui relaient une info dépend souvent d'un hasard administratif (une route ou un incident à cheval sur deux communes limitrophes) plutôt que de l'importance réelle de l'information. Un feu de garrigue mentionné par une seule commune n'est pas moins urgent qu'un problème de circulation mentionné par deux.
3.4 Point explicitement écarté
Toute forme de priorité liée à une contrainte commerciale (prix, places limitées payantes) est à proscrire du ranking : cela reviendrait à favoriser artificiellement un producteur de contenu, ce que les principes fondateurs interdisent. Une contrainte de capacité réelle (places limitées, gratuites ou payantes) pourrait légitimement justifier un facteur d'urgence, mais ce facteur n'est pas retenu pour la V1 (dépendance technique trop lourde : nécessite un flux temps réel de taux de remplissage).
3.5 La gravité des alertes critiques : un état binaire ne suffit pas
La criticité, posée comme un état actif/inactif (document 3), doit en réalité porter une gradation de gravité à l'intérieur même de l'état actif — sans quoi des informations critiques de nature très différente se retrouvent artificiellement à égalité. Le repas d'un EHPAD n'est pas critique ; un accident bloquant une autoroute l'est de façon pratique ; un feu de garrigue poussé par le vent vers une zone habitée l'est de façon vitale. Trois paliers sont retenus, dans l'esprit des dispositifs de vigilance déjà connus du public :
- Information pratique (travaux, déviation, route coupée sans danger immédiat).
- Alerte (accident bloquant, embouteillage important).
- Danger (feu progressant vers une zone habitée, alerte météo extrême) — sécurité vitale, priorité absolue.
Cette gravité influence à la fois le sous-classement entre informations critiques concurrentes, et probablement le rayon de diffusion (un « danger » justifie un rayon plus large qu'une simple information pratique, cohérent avec la section 3.2).
Différence importante avec l'envergure : contrairement à l'envergure des événements ordinaires, la gravité d'une alerte de sécurité doit être déclarée explicitement par un producteur habilité (mairie, service de sécurité), avec un choix simple à 3 options, plutôt que déduite automatiquement. Se tromper automatiquement sur la gravité d'un feu de forêt a des conséquences potentiellement graves ; l'enjeu justifie ici la charge de saisie, contrairement à l'envergure d'une brocante.
Point ouvert, à traiter avec la mairie pilote : qui est habilité à déclencher une alerte critique, et comment garantir une couverture en dehors des heures ouvrées (voir section 8.3).
3.5bis Deux mécanismes d'urgence distincts, à ne pas confondre
Ce mécanisme de criticité (paliers info/alerte/danger, court-circuit dans le Ranking Engine) n'était pas prévu dans la conception initiale du produit — il s'est introduit au fil de la construction du document, avant qu'on réalise qu'un système d'alertes séparé existait déjà côté implémentation. Les deux coexistent désormais, à des niveaux différents, et il faut garder leur distinction claire :
- Bandeau d'alerte (
AlertsTickerWeb) : une bannière défilante d'une ligne (fond noir/bleu/rouge selon l'origine), texte court, en dehors du feed et du Ranking Engine — ce n'est jamais une Card, jamais scorée, jamais classée. Réservée à la mairie (les autres acteurs pouvaient historiquement en créer, cette possibilité a été retirée pour prévenir les abus). - Publication taguée urgente (criticité du Ranking Engine, section 3.5) : une publication complète (titre, photo, texte) comme une autre, mais portant un niveau de gravité qui la fait court-circuiter le tri normal dans le feed. Soumise aux mêmes principes de production habilitée que le bandeau.
Les deux mécanismes ne se substituent pas l'un à l'autre : le bandeau sert l'urgence immédiate et minimale ; la publication taguée urgente sert une information plus riche qui mérite malgré tout une priorité de visibilité dans le feed.
4. Le Ranking Engine
4.1 Ce que le Ranking Engine n'est pas
- Ce n'est pas un score global unique à poids fixes mélangeant tous les facteurs dans une seule formule opaque (rejeté depuis le début : illisible, impossible à auditer, effets de bord imprévisibles).
- Ce n'est pas non plus un empilement de règles IF-THEN imbriquées (rejeté : devient une usine à gaz dès que les cas particuliers s'accumulent).
4.2 Ce que le Ranking Engine est
Une combinaison de facteurs nommés, indépendants, chacun une petite fonction simple et auditable séparément. Chaque facteur peut être expliqué isolément à un utilisateur qui demanderait « pourquoi je vois ceci en premier ? ».
4.3 Les facteurs identifiés
| Facteur | Nature | Rôle |
|---|---|---|
| Criticité / priorité intrinsèque | État temporaire, court-circuite le reste | Sécurité, urgence — toujours en tête tant que l'état est actif |
| Relation directe (acteur suivi) | Binaire | Inclusion inconditionnelle, sans filtre géographique |
| Affinité catégorielle | Dérivée (acteurs suivis + centres d'intérêt + seuil sur « ça m'intéresse ») | Élargit la portée territoriale au-delà du rayon de base |
| Proximité géographique pondérée | Rôle de la ville : favorite > GPS > suivie | Filtre d'entrée du flux territorial |
| Envergure intrinsèque | Déduite de signaux structurels (voir 3.3) | Permet de dépasser le rayon habituel |
| Fraîcheur de publication | Décroissance depuis la date de parution | Pic à la publication, puis décroissance |
| Proximité temporelle à l'événement | Courbe en cloche centrée sur la date d'occurrence | Fait remonter un événement à l'approche de sa date |
4.3.1 Barème de poids initial (échelle 0-100)
Valeurs de départ, ajustables empiriquement à l'usage. Discutées et validées facteur par facteur.
| Facteur | Valeur | Nature |
|---|---|---|
| Criticité | Court-circuit absolu | Binaire, hors échelle — passe systématiquement devant tout le reste tant que l'état est actif |
| Relation directe (acteur suivi) | 60 | Binaire (présent/absent) |
| Affinité catégorielle | 35 | Fixe pour la V1 (pas de gradation continue — à revoir avec des cas réels et un éventuel signal déclaratif utilisateur, ex. un classement/retour explicite) |
| Proximité géographique — ville favorite | 45 | Fixe |
| Proximité géographique — ville GPS | 40 | Fixe |
| Proximité géographique — ville suivie | 20 | Fixe (ne suffit jamais seul à l'inclusion, doit se combiner à un autre facteur) |
| Envergure — hyper-local | +0 | Bonus additif |
| Envergure — communal | +10 | Bonus additif |
| Envergure — intercommunal | +25 | Bonus additif |
| Envergure — régional/national | +50 | Bonus additif |
| Fraîcheur / proximité temporelle | Pic commun à 60 | max(fraîcheur, proximité), les deux courbes continues (4.4) ; proximité strictement nulle hors de sa fenêtre d'anticipation (4.4.1) — décroissance fraîcheur par défaut sur 7 jours, montée de proximité pilotée par le niveau d'envergure |
| Notoriété de l'acteur (départage) | -10 à +10, paramétrable en backoffice | Bonus additif séparé, calculé par moyenne mobile lissée de l'interest_count sur les N derniers événements de l'acteur (N=5 par défaut, paramétrable). Ne sert qu'au départage entre événements proches dans le temps — jamais assez fort pour inverser l'ordre entre deux dates éloignées. Jamais affiché publiquement ni sa formule dévoilée (voir 4.6). |
Ce barème permet, par construction, qu'un événement d'envergure communale à son pic (60 + 10 = 70) dépasse une publication ordinaire d'un acteur suivi (60), sans qu'aucune règle spécifique n'ait été écrite pour ce cas précis — la hiérarchie émerge de l'addition des facteurs, pas d'une exception codée à la main.
4.6 Notoriété de l'acteur : un signal secret, anti-gaming par construction
Origine du besoin. La cascade d'envergure (3.3) distingue les acteurs par type (kind), mais un type seul ne suffit pas à distinguer, par exemple, une association hyper-locale isolée d'une association qui organise l'événement le plus important de l'année (ex. un comité des fêtes, qui est un kind: "association" comme un autre en base — aucun champ ne le distingue structurellement). Plutôt que de classer manuellement chaque association (ce qui ne passe pas à l'échelle et contredit le principe UX-first), un score de notoriété propre à l'acteur, dérivé de son historique d'engouement, sert de signal de départage.
Portée volontairement limitée : le départage, pas la restructuration. Ce facteur n'agit pas sur le niveau d'envergure lui-même (qui reste déterminé par la cascade de 3.3, corrigée pour utiliser une seule et même résolution partout — voir note d'implémentation), mais comme un bonus additif séparé et plafonné (-10 à +10), appliqué au score final. Cette fourchette est volontairement trop faible pour dominer la proximité temporelle (pic à 60) : un événement demain reste toujours devant un événement dans un mois, quelle que soit la notoriété respective des deux acteurs. Le seul effet réel se manifeste quand plusieurs événements sont proches dans le temps : l'acteur historiquement plus engageant passe légèrement devant.
Calcul. Moyenne mobile lissée de l'interest_count sur les N derniers événements de l'acteur (N=5 par défaut), plutôt qu'un score basé sur le dernier événement seul — le lissage limite mécaniquement l'effet d'un pic isolé de gaming (inciter artificiellement au clic sur un seul événement) et permet au score d'évoluer dans les deux sens : un acteur dont l'engouement décline verra son bonus diminuer progressivement, un acteur en croissance le verra augmenter. N et les bornes de la fourchette sont paramétrables en backoffice, pour permettre d'observer et d'ajuster l'impact réel une fois en usage.
Secret par construction, pas par exception. Contrairement au bonus partenaire (7.3), qui doit être visible et tagué parce qu'il s'agit d'un mécanisme marchand (l'utilisateur a le droit de savoir qu'un contenu est mis en avant contre paiement), la notoriété n'est ni payante ni visible publiquement — ni son score, ni sa formule. C'est un principe délibéré, dans l'esprit d'un moteur de recherche classique qui ne dévoile jamais le détail de son algorithme de pertinence pour ne pas faciliter sa manipulation : rendre le calcul public inviterait au gaming qu'il est censé limiter. Ce facteur reste démontrable et auditable dans le code (aucune opacité vis-à-vis de l'équipe elle-même), mais n'est exposé nulle part côté producteur ou utilisateur final — uniquement dans le panneau de debug interne (voir 8.2), réservé à l'administrateur plateforme.
Limite assumée. Ce signal mesure l'anticipation (engouement avant l'événement), pas la satisfaction réelle des participants — DMV ne dispose d'aucun signal direct de qualité post-événement. Un acteur qui déçoit à répétition verra son score baisser indirectement et lentement, au fil des prochaines éditions où l'anticipation faiblit, pas immédiatement après un événement raté.
4.4 Le cas des informations datées : maximum de deux courbes correctement bornées
Une information avec une date d'occurrence future (ex : une fête de village) combine deux dynamiques temporelles distinctes, à ne pas confondre :
- Fraîcheur de publication : pic au moment de la parution, décroissance continue ensuite (demi-cloche de Gauss) — répond à « les habitants sont-ils déjà informés ? ».
- Proximité temporelle à l'événement : montée progressive vers la date cible, strictement nulle au-delà de la fenêtre d'anticipation (voir 4.4.1) — répond à « dois-je m'organiser maintenant ? ».
Décision finale (après un aller-retour) : combinaison par maximum, max(fraîcheur, proximité), les deux courbes calculées en continu. Un découpage net en zones (interrupteur brutal entre les deux courbes à la frontière de la fenêtre) a été envisagé puis abandonné : il introduisait une discontinuité artificielle (un saut de score pouvant atteindre plusieurs dizaines de points d'un jour à l'autre, sans qu'aucune information n'ait changé sur le fond), pire que le problème qu'il cherchait à résoudre.
Il n'y avait en réalité aucun bug dans le maximum ni dans la courbe de proximité. Le symptôme qui a motivé cet aller-retour (un score de 49 à 62 jours d'un événement communal, largement hors de sa fenêtre de 28 jours) a été interprété à tort comme une fuite résiduelle de la proximité. En réalité, c'était une contribution parfaitement légitime de la fraîcheur de publication (l'annonce avait été postée 2 jours plus tôt) — le garde-fou clampant la proximité à 0 hors fenêtre était déjà correctement en place depuis le début. Vérification faite après coup : max(fraîcheur, 0) = fraîcheur hors fenêtre, sans discontinuité, testé et confirmé (variation ≤ 2 points entre jours consécutifs à la frontière). Leçon retenue : vérifier la décomposition par facteur d'un score avant de conclure à un bug sur la base du seul total.
- Hors fenêtre d'anticipation :
max(fraîcheur, 0) = fraîcheur— la proximité n'a simplement aucune influence, de façon continue et naturelle, sans qu'aucun interrupteur ne soit nécessaire. - Dans la fenêtre d'anticipation : les deux courbes sont continues (la fraîcheur décroît doucement, la proximité monte doucement depuis 0) ; le maximum suit celle des deux qui est la plus haute à chaque instant, avec un croisement fluide, jamais un saut.
- Après l'événement : reprise de la fraîcheur, ancrée sur la date de l'événement (détail en 4.4.2).
4.4.1 Étalement des courbes : ne pas figer une largeur unique
La largeur de la fenêtre d'anticipation ne doit pas être une constante globale identique pour toutes les publications et tous les événements — ce serait reproduire, sur la dimension temporelle, l'erreur déjà écartée pour le rayon géographique fixe (section 3). Une alerte de sécurité doit décroître vite une fois l'urgence retombée ; une information municipale structurante reste pertinente plus longtemps. Un événement local ne nécessite d'anticipation que dans les derniers jours ; un événement d'envergure nationale doit commencer à monter dans les murs des semaines à l'avance.
Principe retenu : l'étalement est dérivé de la catégorie de l'information et de son envergure intrinsèque (le même facteur d'envergure défini en 3.3), pas un paramètre saisi par le producteur ni calculé individuellement au cas par cas. Cette largeur détermine la fenêtre au-delà de laquelle la courbe de proximité temporelle doit strictement retomber à zéro (voir 4.4).
Distinction supplémentaire retenue entre actualité et événement : une actualité pure (sans date d'occurrence future) suit une décroissance de fraîcheur plus marquée, relativement linéaire — il est normal et attendu qu'elle sorte de la vue principale après un certain temps. Un événement à venir suit en revanche une pente plus faible sur sa phase de montée (proximité temporelle) : il est normal qu'il sorte des publications les plus visibles longtemps à l'avance, à condition qu'il reste accessible (voir 4.4.3) et qu'il remonte correctement à l'approche de sa date.
Pour éviter tout risque de sur-calibration (un réglage fin par publication, ou pire, par village), l'envergure est traitée comme une échelle discrète à quatre niveaux, et non comme un curseur continu :
| Niveau | Exemple | Anticipation typique |
|---|---|---|
| Hyper-local | Repas EHPAD, réunion de quartier | 1-2 jours |
| Communal | Fête du village, brocante | 2-4 semaines |
| Intercommunal / bassin de vie | Festival multi-communes | 1-2 mois |
| Régional / national | Concert d'envergure nationale | Plusieurs mois |
Chaque niveau porte un étalement par défaut, ajustable globalement mais pas au cas par cas. Le niveau est hérité par défaut du type d'acteur ou de la catégorie d'événement (un EHPAD publie par défaut en « hyper-local », une mairie publie ses fêtes par défaut en « communal »), avec correction possible mais rare. Ceci respecte la contrainte UX-first déjà posée : le producteur ne règle jamais une courbe, il choisit ou fait déduire un niveau, le moteur calcule le reste.
La variabilité réelle d'un village à l'autre (les habitudes d'anticipation ne sont pas identiques partout) n'est pas traitée dans la V1 comme un problème de conception, mais comme un ajustement empirique des valeurs par défaut, observé une fois le prototype déployé sur plusieurs communes — jamais comme un paramétrage fin par village.
4.4.2 Asymétrie montée / descente après l'événement
La phase qui suit un événement ne doit pas être une simple symétrie de la phase de montée (ce qui supposerait que l'anticipation et l'oubli suivent la même vitesse). Dans les faits, l'anticipation se construit progressivement (organisation, réservation, envie), tandis que l'intérêt retombe plus vite une fois l'événement passé — les gens passent à autre chose plus vite qu'ils ne se préparent.
Principe retenu : après la date de l'événement, l'information ne suit plus une courbe symétrique dédiée. Elle réutilise la fonction de décroissance de fraîcheur déjà définie (point 1 ci-dessus), mais ancrée sur la date de l'événement plutôt que sur la date de publication. Autrement dit, une fois l'événement passé, l'information redevient une actualité ordinaire dont l'horloge de fraîcheur repart de zéro à ce moment précis.
Le cycle complet d'une information datée se déroule donc en trois temps, les deux premiers résultant du maximum continu entre fraîcheur et proximité (4.4), le troisième reprenant la fraîcheur seule sur un nouveau point d'ancrage :
- Avant l'événement :
max(fraîcheur de publication, proximité temporelle), les deux courbes calculées en continu, sans interrupteur — la proximité étant strictement nulle hors de sa fenêtre (4.4.1), le maximum suit naturellement la fraîcheur tant qu'on est loin de l'événement, puis bascule en douceur vers la proximité à l'approche de la date. - Jour de l'événement : pic (proximité à son maximum).
- Après l'événement : reprise de la fonction standard de décroissance de fraîcheur, ancrée sur la date de l'événement — pas de courbe spécifique supplémentaire.
Cette approche ne crée aucune nouvelle fonction : elle réutilise des fonctions déjà définies, combinées par un simple maximum, dans le même esprit de facteurs nommés et réutilisables que le reste du document.
4.4.3 Retrieval avant ranking : un modèle à deux couches, vivante puis archive
Un point resté implicite jusqu'à la mise en production mérite d'être formalisé : tout le Ranking Engine décrit dans ce document répond à la question « comment ordonner un ensemble de publications entre elles ? », mais jamais à la question « quel est cet ensemble ? ». Dans une architecture avec pagination, ces deux questions sont indépendantes, et un score bien calibré ne compense jamais une présélection qui l'ignore.
Principe fondateur, à ne jamais perdre de vue : une publication ne doit jamais devenir inaccessible. Le rôle du Ranking Engine est de faire vivre un classement dynamique sur une fenêtre bornée, pas de définir une fin au mur ni de retenir l'utilisateur — cohérent avec le principe déjà posé en section 5 (pas de logique d'engagement, pas de scroll infini artificiel). Une fois qu'une publication sort de sa fenêtre de pertinence dynamique, elle ne disparaît pas : elle rejoint simplement l'archive chronologique classique, toujours consultable par scroll.
Modèle retenu, à deux couches distinctes.
Couche 1 — la zone vivante. Un corpus borné, recalculé à chaque nouveau chargement de session, composé de deux requêtes de nature différente :
- Événements (objets porteurs d'une date et heure de début/fin et d'un lieu) : tous les événements futurs sans limite de fenêtre — la largeur de la phase d'anticipation étant déjà pilotée par l'envergure (4.4.1), une fenêtre de retrieval fixe la contredirait — plus les événements dont la date est passée depuis moins de 14 jours (pour laisser vivre la queue de décroissance post-événement définie en 4.4.2). Un garde-fou technique (pas une règle de pertinence) plafonne cette recherche à 12 mois dans le futur, pour éviter qu'une donnée aberrante ou un volume croissant de communes ne dégrade les performances.
- Actualités (objets sans date d'occurrence, uniquement une date de publication) : les publications des 20 derniers jours.
Cette zone vivante passe par le Ranking Engine complet (criticité, relation, affinité, géo, envergure, temporel) et se réordonne effectivement d'un chargement à l'autre — c'est elle qui constitue le « mur vivant ».
Couche 2 — l'archive. Tout le reste : les publications sorties de la zone vivante (événements de plus de 14 jours passés, actualités de plus de 20 jours), listées dans l'ordre chronologique classique déjà en place (publie_le DESC, pagination par curseur, mécanisme inchangé depuis avant ce chantier). Cette zone est stable — son ordre ne varie pas d'un jour à l'autre — et reste accessible indéfiniment par scroll, exactement comme avant l'introduction du Ranking Engine.
Transition entre les deux couches au scroll. Une fois la zone vivante épuisée, le scroll bascule silencieusement vers l'archive, sans rupture visible pour l'utilisateur. La requête d'archive doit exclure tout ce qui a déjà été servi dans la zone vivante, pour éviter tout doublon au fil du scroll.
Fenêtres en durée, pas en volume. Les fenêtres définissant la zone vivante (14 jours pour les événements passés, 20 jours pour les actualités) sont volontairement exprimées en durée, pas en nombre de publications — elles répondent à une question de fraîcheur temporelle, indépendante de l'activité d'une commune donnée. Les exprimer en nombre (« les 20 dernières actualités ») ferait remonter des publications anciennes sans lien avec une réelle fraîcheur dans les communes peu actives.
Complément de page pour les communes peu actives. La variabilité de volume entre communes (une commune très active vs une commune calme) n'est pas traitée en ajustant les fenêtres, mais par un mécanisme de complément de page : si la zone vivante contient moins d'éléments que la taille de page habituelle, la première page est complétée automatiquement avec le haut de l'archive, pour que l'expérience de premier écran reste cohérente quel que soit le volume d'activité réel du territoire — sans jamais forcer artificiellement du contenu au-delà de ce qui existe (cohérent avec le principe de densité fidèle, section 3).
4.5 Ce qui reste en amont et ne change pas
Le pipeline déjà posé (Chargement → Filtrage → Validation → Classification → Priorisation) reste la base indispensable. Le Ranking Engine ne s'applique qu'aux candidats déjà valides et filtrés — il ne remplace aucune étape existante, il vient combler ce qui, dans les documents précédents, était nommé tour à tour « pertinence contextuelle », « classement », « composition », « densité utile », sans jamais être formalisé.
5. Principes transverses à ne pas perdre de vue
- Déterminisme : mêmes données, mêmes règles → même résultat. Chaque facteur du Ranking Engine est une fonction pure, testable isolément.
- Explicabilité : chaque publication porte un tag de justification (section 2.3). Aucun facteur ne doit rester une boîte noire.
- Neutralité vis-à-vis des producteurs : aucun facteur ne doit favoriser une publication pour des raisons commerciales (section 3.4).
- Pas de logique d'engagement : le mur a une fin visible ; il ne cherche pas à maximiser le temps passé, le nombre d'ouvertures ou le scroll. L'effet d'anticipation recherché (« l'info avant qu'on sache qu'on en a besoin ») est un résultat du bon ranking déclaratif, pas une fonctionnalité de prédiction comportementale séparée.
- Séparation V1 / Adaptive Engine : tous les facteurs retenus pour la V1 sont déclaratifs (suivi explicite, intérêt marqué avec seuil documenté, géographie choisie). Aucun apprentissage comportemental continu n'entre dans le Ranking Engine V1. Toute évolution vers une personnalisation apprise reste un sujet distinct, à traiter consciemment plus tard.
6. La mairie : un acteur à statut particulier, pas un objet séparé
6.1 Principe métier
La mairie est un acteur comme un autre dans le modèle de données (kind === "mairie"), avec un socle commun à tous les acteurs (identité, coordonnées, horaires, visuels, publications via le même Ranking Engine). Elle n'est pas un objet séparé nécessitant une architecture parallèle — cohérent avec le principe d'un seul moteur posé en section 7. Son périmètre est simplement étendu : acteur_id mairie + communeId, avec des modules additionnels réservés (Élus, Collectes, Commune) et une capacité exclusive — le déclenchement d'alertes critiques (section 3.5).
6.2 Gratuité du socle civique, position actuelle non définitive
L'accès de base de la mairie à DMV est gratuit et sans engagement contractuel. Ce choix est délibéré pour la phase actuelle, pour deux raisons :
- Positionnement : une collectivité ne devrait pas payer pour informer ses administrés ; la gratuité évite tout rapport de force client-fournisseur et permet de traiter la mairie comme un partenaire de contenu plutôt qu'un client.
- Responsabilité juridique : pour l'instant, DMV n'est volontairement pas positionné comme un canal de communication officiel — les alertes relayées n'engagent pas la responsabilité de la plateforme au même titre qu'un canal officiel (SMS d'alerte préfectoral, sirène). Cette position doit rester visible à l'utilisateur final tant qu'elle est en vigueur, pas seulement actée dans des CGU : chaque alerte critique doit porter une mention explicite de relais (ex. « information relayée par [mairie], se référer aux canaux officiels pour toute décision d'urgence »), pour éviter toute confusion sur ce que l'habitant croit recevoir.
Cette position n'est pas définitive. Devenir un canal officiel (avec la responsabilité juridique que cela implique) est une évolution possible, mais elle dépend de deux conditions à évaluer le moment venu, pas de principe :
- Un cadre contractuel solide avec la ou les communes concernées, définissant clairement les responsabilités de chaque partie.
- Un modèle économique qui couvre le risque et l'investissement que représente cet engagement (un statut officiel n'est pas un service qui se donne au même tarif qu'un relais informel).
Tant que ces deux conditions ne sont pas réunies, la position par défaut reste celle du relais gratuit et non engageant. Le sujet devra être réévalué si une mairie (notamment la mairie pilote) exprime un intérêt fort pour un statut officiel, ou si le modèle économique de la version payante le rend viable.
6.3 Séparation stricte entre outillage payant et neutralité du ranking
Une version avec abonnement est envisagée pour financer le développement de fonctionnalités plus spécifiques à la mairie (les collectivités disposent souvent de subventions dédiées à la numérisation de leur communication). Le critère de tri pour toute fonctionnalité candidate à cette offre payante est le suivant :
Est-ce que la fonctionnalité change ce que voit l'habitant dans son classement, ou est-ce qu'elle change ce que la mairie peut faire en coulisses ?
- Change le classement / la visibilité → à proscrire absolument, même en option payante. Une mairie ne doit jamais pouvoir acheter une meilleure place dans le mur de ses administrés — ce serait le même favoritisme déjà explicitement écarté pour les acteurs commerciaux (section 3.4) et pour
coeff_annuaire(section 7.3), avec un risque de confiance publique aggravé s'agissant d'une collectivité. - Reste de l'outillage neutre → candidat légitime au payant.
Pistes déjà identifiées comme neutres vis-à-vis du ranking, donc compatibles avec une offre payante :
- Mise à disposition de documents (cadastre, comptes rendus de conseil municipal, etc.).
- Statistiques précises d'usage.
Notifications : couplées aux alertes, donc gratuites, pas une piste payante. Les notifications ne sont pas traitées comme un outil marketing générique disponible à la mairie pour tout type de contenu — elles sont associées strictement aux alertes critiques (section 3.5), dont elles héritent les mêmes règles de gravité et de portée. Ce couplage ferme un risque identifié : rendre les notifications payantes ou d'usage libre ouvrirait une forme de visibilité achetée indirecte (une mairie qui notifie à volonté obtient plus d'attention que ses publications ordinaires ne le justifieraient dans le classement), et suggérer que « la rapidité d'alerte dépend du budget de la mairie » est intenable sur un sujet de sécurité publique. Les notifications restent donc rattachées au socle civique gratuit (section 6.2), et sortent de la liste des leviers de la version payante.
Cette liste reste à affiner ; il faut identifier d'autres leviers payants que les notifications pour construire une offre attractive pour la mairie. Premières pistes évaluées contre le critère de neutralité :
| Piste | Neutralité vis-à-vis du ranking | Réserve |
|---|---|---|
| Outils de pilotage (publication programmée, brouillons, workflow de validation) | Neutre | Aucune — purement organisationnel côté back-office |
| Export / interopérabilité (flux RSS/API, republication vers le site municipal) | Neutre | Aucune — diffusion vers l'extérieur de DMV, pas de levier interne |
| Analyse comparative (benchmarks anonymisés entre communes) | Neutre | Nécessite de garantir qu'aucune commune ne soit identifiable sans consentement — question de confidentialité, pas de ranking |
| Support et accompagnement (formation, assistance dédiée) | Neutre | Aucune — service humain, pas une fonctionnalité produit ; souvent ce que les subventions de numérisation financent le plus volontiers |
| Personnalisation de marque (thème visuel de la fiche) | Neutre, sous condition | À restreindre strictement à la fiche propre de la mairie ; ne doit jamais influencer son apparence ou sa mise en avant dans le flux personnel d'un habitant (un traitement visuel privilégié dans le mur d'autrui serait un contournement du principe de neutralité sans toucher formellement au score) |
| Habillage de l'app/du site aux couleurs de la commune | Neutre, sous condition | Doit s'appliquer uniformément à tous les habitants de la commune (contenant), sans jamais favoriser le contenu de la mairie dans le flux (contenu) |
| Récupération automatique des publications depuis le site de la mairie (Provider dédié) | Neutre | Aucune — connecteur d'ingestion en amont du pipeline ; l'information récupérée doit quand même passer par Filtrage/Validation/Classification avant d'entrer dans le Ranking Engine, pas de raccourci pour une source « officielle » |
| Statistiques poussées (vues, portée, engagement de ses propres publications) | Neutre | Aucune — service d'information sur sa propre activité, déjà couvert par « Statistiques précises d'usage » |
Point de vigilance transverse : ces pistes convergent vers une intégration de plus en plus profonde de DMV dans l'identité numérique de la mairie (image, contenu, données). C'est une bonne stratégie d'adoption, mais tant que la position reste celle du relais non officiel (section 6.2), il faudra veiller à ce qu'une dépendance technique croissante ne crée pas, de fait, une attente implicite de statut officiel avant que les conditions posées en 6.2 (cadre contractuel, modèle économique) ne soient réunies.
7. Unification finale : un seul moteur, une requête, un feed de résultats
En résumé, en mots simples : il n'existe pas un moteur MurVille, un moteur Mon Espace, un moteur annuaire, un moteur recherche, et demain un moteur annonces. Il existe un seul pipeline (ingestion → Cards homogènes → classement → liste triée) et un seul composant d'affichage générique capable de rendre n'importe quel type de Card. MurVille, Mon Espace, l'annuaire, la recherche et les futurs feeds (annonces, etc.) ne sont plus des systèmes séparés — ce sont des appels paramétrés à ce même pipeline unique : une requête différente (explicite ou implicite), des types de Cards autorisés en sortie différents, mais toujours le même moteur de classement qui ordonne tout et produit la liste finale. C'est ce qui permet de n'avoir qu'un seul composant d'affichage et un seul pipeline à maintenir, plutôt que N implémentations parallèles à faire évoluer séparément.
L'ensemble des surfaces construites au fil de ce document — MurVille, mur personnel, feed acteur, recherche annuaire, recherche riche multi-intentions — ne sont pas des moteurs distincts. Ce sont des variations d'une même requête posée à un seul moteur, dont seule la sortie diffère.
7.1 Toute surface répond à une requête, explicite ou implicite
| Surface | Requête | Nature | Type de résultat filtré en sortie |
|---|---|---|---|
| MurVille | « Que se passe-t-il dans mon village ? » | Implicite (dérivée du contexte de navigation) | Publications uniquement |
| Feed acteur | Pas de requête par défaut | — | Aucun feed par défaut ; simple consultation d'une fiche |
| Recherche annuaire | « Je cherche tel type d'acteur » | Explicite (texte court, filtré par kind) | Acteurs uniquement |
| Mon Espace | « Qu'est-ce qui m'intéresse ? » | Implicite (dérivée du profil : suivis, villes, intérêts) | Publications + acteurs suivis, fusionnés (section 2) |
| Recherche riche (ex. Cap d'Agde) | Requête explicite multi-intentions en langage naturel | Explicite, décomposée par un LLM en sous-requêtes structurées (section 1.3) | Tout type, composé selon les intentions détectées |
Le feed acteur n'ayant pas de requête, il n'a pas besoin de ranking par défaut — ce qui est cohérent avec l'absence de scoring déjà observée en pratique (tri chronologique simple).
7.2 Un seul moteur, deux jeux de facteurs combinables
Le Ranking Engine des publications (criticité, affinité relationnelle, affinité catégorielle, proximité géographique, envergure, fraîcheur, proximité temporelle — section 4) et le scoring textuel des acteurs (matching sur nom/catégorie/tags) ne sont pas deux systèmes concurrents à faire cohabiter. Ce sont deux jeux de facteurs pour un même moteur, chacun adapté à la nature de l'objet noté (publication vs acteur), mais combinables dans un seul pipeline dès qu'une requête (explicite ou implicite) porte sur les deux types à la fois — c'est le cas de la recherche riche multi-intentions.
Les briques de normalisation textuelle déjà présentes et bien isolées (normalizeText, tokenizeSearch) deviennent, dans ce cadre, un facteur réutilisable parmi d'autres — au même titre que la criticité ou l'affinité catégorielle — pas un système à part destiné aux seuls acteurs.
7.2.1 Barème de poids du Ranking Engine des acteurs
Le matching textuel existant (computeActorSearchScore) est conservé tel quel comme référentiel d'échelle — il domine dès qu'une recherche textuelle est saisie, et les nouveaux facteurs s'y additionnent :
| Facteur | Valeur | Nature |
|---|---|---|
| Matching textuel (nom, catégorie, tags) | 100 / 70 / 40 / 25 | Existant, inchangé (voir audit searchUtils.ts) |
| Affinité relationnelle (acteur suivi) | +30 | Binaire, absent du système actuel — à ajouter |
| Proximité géographique | +20 à 0 km, dégressif jusqu'à +0 à 15 km | Distance déjà calculée (haversineKm) mais non branchée au score — à ajouter |
Bonus partenaire (fusion en_avant payant + coeff_annuaire) | +15, plafonné, additif | Remplace le multiplicateur actuel (voir 7.3) ; jamais au-dessus de l'affinité relationnelle |
Fiche non revendiquée (is_claimed = false) | -20 | Malus plutôt que bonus inversé : une fiche non enrichie reflète une information moins à jour, pas un jugement sur l'acteur lui-même |
Le passage d'un bonus « claimed » à un malus « non-claimed » n'est pas qu'une reformulation arithmétique : il change l'état par défaut du système. Une fiche enrichie et revendiquée devient la norme attendue (score neutre), et c'est l'état orphelin — l'exception, ~3000 fiches sur 3500 à Bessan au démarrage — qui est spécifiquement abaissé, plutôt que d'artificiellement gonfler la majorité des fiches déjà revendiquées.
7.3 Le cas du bonus partenaire (coeff_annuaire et en_avant payant) : additif, pas multiplicatif
Le coefficient d'abonnement commercial (coeff_annuaire) et le boost en_avant payant (mise en carrousel sponsorisée) sont de vrais mécanismes de modèle économique de la plateforme — à distinguer d'un favoritisme rendu à un tiers (ex. la soirée latina payante écartée en section 3.4), puisqu'ici la transaction se fait directement avec DMV. Ils restent cependant soumis aux mêmes garde-fous que tout facteur du Ranking Engine, et sont fusionnés conceptuellement en un seul bonus partenaire :
- Additif et plafonné, jamais multiplicatif. Un multiplicateur amplifie disproportionnellement tous les autres facteurs (un score de 216 devient 432 avec un simple ×2), au point de pouvoir faire remonter un mauvais match devant un excellent match gratuit. Un bonus additif et borné (+15, voir 7.2.1) ne fait que départager des cas déjà proches, sans jamais inverser la pertinence de fond — et reste toujours inférieur à l'affinité relationnelle de l'utilisateur (+30).
- Visible et tagué, comme tout facteur influençant le classement (principe de transparence de la section 2.3 et 4.3) — un badge « acteur partenaire » plutôt qu'un boost silencieux.
7.4 Architecture technique : Cards, Knowledge Engine, Experience Engine
Cette structure technique a été conçue avant la calibration détaillée du Ranking Engine (sections 4 et 7.2.1), mais elle formalise correctement le principe d'un seul moteur déjà posé en 7.1-7.2. Elle est retenue comme vocabulaire et architecture de référence, avec deux corrections issues du travail effectué depuis.
Ce qui est retenu tel quel
- Card : enveloppe normalisée transportant tout type de contenu (publication, événement, alerte, acteur, annonce, et tout type futur), avec une structure commune (
id,type,source,score,priority,context,freshness,payload,actions,metadata) et un payload spécifique par type, jamais lu par le moteur lui-même. C'est la réponse concrète à la fusion multi-objets (publications, acteurs, et futurs types comme les annonces) évoquée en section 1.3. - Knowledge Engine : responsable de la récupération et de la normalisation des données en Cards — correspond aux Providers et à l'étape de Chargement/Classification déjà posés dans le pipeline (documents antérieurs). Ne décide jamais de ce qui sera affiché.
- Experience Engine : responsable du calcul de score, de l'application des priorités, de la prise en compte du contexte, de la sélection finale — c'est le nom architectural de ce que ce document appelle le Ranking Engine (section 4), avec ses barèmes chiffrés (4.3.1, 7.2.1).
- Renderers séparés par type : le moteur ignore la façon dont une Card est affichée, cohérent avec le principe déjà posé que le Context Engine ne connaît aucune technologie (document 2).
- Principe d'extensibilité : ajouter un nouveau type de Card (ex. une future Card « Annonce/Logement », cf. section 1.3) ne nécessite qu'un nouveau payload, un renderer, et des règles de score propres à ce type — jamais de modification structurelle du moteur.
Deux corrections apportées par le travail effectué depuis
-
Diversification par alternance de types → remplacée par la limitation de répétition par source. L'idée d'origine (forcer une alternance Publication/Événement/Commerce/Publication/Association/Météo pour éviter un mur monopolisé) est un mécanisme de quotas par catégorie, explicitement écarté au profit de la représentation fidèle du territoire (section 3 des échanges fondateurs). Le bon mécanisme, déjà validé, est de limiter la répétition d'une même source (un acteur qui publie 40 fois ne doit pas monopoliser le mur), pas d'imposer une alternance entre catégories — sans quoi un jour réellement riche en événements et pauvre en publications municipales afficherait un mur artificiellement rééquilibré, donc infidèle à la réalité du territoire.
-
Hiérarchie de priorité rigide par type → remplacée par la criticité comme état transversal. La hiérarchie « Alerte sécurité > Urgence mairie > Information utile > Publication > Découverte » fige la priorité à la catégorie de la Card, ce qui empêcherait des résultats pourtant validés par le barème chiffré — comme un événement d'envergure communale (Card de type Publication, score 70) dépassant légitimement une publication ordinaire d'un acteur suivi (score 60, section 4.3.1). Le mécanisme retenu est que seule la criticité en tant qu'état (section 3.5 : information / alerte / danger) court-circuite le score, quel que soit le type de la Card — une Alerte n'est jamais prioritaire parce que c'est une Alerte, mais parce qu'elle porte un état actif de gravité. Le reste du classement (types non critiques) suit exclusivement l'addition de facteurs nommés du Ranking Engine, jamais une hiérarchie de catégories.
7.5 Le carrousel « mise en avant » : un espace publicitaire séparé, jamais mêlé au classement
Deux mécanismes de mise en avant existent, tous deux confinés à un carrousel dédié en haut de page, distinct du flux principal classé par le Ranking Engine :
- Boost « mise en avant » : met en avant la fiche d'un acteur dans l'annuaire (carrousel), sans effet sur son classement dans le flux ou l'annuaire lui-même.
- Boost « publication » : met en avant une publication précise dans le carrousel (ex. une association qui booste l'annonce d'un tournoi pour maximiser l'affluence), pour une durée choisie (ex. 7 jours), sans effet sur le score ni la position de cette publication dans le flux principal.
Ces deux boosts sont assimilables à un espace publicitaire classique : achetables, mais aussi gagnables (mécanisme de gamification producteur, indépendant du paiement). Ils respectent le principe de neutralité déjà posé (3.4, 6.3, 7.3) précisément parce qu'ils sont cantonnés à un espace visuellement séparé du classement organique — jamais un facteur du Ranking Engine lui-même.
Garde-fou pour l'évolution des conditions de gain : si les conditions permettant de gagner un boost venaient à évoluer, elles doivent rester déconnectées de tout signal de popularité (ex. volume de « ça m'intéresse », nombre de suiveurs) — sans quoi l'attribution du boost recréerait indirectement le problème que le lissage de la notoriété (4.6) cherche justement à éviter, cette fois au niveau de l'accès au carrousel plutôt que du classement. Des conditions de gain basées sur l'activité du producteur lui-même (régularité de publication, complétude du profil) restent saines, puisqu'elles ne dépendent d'aucun signal d'engagement des visiteurs.
8. Prochaines étapes
8.1 Points tranchés
- Flux relationnel vs territorial : pas de priorité de structure — tri unique par score, la relation directe étant un facteur fort mais non absolu (seule la criticité court-circuite les deux flux indifféremment).
- Combinaison fraîcheur / proximité temporelle : après un aller-retour (max → découpage en zones → retour au max), la décision finale est
max(fraîcheur, proximité), à condition que la courbe de proximité soit strictement nulle hors de sa fenêtre d'anticipation. Le découpage en zones a été abandonné : il introduisait une discontinuité brutale à la frontière, pire que le problème (une fuite de la courbe de proximité, pas un défaut du maximum) qu'il cherchait à corriger. - Échelle d'envergure : quatre niveaux discrets (hyper-local / communal / intercommunal / régional-national), hérités par défaut de la catégorie d'acteur, jamais réglés au cas par cas ni par village.
- Un seul moteur, requêtes explicites/implicites, sortie filtrée par type (section 7) : MurVille, mur personnel, feed acteur, recherche annuaire et recherche riche sont des variations d'un même moteur, pas des systèmes séparés.
coeff_annuaire: bonus additif et plafonné, jamais multiplicatif, toujours tagué visuellement (section 7.3).- Barème de poids du Ranking Engine des publications (section 4.3.1) et des acteurs (section 7.2.1) : entièrement fixés, facteur par facteur.
- Envergure (section 3.3) : cascade à deux signaux (type d'acteur, volume d'intérêt agrégé) ; la diffusion multi-communes est explicitement écartée comme signal.
- Gravité des alertes critiques (section 3.5) : trois paliers (information / alerte / danger), déclarés explicitement par un producteur habilité plutôt que déduits.
- Statut de la mairie (section 6) : acteur à capacités étendues, socle civique gratuit et sans engagement, DMV non positionné comme canal officiel, séparation stricte entre outillage payant et neutralité du ranking.
- Architecture technique du moteur (section 7.4) : structure Card / Knowledge Engine / Experience Engine retenue comme vocabulaire de référence, avec deux corrections (répétition par source plutôt que diversification par type ; criticité comme état transversal plutôt que hiérarchie de types).
- Audit de l'existant (mur personnel, feed acteur, recherche/annuaire) : réalisé — voir constats ci-dessous.
8.2 État de l'implémentation (mis à jour)
Déjà implémenté, conforme aux barèmes et principes de ce document :
- Ranking Engine publications (
lib/publicationScoring.ts) : 6 facteurs (circuit-breaker criticité, relation directe, affinité catégorielle, géo favorite/GPS/suivie, envergure, temporel max), avec tags d'explication. - Ranking Engine acteurs (
lib/searchUtils.ts) : 5 facteurs (matching textuel,is_followed+30, géo dégressif, bonus partenaire additif plafonné +15, malus non-claimed -20).coeff_annuaireconverti en additif — point le plus urgent de l'audit, traité. - Envergure (
lib/envergure.ts) : 4 niveaux hérités du type d'acteur, bump parinterest_countconfigurable. - Criticité (
lib/criticite.ts) : 3 paliers avec expiration (criticite_expires_at), circuit-breaker absolu. - Signal « ça m'intéresse » : table
publication_interests+interest_countagrégé, sync serveur + localStorage anonyme. - Configs injectables : 4 tables
app_settings, seeders et pages backoffice — permet l'ajustement empirique des poids sans redéploiement. - Unifications techniques :
/rechercheet l'annuaire commune utilisent désormais le même scoring ;MonEspaceActorFeedn'est plus purement chronologique. - Flux territorial multi-communes (
usePublicationsaveccommuneIds[]etcommuneRoles).
Points de vérification avant de poursuivre (non conceptuels, mais à confirmer avant la Phase A/B du chantier Card) :
- Module
app/core/context-engine/préexistant : audité — confirmé neutre (aucune priorité figée parCardKind, aucune diversification par alternance de types, aucun doublon avec le scoring existant).CardDescriptoretActionKindsont réutilisés comme fondation pourlib/feedEngine/sans modification. Point de vigilance pour la Phase B : le champpriority?: numberdoit être utilisé comme circuit-breaker (tri à deux clés : criticité d'abord, score ensuite), jamais fusionné additivement dans le score — et l'état de criticité doit rester indépendant duCardKind(une Cardpublicationdoit pouvoir devenir critique, pas seulement une Cardalert). - Nommage des rôles de ville : audité et confirmé sain.
profile(résidence) etcontributor→ rôlefavorite(+45, correct) ;actor,favori,test/fallback→ rôlefollowed(+20, correct). Valeurs numériques en base confirmées conformes au barème (45 / 40 / 20). - Cas
managed(mairie gérée) →favorite(+45) : point discutable non résolu. Actuellement, la commune gérée par un admin mairie est traitée comme sa résidence, sous l'hypothèse qu'un gestionnaire réside presque toujours dans la commune qu'il gère — vrai dans la majorité des cas, mais pas garanti (agent intercommunal, adjoint résidant dans une commune voisine). Décision à prendre : garder ce raccourci tel quel, ou créer un rôlemanageddistinct avec son propre poids, découplé de l'hypothèse de résidence. - Rôle
gps(+40) défini mais jamais assigné : cohérent avec l'absence actuelle de captation de géolocalisation en temps réel dans le scoring — pas un bug, un poids du barème qui reste inerte tant que cette fonctionnalité n'est pas branchée. À reconnecter le jour où la géolocalisation active sera intégrée au calcul.
Encore ouvert :
Limitation de répétition par source— implémentée (experienceEngine.ts,limitRepetitionBySource), exemptée pour les Cards acteur (raisonnement validé : la règle n'a d'objet que pour les publications répétées d'un même acteur).Type Card unifié, Knowledge Engine, Experience Engine assemblés— Phases A et B complètes (lib/feedEngine/), avec tri à deux clés (criticité en circuit-breaker viaprioritybooléen, score comparé uniquement au sein d'un même groupe de priorité) et config injectable (feed_engine_config,maxPubsPerSource: 3par défaut, à ajuster empiriquement). Encore non branché sur le pipeline en production (WallLayout, usePublications) — c'est la phase suivante.- Fusion Publications + Acteurs suivis dans Mon Espace (section 7.1) — décision produit non tranchée, ne pas implémenter avant clarification (ce n'est pas ce qui a été décidé, à ne pas confondre avec la fusion des flux relationnel/territorial de publications déjà actée en section 2).
- Recherche riche multi-intentions avec LLM (section 1.3) — reportée.
- Publications épinglées (
epingle=true) sans ordre distinct — ajustement mineur toujours en attente.
Chantier en cours — modèle à deux couches (zone vivante / archive) et corrections associées :
- Retrieval à deux couches (4.4.3) implémenté (Phase D) : zone vivante bornée (événements futurs sans limite + 14 jours passés, actualités 20 jours) puis bascule vers l'archive chronologique. Complément de première page pour les communes à faible volume ajouté en correctif.
- Bug critique corrigé : mutation partagée du query builder Supabase entre les requêtes A/B de la zone vivante (vidait systématiquement les résultats) — corrigé par la création d'un builder frais par requête.
- Bug critique en cours de correction : publications exclues par
limitRepetitionBySourcedevenues inaccessibles (n'atteignaient ni la zone vivante ni l'archive), absence de bascule vers l'archive au scroll, absence de tri stable en cas de score égal — correctif demandé, résultat en attente de confirmation. - Point clarifié : l'hypothèse d'une double résolution du niveau d'envergure (une pour le bonus, une autre pour la largeur de courbe) était fausse — investigation confirmée : une seule variable
levelest utilisée partout danspublicationScoring.ts, résolue correctement à "communal" pour la mairie. - Aller-retour complet sur la combinaison temporelle, refermé — avec un enseignement méthodologique : somme (rejetée) → max restauré → découpage en zones (implémenté suite à un score jugé suspect) → retour définitif au max, désormais testé et confirmé continu à la frontière (variation ≤ 2 points entre jours consécutifs, contrainte ≤ 10 vérifiée sur toute la plage). Bilan rétrospectif : le
max()original n'a jamais été bugué — la fonction de proximité retournait déjà correctement 0 hors de sa fenêtre. Le score jugé suspect à l'origine de tout ce détour (E2, 49 à 62 jours d'un événement communal) était en réalité une contribution légitime de la fraîcheur de publication (l'annonce avait été postée 2 jours plus tôt), pas une fuite de la proximité. Le détour par les zones a introduit trois régressions réelles (E1, E4, E6) sur le jeu de test, sans jamais corriger de bug existant — depuis annulées. Leçon retenue : un score final jugé suspect doit être vérifié dans sa décomposition par facteur avant de conclure à un bug — un chiffre élevé peut être la contribution légitime d'un autre facteur que celui qu'on soupçonne. - Panneau de debug (score, détail par facteur, tags, zone d'appartenance, objet Card brut) ajouté sur MurVille en environnement beta uniquement, pour permettre la vérification visuelle directe du Ranking Engine. Restriction d'accès à ajouter : réservé à l'administrateur plateforme, pas seulement conditionné à l'environnement beta (un accès beta n'implique pas automatiquement un accès admin — les deux conditions doivent être vérifiées).
- Nouveau facteur : notoriété de l'acteur (4.6) — bonus additif séparé (-10 à +10, paramétrable), calculé par moyenne mobile lissée de l'
interest_countsur les N derniers événements (N=5 par défaut, paramétrable), destiné à départager des événements proches dans le temps sans jamais dominer la proximité temporelle. Jamais affiché publiquement, y compris sa formule — visible uniquement dans le panneau de debug, réservé à l'administrateur plateforme. À implémenter.
8.3 Actions de fond, non conceptuelles
- Migration du Ranking Engine vers l'API (chantier futur, décidé mais non planifié dans l'immédiat) : le calcul du score (
lib/feedEngine/*) s'exécute aujourd'hui côté navigateur. Architecture cible actée :- Score de base par publication (criticité + envergure + temporel + notoriété, fusionnés) : calculé et mis en cache côté serveur, par commune, invalidé à chaque nouvelle publication et à chaque changement de jour (la courbe temporelle évolue avec la date). Ce score de base est objectif — identique quel que soit l'utilisateur qui consulte — et sert indifféremment à MurVille et à Mon Espace, y compris en agrégation multi-communes (simple union des listes déjà scorées de chaque commune, sans recalcul).
- Couche d'affinité personnelle (relation directe, affinité catégorielle, rôle de ville favorite/GPS/suivie) : appliquée en plus du score de base, au moment de la fusion. Rien à protéger ici — ce sont des données que l'utilisateur connaît déjà sur lui-même (qui il suit, où il habite). Peut rester calculée côté client sans risque, ou côté serveur par simple commodité, sans contrainte de confidentialité.
- Seule la contribution de notoriété (4.6), imbriquée dans le score de base, doit rester non détaillée dans les réponses API pour un non-admin — le score de base peut être transmis tel quel (un nombre), sans jamais exposer sa décomposition.
- Limite assumée sur le secret : même protégé côté serveur, le calcul de notoriété reste partiellement reconstituable par un tiers déterminé (les autres facteurs étant documentés et déterministes, leur soustraction du score de base révélerait la contribution de notoriété). Ce n'est pas considéré comme critique : le lissage par moyenne mobile (4.6) rend déjà le gaming ponctuel inefficace par construction — c'est cette propriété qui protège réellement le système, le secret n'étant qu'une couche additionnelle, pas la seule ligne de défense.
- Ce chantier est volontairement reporté : l'implémentation actuelle côté client reste en usage pendant la phase de calibration et de test, pour itérer rapidement. Le transfert vers l'API interviendra une fois le comportement du Ranking Engine jugé satisfaisant.
0bis. Centres d'intérêt visibles et modifiables par l'utilisateur (nouvelle piste produit) : plutôt que de laisser l'affinité catégorielle dérivée (section 4.3.1) purement silencieuse, l'exposer dans le profil utilisateur (« vous vous intéressez à... »), avec possibilité pour l'utilisateur de corriger ou supprimer une catégorie mal déduite. Renforce le principe déjà posé (signal déclaratif plutôt que comportemental caché) en transformant une inférence en signal confirmé ou corrigé explicitement — une correction manuelle devient une donnée plus fiable que l'inférence de départ. Non implémenté, à ajouter à la liste des évolutions futures du mur personnel.
- Modéliser un premier nouveau type de Card au-delà de publications/acteurs (ex. annonces/logement, cf. section 1.3) pour valider en pratique le principe d'extensibilité posé en 7.4 — payload propre, règles de score propres, sans modification du moteur.
- Confronter l'architecture au point de vue d'un maire ou d'un élu local — angle mort identifié, absent des trois personas déjà couverts. Points précis à aborder avec la mairie pilote :
- Habilitation et astreinte pour le déclenchement des alertes critiques : qui peut déclencher une alerte en dehors des heures ouvrées (le week-end, en soirée) ? Le système actuel semble reposer sur un seul agent municipal ; à vérifier si le maire/les adjoints doivent avoir un accès direct, et si une astreinte existe ou doit être organisée côté commune.
- Gouvernance plus large des informations municipales (validation, responsabilité légale de ce qui est publié).
- Implémenter chaque facteur comme une fonction pure et testable indépendamment, pour préserver le déterminisme dès la mise en œuvre technique.