0d3b2420c3
CI / Contraintes NON-NÉGOCIABLES (CLAUDE.md) (push) Has been cancelled
CI / Validation JSON (schémas Faisabilité) (push) Has been cancelled
CI / Qualité documentaire (liens + 4Big) (push) Has been cancelled
CI / Publiciste · parser + schéma + generator (unittest) (push) Has been cancelled
CI / RBAC · 50 rôles + schéma (unittest) (push) Has been cancelled
CI / Faisabilité · générateur 4 volets + round-trip (unittest) (push) Has been cancelled
CI / RBAC · fixtures ERPNext (Role + Custom DocPerm) (push) Has been cancelled
CI / RBAC · plan User Permission (row-level) (push) Has been cancelled
CI / RBAC · Role Profile (bundles par portail) (push) Has been cancelled
CI / RBAC · run-book d'application unifié (agrégat 3 volets) (push) Has been cancelled
CI / Faisabilité · dossier bancable trilingue FR/EN/ES (push) Has been cancelled
CI / CRM · workflow vente ERPNext (lead → CONFOTUR) (push) Has been cancelled
CI / CRM · DocType porteur OTO Dossier Vente (push) Has been cancelled
CI / CRM · barème commissions vendeurs (push) Has been cancelled
CI / Fiscal · e-CF DGII (Compupar) (push) Has been cancelled
CI / Frontend · Workspaces 5 portails rôle (push) Has been cancelled
CI / Legal · DocType CONFOTUR Application (push) Has been cancelled
CI / QA · Audit 5D conformité (push) Has been cancelled
CI / SEO · mots-clés trilingues + schema.org + hreflang (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled
64 lines
2.6 KiB
Python
64 lines
2.6 KiB
Python
"""Assemblage du bundle SEO + manifeste (deterministe, sans horloge)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from . import hreflang, keywords, schemaorg
|
|
|
|
|
|
def _manifest(spec: dict, master: dict, kws: list, sorg: dict, href: dict) -> dict:
|
|
langs = spec["site"]["langs"]
|
|
per_lang = {lang: sum(1 for k in kws if k["lang"] == lang) for lang in langs}
|
|
listing_type = spec["schema_org"]["listing_type"]
|
|
listings = [n for n in sorg["@graph"] if n.get("@type") == listing_type]
|
|
offers = sum(len(n.get("offers", [])) for n in sorg["@graph"])
|
|
regime = spec["lexicon"].get("regime")
|
|
confotur = bool(regime) and any(
|
|
any(regime["requires_inclus"] in (s or "") for s in p.get("inclus", []))
|
|
for p in master["projets"]
|
|
)
|
|
return {
|
|
"deliverable": "seo",
|
|
"sprint": "6",
|
|
"roadmap_line": "L60",
|
|
"source_input": "projets_master.json (sortie Publiciste · derivee de data_room/PXX)",
|
|
"base_url": spec["site"]["base_url"],
|
|
"langs": langs,
|
|
"default_lang": spec["site"]["default_lang"],
|
|
"x_default_lang": spec["site"]["x_default_lang"],
|
|
"confotur_documented": confotur,
|
|
"counts": {
|
|
"keywords_total": len(kws),
|
|
"keywords_per_lang": per_lang,
|
|
"schema_org_nodes": len(sorg["@graph"]),
|
|
"listings": len(listings),
|
|
"offers": offers,
|
|
"hreflang_pages": len(href["pages"]),
|
|
"projects": len(master["projets"]),
|
|
},
|
|
"targets": spec["targets"],
|
|
"anti_invention": (
|
|
"Mots-cles = compositions de faits documentes (nom/localisation "
|
|
"data_room) + lexique editorial generique ; un mot-cle ne peut "
|
|
"porter que les chiffres presents dans son champ projet source. "
|
|
"schema.org n'emet un prix que pour un projet 'disponible' dont la "
|
|
"typologie porte un prix sourcE (USD · #10)."
|
|
),
|
|
"hors_perimetre_vps": [
|
|
"Injection des balises <link hreflang> + <script JSON-LD> dans les pages www/ (Frontend/SEO · VPS #8)",
|
|
"Generation + soumission sitemap.xml et Google Search Console (VPS)",
|
|
"Traduction editoriale des contenus longs FR/EN/ES (agent SEO)",
|
|
],
|
|
}
|
|
|
|
|
|
def build_bundle(spec: dict, master: dict) -> dict:
|
|
kws = keywords.generate(spec, master)
|
|
sorg = schemaorg.generate(spec, master)
|
|
href = hreflang.generate(spec, master)
|
|
return {
|
|
"keywords": kws,
|
|
"schema_org": sorg,
|
|
"hreflang": href,
|
|
"manifest": _manifest(spec, master, kws, sorg, href),
|
|
}
|