Démonstrateur de gouvernance de portefeuille IA
Auteur : Olivier Vitrac, PhD, HDR Head of Adservio Innovation Lab · Adservio · La Défense ✉ olivier.vitrac@adservio.fr
Édition : Avril 2026 · v0.1 URL publique : https://valueadservio.pages.dev
ValueAdservio est un démonstrateur de gouvernance de portefeuille IA. Il n'est pas une application classique : c'est un noyau scientifique déterministe (/api/kernel/*, OpenAPI 3.1). Le même noyau peut être appelé par une analyste devant son écran, par un agent LLM autonome, ou par un script batch — les trois voies produisent exactement les mêmes nombres pour les mêmes entrées.
Le démonstrateur permet, à partir d'une simple description d'un cas d'usage IA, d'estimer :
la valeur ajustée annuelle attendue (€/an),
la valeur cumulée à 3 ans (€),
le retour sur investissement à 3 ans (%),
le délai de retour (
la matrice de fragilités du scénario (10 règles déterministes),
l'action recommandée par le noyau (11 valeurs : pilot, fund_full, redesign, stop, …),
le suivi du réalisé vs le projeté (M+3, M+6, M+12).
Toutes ces sorties sont reproductibles et traçables : aucun appel sortant, aucun aléa, aucune boîte noire.
Le noyau ValueAdservio diffère d'une application web classique par sa discipline architecturale :
┌──────────────────────────────────┐│ NOYAU DÉTERMINISTE ││ (kernel/*.js) ││ │humain ───→ │ • aucune sortie réseau │ ───→ JSONLLM ───→ │ • aucun état persistant │ ───→ JSONservice ───→ │ • aucune source d'aléa │ ───→ JSON│ • formules figées en code ││ • testé par 162 tests unitaires│└──────────────────────────────────┘
Trois invariants non négociables :
Le noyau ne peut pas appeler vers l'extérieur (ni LLM, ni base de données, ni service tiers). Il prend du JSON, il rend du JSON.
Le noyau n'a pas de mémoire. Chaque appel est indépendant ; deux appels identiques produisent des sorties identiques.
Le noyau est l'unique source de vérité numérique. L'interface affiche, le LLM propose, mais c'est le noyau qui calcule.
Cette discipline rend le système auditable (un commit = un comportement) et gouvernable (un changement de politique passe par un fichier data/policy.*.json versionné, jamais par un prompt).
xxxxxxxxxxkernel/constants.js SCEN, PRISMES, MONET, KERNEL_VERSIONschema.js types JSON-Schema-litevalidator.js validation entrée bundlemigrations.js legacy → canonique (v0 → v1)scoring.js cScore(p), scoreDecomp(p)value.js cVal(p), cPrime(p), cPAdopt(p), cValAdj(p)roi.js cVal3(p), cROIprog(p), cPayback(p), cValCurve, cCostCurvelifecycle.js LIFECYCLE_TRANSITIONS, transitionLifecycle, recordDecisionEventfalsification.js falsifyProject(p, options) — 10 règlesrecommendation.js recommendAction(p, options) — 11 actionsreconciliation.js reconcileClaims(claims, output, taxonomy)realization.js realizationReport(p) — M+3 / M+6 / M+12comparison.js compareProjects(projects, references)simulation.js evaluateProject, evaluateProjectVariant, generateSimulationOutputindex.js API publique
Le même noyau, deux transports :
xMode A — Sans humain dans la boucle (REST)agent ⇄ /api/kernel/* → JSON déterministeCas d'usage : évaluation par lots, sweeps de régression,boucle agentique LLM ↔ noyau.Mode B — Avec humain dans la boucle (cockpit)LLM produit un scénario → encode l'URL → utilisateur clique →cockpit ouvre l'assistant → humain révise / valide / ajoute.Cas d'usage : démonstration interactive, négociation d'hypothèses,revue exécutive.
L'interface graphique est un consommateur parmi d'autres du noyau, au même titre qu'un LLM externe ou qu'un script batch. Voir docs/wave-5-plan.md pour la spécification de la couche REST.
Toutes les formules ci-dessous sont implémentées textuellement dans kernel/*.js. Les notations sont conservées pour la lisibilité ; les noms canoniques sont donnés en regard.
où :
| Symbole | Signification | Plage typique |
|---|---|---|
| population impactée | 1–10 000 personnes/cas | |
| jours travaillés/an | 200–250 (par défaut 220) | |
| part de temps concernée | 0–100 % | |
| gain IA attendu | 0–100 % | |
| adoption attendue | 0–100 % | |
| coefficient de scénario (prudence ↔ ambition) | 0,4–0,9 | |
| coût journalier chargé | 100–2 000 €/j |
Les caps
avec cinq prismes booléens kernel/constants.js. La plage résultante est
où
Note ADOPT-001. Le taux
intervient deux fois — dans comme « adoption attendue » et dans comme « facteur de confiance ». Ce comportement hérité de la version v0 est préservé pour la compatibilité de régression et est documenté dans docs/intentional-behavior.md. Les puces de sensibilité ±10% sur le palier Adoption × scénario du waterfall rendent cette double application visible (asymétrie typique : +16 k€ / −15 k€).
C'est la grandeur principale affichée dans toutes les vues du cockpit.
avec les coefficients kernel/scoring.js.
La courbe de valeur ajustée mois-à-mois suit une rampe d'adoption :
où
avec
Soit data/taxonomy.json, l'écart relatif est :
Le verdict de réconciliation est :
Règle d'or (AC8) : la valeur du noyau n'est jamais écrasée par la revendication du LLM. La divergence est affichée, la décision revient à l'humain.
L'action recommandée est calculée par recommendAction(p, options). La politique est externalisée dans data/policy.recommendation.json et est spécifique au cycle de vie du projet :
Pré-déploiement (reject / park / pilot / fund_full ; règles de garde sur les fragilités HIGH.
Post-déploiement (scale / continue / correct / rebaseline / stop.
Les seuils sont audités et sont publiés en JSON dans le dépôt — pas dans un prompt.
La page d'accueil présente cinq tuiles KPI globales (nombre de projets, valeur ajustée annuelle agrégée, budget 3 ans, projets à fort score, projets à risque) et la matrice de portefeuille Valeur × Score.
Chaque point de la matrice est un projet ; sa couleur indique le statut de cycle de vie, sa taille la racine carrée du budget. Cliquer un point ouvre la vue Simulation du projet correspondant.
Le bouton + Nouveau projet lance l'assistant de définition pour un projet vierge ; le bouton + Importer scénario LLM ouvre la modale d'import d'un bundle JSON produit par un agent LLM externe.
La bibliothèque expose les 7 fiches d'amorçage synthétiques (les scenario cards de data/scenario-cards.json). Elles servent de few-shot examples à un agent LLM : c'est en lisant ces fiches qu'un LLM apprend la convention de modélisation du démonstrateur.
Chaque fiche affiche :
la famille IA (chip de couleur — RAG, RPA, Chat, Fraude…),
la valeur ajustée annuelle (vert / bleu / gris selon le palier),
un mini-graphe de la trajectoire de cash cumulé sur 36 mois,
le confiance RICE et le payback,
l'action recommandée par le noyau (chip de couleur),
trois boutons : Ouvrir (vue simulation), Modèle (clone vers un brouillon utilisateur — voir §4.6), JSON (tiroir détail).
La vue Simulation est la plus dense ; elle expose toutes les sorties déterministes du noyau pour un projet donné :
Sélecteur de variante (Prudent / Central / Ambitieux) — applique les multiplicateurs SCEN à state.evaluated) n'est pas modifiée.
Carte de valeur — quatre KPI principaux (
Décomposition (waterfall) — décomposition pas-à-pas de
Graphique 36 mois — courbe de valeur totale, courbe de cash, coût cumulé, marqueur de payback. Animations d'entrée déterministes.
Carte de décision — action recommandée par le noyau avec raisonnement et gates requis.
Cycle de vie — état courant + boutons des transitions autorisées par kernel/lifecycle.js. Chaque transition recalcule l'action recommandée et ajoute un événement lifecycle_transition à decision_events (AC11).
Affiner ce scénario — boutons + Bundle d'itération (export vers LLM) et Modifier (clone ou édition en place).
En cliquant sur Ambitieux, les multiplicateurs SCEN deviennent
La vue Priorisation combine la matrice du portefeuille avec une table triable sur huit colonnes : titre, famille, valeur ajustée, confiance RICE, risque, effort, score, statut. La colonne Action affiche le verdict du noyau (chip de couleur) — pas une étiquette manuelle.
C'est la vue à présenter en revue de portefeuille : elle permet en un coup d'œil de hiérarchiser les projets et d'identifier les quick wins (haut/droite de la matrice).
La vue Comparaison superpose deux projets sur :
une trajectoire 36 mois (deux courbes + marqueurs de payback),
un tableau métrique avec colonne
un guide vertical de survol JS qui affiche les valeurs mois par mois.
C'est l'outil à utiliser pour arbitrer entre deux options similaires (par exemple : RAG conformité vs Voicebot relation client).
La vue Suivi & Contrôle ne montre que les projets actifs (statut realized_values), le noyau émet une action recommandée post-déploiement :
| Écart | Action |
|---|---|
scale (passer à l'échelle) | |
continue (poursuivre) | |
correct (corriger via onboarding/coaching) | |
rebaseline (rebaser le scénario) | |
stop (arrêter) |
Le tableau Valeur réalisée vs projetée détaille les écarts à M+3, M+6, M+12 sur quatre métriques (utilisateurs, adoption, gain IA, valeur ajustée).
Le bouton + Enregistrer une décision ouvre une modale dédiée qui expose la suggestion du noyau et son raisonnement, puis demande à l'utilisateur de confirmer ou de surcharger. Une décision enregistrée n'altère jamais le statut du cycle de vie (invariant AC11) ; elle est purement consultative et auditée.
L'assistant de définition remplace le copier-coller JSON par une interface en quatre blocs.
Champs textuels et listes déroulantes : titre, famille IA, domaine métier, entité, description courte, hypothèse de valeur en une phrase. Les champs textuels ne déclenchent pas de recalcul du noyau (le texte ne déplace pas les nombres).
Curseurs numériques pour
Curseurs pour N, c, g, a) ont un bouton Justifier qui ouvre un panneau de saisie d'hypothèses :
Le panneau capture :
Source (business_estimate, benchmark_or_hypothesis, observed_data, regulatory_benchmark),
Confiance (low / medium / high),
Évidence (texte libre),
Gate de falsification (critère mesurable qui invaliderait l'hypothèse).
Ces données sont écrites dans assumptions.{champ} et survivent à l'export / réimport (AC-W4A-7).
Cinq cases readiness gates (
xxxxxxxxxxAnnuler · Enregistrer le brouillon · Exporter JSON · Ajouter au portefeuille
Annuler — abandonne le brouillon (avec confirmation si modifié).
Enregistrer le brouillon — stocke en sessionStorage (perdu à la fermeture de l'onglet).
Exporter JSON — produit un bundle conforme au schéma data/schema.llm-input.json, téléchargeable.
Ajouter au portefeuille — valide via le schéma, lance le noyau, ajoute à state.evaluated avec provenance.kind = "user_authored".
La modale d'import accepte un bundle JSON conforme à la version 0.1.1 du schéma. Le bloc optionnel llm_analysis est restitué dans le tiroir d'analyse mais n'est jamais lu par le noyau (AC3).
Le bouton Charger l'exemple insère un bundle de démonstration (data/sample-llm-input-with-analysis.json) qui illustre la convention attendue d'un agent LLM.
⚠ Ne jamais coller de données confidentielles d'entreprise dans un LLM public. Utiliser un environnement LLM privé approuvé pour les données sensibles (gate G2).
Le tiroir { } JSON (en haut à droite) affiche, pour le projet sélectionné, trois onglets :
Input — l'entrée canonique normalisée par le noyau,
Output — la sortie complète d'evaluateProject (métriques, décomposition, fragilités, action recommandée),
Session — le bundle complet (entrée + sortie + comparaisons + métadonnées).
C'est le seul endroit du cockpit où apparaissent les codes bruts du schéma (scenK, cm, el[5], …). L'interface partout ailleurs utilise des étiquettes françaises.
Le bouton ? dans la barre du haut (ou la touche ? du clavier) ouvre un panneau d'aide qui glisse depuis la gauche, sans masquer le reste du cockpit. Trois onglets :
Sans LLM — explorer le portefeuille (Bibliothèque, Priorisation, Comparaison, Suivi & Contrôle), manipuler un projet (Variante, Cycle de vie, sensibilité ±10%), créer ou éditer (+ Nouveau projet, Modèle, Modifier, Justifier), sauvegarder ou partager (brouillon, JSON, tiroir).
Avec LLM — Mode A REST avec liens directs vers /api/openapi.json, /api/tools et /api/kernel/version ; Mode B URL avec les formats #importz= (compact) et #import= (simple), et les limites de taille ; importation et raffinement (+ Importer scénario LLM, + Bundle d'itération, URL partage).
Concepts clés — provenance (✓ ✎ ✏), invariants AC3/AC4/AC8/AC11/G7, architecture du noyau, variantes SCEN low/base/high, note ADOPT-001, lien vers le présent manuel.
Touche Esc pour fermer. Visible sur toutes les vues. Le panneau ne capte pas les clics tant qu'il est fermé (pointer-events: none).
Toute valeur affichée dans le cockpit porte exactement un des trois marqueurs de provenance :
| Badge | Tier | Source | Traitement |
|---|---|---|---|
| ✓ | kernel | calculé par kernel/*.js | autoritaire |
| ✎ | llm | bloc llm_analysis du bundle importé | consultatif |
| ✏ | input | saisie utilisateur ou assumptions.* | hypothèse |
Règle de conflit (AC8) : lorsqu'une revendication LLM diverge d'une sortie noyau, les deux sont affichées côte-à-côte avec un tag ⚠ Divergence et l'écart en pourcentage. La valeur du noyau n'est jamais écrasée. L'arbitrage revient à l'humain.
Cette voie (Wave 5A, livrée — déployée à https://valueadservio.pages.dev/api/) expose le noyau verbatim sous HTTP. Elle permet à un agent LLM, à un script batch ou à un service tiers de tester des centaines de variations sans intervention humaine. Le noyau ne stocke rien ; chaque appel est une fonction pure de son entrée.
18 endpoints — 4 GET (métadonnées) + 14 POST (compute) :
xxxxxxxxxxGET /api/kernel/version → { kernel_version, generated_at }GET /api/kernel/lifecycle/transitions → table des transitions autoriséesGET /api/kernel/policy/recommendation → seuils de recommandationGET /api/kernel/policy/falsification → seuils de fragilitéPOST /api/kernel/normalize → applique alias legacy + défautsPOST /api/kernel/validate → validation JSON-Schema-litePOST /api/kernel/evaluate → évaluation déterministe complètePOST /api/kernel/evaluate-variant → SCEN low / base / highPOST /api/kernel/evaluate-portfolio → agrégat sur N projetsPOST /api/kernel/aggregate → agrégation à partir de projets déjà évaluésPOST /api/kernel/falsify → 10 règles de fragilitéPOST /api/kernel/recommend → action recommandée (pré- ou post-déploiement)POST /api/kernel/reconcile → kernel ↔ revendications LLM (AC5/AC8)POST /api/kernel/realize → écart projeté/réalisé M+3/M+6/M+12POST /api/kernel/compare → similarité entre projets et référencesPOST /api/kernel/simulate → bundle simulation-output completPOST /api/kernel/lifecycle/transition → mutation lifecycle (deep-clone, AC11-clean)POST /api/kernel/lifecycle/event → événement de décision (deep-clone, AC11-clean)
Découverte automatique pour agents LLM :
GET /api/openapi.json — spec OpenAPI 3.1 complète (compatible Swagger UI, Stoplight, Anthropic Tool Use, OpenAI function calling).
GET /api/tools — descripteurs prêts à coller : format Anthropic ({name, description, input_schema}) et format OpenAI ({type:"function", function:{...}}), tous générés depuis la même source de vérité (functions/api/_registry.js).
Enveloppe de réponse uniforme :
xxxxxxxxxx{ "ok": true, "operation": "evaluate", "kernel_version": "0.1.0", "created_at": "2026-04-29T22:30:00Z", "result": { } }
{ "ok": false, "operation": "evaluate", "error": { "code": "VALIDATION_ERROR", "message": "...", "details": [] } }Codes d'erreur : VALIDATION_ERROR · UNSUPPORTED_MEDIA (415) · METHOD_NOT_ALLOWED (405) · PAYLOAD_TOO_LARGE (413, > 1 MiB) · KERNEL_THREW (500). Aucune trace d'exécution n'est jamais retournée. Voir docs/api.md pour le contrat de transport complet et les exemples curl.
xxxxxxxxxx1. agent LLM produit un scénario JSON conforme au schéma2. agent encode le bundle dans une URL fragmenthttps://valueadservio.pages.dev/#importz=<base64url(deflate(JSON))>3. utilisateur clique sur l'URL4. cockpit ouvre l'assistant pré-rempli avec le scénario proposé5. humain révise (curseurs, panneaux de justification),décide d'enregistrer brouillon, d'exporter JSON, ou d'ajouter au portefeuille6. cockpit n'envoie rien au serveur — toute persistance est locale(sessionStorage) ou explicite (export JSON)
Cette voie (Wave 5B, livrée) supprime le copier-coller tout en gardant l'humain dans la boucle : l'agent propose, l'humain dispose.
Deux formats de fragment :
#importz=<base64url(deflate(JSON))> — compact, par défaut (~36% plus petit que la forme simple ; ≈ 500 octets pour un projet typique).
#import=<base64url(JSON)> — compatibilité, sans déflation (utile pour les LLM qui n'ont pas d'exécution de code et ne peuvent pas appliquer deflate).
Limites : 8 000 caractères par fragment ; 128 KiB de JSON décodé ; mono-projet uniquement (mode: "project"). Au-delà, préférer l'API REST (Mode A) ou l'import JSON manuel.
Génération depuis le cockpit : un bouton « URL partage » est disponible à deux endroits — sur la barre d'actions de l'assistant Définition (à côté d'Exporter JSON) et sur la carte d'affinement de la vue Simulation (à côté de + Bundle d'itération). Il copie automatiquement l'URL dans le presse-papiers.
Garanties : le fragment d'URL n'est jamais envoyé au serveur (contrairement à un paramètre ?import=...). Il est validé contre le schéma data/schema.llm-input.json avant tout chargement ; un bundle invalide affiche une bannière d'erreur sans modifier l'état. Le fragment est nettoyé de la barre d'URL après chargement (history.replaceState) — un rafraîchissement n'importe pas une seconde fois.
Reproductibilité : entrée identique → sortie identique, à toutes les versions taggées du dépôt. Le noyau in-process (cockpit) et le noyau REST (/api/kernel/*) produisent les mêmes nombres pour les mêmes entrées.
Auditabilité : chaque commit est une politique. Les seuils sont externalisés dans data/policy.*.json. La spec REST est régénérée depuis un registre unique (functions/api/_registry.js) — pas de dérive possible entre exports kernel, handlers HTTP, et descripteurs OpenAPI/tools.
Non-pollution LLM : aucune sortie numérique du cockpit ne provient d'un LLM. Le LLM propose, le noyau dispose. AC8 : la valeur du noyau n'est jamais écrasée par une revendication LLM.
Souveraineté : aucun appel sortant depuis le noyau. Aucune clé API stockée côté client. Aucun service tiers requis. L'URL fragment (#importz=) ne quitte jamais le navigateur.
Sans build : le démonstrateur fonctionne sur un simple serveur HTTP statique. Aucun Webpack, aucun Vite, aucun React. Les Pages Functions de Cloudflare exécutent directement les modules ES du noyau (V8 sans transpilation).
Exactitude économique des coefficients par défaut (les valeurs de
Confidentialité sur le déploiement public *.pages.dev : l'URL n'est pas authentifiée. Pour des données sensibles, ajouter Cloudflare Access ou déployer sur un endpoint privé (gate G2).
| Document | Contenu |
|---|---|
base/UI_WORKPLAN.md | spécification UI complète |
base/CHANGELOG.md | historique des amendements (v1 → v5) |
docs/api.md | contrat de transport REST (enveloppe, codes d'erreur, CORS, exemples curl, boucle agentique) |
docs/wave-4a-plan.md | spécification de la boucle d'authoring humain |
docs/wave-5-plan.md | spécification de la couche REST + URL fragment |
docs/intentional-behavior.md | décisions de conception assumées (ADOPT-001, RAMP-001, SCEN-001, PLATFORM-001) |
docs/handoff-20260428.md | état des lieux et plan de continuité |
/api/openapi.json (en ligne) | spec OpenAPI 3.1 du noyau REST (compatible Swagger UI, Stoplight) |
/api/tools (en ligne) | descripteurs Anthropic + OpenAI prêts à coller dans une boucle tool-use |
Pour toute question, ré-implémentation, ou ré-étalonnage : olivier.vitrac@adservio.fr
ValueAdservio — Adservio Innovation Lab — Groupe Adservio, France. Privé — développé pour l'usage interne d'Adservio. Non redistribuable.