Tableau de bord /nouvelles — Guide pour assistants IA

Pour le contexte général du site CJEAO, voir CLAUDE.md à la racine.

Le tableau de bord /nouvelles est la fonctionnalité la plus complexe du site : ~20 sources de données opérationnelles + ~15 sources d’articles de presse, le tout agrégé en widgets sur une seule page. C’est de loin la zone où l’on passe le plus de temps de développement.


Architecture en 1 minute

┌──────────────┐    cron     ┌────────────────────┐    REST   ┌──────────┐
│ Source web   │ ─────────→  │  Netlify Function  │  ──────→  │ Supabase │
│ (RSS, HTML,  │             │  nouvelles-*.js    │   upsert  │  table   │
│  API, CSV)   │             └────────────────────┘           └────┬─────┘
└──────────────┘                                                   │
                                                                   │ SELECT (anon)

                                                          ┌─────────────────┐
                                                          │ nouvelles.astro │
                                                          │  render widget  │
                                                          └─────────────────┘

Inventaire des sources

Données opérationnelles (1 table = 1 ligne unique, schéma {id:1, ...payload, mis_a_jour})

FunctionTableSourceCronNotes
nouvelles-meteo.jsnouvelles_meteoOpen-Meteo API0 */1 * * *La Sarre 48.8°N/-79.2°O
nouvelles-essence.jsnouvelles_essenceRégie énergie QC2×/jour8 stations MRC + comparatif régional
nouvelles-sopfeu.jsnouvelles_sopfeuSOPFEU2×/jourZone La Sarre-Amos
nouvelles-finances.jsnouvelles_financesgold-api + frankfurter.dev1×/jourOr USD/oz, USD-CAD, EUR-CAD + variation 7j
nouvelles-hydro.jsnouvelles_hydroHydro-Québec Info-Pannesaux 4h
nouvelles-routes.jsnouvelles_routesQuébec 511aux 3hRoutes 101, 111, 117, 393 — filtré aux municipalités d’A-T via netlify/lib/abitibi-temiscamingue.js
nouvelles-alertes-meteo.jsnouvelles_alertesEnv. Canada MSC GeoMetaux 3h
nouvelles-urgences.jsnouvelles_urgencesMSSS Québec0 */1 * * *Achalandage urgences CIUSSS A-T
nouvelles-emplois.jsnouvelles_emploiscjeao.jobstat.caaux 6h
nouvelles-bornes.jsnouvelles_bornes_electriquesCircuit Électriqueaux 6h80 km autour La Sarre
nouvelles-air.jsnouvelles_airOpen-Meteo Air Qualityaux 3h
nouvelles-aurores.jsnouvelles_auroresNOAA SWPC + calcul lune + IMO météoresaux 6h
nouvelles-eau.jsnouvelles_eauCEHQ + Env. Canada2×/jourLac Macamic, Abitibi, Kinojevis, Harricana
nouvelles-internet.jsnouvelles_internetM-Lab NDT via BigQuery1×/semVitesses par FAI
nouvelles-transport.jsnouvelles_transportCSS Lac-Abitibi0 */1 * * *Alerte annulation/retard transport scolaire

Sources d’agenda (widget consolidé)

FunctionTableSourceCronOnglet widget
nouvelles-loisirs-ao.jsnouvelles_activites_loisirsloisirs.ao.ca0 8 * * *« Loisirs »
nouvelles-cciao.jsnouvelles_cciaocciao.ca4×/jour« CCIAO »
nouvelles-agenda-lasarre.jsnouvelles_activites_ville_lasarrelasarre.ca/calendrier15 8 * * *« Ville LS » (max 3 — voir pièges)
(table activites interne)activitesSaisie manuelle CJEAO« Carrefour »

Le widget Agenda déduplique entre Loisirs A-O et Ville LS (match flou par date + similitude de titre). Ville LS gagne en cas de conflit (source officielle).

Sources d’articles de presse (table partagée nouvelles_articles, schéma

{source, titre, url, resume, date_publication, image_url, mis_a_jour})

Chaque function fait INSERT ... ON CONFLICT DO UPDATE (upsert) avec une contrainte unique sur url.

