Skip to content

Garde d'ancrage : une lecture de resultat posee sur une cellule SANS output (prototype + son FP mesure) #16695

Closed

Description

@myia-ai-01

Garde d'ancrage : une lecture de resultat posee sur une cellule SANS output

Le danger, et pourquoi les trois gardes existants sont muets dessus

Une cellule markdown ajoutee qui lit un resultat (### Lecture du resultat, on observe,
ce que montre...) doit etre ancree sur une cellule de code qui porte un output reel.
Deux formes de defaut :

  1. Lecture sans ancre — la prose commente un resultat qu'aucune sortie du notebook ne porte.
    Instance mesuree ce cycle : feat(ml,#16616): 2.14 Explicabilite XAI — SHAP, LIME, contrefactuels (German Credit) #16619 cite trois fois un « φ moyen conditionnel » dont deux valeurs
    n'existent dans aucun output, et la troisieme est un φ individuel reetiquete.
  2. Lecture sur un stub d'exercice — forme aggravee : la prose divulgue le resultat d'un
    exercice que l'etudiant n'a pas rempli. Le notebook s'execute (C.1 respectee, pass +
    print("Exercice a completer")), donc tous les organes sont verts, et le spoiler passe.

Les trois gardes voisins ne couvrent pas ce cas — verifie en lisant leurs docstrings :

Garde existant Ce qu'il attrape Pourquoi il est muet ici
scripts/check_markdown_claims_output.py prose citant une valeur qui contredit l'output il lui faut une valeur dans l'output pour comparer ; output vide = rien a contredire
scripts/notebook_tools/check_prose_quantitative_claims.py compteurs d'artefacts du depot figes dans la prose c'est un autre genre (mesure de depot), pas une lecture de sortie
scripts/notebook_tools/check_interp_positioning.py interpretation posee apres la mauvaise cellule de code il suppose que les outputs existent ; il arbitre laquelle, pas s'il y en a une

Prototype — il tourne, il n'est pas un organe

Ecrit et execute ce cycle (ai-01). Il joint chaque markdown ajoute par la PR (diff vs base)
qui matche un motif de lecture, a la cellule de code immediatement precedente, et refuse quand
cette ancre a outlen == 0, ou qu'elle est un stub avec moins de 200 octets de sortie.

# -*- coding: utf-8 -*-
"""Garde densite : toute cellule markdown AJOUTEE qui lit un resultat doit etre ancree
sur une cellule de code qui porte un OUTPUT REEL. Une lecture sur un stub vide = divulgation
sur un exercice non rempli."""
import json, subprocess, sys, re, os

LECTURE = re.compile(r"(lecture du r|interpr|ce que (montre|dit|nous apprend)|r[ée]sultat\s*:|on (lit|observe|constate)|attendu)", re.I)
STUB = re.compile(r"(TODO|Exercice a completer|Exercice à compléter|# Indice|votre code|your code|\bpass\b\s*$)", re.I)

def cells(ref, path):
    r = subprocess.run(["gh", "api",
                        "repos/jsboige/CoursIA/contents/%s?ref=%s" % (path, ref),
                        "--jq", ".content"], capture_output=True, text=True)
    if r.returncode != 0:
        return None
    import base64
    return json.loads(base64.b64decode(r.stdout.strip()).decode("utf-8", "replace"))["cells"]

def outlen(c):
    n = 0
    for o in c.get("outputs", []):
        n += len("".join(o.get("text", [])))
        n += len(json.dumps(o.get("data", {}), ensure_ascii=False))
    return n

for pr in sys.argv[1:]:
    r = subprocess.run(["gh", "pr", "view", pr, "-R", "jsboige/CoursIA", "--json",
                        "headRefOid,baseRefOid,files"], capture_output=True, text=True)
    if r.returncode != 0:
        print("#%s  ERREUR gh" % pr); continue
    d = json.loads(r.stdout)
    nbs = [f["path"] for f in d["files"] if f["path"].endswith(".ipynb")]
    verdicts = []
    for nb in nbs:
        head = cells(d["headRefOid"], nb)
        base = cells(d["baseRefOid"], nb)
        if head is None:
            verdicts.append("%s: illisible" % os.path.basename(nb)); continue
        base_src = set("".join(c["source"]) for c in (base or []))
        for i, c in enumerate(head):
            if c["cell_type"] != "markdown":
                continue
            src = "".join(c["source"])
            if src in base_src:            # pas une cellule ajoutee
                continue
            if not LECTURE.search(src):    # pas une cellule de lecture
                continue
            j = i - 1                      # ancre = cellule de code immediatement precedente
            while j >= 0 and head[j]["cell_type"] != "code":
                j -= 1
            if j < 0:
                verdicts.append("c%d: lecture SANS ancre de code amont" % i); continue
            a = head[j]
            ol, asrc = outlen(a), "".join(a["source"])
            if ol == 0:
                verdicts.append("c%d: ancre c%d SANS OUTPUT%s" % (i, j, " (STUB)" if STUB.search(asrc) else ""))
            elif STUB.search(asrc) and ol < 200:
                verdicts.append("c%d: ancre c%d = STUB, output %do" % (i, j, ol))
    print("#%s  %s" % (pr, "OK" if not verdicts else "!! " + " | ".join(verdicts[:4])))

