The owner reviews what changed, why it changes behavior and what remains untested. A short diff is easier to assess than an unrelated cleanup mixed into a bug fix. Read the proposed implementation, not only its summary or a green check panel.
Our narrow rubric flags out-of-scope files, duplicate changes, unchanged files, API changes, remaining float calls and a few obvious execution risks. A rubric cannot prove arbitrary code safe. It helps organize a small review while acceptance tests check the stated decimal contract and a human explains the chosen algorithm.
The tempting patch round(float(text)*100) fixes the reported fraction but preserves input coercion and the wrong grammar. Tests of only "0.29" would miss that. A second candidate changes the expected result instead of repairing the implementation; its file change is outside the final task scope.
Inspect imports, top-level effects, the public function signature and the reasoning about integer arithmetic. The strict correction parses ASCII groups, converts whole and fractional digits separately, and applies the sign after constructing an integer magnitude. It never needs a floating-point conversion.
import ast
code = "def parse_cents(text: str) -> int:\n return round(float(text)*100)\n"
tree = ast.parse(code)
calls = sorted(node.func.id for node in ast.walk(tree)
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name))
print(calls)
print("float remains", "float" in calls)Static review and dynamic acceptance answer different questions. The AST rubric can accept a signature-preserving return0 as structurally unremarkable; acceptance then rejects it. Conversely a candidate could pass many examples while changing an unrelated file or adding a top-level side effect; review should still reject it.
Small diff scope improves confidence but does not remove the need for explanation. Ask why the grammar is complete, why the integer formula preserves cents, which limits are deliberate, and what behavior remains outside the task. No dependency or network access is needed for this correction.
allowed = {"money.py"}
changes = [("money.py", "fix"), ("tests.py", "expected 28")]
for path, summary in changes:
print(path, "in scope" if path in allowed else "reject scope")
print("test success alone is insufficient")Implement the static rubric, then compare its findings with behavioral acceptance. Inspect both flawed proposals in the lab. The final lesson binds the final written source, the actual checks and the explanation into one reproducible review trail.
A plausible patch still needs evidence.
Choose a bundled proposal to inspect its real Git diff and observed checks. This is an offline teaching replay, with no live model session. The owner’s final task permits changes only to money.py.
1 · Actual observed checks
- failedunit
python -I acceptance_runner.pyObserved exit 1 - passedsyntax
python -I syntax_check.pyObserved exit 0 - passedformat
ruff format --check money.pyObserved exit 0 - passedtype
mypy --strict money.pyObserved exit 0
Source and sealed-test snapshot
de2089e0cdc68fe40f443d6f1333e12de19fb3ad1a378424afad64f2d4dbde8aA digest identifies bytes. It does not authenticate arbitrary agent claims.
2 · Review findings
acceptance:exponentacceptance:fraction_digitsacceptance:leading_zeroacceptance:oversizeacceptance:plusacceptance:spacesacceptance:typeacceptance:unicode_digitsfailed:unitfloat_remains:money.py
3 · The real Git diff
diff --git a/money.py b/money.py
index 98feda2..2605956 100644
--- a/money.py
+++ b/money.py
@@ -1,2 +1,2 @@
def parse_cents(text: str) -> int:
- return int(float(text) * 100)
+ return round(float(text) * 100)