MedGuru Docsv0.17.0 Voir le changelog
Skip to content

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 :

  1. Inspecte le bloc <catalog>
  2. Cherche la SEULE meilleure correspondance selon :
    • Surface de la pub sur la page (± 5 %)
    • Position (couverture vs intérieur)
    • Indices visuels (fond coloré, bleed…)
  3. Renvoie :
    • format_code : le code exact du catalogue
    • format_confidence : score 0..1 de certitude du match
  4. 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 :

json
{
  "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)

ColonneTypeUsage
match_confidenceNUMERIC(3,2)Score 0..1 du match catalogue (distinct de confidence qui est la certitude globale)
proposed_new_format_dataJSONBPayload 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.magazine est NULL → bandeau ambre « Magazine inconnu » + CTA « Créer un magazine » (lien vers Backstage).
  • Si le magazine existe mais n'a aucun AdFormat actif → 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_data non 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 AdFormat draft rattaché au magazine
      • Lie les N ads concernées (set ad_format_ref_id)
      • Clear formatUnknown + proposedNewFormatData
      • Audit event catalog.adformat.created_from_proposal
    • 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) :

  • extract v1 : contenu initial, enum générique uniquement
  • extract v2 : contenu de extract.v6.txt, matching ciblé + propositions (actif depuis cette PR)

L'opérateur peut basculer via Backstage → Prompt Studio si besoin de revenir à la v1.

Références

  • Parent epic : #538
  • Cette PR : #540
  • UI review à venir : #541
  • Prompt : ingestion-service/prompts/extract.v6.txt
  • Migration DB : data-api/migrations/Version20260423000000.php
  • Seed prompt : data-api/migrations/Version20260423000100.php

Version v0.17.0 — Changelog