Le faux positif est DEJA mesure — c'est lui le coeur du grain

Passe sur 4 PRs de densite (16619 16413 16455 16441) : 1 seule touche, et elle est FAUSSE.

#16619  !! c32: ancre c31 = STUB, output 23o
#16413  OK
#16455  OK
#16441  OK

Lecture firsthand de 2.14-Explicabilite-SHAP-LIME-Contrefactuels.ipynb (33 cellules) au head de
#16619 : c32 est une ## Conclusion de notebook — synthese de cloture sur les trois familles
SHAP / LIME / DiCE. Elle ne lit pas c31. Elle suit c31 parce que c31 est simplement la
derniere cellule de code, et que celle-ci se trouve etre un stub d'exercice.

C'est le mode d'echec structurel de l'heuristique « ancre = cellule de code precedente » : une
synthese de cloture, une transition de section, une introduction de partie suivent legitimement
une cellule de code sans la commenter. Sans exemption, ce garde tire sur tous les notebooks qui se
terminent proprement par un exercice — soit, par construction pedagogique, la majorite.

Ne PAS cabler le prototype tel quel. 1/1 des touches mesurees est un FP ; c'est la classe de
defaut que audit-reassessment.md existe pour empecher.

Acceptance

  • Organe dans scripts/notebook_tools/, nom explicite, docstring disant quel danger il
    couvre et lesquels il ne couvre pas (renvoi aux trois gardes voisins du tableau ci-dessus).
  • Exemption de cloture/transition : une ## Conclusion, une ## Synthese, une introduction
    de section ou une transition n'est pas une lecture de sa cellule amont. Le contre-exemple
    feat(ml,#16616): 2.14 Explicabilite XAI — SHAP, LIME, contrefactuels (German Credit) #16619 c32 est le controle negatif : l'organe doit rendre OK dessus.
  • Controle positif exige : construire (ou trouver) le cas ou une lecture commente vraiment
    une ancre sans output, et montrer que l'organe le refuse. Sans controle positif, un organe
    qui ne dit jamais rien est indiscernable d'un organe debranche.
  • Mesure sur un lot d'au moins 15 PRs notebook (ouvertes + recemment mergees), avec le compte
    FP/VP declare dans le body de la PR.
  • Enregistrement dans scripts/ci/fast_lane_registry.py en blocking=False d'abord
    (advisory), comme check_output_collapse.py (ci: aucun organe ne mesure l'effondrement du VOLUME de sortie des notebooks (angle mort demontre sur #15209) #15327). Promotion en bloquant seulement apres
    une mesure de FP a zero.

Contexte

Mandat user recurrent : cristalliser tout outil neuf plutot que le laisser mourir dans un
scratchpad. Ce prototype a ete ecrit et mesure pendant le cycle /coordinate du 2026-09-18 ; il est
recopie ici integralement pour cette raison.

See #16619 · See #13815

Activity

  1. jsboige commented on Sep 18, 2026

    @jsboige
    Owner

    [CLAIMED] #16695 — myia-po-2023:CoursIA — 2026-09-18T16:57Z

    Dispatch ai-01 recu (DM 16:32Z) et acke. Livraison au cycle suivant (c.19) : detecteur d'ancrage de lecture corrige du FP « ## Conclusion » (controle negatif #16619 c32 exige OK), controle positif construit, enregistrement blocking=False (precedent #15327).

  2. jsboige commented on Sep 18, 2026

    @jsboige
    Owner

    [DELIVERED] PR #16704 — organe check_reading_anchor.py + TRANCHE13 advisory. Mesure 18 PRs : 0 FP (les 4 touches initiales = ouvertures Lean #16656, classe LECTURE_SANS_ANCRE retirée, 0 VP réel sur le lot). Contre-exemple #16619 c32 : OK (contrôle négatif du self-test). 2 positifs construits au self-test (ANCRE_SANS_OUTPUT, ANCRE_STUB). Self-test 6/6, pytest 10/10 + 73/73. — myia-po-2023:CoursIA 2026-09-18T15:00Z

  3. added 2 commits that reference this issue on Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions