Files
Claude Code DTP Worker 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
[DTP-Worker 20260730_085719] Auto exec · session 20260730_085719
2026-07-30 09:12:19 +00:00

84 lines
2.8 KiB
Python

"""Dependances partagees du generateur SEO · reutilisation stricte (#5).
On NE reimplemente rien qui existe deja dans le mandat :
- `validator` (validateur JSON-Schema draft-07 maison, zero pip) et
- `branding` (tokens de marque #4 + STATUTS_SANS_PRIX / devise #10)
sont importes du livrable Publiciste, source unique de verite pour ces deux
briques. Le contrat d'entree consomme est `projets_master.schema.json` (contrat
Faisabilite<->Publiciste), lui aussi partage.
Fonctions locales minimales : chargement des fichiers + `slugify` deterministe
(pas de dependance externe, pas d'horloge).
"""
from __future__ import annotations
import json
import os
import re
import sys
import unicodedata
_HERE = os.path.dirname(os.path.abspath(__file__))
_SEO = os.path.dirname(_HERE)
_DELIVERABLES = os.path.normpath(os.path.join(_SEO, "..")) # 05_deliverables_mvp/
_PUBLICISTE = os.path.join(_DELIVERABLES, "publiciste")
_FAISABILITE = os.path.join(_DELIVERABLES, "faisabilite")
if _PUBLICISTE not in sys.path:
sys.path.insert(0, _PUBLICISTE)
from lib import branding # type: ignore # noqa: E402
from lib import validator as maison # type: ignore # noqa: E402
# Chemins des contrats/entrees par defaut.
SPEC_PATH = os.path.join(_SEO, "seo_spec.json")
SCHEMA_PATH = os.path.join(_SEO, "seo.schema.json")
MASTER_DEFAULT = os.path.join(_SEO, "fixtures", "projets_master.json")
MASTER_SCHEMA_PATH = os.path.join(_FAISABILITE, "projets_master.schema.json")
def load_json(path: str) -> dict:
with open(path, encoding="utf-8") as fh:
return json.load(fh)
def load_spec() -> dict:
return load_json(SPEC_PATH)
def load_master(path: str | None = None) -> dict:
return load_json(path or MASTER_DEFAULT)
def validate_master(master: dict) -> list[str]:
"""Verifie que l'entree respecte le contrat Publiciste avant tout usage."""
return list(maison.validate(master, load_json(MASTER_SCHEMA_PATH)))
def validate(instance, schema: dict) -> list[str]:
return list(maison.validate(instance, schema))
_SLUG_STRIP = re.compile(r"[^a-z0-9]+")
def slugify(text: str) -> str:
"""Slug URL deterministe : minuscule, sans accent, mots joints par '-'.
Conserve les chiffres presents dans la donnee source (ex. « 1069 Crisfer »
-> « 1069-crisfer ») : on ne retire ni n'invente aucun chiffre (#6)."""
norm = unicodedata.normalize("NFKD", text)
norm = "".join(c for c in norm if not unicodedata.combining(c))
return _SLUG_STRIP.sub("-", norm.lower()).strip("-")
def digits(text: str) -> set[str]:
"""Ensemble des chiffres presents dans une chaine (audit anti-invention)."""
return set(re.findall(r"\d", text))
# Re-exports pour les modules soeurs (une seule source d'import).
STATUTS_SANS_PRIX = branding.STATUTS_SANS_PRIX
DEVISE_PRIMAIRE = branding.DEVISE_PRIMAIRE