Tous les changements notables du projet sont documentés ici.
Format basé sur Keep a Changelog. Le projet suit un versionnement informel (pas de tags pour l'instant).
- 2026-08-08 — Ajout dans chaque catégorie du digest RSS d'une annexe déterministe Évaluation du scoring. Chaque article retenu (score 3 à 5) affiche son score initial de phase 1 et la raison du modèle de filtrage. Le rendu est produit par Python, sans appel LLM supplémentaire, et couvert par des tests unitaires.
- 2026-08-07 — Suppression du trou de collecte nocturne : les horaires
7h/13h/19h créent un intervalle de 12h incompatible avec l'ancienne fenêtre
fixe de 8h. Le pipeline repart désormais de
last_success.json, avec 60 min de chevauchement, et n'avance cet état qu'après un run terminé avec succès. - 2026-08-07 — Maîtrise du coût avant les appels LLM : tri par fraîcheur, plafond de 50 articles par source puis 200 articles par run avant la phase 1. Les garde-fous sont couverts par des tests unitaires.
- 2026-08-07 —
logs/audit-details.mdn'est plus versionné. Il est publié comme artefact GitHub Actions avec une rétention de 30 jours ; seuls le résumé et les erreurs restent dans Git afin de limiter la croissance du dépôt. - 2026-08-07 — Documentation d'architecture mise à jour : fenêtre dynamique, état de succès, plafonds pré-LLM, artefacts et nouveaux paramètres de tuning.
- 2026-06-22 —
SCORING_PROMPTsimplifié pour maximiser le rappel (« on loupe trop d'articles ») :- Schéma de sortie réduit à
{score, decision, tags, raison}(abandon deconfiance,signal_principal,plafond_appliqué, ettag+tags_secondairesfusionnés en une listetagsde 1-3). - Décision strictement déterministe sans exception : 5→read_now, 4→read_later, 3→skim, 2/1→archive. Rétention = score ≥ 3.
- Suppression de tous les plafonds (recall max, bruit marketing assumé).
- Recherche IA théorique sortie du score 2 (peut atteindre 3 = retenu).
digest.pyparsetags(1ᵉʳ = principal, reste = secondaires) ;audit.pyperd les colonnes Conf/Signal/Plafond (devenues constantes) + code mort retiré.
- Schéma de sortie réduit à
- 2026-06-22 — Refonte du
SCORING_PROMPTpour le rendre déterministe :- Règle d'agrégation explicite : 4 axes notés sur {0,1,2} (Actionabilité,
Fiabilité, Nouveauté, Profondeur), score brut =
2 + Aavec A=0 ⇒ score ≤ 2, F=0 ⇒ score = 2, élévation à 5 réservée aux signaux forts vérifiables. Les anciennes bandes deviennent des exemples de calibration. - Mapping score→décision déterministe (5→read_now, 4→read_later, 3→skim, 2/1→archive) avec exceptions strictes chiffrées (A=2, P≥1, urgence explicite).
- Deux nouveaux tags définis :
ia_stratégie,ia_management_equipe(+ ordre de précédence des tags).confianceetsignal_principaloutillés.
- Règle d'agrégation explicite : 4 axes notés sur {0,1,2} (Actionabilité,
Fiabilité, Nouveauté, Profondeur), score brut =
- 2026-06-22 —
ACCEPTED_DECISIONSinclut désormais"skim"→{read_now, read_later, skim}, ce qui revient à retenir tout score ≥ 3 (avant : score 4-5 seulement, rétention ~0 % les jours calmes).
-
2026-06-22 — Métriques d'audit : depuis l'ajout de
skimàACCEPTED_DECISIONS, les articlesskim(retenus dans le digest) étaient comptés dansArchet exclus deRetenue%→ rétention sous-estimée (17 % affiché vs 31 % réel). Ajout d'un compteurskim, d'une colonneSkimdansaudit-summary.md, etRetenue% = (RN + RL + Skim) / Trouvés. -
2026-06-22 —
llm_client:resp.choices[0]levaitTypeError: 'NoneType' object is not subscriptablequand OpenRouter renvoiechoicesvide/None (réponse d'erreur/refus) → article perdu au scoring. Garde ajoutée (retourne "" comme pourcontent=None). -
2026-06-22 — Bloquant d'exécution dans la refonte du
SCORING_PROMPT: accolades du schéma JSON non échappées →KeyErrorà chaque.format()(digest vide permanent). Toutes les accolades littérales doublées ({{ }}). -
2026-06-22 —
llm_client.complete()n'envoyait aucunetemperature→ les deux providers échantillonnaient à leur défaut (1.0), rendant le scoring non reproductible (un même article pouvait basculer de score d'un run à l'autre, bruit dans les logs d'audit). Ajout detemperature=0(constanteTEMPERATURE) sur tous les appels. Déterminisme quasi-total — résiduel possible côté MoE/routage OpenRouter, atténuable via seed + provider.order. -
2026-06-21 — Workflow
digest.yml: collision augit pushquand deux runs se chevauchaient (! [rejected] (fetch first)). Ajout d'un garde-fouconcurrency: build-digest(cancel-in-progress=false) qui sérialise les runs,git pull --rebase --autostashavant push en défense en profondeur.
-
2026-06-21 — Cron décalé d'1h en heure d'été :
0 6,12,18→0 5,11,17pour viser 7h/13h/19h Paris l'été (UTC+2). 1h trop tôt l'hiver, compromis assumé (GitHub Actions n'a pas de support DST). -
2026-06-21 — Audit
logs/audit-errors.mdjamais créé tant qu'aucune erreur ne survenait (le besoin était 3 logs persistés, seuls 2 existaient). Le fichier est désormais toujours écrit avec son en-tête.
-
2026-06-21 —
logs/audit-details.mdtrace désormais tous les articles scorés en phase 1 (et plus seulement score ≥ 3), avec une ligne de distribution des scores en tête de bloc. Motivation : un run sans rétention affichait_Aucun article score ≥ 3_et masquait les scores/raisons, rendant l'audit aveugle exactement quand on en avait besoin. -
2026-04-25 — Déclencheur
pushmanquant dans.github/workflows/digest.yml. Le README §3 documentait 3 déclencheurs (cron,workflow_dispatch, push surmain) mais le workflow n'en avait que 2. Ajout depush: branches: [main]avecpaths-ignoresur**.mdetLICENSEpour éviter un build inutile à chaque modification de doc.
- 2026-04-25 — Documentation inline de
digest.pyétoffée pour faciliter la prise en main par un dev tiers (code inchangé, 277 → 404 lignes) :- Docstring module avec vue d'ensemble des 6 étapes du pipeline et des effets de bord persistés
- Section Configuration : chaque paramètre commenté avec sa sémantique et la justification du défaut
- Docstrings de fonctions (Args / Returns / comportement d'erreur)
- Commentaires aux points subtils : tolérance feed-par-feed sur les
erreurs de fetch, extraction regex du JSON dans la réponse Haiku,
persistance de
seen[hash]dès l'appel Haiku indépendamment du score, ordre de persistance RSS puis seen pour cohérence sur crash
- Intégration Jina Reader ou Wallabag pour fetcher le contenu complet des articles (feeds RSS souvent tronqués)
- Retry avec backoff sur les appels API Anthropic
- Batch scoring pour paralléliser les appels Haiku
Première version fonctionnelle en production.
- Pipeline complet
digest.py: lecture OPML → fetch RSS → déduplication (seen.json) → scoring Haiku → synthèse Sonnet → génération RSS - Fichier
prompt.pyséparé avec les deux prompts (scoring + synthèse) pour faciliter l'itération sans toucher au code - Workflow GitHub Actions
digest.ymlavec cron 3x/jour (7h, 13h, 19h heure de Paris) et déclenchement manuel (workflow_dispatch) - Déploiement automatique sur GitHub Pages via branche
gh-pages - OPML
sources.opmlavec 53 sources organisées en 5 catégories :- Sources françaises IA (15)
- Grandes sources internationales (15)
- Labos & éditeurs (11)
- Recherche & veille technique (7)
- Automatisation & agents (5)
- Dédoublonnage par hash SHA1 d'URL avec fenêtre glissante de 14 jours
- Conversion Markdown → HTML artisanale pour le contenu RSS
- Documentation complète dans
README.md(660 lignes) - Licence MIT
LOOKBACK_HOURS = 8(fenêtre de temps par run)MIN_SCORE = 3(seuil de pertinence Haiku)MAX_ARTICLES_PER_CATEGORY = 20(garde-fou coût API)SEEN_RETENTION_DAYS = 14HAIKU_MODEL = "claude-haiku-4-5-20251001"SONNET_MODEL = "claude-sonnet-4-6"
Les sources suivantes sont listées dans l'OPML avec leur raison mais n'ont pas de flux RSS exploitable :
- Bloomberg Technology (pas de RSS officiel)
- Financial Times AI (paywall)
- The Information (paywall)
- Superhuman AI (newsletter, convertir via Kill the Newsletter)
- Substack AI (agrégateur)
- Mistral AI News (pas de RSS officiel)
- Lindy Blog (pas de RSS officiel)
- Papers with Code (pas de RSS global)
- Stanford AI Index (rapports annuels uniquement)
- Anthropic News/Engineering via Olshansk/rss-feeds
- Hugging Face Daily Papers via
papers.takara.ai
- digest.yml placé à la racine : déplacé vers
.github/workflows/pour que GitHub Actions le détecte (les dossiers cachés.githubsont souvent oubliés lors d'un upload via l'interface web) - Erreur
Client.__init__() got an unexpected keyword argument 'proxies': conflit de version entreanthropic==0.39.0(trop ancien) ethttpxinstallé par défaut. Corrigé en passant àanthropic>=0.49.0et en ajoutanthttpx>=0.27.0dansrequirements.txt - GitHub Pages inactif après le premier run : configuration manuelle
nécessaire (Settings → Pages →
gh-pages// (root)) après création automatique de la branche par le premier run réussi
- Repo public (nécessaire pour GitHub Pages gratuit)
- Minutes GitHub Actions illimitées sur repo public
- Consommation réelle observée : ~100 minutes/mois estimées (large marge)
- Clé API stockée dans GitHub Secrets (
ANTHROPIC_API_KEY) - Budget Anthropic plafonné à 20$/mois, auto-reload désactivé
Phase de conception et construction avant première mise en production.
- Stratégie de curation choisie : digest synthétisé par IA, 3x/jour, livraison par RSS sur iPhone (Reeder)
- Stack technique retenue : Python + GitHub Actions + GitHub Pages
(vs VPS, Raspberry Pi, n8n, Claude Code
-p) - Modèles Claude : split Haiku (scoring) + Sonnet (synthèse) pour optimiser le ratio qualité/coût
- Source de vérité des sources : OPML standard (vs URLs hardcodées) pour double usage pipeline Python + import Reeder
- Persistance :
seen.jsoncommité dans le repo (vs SQLite éphémère sur le runner, Redis, DynamoDB) - Format RSS : un item par run contenant tout le digest (vs un item par article)
- Audit des 50 sources initiales proposées (sites francophones IA, grandes sources internationales, labos, recherche, automation)
- Vérification des URLs RSS via recherche web (contournement des blocages anti-bot qui empêchaient les tests directs)
- Identification des feeds tiers nécessaires (Anthropic, HF Daily Papers)
- Documentation des sources sans RSS (9 sur 53) avec raison explicite
- Structure hiérarchique OPML en 5 catégories pour permettre un scoring/synthèse différenciée par catégorie