Détection ciblée des publicités par magazine
Avec F-DETECT-TARGETED PR 2 (#540), le pipeline de détection des publicités est désormais guidé par le catalogue tarifaire du magazine analysé.
Fonctionnement
1 · Magazine identifié (PR 1)
À l'upload, l'identification automatique détermine à quel magazine du catalogue le PDF est rattaché (Import.magazine_id).
2 · Chargement du catalogue
Le worker d'ingestion appelle GET /internal/magazines/{id}/active-formats?date=YYYY-MM-DD et récupère la liste des AdFormat actifs pour la période de l'import (ex. grille 2025 si l'import est de 2025).
3 · Injection dans le prompt
Un bloc <catalog>...</catalog> est préfixé au prompt Claude Vision (extract.v6.txt), listant pour chaque format :
code(identifiant unique, ex.PAGE_QUADRI_2025_A3F9)label(libellé humain, ex. « 1 page quadri »)surfacePct(surface attendue sur la page, ex. 100 %)positionType(inside, cover_2, cover_3, cover_4)
4 · Matching par Claude
Pour chaque publicité détectée, Claude :
- Inspecte le bloc
<catalog> - Cherche la SEULE meilleure correspondance selon :
- Surface de la pub sur la page (± 5 %)
- Position (couverture vs intérieur)
- Indices visuels (fond coloré, bleed…)
- Renvoie :
format_code: le code exact du catalogueformat_confidence: score 0..1 de certitude du match
- Seuils :
- ≥ 0.85 → auto-match, pub rattachée au format sans intervention
- 0.60 – 0.84 → format pré-rempli +
format_needs_review(l'éditeur valide en UI F06) - < 0.60 → proposition de nouveau format obligatoire (cf. étape 5)
5 · Proposition de nouveau format
Quand Claude n'a aucun match convaincant (< 0.60), il propose un nouveau format à créer dans le catalogue :
{
"format_code": null,
"format_confidence": 0.42,
"proposed_new_format": {
"label": "1/4 page verticale bichromie",
"positionHint": "INSIDE",
"surfaceHint": "1/4 page",
"reason": "Pas d'équivalent dans le catalogue mais détecté sur la page"
}
}Cette proposition est persistée en base (ads.proposed_new_format_data) et l'UI de review (PR 3 — à venir) proposera un bouton « Valider le format proposé » qui créera un AdFormat à la volée, rattaché au magazine.
Colonnes DB ajoutées (ads)
| Colonne | Type | Usage |
|---|---|---|
match_confidence | NUMERIC(3,2) | Score 0..1 du match catalogue (distinct de confidence qui est la certitude globale) |
proposed_new_format_data | JSONB | Payload libre de la proposition IA, consommé par l'UI review |
Index partiel idx_ads_has_proposal pour accélérer le filtre « pubs non classées » dans l'UI.
Fallback (magazine sans catalogue)
Si Import.magazine_id est NULL OU le magazine n'a aucun AdFormat actif, le bloc <catalog> n'est pas injecté. Claude retombe sur l'enum générique ad_format (legacy full_page, half_page_horizontal, etc.) et format_code, format_confidence, proposed_new_format restent null.
L'UI fallback est livrée en PR 3 (#541). Sur la page de review d'un import :
- Si
Import.magazineestNULL→ bandeau ambre « Magazine inconnu » + CTA « Créer un magazine » (lien vers Backstage). - Si le magazine existe mais n'a aucun
AdFormatactif → bandeau bleu « Magazine sans tarifs » + CTA « Importer les tarifs » (lien vers la sandbox rate card). - Si au moins une pub a un
proposed_new_format_datanon nul → panel « Pubs non classées » avec une carte par proposition (plusieurs pubs avec le même label sont groupées) :- Champs label/position/surface éditables avant validation
- Bouton « Valider le format (N) » qui appelle
POST /api/governance/magazines/{id}/formats/from-proposal:- Crée un
AdFormatdraft rattaché au magazine - Lie les N ads concernées (set
ad_format_ref_id) - Clear
formatUnknown+proposedNewFormatData - Audit event
catalog.adformat.created_from_proposal
- Crée un
- Bouton X (Ignorer) retire la proposition de la session (les ads restent en UNKNOWN côté DB).
Versioning du prompt
Le prompt est versionné via Prompt Studio (#480) :
extractv1 : contenu initial, enum générique uniquementextractv2 : contenu deextract.v6.txt, matching ciblé + propositions (actif depuis cette PR)
L'opérateur peut basculer via Backstage → Prompt Studio si besoin de revenir à la v1.