Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions scripts/check_markdown_claims_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,16 @@ def _nearest_inline_code_span(prose: str, pos: int) -> str | None:
r"[<>≤≥]=?\s*\d|\d\s*[<>≤≥]=?",
)

# Family D (#14905): grid coordinates inside parentheses, like
# '(2,2)' / '(1,1)', are TUPLES (cell positions), not francophone decimals.
# _normalize_num collapses '(2,2)' -> '2.2', so the detector searches for a
# '2.2' that never exists in the 3x3-grid code output and flags a legitimate
# prose coordinate as fabricated (DecPyMC-7 md[67]: 'un but en (2,2) et un
# obstacle en (1,1)'). Criterion (acceptance #14905): a SINGLE digit on each
# side of a comma, wrapped in a parenthesized pair. '(0,75)' -- two digits
# after the comma -- is a genuine decimal and stays eligible.
_COORD_TUPLE_RE = re.compile(r"\(\s*\d\s*,\s*\d\s*\)")


def _line_around(src: str, match_pos: int, match_end: int) -> str:
"""Return the source line containing the [match_pos, match_end) span.
Expand Down Expand Up @@ -554,6 +564,25 @@ def _is_threshold_expression(src: str, match_pos: int, match_end: int) -> bool:
return bool(_THRESHOLD_OP_RE.search(span))


def _is_coordinate_tuple(src: str, match_pos: int, match_end: int) -> bool:
"""Family D (#14905): a SINGLE digit on each side of a comma wrapped in
parentheses -- '(2,2)' / '(1,1)' -- is a grid-coordinate tuple, not a
francophone decimal. _normalize_num collapses '(2,2)' -> '2.2', so the
detector hunts for a '2.2' that never exists in the 3x3-grid code output
and flags a legitimate prose coordinate as fabricated (DecPyMC-7 md[67]:
'un but en (2,2) et un obstacle en (1,1)'). Suppressing this match
requires BOTH: the token is exactly '<digit>,<digit>' AND it is wrapped
by an immediately-enclosing parenthesized pair. '(0,75)' -- two digits
after the comma -- is a genuine decimal and stays eligible.
"""
token = src[match_pos:match_end].strip()
if not re.fullmatch(r"\d,\d", token):
return False
start = max(0, match_pos - 1)
end = min(len(src), match_end + 1)
return bool(_COORD_TUPLE_RE.search(src[start:end]))


def _is_scalar(value: object) -> bool:
"""Return whether value is a finite JSON scalar supported by claims."""
if isinstance(value, bool):
Expand Down Expand Up @@ -853,6 +882,8 @@ def check_notebook(path: Path) -> dict:
continue
if _is_threshold_expression(prose, match_pos, match_end):
continue
if _is_coordinate_tuple(prose, match_pos, match_end):
continue
# Search the normalized form in the output text (plus adjacent
# variants: e.g. "0.24" present in "0.2385")
if norm not in out_text and not _fuzzy_present(norm, raw_out_text):
Expand Down
78 changes: 78 additions & 0 deletions scripts/tests/test_check_markdown_claims_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,84 @@ def test_diagnostic_medical_threshold_clean(self, tmp_path):
)


class TestCoordinateTupleFilter:
"""c.NNN (#14905): token '(N,N)' avec un chiffre UNIQUE de part et
d'autre de la virgule, entre parentheses, est un tuple positionnel
(grille), PAS un decimal francais. `_normalize_num` collapsait
'(2,2)' -> '2.2', donc le detecteur cherchait un '2.2' inexistant
dans la sortie d'une grille 3x3 et flaguait une prose pedagogique
legitime comme fabriquee (DecPyMC-7 md[67]). Criteres d'acceptation
(#14905) : '(0,75)' -- deux chiffres apres la virgule -- reste un
vrai decimal (eligibile), et une VRAIE fabrication reste attrapee.
"""

def _scan_tmp(self, tmp_path, cells):
import json as _json
nb = _mk_nb(cells)
p = tmp_path / "fixture.ipynb"
p.write_text(_json.dumps(nb, ensure_ascii=False), encoding="utf-8")
return check_notebook(p)

def test_grid_coordinates_clean(self, tmp_path):
"""Founding case: DecPyMC-7 md[67] -- cible/obstacle positions on a
3x3 grid. Prose cites '(2,2)' and '(1,1)'; the grid output never
prints '2.2' nor '1.1'. BEFORE (issue #14905) this returned
FABRICATION_DETECTED; AFTER the filter it is CLEAN."""
cells = [
_code_cell(
"g = {(2,2):'B', (1,1):'O'}\n"
"for r in range(3):\n"
" for c in range(3):\n"
" print(r, c, '->', 'B' if (r,c) in g else 'E')\n",
[_stream_output(
"0 0 -> E\n0 1 -> E\n0 2 -> E\n"
"1 0 -> E\n1 1 -> O\n1 2 -> E\n"
"2 0 -> E\n2 1 -> E\n2 2 -> B\n"
)],
),
_md_cell(
"Dans une grille 3x3, la cible est un but en (2,2) et "
"un obstacle en (1,1).\n"
),
]
result = self._scan_tmp(tmp_path, cells)
assert result["verdict"] == "CLEAN", (
f"Expected CLEAN after #14905 coordinate-tuple filter, "
f"got {result['verdict']} with findings: {result['findings']}"
)

def test_parenthesized_short_decimal_still_eligible(self, tmp_path):
"""'(0,75)' has TWO digits after the comma -- a genuine decimal,
not a single-digit coordinate. Acceptance criterion #14905: this
must stay eligible (a real fabrication here is still flagged).
Output prints nothing close to '0.75', so it is FABRICATION."""
cells = [
_code_cell("print('loss=0.23')", [_stream_output("loss=0.23")]),
_md_cell("La precision mesuree est (0,75).\n"),
]
result = self._scan_tmp(tmp_path, cells)
assert result["verdict"] == "FABRICATION_DETECTED", (
f"Expected FABRICATION_DETECTED for '(0,75)' (two digits, real "
f"decimal), got {result['verdict']} with findings: {result['findings']}"
)

def test_real_fabrication_still_detected(self, tmp_path):
"""CONTROLE POSITIF (cf TestFounderPreserved): the c.290 pathologie
-- prose cites a magnitude ABSENT from the output -- must STILL be
flagged after the coordinate filter. Guards the false-negative
edge: '~1,2 M' / '0,09 %' remain detected."""
cells = [
_code_cell("print('trainable params: 3,145,728 (0.24%)')", [
_stream_output("trainable params: 3,145,728 (0.24%)"),
]),
_md_cell("On attend ~1,2 M de parametres entrainnables, soit ~0,09 %."),
]
result = self._scan_tmp(tmp_path, cells)
assert result["verdict"] == "FABRICATION_DETECTED", result
norms = {f["normalized"] for f in result["findings"]}
assert "0.09" in norms, result


# -----------------------------------------------------------------------
# c.415 (#11873) -- integration: REAL fabrication still detected
# -----------------------------------------------------------------------
Expand Down
Loading