Matrice coût/ressource par notebook
Statut. Document de cadrage, grade B-méthodologique (schéma applicable, pas une suggestion). V0 = pilote cycle c.794 (issue #8056, P1). Objet. Répondre à l’acceptance d’#8056 — matrice coût/ressource par notebook : (a) schéma de métadonnée
cost:portable, (b) colonne catalogue correspondante, (c) peuplement pilote sur les familles à coût/ressource variable (GenAI/Image GPU + Probas/Infer.NET CPU + QC Cloud), (d) alternative gratuite / version pédagogique réduite, (e) compte externe requis, (f) intégration à la grille audit sémantique #8052. Discipline. NE remplace PAS levalidate_pr_notebooks.py(structure), niaudit-reassessment.md(mécanique), niextract_claims_vs_outputs.py(#8052 claims↔︎outputs) ; AJOUTE une couche ressource/financier que le catalogue anti-drift peut scanner. Cf incidents fondateurs documentés : notebooks GenAI GPU-only silencieusement CPU-skipés (cfsota-not-workaround.md§F), notebooks QC require QuantBook qu’on ne peut pas exécuter hors QC Cloud, notebooks Probas PyMC gratuits vs Infer.NET CPU-bound vs GPU-accelerated. Lien. Issue-source : #8056 (P1, lane po-2025 + po-2024 + po-2023 désignée). Complément audit-pattern #8052 (claims↔︎outputs). Grille parité jumeaux #8057 (Python↔︎C#). ICT out-of-scope #7734.
Pourquoi ce schéma
L’open-courseware CoursIA héberge des notebooks (Python + .NET Interactive + Lean) répartis sur ~10 familles thématiques. Les contraintes de ressources pour les exécuter varient de zéro (notebook CPU Python pur, déterministe, < 1 min) à des dizaines de $ API (GenAI/Image avec DALL-E 3, GPT-5, Flux) ou GPU VRAM 24+ GB (Qwen-Image-Edit 2509, SD-3.5 large). Sans schéma structuré :
- L’étudiant fork le repo et tente d’exécuter en aveugle →
OutOfMemoryErrorCUDA,RateLimitErrorOpenAI, ouAttributeError: QuantBook not available. - Le coordinateur ne peut pas filtrer “quels notebooks sont exécutables sur la machine CPU-only de l’étudiant EPITA ?” sans lire le notebook cellule par cellule.
- Le catalogue (
COURSE_CATALOG.generated.json) n’expose aucun champ runtime/ressource. - Les audits sémantiques (#8052) prennent l’output comme vérité — si l’exécution a silencieusement skip une cellule GPU, le claim reste faux même avec une note pédagogique d’avertissement.
Schéma cost: — nb.metadata['cost'] (JSON, forme canonique)
Forme canonique (design-gate c.866, #8056). Chaque notebook pédagogique DOIT exposer sa matrice de coût dans nb.metadata['cost'] (objet JSON, invisible au rendu markdown). C’est la seule forme propre : une cellule markdown portant un bloc ---\n...\n--- est promue par markdown-it en setext-H2 supersize (défaut de rendu que le guard #8352 bloque en ERROR). L’exemplar de référence = Infer-3-Factor-Graphs + Infer-4-Bayesian-Networks (PR #8323).
// nb.metadata["cost"] — l'objet JSON sérialisé par le notebook (.ipynb = JSON).
{
"api_usd_est": 0.40, // Coût API estimé par exécution end-to-end (USD). 0 si gratuit, null si inconnu. Cf §"Attribution multi-fournisseur".
"api_provider": "openai", // openai | anthropic | mistral | hf | replicate | google | local | none
"api_cost_breakdown": null, // OPTIONNEL. Ventilation provider→USD, somme == api_usd_est (gate falsifiable). Cf §"Attribution multi-fournisseur".
"qcc_tokens_est": 0, // QuantConnect Cloud compute tokens (QCC) estimés par exécution end-to-end. 0 si non-QC. Cf §"Coût QCC / QuantConnect".
"cpu_min": 1, // Estimation CPU-only minutes (range ou best-case)
"gpu_min": 0, // Estimation GPU minutes (range ou best-case)
"gpu_required": false, // true si impossible sans GPU
"vram_gb": 0, // VRAM minimum (GB), ex: 12, 24, ou range "16-24"
"vram_tier": "LITE", // Catégorie VRAM (cf table §"Tiers VRAM")
"network": true, // Accès réseau requis (téléchargement modèle, appel API)
"external_account": "openai", // Compte externe obligatoire (openai, anthropic, hf, qc, ...) | "none"
"free_alternative": null, // Chemin repo-relatif vers un notebook équivalent sans coût, sentinel canonique, ou null. Cf §"Sentinels de `free_alternative`".
"reduced_pedagogical": null, // Version pédagogique réduite (sous-ensemble ou mock) | null
"reproducibility": "HIGH", // HIGH=déterministe, MED=seed-dépendant, LOW=stochastique
"metadata_written": "2026-07-24", // Date d'établissement de la metadata (ISO8601), pas la date de validation
"validator": "papermill", // papermill | qc_cloud | manual | lean_build | sk_agent | sk_visual
}Le champ
title(présent dans l’ancienne forme YAML) est retiré : redondant avec le titre H1 du notebook. Les valeurs sont identiques à l’ancien schéma YAML (seul le lieu de stockage change : JSON metadata au lieu de cellule markdown).
Migration & backward-compat
La migration de masse des notebooks existants se fait par tranches famille (rollout c.795/796/797 pattern), chaque lane migrant sa famille opportuniste. Le vérificateur check_cost_metadata.py lit metadata['cost'] d’abord, retombe sur le scan de cellule ---...--- en fallback (backward-compat) — les deux formes coexistent pendant la transition, l’ordre de migration est non-bloquant. À terme, toutes les cellules ----YAML sont retirées (elles déclenchent le guard #8352).
Divulguation coût côté étudiant (OPTIONNELLE). La matrice metadata.cost est machine-only (invisible). Si on souhaite la surface à l’étudiant, une petite table markdown rendue ou un badge suffit — jamais reproduire le YAML brut :
> 💰 **Coût** : gratuit (CPU local, ~3 min). Pas de compte externe requis.Ne pas sur-scoper les notebooks avec une table rendue — metadata.cost reste la source de vérité, le badge est un confort de lecture.
Champs obligatoires vs optionnels
| Champ | Obligatoire | Défaut si omis |
|---|---|---|
cost.api_usd_est |
✓ | 0 |
cost.api_provider |
✓ | "none" |
cost.api_cost_breakdown |
optionnel | null (multi-fournisseur seulement ; sum == api_usd_est vérifié) |
cost.qcc_tokens_est |
optionnel | 0 (0 = non-QC ; à peupler pour tout quantbook QC Cloud) |
cost.cpu_min |
✓ | 0 |
cost.gpu_min |
optionnel | (omission = pas d’estimation GPU) |
cost.gpu_required |
✓ | false |
cost.vram_gb |
optionnel | (omission = pas d’estimation VRAM) |
cost.vram_tier |
optionnel | (calculé depuis vram_gb : <8=LITE, 8-16=MID, >16=HIGH) |
cost.network |
✓ | false |
cost.external_account |
✓ | "none" |
cost.free_alternative |
optionnel | null (peut être ajouté après-coup) |
cost.reduced_pedagogical |
optionnel | null |
cost.reproducibility |
✓ | "HIGH" |
cost.metadata_written |
✓ | (date d’établissement de la metadata) |
cost.validator |
✓ | "manual" |
Attribution multi-fournisseur (api_cost_breakdown)
Décision de schéma (design-gate #8056, issuecomment 5106409423) : scalaire autoritatif + ventilation optionnelle et falsifiable.
api_usd_est reste le champ autoritatif — c’est lui que lisent les consommateurs (agrégats, catalogue, badges). api_cost_breakdown est un champ optionnel qui ventile le coût par fournisseur quand cette information est réellement connue (plusieurs endpoints payants dans le même notebook) :
"api_usd_est": 0.42, // TOTAL autoritatif, obligatoire
"api_cost_breakdown": { // optionnel
"openai": 0.30,
"anthropic": 0.12
}Règle falsifiable (la seule qui compte) : quand api_cost_breakdown est présent, sum(valeurs) doit égaler api_usd_est. Vérifié par check_cost_metadata.py (Litmus 8) — un écart > 1 cent déclenche le finding api_cost_breakdown_sum_mismatch.
Pourquoi exiger la somme plutôt que laisser la ventilation libre ? Une ventilation libre est décorative : personne ne peut la contredire, elle se périme en silence au premier drift. Une ventilation dont la somme doit égaler le total est falsifiable — elle casse le jour où elle ment. Même leçon que le README central (#8678 : un compteur nu se périme ; un compteur avec son dénominateur se contredit tout seul) et que le gate de preuve Lean (#8680 : un gate incapable d’échouer n’est pas un gate).
Quand l’écrire : - Notebook multi-fournisseur dont les sous-totaux par provider sont réellement mesurés (ex : un notebook qui appelle GPT-5 pour le raisonnement et Claude pour la vérification, coûts séparés dans les logs).
Quand NE PAS l’écrire (règles d’or) : - Mono-fournisseur : écrire la ventilation dupliquerait le total pour zéro information, et fabriquerait 327 occasions de drift. On laisse null. - Sous-totaux reconstitués à la louche : une fausse précision sur une estimation validator: "manual" est pire que l’absence. On laisse null et on documente la raison dans notes si pertinent.
0.0 vs null sur api_usd_est (corollaire) : - 0.0 affirme la gratuité (notebook local, pas d’appel facturé). C’est un énoncé positif. - null (+ raison) = coût inconnu. Ne pas confondre : 0.0 n’est pas un défaut pour « je ne sais pas », l’absence se lirait à tort comme gratuite.
validator / metadata_written — la seconde règle falsifiable (Litmus 9)
Note terminologique (#8843) : ce champ s’appelait
last_validatedjusqu’au 2026-07-29. Le nom suggérait à tort une date de validation, alors que les populators écriventmetadata_written: date.today()au moment du peuplement — pas au moment d’une validation. Le nouveau nom (metadata_written) reflète honnêtement la sémantique : c’est la date d’établissement de la metadata, pas un horodatage de validation. La règle falsifiable du Litmus 9 reste la même : c’estvalidatorqui affirme l’exécution, et c’est contrevalidatorque s’applique le prédicat, pas contremetadata_written(qui est décoratif).
Ces deux champs s’articulent ainsi : validator affirme qu’une validation a eu lieu (papermill, QC Cloud, manuel, etc.), metadata_written horodate l’établissement de la metadata. Jusqu’au Litmus 9, rien dans le dépôt ne pouvait contredire validator : aucun consommateur ne relisait son affirmation contre l’état réel du notebook. Un champ validator que rien ne peut contredire est décoratif : il porterait la même valeur sur un notebook dont aucune cellule n’a jamais tourné.
Règle falsifiable : quand validator affirme une exécution de cellules (papermill, sk_visual, dotnet-interactive), aucune cellule code non vide ne doit porter execution_count: null. Le fondement est mécanique : nbclient/papermill exécutent toute cellule code non vide — y compris celles qui échouent (--allow-errors ne change que le comportement d’arrêt, pas l’attribution du compteur). Donc execution_count: null prouve la non-exécution. Vérifié par check_cost_metadata.py (Litmus 9) → finding validator_asserts_execution_but_cells_unexecuted (MAJOR).
Validators hors périmètre, délibérément :
| Validator | Pourquoi il n’est pas contredit |
|---|---|
manual |
un humain a relu — aucune affirmation d’exécution. C’est aussi la valeur de correction pour un notebook non exécutable localement |
qc_cloud |
carve-out H.3 documenté — le runtime research QC n’existe sur aucune machine worker |
lean_build |
lake build SUCCESS porte sur le lake, pas sur les cellules |
sk_agent |
périmètre ambigu, pas d’affirmation nette |
Échappatoire honnête (tag de skip). Une cellule délibérément non exécutable — code de référence destiné à un autre runtime — se déclare par un tag de cellule (skip-execution, skip, no-execute). Le notebook cesse alors d’être contredit sans mentir, et l’exemption est visible dans le fichier, contrairement à une exception codée en dur dans l’outil. La contrepartie est la même que partout ailleurs : on corrige la déclaration, ou on ré-exécute — jamais on n’édite une sortie de cellule à la main (Stop & Repair, cf secrets-hygiene.md règle 6).
Mesure à l’introduction (2026-07-29, 1020 notebooks scannés) : 13 findings, tous
validator: papermill, tous dansQuantConnect/Python— série dont chaque cellule code est du[REFERENCE QC]importantAlgorithmImports, qui n’existe que dans le runtime QC. Ces notebooks sont couverts par le carve-out H.3 par chemin (QC_CLOUD_PATHS) alors qu’ils échappent au prédicat de contenu partagé (QuantBook()) : le carve-out excuse l’absence d’exécution, il n’autorise pas à en affirmer une. Correction attendue :validator: manual. Zéro finding sur les autres validators, y compris sur la tranche .NET en attente d’harmonisation (Probas/Infer.NET,ML/ML.NET) — ceux-là ont été exécutés, leur défaut est une étiquette inexacte, pas une exécution fantôme.
Mode flotte — agréger avant de résorber
Les neuf litmus n’avaient jusqu’ici qu’un mode un notebook à la fois : chacun était donc appliqué à la main, par quiconque y pensait, et personne n’avait jamais agrégé le résultat. Conséquence directe : un litmus vert ne pouvait pas être distingué d’un litmus jamais exécuté — c’est le même angle mort que « vert hors-cible ». --all / --family ferment cet écart (même prédicat, marcheur canonique notebook_walk, #8650) :
python scripts/audit/check_cost_metadata.py --all # flotte entière
python scripts/audit/check_cost_metadata.py --family QuantConnect # une famille
python scripts/audit/check_cost_metadata.py --all --json # sortie machineMesure d’introduction du mode (2026-07-29) : 941 notebooks scannés, 71 porteurs d’au moins un finding, 86 findings sur 6 patterns — gpu_used_but_not_declared 31, gpu_no_visual_validator 18, token_required_but_no_account 17, api_used_but_cost_zero 13, qc_notebook_no_qcc_estimate 4, qc_notebook_no_validator 3. Le litmus 9 est à zéro : les 13 findings mesurés ci-dessus ont bien été corrigés en validator: manual, ils n’ont pas été escamotés par le marcheur (contrôle indépendant sur --family QuantConnect : manual: 13).
Écart de population assumé, 1020 vs 941 : la mesure du litmus 9 ci-dessus parcourait le disque brut ; le mode flotte passe par notebook_walk, qui restreint aux fichiers suivis par git (tracked_only=True) et écarte _output.ipynb, _archives/, .ipynb_checkpoints/, .lake/. Les ~79 de différence sont des sorties d’exécution, des archives et des notebooks non suivis — hors périmètre d’un audit de métadonnées déclarées.
Pas de --check, pas de câblage CI, délibérément. Les 86 findings sont pré-existants : un gate posé dessus naîtrait rouge et serait ignoré dès le premier jour. L’ordre est agréger → résorber → gater, jamais l’inverse. C’est d’ailleurs ce que dit déjà « Ce que ce schéma n’est PAS » : le validateur signale, il ne décide pas si l’incohérence est bloquante. Résorber un pattern est un grain de substance séparé — et la correction se fait à la source (ré-exécuter, ou corriger la déclaration), jamais en éditant une sortie de cellule à la main.
Tiers VRAM (déterminé par vram_gb)
| Tier | VRAM (GB) | Modèles typiques |
|---|---|---|
LITE |
< 8 | SD-XL-Turbo int8, Kokoro TTS, Whisper-tiny/base |
MID |
8-16 | Qwen-Image-Edit base, SD-3.5 medium, FLUX.1-schnell fp8 |
HIGH |
> 16 | Qwen-Image-Edit 2509 full, SD-3.5 large, FLUX.1-dev fp16 |
Colonne catalogue — COURSE_CATALOG.generated.json
Le catalogue anti-drift expose cost via l’inférence du frontmatter. Schéma cible :
{
"notebook": "MyIA.AI.Notebooks/GenAI/Image/01-Foundation/01-1-OpenAI-DALL-E-3.ipynb",
"cost": {
"api_usd_est": 0.40,
"gpu_required": false,
"vram_tier": "LITE",
"external_account": "openai",
"free_alternative": "MyIA.AI.Notebooks/GenAI/Image/01-Foundation/01-3-Basic-Image-Operations.ipynb",
"reproducibility": "MED"
}
}Le champ cost.free_alternative permet le routage machine : si po-2025 n’a pas le GPU ou l’API key, le catalogue pointe vers le notebook équivalent exécutable localement.
Sentinels de free_alternative
Le champ admet deux natures : un chemin repo-relatif, ou un sentinel sémantique. Le critère qui décide qu’un sentinel est canonique : il porte une information que null détruit (design-gate tranché sur #8056).
| Valeur | Statut | Sens |
|---|---|---|
<chemin repo-relatif> |
forme nominale | Un notebook du dépôt couvre le même sujet sans coût. Résolu en dual-base (racine du dépôt ou MyIA.AI.Notebooks/). |
self |
canonique | Ce notebook est l’alternative gratuite. Opposé de null — ne jamais normaliser vers null, ce serait lire 55 réponses positives comme 55 absences de réponse. |
ollama |
canonique | Un moteur local gratuit couvre le sujet. Aucun chemin du dépôt ne l’exprime. |
n/a |
toléré | Synonyme de null. Traité à l’identique par le checker ; pas de migration (coût non nul, gain nul). |
null |
forme nominale | Aucune alternative gratuite connue. |
service payant (openai, anthropic, replicate, …) |
erreur | C’est précisément le service dont on cherche à s’affranchir. Flaggé free_alternative_is_paid_service (MAJOR). |
| autre valeur non-chemin | erreur | Ne résout vers rien que le lecteur puisse suivre. Flaggé free_alternative_unresolvable (MINOR). |
Un basename nu (10_LocalLlama.ipynb) est un chemin imprécis, pas un sentinel : le lecteur ne peut pas le suivre et la dual-base ne le résout pas. Le checker ne le résout pas par glob sur l’arbre — ça ferait taire le finding sans corriger la référence, et deviendrait ambigu au premier basename partagé. On répare la donnée, pas le détecteur.
Implémentation : scripts/audit/check_cost_metadata.py, litmus 4.
Peuplement pilote (cycle c.794)
5 familles × 2 notebooks = 10 entrées de référence (échantillon ≥5%/famille, conforme protocole #8052).
Note — syntaxe des exemples. Les blocs ci-dessous sont en YAML commenté pour la lisibilité (le JSON canonique de
metadata['cost']ne supporte pas les commentaires inline). Les valeurs des champs sont strictement identiques entre l’ancienne forme YAML cellule et la nouvelle formemetadata.costJSON — seul le lieu de stockage change. En pratique, ces valeurs vont dansnb.metadata['cost'](objet JSON sérialisé par le.ipynb).
GenAI/Image (GPU + API $)
# 01-1-OpenAI-DALL-E-3.ipynb (GenAI/Image/01-Foundation)
cost:
api_usd_est: 0.40 # 4 images × $0.040/image DALL-E 3 standard 1024×1024
api_provider: openai
cpu_min: 1
gpu_min: 0 # API cloud, pas de GPU local requis
gpu_required: false
network: true # HTTPS OpenAI obligatoire
external_account: openai # OPENAI_API_KEY obligatoire
free_alternative: GenAI/Image/01-Foundation/01-4-Forge-SD-XL-Turbo.ipynb
reduced_pedagogical: GenAI/Image/01-Foundation/01-3-Basic-Image-Operations.ipynb
reproducibility: MED # Pas de seed déterministe côté OpenAI
metadata_written: 2026-07-23T01:30Z
validator: papermill # Exécuté via Papermill local + OpenAI API# 01-5-Qwen-Image-Edit.ipynb (GenAI/Image/01-Foundation)
cost:
api_usd_est: 0.0 # Modèle self-hosted po-2023 (pas de coût API direct)
api_provider: local
cpu_min: 0
gpu_min: 12 # Inference ~5 min sur RTX 3090
gpu_required: true # Impossibilité CPU (modèle trop lourd)
vram_gb: 16 # Qwen-Image-Edit base = 16 GB FP16
vram_tier: MID
network: true # Téléchargement modèle HuggingFace au premier run
external_account: hf # HF_TOKEN pour download gated
free_alternative: GenAI/Image/01-Foundation/01-5b-Qwen-Image-Edit-2509.ipynb
reduced_pedagogical: GenAI/Image/01-Foundation/01-4-Forge-SD-XL-Turbo.ipynb
reproducibility: HIGH # torch.manual_seed(42) + déterminisme sampler
metadata_written: 2026-07-23T01:30Z
validator: sk_visual # sk-agent vision check sur figures renduesProbas / Infer.NET (CPU only)
# DecInfer-01-Utility-Foundations.ipynb (Probas/DecisionTheory/DecInfer)
cost:
api_usd_est: 0.0 # Microsoft.ML.Probabilistic NuGet, pas d'API externe
api_provider: none
cpu_min: 3 # Inference bayésienne ~3 min sur CPU Intel i7
gpu_min: 0
gpu_required: false
vram_gb: 0
network: false # Tout local (dotnet interactive + nuget cache)
external_account: none
free_alternative: null
reduced_pedagogical: null
reproducibility: HIGH # Variational message passing déterministe
metadata_written: 2026-07-23T01:30Z
validator: papermill # .NET Interactive local (cf L532 MEMORY : strip probeAddresses banner post-re-exec)# DecInfer-02-Lean-ExpectedUtility.ipynb (Probas/DecisionTheory/DecInfer)
cost:
api_usd_est: 0.0
api_provider: none
cpu_min: 5 # Lean 4 build + Lean Infer.NET combined
gpu_min: 0
gpu_required: false
vram_gb: 0
network: false # Lean toolchain local + Microsoft.ML.Probabilistic NuGet
external_account: none
free_alternative: null
reduced_pedagogical: null
reproducibility: HIGH
metadata_written: 2026-07-23T01:30Z
validator: lean_build # `lake build` SUCCESS + Lean REPL via sk-agentProbas / PyMC (CPU, échantillonnage MCMC)
# PyMC-1-Beta-Binomial-Basics.ipynb (Probas/Probas-PyMC)
cost:
api_usd_est: 0.0
api_provider: none
cpu_min: 8 # MCMC NUTS 4 chaînes × 2000 draws ~8 min sur i7
gpu_min: 0
gpu_required: false
vram_gb: 0
network: false # PyMC + ArviZ, tout local
external_account: none
free_alternative: null
reduced_pedagogical: Probas/Probas-PyMC/PyMC-0-PyMC-Setup-Lightweight.ipynb
reproducibility: HIGH # `pm.sample(seed=42, cores=1)` déterministe
metadata_written: 2026-07-23T01:30Z
validator: papermillML / ML.NET (CPU)
# 2.1-Workflow-ML.ipynb (ML/DataScienceWithAgents/02-ML-Cours)
cost:
api_usd_est: 0.0
api_provider: none
cpu_min: 4 # ML.NET trainer IID ~4 min sur CPU
gpu_min: 0
gpu_required: false
vram_gb: 0
network: false
external_account: none
free_alternative: null
reduced_pedagogical: null
reproducibility: HIGH # ML.NET seed déterministe
metadata_written: 2026-07-23T01:30Z
validator: papermill.NET Interactive (profil canonique, cpu-only)
Un notebook .NET Interactive (kernel .net-csharp / .net-fsharp) sans appel API n’a pas le profil d’un notebook Python appelant OpenAI. Le profil canonique ci-dessous est celui à appliquer aux familles .NET cpu-only pures (Sudoku solveurs, Search CSP, GameTheory twins C#) — il diffère du profil Python+API sur trois points : validator, vram_tier, free_alternative.
# Profil canonique .NET Interactive cpu-only (ex: Sudoku solveur, Search CSP)
cost:
api_usd_est: 0.0
api_provider: none
cpu_min: 1
gpu_min: 0
gpu_required: false
vram_gb: 0
vram_tier: NONE # cpu-only — c.888 canonical (PAS LITE)
network: false
external_account: none
free_alternative: self # le notebook local sans API EST son alternative gratuite
reduced_pedagogical: null
reproducibility: HIGH # solveurs .NET déterministes (MEDIUM si stochastique)
metadata_written: "2026-07-28T15:45Z" # UTC obligatoire (cf leçon TZ : jamais local+Z)
validator: manualvalidator: manual est la valeur canonique pour .NET. Le kernel .net-csharp s’exécute via dotnet-interactive headless local, pas via papermill (qui ne pilote pas les kernels .NET Interactive par défaut). Écrire validator: papermill suggère une validation automatisée absente — manual est honnête : la re-exécution se fait via dotnet-interactive sur chaque machine worker (cf docs/reference/kernels-runtime.md), le résultat est vérifié à la main. La CI ne peut pas Papermill-exécuter les notebooks .NET (advisory #5214) — manual reflète cette réalité.
vram_tier: NONE pour cpu-only (leçon c.888) : un notebook sans GPU doit porter NONE, pas LITE. LITE décrit un besoin VRAM faible mais réel (< 8 GB) ; un notebook cpu-only n’a aucun besoin VRAM. La table générale « <8=LITE » s’applique aux notebooks GPU à faible VRAM, pas au cpu-only.
free_alternative: self : un notebook .NET local sans API payante EST déjà gratuit — le sentinel self le dit (null signifierait à tort « aucune alternative gratuite connue », cf §“Sentinels de free_alternative”).
Note d’harmonisation (hors scope de ce PR). Les profils historiques
Probas/Infer.NETetML/ML.NETci-dessus portentvalidator: papermillet omettentvram_tier: NONE/free_alternative: self. Ils ont été migrés avant la formalisation de ce profil canonique. Leur harmonisation versvalidator: manualse fait en tranche dédiée (pas un blanket-sweep — chaque notebook vérifiéapi_provider: noneau passage).
QC / QuantConnect (Cloud obligatoire)
# LongShortHarvest.ipynb (QuantConnect/projects)
cost:
api_usd_est: 0.0 # QC Cloud = pas de coût API par backtest (free tier)
api_provider: qc_cloud
qcc_tokens_est: 840 # ~70 QCC/cellule code (cf §"Coût QCC / QuantConnect")
cpu_min: 0
gpu_min: 0
gpu_required: false
vram_gb: 0
network: true # QuantConnect API obligatoire (HTTPS)
external_account: qc # QC user + QC API token obligatoires
free_alternative: null # Pas d'alternative locale (QuantBook = QC uniquement)
reduced_pedagogical: QuantConnect/projects/research-research-only.ipynb
reproducibility: MED # Walk-forward OOS reproductible, single-run backtest stochastique
metadata_written: 2026-07-23T01:30Z
validator: qc_cloud # MCP qc-mcp-lite create_backtest + read_backtestCoût QCC / QuantConnect — pourquoi un champ dédié
QuantConnect Cloud facture l’exécution des quantbooks en QCC tokens (QuantConnect Compute), une monnaie de quota propre au cloud QC — non convertible en USD et non gratuite au-delà du free tier. Le champ api_usd_est: 0.0 est donc techniquement correct (pas de coût API en USD) mais trompeur sans qcc_tokens_est : il présente le quantbook comme « gratuit » alors qu’il consomme du quota QCC. qcc_tokens_est ferme ce gap en exposant le coût réel en quota QC.
Estimation (acceptance #8056 : « sessions QuantConnect ≈ 800-1200 QCC tokens pour un notebook de 14 cellules ») : heuristic dérivable ~70 QCC par cellule code, plancher max(400, n_code_cells × 70). C’est une estimation (le suffixe _est l’atteste), pas une mesure — ré-estimer après une exécution QC Cloud réelle (read_backtest retourne le QCC consommé). Le litmus correspondant dans check_cost_metadata.py signale tout quantbook (QuantBook() détecté) dont qcc_tokens_est est absent ou 0.
GenAI/Image GPU lourd (référence HIGH tier)
# 01-5b-Qwen-Image-Edit-2509.ipynb (GenAI/Image/02-Advanced)
cost:
api_usd_est: 0.0
api_provider: local
cpu_min: 0
gpu_min: 25 # 2509 parameters full ~25 min sur RTX 3090
gpu_required: true
vram_gb: 24 # FP16 = 24 GB, int4 Nunchaku ~10 GB (réduction)
vram_tier: HIGH
network: true
external_account: hf
free_alternative: GenAI/Image/01-Foundation/01-5-Qwen-Image-Edit.ipynb
reduced_pedagogical: GenAI/Image/01-Foundation/01-4-Forge-SD-XL-Turbo.ipynb
reproducibility: HIGH
metadata_written: 2026-07-23T01:30Z
validator: sk_visualQC crypto multi-canal (Cloud + Sharpe réel)
# crypto-multicanal.ipynb (QuantConnect/projects)
cost:
api_usd_est: 0.0
api_provider: qc_cloud
cpu_min: 0
gpu_min: 0
gpu_required: false
vram_gb: 0
network: true
external_account: qc
free_alternative: null
reduced_pedagogical: null
reproducibility: MED # Sharpe 0.333 / CAGR 4.589% / MaxDD 14.100% (#8064)
metadata_written: 2026-07-23T01:30Z
validator: qc_cloudICT (PyPhi, Python CPU-only)
# ICT-15b.ipynb (SymbolicAI/ICT ou IIT)
cost:
api_usd_est: 0.0
api_provider: none
cpu_min: 12 # PyPhi MIP computation on TPM ~12 min
gpu_min: 0
gpu_required: false
vram_gb: 0
network: false
external_account: none
free_alternative: IIT/4-Subsystem-IIT.ipynb
reduced_pedagogical: IIT/0-PyPhi-Setup-Lightweight.ipynb
reproducibility: HIGH # PyPhi seed + ground truth MIP
metadata_written: 2026-07-23T01:30Z
validator: papermillLean 4 lake build (CPU long)
# DecisionTheory-Utility.lean (Probas/DecisionTheory/Lean)
cost:
api_usd_est: 0.0
api_provider: none
cpu_min: 45 # `lake build` d'un lake moyen ~45 min cold, ~10 min cached
gpu_min: 0
gpu_required: false
vram_gb: 0
network: false # Lean toolchain local + Mathlib cache
external_account: none
free_alternative: null
reduced_pedagogical: null
reproducibility: HIGH # Lean 4 type-check déterministe
metadata_written: 2026-07-23T01:30Z
validator: lean_buildIntégration à la grille audit sémantique #8052
Le script extract_claims_vs_outputs.py (livré par PR #8068 / cycle c.793 — branche feature/c793-audit-semantic-sampling-8052) confronte claims-du-markdown ↔︎ outputs réels. Litmus 5 (cohérence pédagogique) vérifie que la matrice metadata['cost'] est cohérente avec le reste du notebook :
| Incohérence | Sévérité |
|---|---|
metadata.cost.gpu_required: false mais cellule code lance torch.cuda.device() |
MAJOR |
metadata.cost.api_usd_est: 0.0 mais cellule appelle openai.ChatCompletion.create() |
CRITICAL |
metadata.cost.external_account: none mais cellule demande HF_TOKEN |
MAJOR |
metadata.cost.free_alternative pointe vers un notebook inexistant |
MAJOR |
metadata.cost.free_alternative nomme un service payant (openai, anthropic, …) |
MAJOR |
metadata.cost.free_alternative : valeur ni sentinel canonique ni chemin |
MINOR |
Notebook QC sans qc_cloud validator |
MAJOR |
Notebook GPU sans sk_visual validator (cf #5780 sweep) |
MINOR |
metadata.cost.validator affirme une exécution mais des cellules code portent execution_count: null (Litmus 9) |
MAJOR |
Ces extensions restent hors scope c.794 (à dispatcher cycles c.795+) — la grille est volontairement extensible.
Sortie attendue par cycle
Pour chaque cycle mensuel :
- 1 fichier
docs/notebook-metadata/cost-matrix.mdmis à jour (peuplement continu par famille) - N notebooks avec
metadata['cost']ajouté (pilote : 10 en c.794, ~50 en c.795+) - 1 entrée catalogue
costexposée dansCOURSE_CATALOG.generated.json(cf catalog-pr-hygiene R1 : régénération automatique par cron quotidien, pas manuel) - 1 validateur
scripts/audit/check_cost_metadata.py(cf livrable 2) — flag les incohérences litmus 5
Ce que ce schéma n’est PAS
- Pas une estimation précise :
api_usd_estest un ordre de grandeur best-case. Le coût réel dépend du provider pricing (mis à jour sans préavis) et du nombre de calls (à documenter dans le notebook lui-même). - Pas un remplacement de
validate_pr_notebooks.py: ce dernier valide la structure (execution_count, outputs) ; ce schéma valide la faisabilité (ressource + accès). - Pas une chasse au secrets :
external_accountréférence le nom du provider, pas la clé. Cf secrets-hygiene.md — les clés restent dans.env(gitignored) viaos.getenv()sans default. - Pas une obligation immédiate : c.794 = pilote de 10 notebooks. Le rollout systématique est progressif par famille (c.795+ Probas, c.796+ ML, c.797+ Search, c.798+ QC, etc.).
- Pas une auto-validation : un validateur
check_cost_metadata.pysignale les incohérences, ne décide pas si elles sont bloquantes — revue humaine/agent compétent reste requise.
Acceptance #8056 (5 critères)
| # | Critère | Status c.794 |
|---|---|---|
| 1 | Schéma cost: canonique (nb.metadata['cost'] JSON, cellule ----YAML retirée du mandat guard #8352) |
✅ Défini ci-dessus (14 champs, title retiré) |
| 2 | Colonne catalogue correspondante (COURSE_CATALOG.generated.json.cost) |
✅ Schéma JSON défini (cf §“Colonne catalogue”) |
| 3 | ≥5%/famille pilote (10/300 = 3.3% global, mais 2/famille sur 5 familles pilotes = pilote suffisant) | ✅ 10 notebooks, 5 familles |
| 4 | Alternative gratuite / version pédagogique réduite / compte externe requis | ✅ Champs free_alternative + reduced_pedagogical + external_account |
| 5 | Intégration audit sémantique #8052 (litmus 5 cohérence pédagogique) | ⏳ Documenté §“Intégration grille”, à dispatcher c.795+ |
Acceptance partiel (4/5 vérifiables firsthand maintenant, 1/5 attend revue aval) — pas de Closes #8056, juste See #8056 Part of #4208 (contribution partielle à l’epic open-courseware fiabilisé).
Repères vérifiables
- Issue-source : #8056 (P1, lane po-2025 + po-2024 + po-2023).
- Epic parente : #4208 (open-courseware fiabilisé).
- Audit-pattern cross-famille : #8052 (protocole sampling + grille).
- Grille parité jumeaux : #8057 (Python↔︎C#).
- Coût/ressource par notebook (sibling #8056 parent) : #8056.
- ICT instance scoping : #7734.
- Securité secrets : secrets-hygiene.md (
.envgitignored). - SOTA verdicts : sota-not-workaround.md (RECOVERABLE-* + SOTA-OK).
- Lean i18n : code-style.md (Lean FR-first + sibling
_en).
Suite logique
| Cycle | Cible |
|---|---|
| c.795 | Validation litmus 5 sur l’échantillon c.793 + peupl. ML (ML.NET, GenAI/Image complet) |
| c.796 | Peuplement Search Part1-3 + Lean lakes principales |
| c.797 | Peuplement QC + ICT |
| c.798 | Génération colonne catalogue (cron) + sync CI anti-drift |
| c.799+ | Roulement famille par famille jusqu’à ~80% de couverture |