MedGuru Docsv0.17.0 Voir le changelog
Skip to content

Tableau de bord des coûts IA

Ce manuel s'adresse aux SUPER_ADMIN, aux ROLE_MANAGER et aux comptes finance qui suivent la consommation IA de la plateforme.

Pourquoi cette feature

MedGuru consomme deux fournisseurs LLM principaux (Anthropic Claude, Google Gemini) sur plusieurs opérations critiques :

  • Pipeline d'ingestion : classify (1 appel par page), extract / extract_deep (1 appel par page-pub détectée), refine_bbox, self_correct.
  • Catalogue tarifs : magazine.identify (Claude Vision sur la couverture PDF), rate-card.extract (extraction des grilles tarifaires).
  • Produit côté UI : superfilter.query (tracking sans coût LLM, pour analyser les usages produit), guru.chat (volume de messages assistant).

Sans observabilité, impossible de répondre à des questions simples : « Combien coûte un import de 100 pages ? », « Quel magazine consomme le plus de tokens ? », « Qui utilise GURU le plus souvent ? ».

Comment y accéder

URL : /backstage/costs (requiert ROLE_MANAGER ou supérieur).

L'item de navigation est visible dans la sidebar Backstage sous Observabilité.

Lecture des KPIs

En haut de la page, quatre cartes synthétiques :

KPIDescription
Coût total périodeSomme des coûts USD calculés via la table ai_model_pricing (input + output + cache). Inclut une tendance % vs la période précédente.
Tokens totauxSomme input_tokens + output_tokens (les cache reads sont comptés à part dans le détail).
Appels totauxNombre d'événements ai_usage_events enregistrés.
Latence moyenneMoyenne duration_ms sur la période. Utile pour détecter des dégradations Anthropic / Gemini.

Le sélecteur en haut à droite ajuste la période : 7 jours, 30 jours, 90 jours.

Tendance journalière

Graphique en lignes (Recharts LineChart) qui montre les coûts USD agrégés par jour sur la période choisie. Permet de repérer :

  • Pics anormaux (un upload massif de magazines, un crawler rejouant des extractions).
  • Tendance de fond (consommation qui grimpe avec l'usage).
  • Effets d'optimisation (passage Haiku → Sonnet visible comme une marche).

Le tooltip survol affiche count (appels) + cost_usd (coût USD) pour un jour donné.

Breakdown par opération

Bar chart horizontal qui ventile la dépense par operation :

  • ingestion.classify, ingestion.extract, ingestion.extract_deep, ingestion.refine_bbox, ingestion.self_correct
  • magazine.identify, rate-card.extract
  • superfilter.query (tracking produit, model='none' donc 0 USD)
  • guru.chat (tracking produit, model='guru.chat' donc 0 USD)

Permet d'identifier d'un coup d'œil où va l'argent : typiquement extract_deep domine, suivi de extract.

Top 10 magazines

Tableau qui liste les magazines triés par coût décroissant. Colonnes :

ColonneDescription
MagazineNom du magazine (ou « (sans magazine) » pour les events superfilter.query / guru.chat qui n'ont pas de magazine associé)
Coût USDSomme des coûts sur la période
AppelsNombre d'ai_usage_events
TokensTotal tokens (input + output)
Coût moyen / appelCoût USD ÷ nb appels

Cliquer sur un magazine ouvre la fiche /admin/magazines/{id} (TODO : pas encore actif).

Top 10 utilisateurs

Identique au top magazines, mais agrégé par actor_id (= user_id).

Permet de repérer :

  • Un compte robot qui spamme superfilter.query (rate limit à mettre en place côté Caddy).
  • Un utilisateur GURU power-user (positif).
  • Des actions admin coûteuses (réingestion forcée d'un gros magazine).

Sources de données

Tous les chiffres viennent de la table ai_usage_events (cf. migration Version20260423030000). La structure :

sql
ai_usage_events (
    id UUID PRIMARY KEY,
    occurred_at TIMESTAMPTZ NOT NULL,
    model VARCHAR(64),                     -- ex: claude-sonnet-4-6, gemini-3.1-pro, none, guru.chat
    operation VARCHAR(64),                 -- ex: ingestion.classify, superfilter.query, guru.chat
    input_tokens INT,
    output_tokens INT,
    cache_read_tokens INT,
    cache_creation_tokens INT,
    duration_ms INT,
    actor_id INT REFERENCES users(id),     -- nullable (pour les events worker)
    import_id UUID REFERENCES imports(id), -- nullable
    magazine_id UUID,                      -- nullable
    regie_id UUID,                         -- nullable
    metadata JSONB,                        -- payload libre par opération
    success BOOL,
    error_code VARCHAR(64)
)

Les coûts sont calculés à la lecture via la table ai_model_pricing (USD per 1M tokens, séparé pour input / output / cache_read / cache_creation). Cela permet de rétro-ajuster les prix sans toucher aux events historiques.

Instrumentation

Les callers actuels de AiUsageRecorder :

CallerOperation(s)Localisation
Worker ingestioningestion.classify, ingestion.extract, ingestion.extract_deep, ingestion.refine_bbox, ingestion.self_correctingestion-service/src/worker/processImport.ts
MagazineIdentifiermagazine.identifydata-api/src/Service/Ingestion/MagazineIdentifier.php
ClaudeRateCardExtractorrate-card.extractdata-api/src/Service/Catalog/ClaudeRateCardExtractor.php
SuperfilterController::list()superfilter.querydata-api/src/Controller/SuperfilterController.php
ConversationController::addMessage()guru.chatdata-api/src/Controller/ConversationController.php

Pour ajouter un nouveau caller :

  1. Injecter AiUsageRecorder (nullable pour BC tests) dans le service / controller.
  2. Appeler record(new UsageEventDto(...)) après l'opération, dans un try/catch best-effort.
  3. Le tracking ne doit jamais bloquer la requête utilisateur.

Endpoints REST

Le frontend consomme 5 endpoints (tous ROLE_MANAGER) :

  • GET /api/backstage/ai-costs/summary?period=30d — KPI cards
  • GET /api/backstage/ai-costs/breakdown?period=30d — bar chart par opération
  • GET /api/backstage/ai-costs/daily-trend?period=30d — line chart journalier
  • GET /api/backstage/ai-costs/top-magazines?period=30d&limit=10
  • GET /api/backstage/ai-costs/top-users?period=30d&limit=10

Implémentés dans BackstageAiCostsController.php.

Cleanup historique

Une command Symfony app:imports:cleanup-old-s3 libère le storage MinIO des imports terminés depuis > 30 jours, sans toucher aux events ai_usage_events qui restent pour analytics. Cron suggéré :

cron
0 3 * * * php bin/console app:imports:cleanup-old-s3 --days=30

Limites connues

  • Les events superfilter.query et guru.chat ont model='none' ou model='guru.chat' → coût USD = 0. Ils sont visibles en volume mais pas en coût.
  • Le dashboard ne montre pas encore de granularité par utilisateur sur le breakdown par opération (top users croisé avec opération). Issue de suivi : à créer si besoin.
  • Le sélecteur de période est limité à 7/30/90j ; pas de date range custom.

Référence des issues

  • Epic : #599
  • S1 (schema DB) : #600
  • S2 (pricing config) : #601
  • S3 (service AiUsageRecorder) : #602
  • S4 (instrumentation data-api) : #603
  • S5 (instrumentation worker) : #604
  • S6 (instrumentation SF + GURU) : #605
  • S7 (endpoints aggregation) : #606
  • S8 (UI page) : #607
  • S9 (tests + docs + CHANGELOG) : #608

Version v0.17.0 — Changelog