FunctionSourceTypeCronFiltre
nouvelles-radio-canada.jsRadio-Canada A-T RSSRSSaux 4hWhitelist 60+ toponymes A-T (MOTS_REGION)
nouvelles-tva-abitibi.jsTVA Abitibi RSSRSSaux 4hAucun (site mono-régional)
nouvelles-mon-abitibi.jsMon Abitibi RSSRSSaux 4hFiltre GUID monabitibi.ca (rejette les sites jumeaux du réseau)
nouvelles-le-citoyen.jsjournallecitoyen.comScraping0 9 * * *Aucun (hebdo régional)
nouvelles-indice-bohemien.jsindicebohemien.orgRSSaux 6h
nouvelles-ville-lasarre.jslasarre.ca/actualitesScraping0 9 * * *À ne PAS confondre avec nouvelles-agenda-lasarre.js
nouvelles-avis-lasarre.jscitoyen.lasarre.ca/avisScraping1×/jour
nouvelles-mrcao.jsmrcao.qc.caScraping1×/jour
nouvelles-cssla.jscssla.gouv.qc.caScraping1×/jour
nouvelles-uqat.jsUQATAPI interne1×/jour
nouvelles-ecole-musique-ao.jsecolemusiqueao.caScraping mono-page0 10 * * *
nouvelles-cisss-at.jsCISSS-AT communiquésScraping1×/jour
nouvelles-orezone.jsorezone.com press releasesScraping1×/jour
nouvelles-sadcao.jssadcao.comScraping1×/jour(récent, vérifier état)

Fonctions auxiliaires


Conventions strictes

Schéma d’une nouvelle table « widget »

create table if not exists public.nouvelles_xxx (
  id smallint primary key default 1,         -- toujours 1 ligne
  ...payload jsonb ou colonnes typées...,
  mis_a_jour timestamptz not null default now()
);
alter table public.nouvelles_xxx enable row level security;
create policy "lecture publique" on public.nouvelles_xxx
  for select to anon using (true);
grant select on public.nouvelles_xxx to anon;

Squelette d’une nouvelle function nouvelles-xxx.js

import { withSentry } from '../lib/sentry.js';

export const config = {
  schedule: '0 8 * * *', // cron Netlify (en UTC)
};

const __sentryHandler = async () => {
  const SUPABASE_URL = process.env.SUPABASE_URL;
  const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
  if (!SUPABASE_URL || !SUPABASE_SERVICE_KEY) {
    console.error('[xxx] Variables manquantes');
    return new Response('Erreur de configuration', { status: 500 });
  }

  // 1. Fetch source
  // 2. Parse
  // 3. Upsert : POST /rest/v1/nouvelles_xxx avec Prefer: resolution=merge-duplicates

  return new Response(JSON.stringify({ ok: true, ... }), { status: 200 });
};

export default withSentry(__sentryHandler);

Ajouter une nouvelle source en 5 étapes

  1. Migration SQL dans supabase/migrations/AAAAMMJJ_nom.sql (appliquer manuellement dans Supabase SQL Editor)
  2. Function netlify/functions/nouvelles-xxx.js selon le squelette
  3. Proxy (nouvelles-proxy.js) — ajouter le fetchTable(...), le res, le data, et la clé dans le payload final
  4. Widget dans src/pages/nouvelles.astro :
  5. Méthodologie (src/pages/nouvelles/methodologie.astro) — ligne dans le tableau des sources

Pièges connus (apprentissages durs)

FacetWP / lazy-load nonce-protégé

RSS de réseaux WordPress multi-sites

API REST WordPress qui n’expose pas les dates d’événements

Pages détail avec dates stale d’une année antérieure

Quebec 511 retourne toutes les entraves de la route


Stratégie de filtrage

Côté source (filtres dans les ingest functions)

Côté affichage (filtre Abitibi-Ouest dans nouvelles.astro)

Dédup agenda


Modèle de lecture côté client

nouvelles.astro lit directement Supabase (clé anon) en priorité — la function nouvelles-proxy.js n’est qu’un filet de secours. Cette stratégie économise des invocations Netlify (les lectures direct Supabase ne coûtent rien côté Netlify) et permet l’auto-refresh sans surcharger nos quotas.

Quand on ajoute une source au widget, il faut donc s’assurer que :


Ce qu’il ne faut pas faire


Notes & todos en cours

Voir docs/notes-nouvelles-todo.md pour le carnet de travail.