4.6 KiB
Rapport de session · 2026-07-30 · session 18
Tâche
Sprint 6 · SEO — Générateur SEO trilingue (mots-clés FR/EN/ES +
schema.org + hreflang). Roadmap L60 : « Refactor mission seo_autonome/ →
200+ mots-clés FR/EN/ES · schema.org · hreflang ».
Sprint 5 étant clos en repo (ONAPI/Legal session 16 · QA Audit 5D session 17, Mobile hors périmètre VPS · #8), c'est le premier volet Sprint 6 réalisable dans le repo. Les deux autres volets Sprint 6 (OTOIA voice Amélie pilote AEC · chat OTOIA embarqué) dépendent d'API externes / du desk VPS → hors périmètre worker.
Décision d'architecture
Le SEO est un livrable de second niveau : sa matière première est
projets_master.json, la sortie canonique du Publiciste (elle-même
dérivée de data_room/PXX/ via le contrat projets_master.schema.json). Le
générateur ne fabrique aucun fait de projet — il compose du SEO à partir de
données déjà sourcées + un lexique éditorial générique non chiffré.
Ce choix respecte #6 (zéro invention) et #5 (zéro duplication) : le générateur
réutilise le validateur maison et les tokens de marque du Publiciste
(lib/validator.py, lib/branding.py → STATUTS_SANS_PRIX, devise USD), sans
les réimplémenter.
Fichiers créés — 05_deliverables_mvp/seo/
seo_spec.json— config site (base_url, langs FR/EN/ES, préfixes, x-default)- lexique éditorial générique (immobilier / à vendre / résidence / pays / régime CONFOTUR) + org schema.org + cibles. Zéro donnée de projet, zéro chiffre.
seolib/{__init__,deps,keywords,schemaorg,hreflang,builder}.pydepsréutilise validateur + branding Publiciste ;slugify/digitsdéterministes.keywords: génération déterministe, chaque mot-clé portescope,projet,intent,categoryet une listesourcesnon vide.schemaorg: graphe JSON-LD (Organization+ uneResidence/projet),offersuniquement si projet disponible + prix sourcé (USD).hreflang:alternateFR/EN/ES +x-defaultpar page.
seo_gen.py— CLIbuild/validate· 15 invariants de cross-cohérence et d'anti-invention.seo.schema.json— contrat de sortie draft-07 (bundle keywords + schema.org + hreflang + manifest).fixtures/projets_master.json— entrée de test : 9 projets P01..P09 (noms sourcés deCLAUDE.md §Projets), tousen_developpement, sans aucun chiffre.out/{seo_keywords,seo_schema_org,seo_hreflang,MANIFEST}.json(hand-off) ·tests/test_seo.py(36 tests dont 8 injections négatives) ·README.md·.gitignore.
Fichiers modifiés
.gitea/workflows/ci.yml: jobseo-tests+ ajout augate.
Anti-invention (cœur · #6)
- Un mot-clé = composition de tokens factuels (
projet:<code>.nom/.localisation, sourçables) et de lexique éditorial (lexicon:*, générique non chiffré). Un invariant vérifie que chaque mot-clé est sourcé et résoluble. - Un mot-clé ne peut porter que les chiffres présents dans son champ projet
source : « 1069 Crisfer » (P09) passe ; un prix injecté (
… 250000) est refusé par l'invariant #7 (test négatif dédié). - schema.org n'émet une
offers/priceque pour un projetdisponibledont une typologie porte unprix_depuis_usdnumérique sourcé ; devise USD (#10). Pour tout statut sans prix : aucun chiffre (invariant #11 + tests). La fixture livrée (9 projetsen_developpement) produit donc 0 offre — aucune valeur inventée dans le hand-off committé.
Résultat
- 258 mots-clés — fr=87 · en=87 · es=84 (cible roadmap 200 dépassée) ;
- schema.org : 10 nœuds (1 Organization + 9 Residence), 0 offre ;
- hreflang : 10 pages (accueil + 9 projets), 4
alternate/page (FR/EN/ES + x-default).
Vérifs
- 36/36 tests SEO verts (dont oracle
jsonschemasi présent) ; - gate CI local vert : guard contraintes · JSON bien formés · docs (liens + score) · YAML valide ;
- régression 377 tests verts au total (341 → +36), 0 module en échec ;
- build déterministe (test dédié : deux builds identiques).
Hors périmètre worker (VPS · #8)
- Injection des balises
<link hreflang>+<script JSON-LD>dans les pageswww/(agent Frontend/SEO) ; - Génération + soumission
sitemap.xmlet Google Search Console ; - Traduction éditoriale des contenus longs FR/EN/ES ;
- Branchement du générateur sur la vraie sortie Publiciste (9 projets réels
data_room/PXX/) via--master.