feat: derive event publication status from the mairie agenda
Pure matching (date locale Cugnaux + titre normalisé) and paginated transport, both fully tested. HTML extraction via DOMParser stays impure and untested (absent from Bun), like formulaire-mairie.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a7b780876b
commit
6c4f4219f1
@@ -0,0 +1,66 @@
|
||||
// Matching événement Nextcloud ↔ carte de l'agenda mairie. Fonctions PURES,
|
||||
// sans DOM (testables) : c'est le cœur du risque produit (précision du match,
|
||||
// fuseau, normalisation du titre). Biais PRÉCISION v1 : dans le doute, on
|
||||
// n'affirme pas « publié » (faux négatif bénin, cf. plan / DECISIONS.md).
|
||||
|
||||
const FUSEAU = "Europe/Paris";
|
||||
|
||||
// en-CA rend directement "YYYY-MM-DD" : même forme que le <time datetime> de
|
||||
// l'agenda, comparable sans reformatage.
|
||||
const FORMAT_JOUR_PARIS = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: FUSEAU,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
|
||||
// Forme stable d'un titre : ce qui doit matcher malgré les écarts cosmétiques
|
||||
// entre le calendrier et la mairie. On RETIRE la ponctuation (sans la remplacer
|
||||
// par une espace) pour que « Atelier B.D. » == « Atelier BD ».
|
||||
export function normaliserTitre(titre) {
|
||||
return titre
|
||||
.normalize("NFD")
|
||||
.replace(/[̀-ͯ]/gu, "") // diacritiques isolés par NFD
|
||||
.replace(/[^\p{Letter}\p{Number}\s]/gu, "") // ponctuation ET emojis
|
||||
.toLowerCase()
|
||||
.replace(/\s+/gu, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Le <time datetime> de l'agenda est une date NUE (pas d'heure) : on la compare
|
||||
// à la date locale de Cugnaux du début, jamais à une conversion de fuseau.
|
||||
export function dateLocaleCugnaux(evenement) {
|
||||
return evenement.journeeEntiere
|
||||
? formaterDateFlottante(evenement.debut)
|
||||
: FORMAT_JOUR_PARIS.format(evenement.debut);
|
||||
}
|
||||
|
||||
// Journée entière = date flottante (RFC 5545) : composantes LOCALES, jamais un
|
||||
// fuseau, sinon la date glisse d'un jour (cf. presentation.js / DECISIONS.md).
|
||||
function formaterDateFlottante(date) {
|
||||
const annee = date.getFullYear();
|
||||
const mois = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const jour = String(date.getDate()).padStart(2, "0");
|
||||
return `${annee}-${mois}-${jour}`;
|
||||
}
|
||||
|
||||
// Index des cartes par `date|titre normalisé` → lien. Deux cartes de même clé
|
||||
// (même jour + même titre normalisé, rare) : la DERNIÈRE indexée l'emporte.
|
||||
export function indexerCartes(cartes) {
|
||||
const index = new Map();
|
||||
for (const carte of cartes) {
|
||||
index.set(cle(carte.date, carte.titre), carte.lien);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// Match STRICT : même date locale ET titre normalisé identique. Rien d'autre ne
|
||||
// vaut « publié » en v1.
|
||||
export function trouverPublication(evenement, index) {
|
||||
const lien = index.get(cle(dateLocaleCugnaux(evenement), evenement.titre));
|
||||
return lien ? { publie: true, lien } : { publie: false };
|
||||
}
|
||||
|
||||
function cle(date, titre) {
|
||||
return `${date}|${normaliserTitre(titre)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user