Repository navigation
feat(repo): repo compare --metric judge:recall_judged reads judged recall - #395
antonwolfy wants to merge 8 commits into
Conversation
…call Second half of intel#291: `repo hits judge` writes `<cell>/hits_judged.json` with `recall_judged`, and `repo compare` could not read it, since every `--metric` came from `grade.json` `metrics`. A `--metric` named `judge:<field>` is now read from the cell's `hits_judged.json` (new `dokimi/repos/compare_judged.py`; `compare.py` only calls it). `recall_judged` is the one field taken; any other `judge:` name is refused with exit 2. A cell reports it only when the file is a `repo_hits_judged.v1` object naming the cell's own cell, task, arm and model, its `recall` equals the cell's current `grade.json` `metrics.recall` (else the cell was regraded since judging), and its `recall_judged` is a finite number; otherwise the metric is absent for that cell, as a missing `grade.json` metric is, and a null is never 0. `--json` gains `judged_cells`: per compared arm, the cells read, with no file, stale, of another cell, unjudged and unreadable (`null` without a `judge:` metric). The text prints one line per arm with a cell not read and one note that compare cannot tell whether the judge was calibrated. A `judge:recall_judged` no selected cell reports is refused with the `repo hits judge` command to run first. `--ledger` passes no metric and is unchanged. Docs: contract §4.5 and §9, reviewing.md, architecture.md, `--metric` help. Changelog fragment (Added). Closes intel#291 Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 6 selected item(s). CI at Review details
|
There was a problem hiding this comment.
🟡 Changes recommended
The recall-only validity check can accept obsolete judgments when credited thread–finding pairs change without changing recall, so it must validate the judged inputs and cover this case with a regression test.
1 open issue
What changed in this PR
1 new must-fix finding.
This PR completes the comparison support for #291, allowing repo compare to read judged recall produced by repo hits judge alongside location-based recall.
Changes:
- Adds
--metric judge:recall_judgedwith artifact checks and unsupported-field refusal. - Reports per-arm inclusion and exclusion counts, plus an uncalibrated-judge note.
- Adds regression tests, CLI help, documentation, and a changelog entry.
| File | Description |
|---|---|
dokimi/tests/test_repo_compare_judged.py |
Tests judged metrics, exclusions, diagnostics, and CLI output. |
dokimi/dokimi/repos/compare.py |
Integrates judged metrics into loading and summaries. |
dokimi/dokimi/repos/compare_judged.py |
Reads judged artifacts and reports per-arm counts. |
dokimi/dokimi/commands/repo_run.py |
Documents the new metric in CLI help. |
dokimi/docs/reviewing.md |
Explains comparison usage and calibration caveats. |
dokimi/docs/repo-evaluation-contract.md |
Defines judged-metric acceptance and output behavior. |
dokimi/docs/architecture.md |
Documents the new reader module. |
dokimi/changelog.d/20261104_120000_antonwolfy_compare-judged-recall.md |
Announces judged-recall comparison support. |
Review effort: Balanced
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #395 +/- ##
=======================================
Coverage 94.75% 94.76%
=======================================
Files 352 353 +1
Lines 46498 46505 +7
Branches 7969 7965 -4
=======================================
+ Hits 44061 44072 +11
+ Misses 1544 1542 -2
+ Partials 893 891 -2
🚀 New features to boost your workflow:
|
…s changed A regrade that credits other (thread, finding) pairs can keep the same recall, so the recall check alone read an obsolete judgment as current. Compare the saved n_threads and ordered pairs with grade.json too. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
…dels check_reported ran on every task-filtered cell before the arm and model selection, so a hits_judged.json of an excluded arm or model satisfied the metric and compare succeeded with no judged-recall row. Run it on the baseline and treatment cells of the --model selection. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
napetrov
left a comment
There was a problem hiding this comment.
Verdict: no blocking thread; one nit and one question on the inline threads. Tests not run here (scipy and regex are missing).
Question with no line: the refusal runs over all selected arms together, so compare exits 0 when the baseline has no readable file and a treatment does (0 of N cells read on the baseline line). That follows the documented "no selected cell reports it" rule. Was a refusal per compared arm intended?
Coverage: checked the diff, the zero-thread cell on head and base (probes/b1probe.py), prior threads T1 and T2 against head code. Not looked at: the test file (not run), hits_judge.py (outside this diff).
…own reason, a non-object match is skipped A zero-thread cell has recall_judged null because it has no pair to judge, not because a pair is unjudged, so it is counted as no_threads and the hint to run repo hits judge again no longer names it. _pairs skips non-object matches as report.load_hits does, so a hand-edited grade.json does not leave a cell stale for good. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
On the question without a line: yes, the refusal over all selected arms together is intended. It is the rule the contract states for a |
|
Round 2 summary (head ffa5143, CI green):
Both new tests fail when their fix is reverted. All review threads are resolved. |
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
Review at ffa5143. The reader is right for the cases it checks: another cell's file, a changed recall, n_threads or credited pairs, an excluded arm or model, and a null never read as 0. One kind of regrade still passes as current, and one hint asks for a rerun that changes nothing; both are in the line comments.
What I ran, at ffa5143:
make ci-local --offline: 15,638 passed, 0 failed, 63 skipped; every check passed (the network and Docker jobs were skipped).- A repro on the test fixtures: a
grade.jsonwhose credited pair(0, 0)has a newcommentafter judging is countedread; an_threads: 0cell gives1 of 2 cells read (1 with no gold thread); run repo hits judge again. - Mutation: with the credited-pairs check removed,
test_credited_pairs_that_changed_with_the_same_recall_make_the_file_stalefails.
Checked, no issues
- Earlier threads: the stale check now compares
n_threadsand the ordered pairs (cb39ae3),check_reportedruns on the baseline and treatment cells of--modelonly (6e1d55e), and a non-object match is skipped asreport.load_hitsskips it (ffa5143). - Refusal over all compared arms together: agreed with the answer to @napetrov. The per-arm line shows a baseline with
0 of N cells read, and a per-arm refusal would block comparing a judged arm with a partly judged one. - Docs and changelog: the contract §9,
reviewing.md,architecture.md, the--metrichelp and theAddedfragment match the code. The new files have the SPDX header.
napetrov
left a comment
There was a problem hiding this comment.
Re-review at ffa5143 (round 6e1d55e..ffa5143). Verdict: no new thread from me.
- My nit (zero-gold-thread cell reported as unjudged): fixed, own reason
no_threads. - My question (non-object
details.matchesentry): answered; entries now skipped asload_hitsdoes. - Round pass found one row: "run repo hits judge again" hint printed when every left-out cell is
no_threads. Same as @vlad-perevezentsev's open thread oncompare_judged.py(line 144); not repeated here.
Coverage: round diff, plain + edge pass, no probes beyond the passes' own. CI green on head.
A regrade can keep the same credited (thread, finding) pairs, recall and n_threads while the thread's comment changed (a `repo run` that re-exported _eval/content with a reworded thread), and compare read the old label as current. `repo hits judge` now writes each pair's `comment` and `message`, the grader's cuts the match holds, in hits_judged.json (two optional fields: repo_hits_judged.v1 only widens). `repo compare --metric judge:recall_judged` compares them with the grade's matches and counts the cell stale when they differ. A file written before the pairs recorded the cuts is checked on (thread, finding) only, as the contract now says. Settles the review thread on compare_judged.py:82 (a regrade that keeps the credited pairs but changes their text). Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
The per-arm line and the refusal said to run `repo hits judge` again even when every cell left out has no gold thread: a rerun writes the same null for such a cell and spends judge calls for nothing. Both now add the hint only when a cell is left out for a reason a rerun can change (RERUN: no file, stale, another cell's file, an unjudged pair, unreadable). The contract and the changelog fragment say so. Settles the review thread on compare_judged.py:144 (the rerun hint for cells with no gold thread). Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Codecov named four changed lines of compare_judged.py no test ran: the grade with no `matches`, a `matches` that is not a list, and a file whose pairs do not line up with the grade's in number. Each is reachable and now has a test of what it decides: a parse-error cell (no matches, no pair) is read with its measured 0 in the task's mean; a grade whose `matches` is not a list (one `report.load_hits` leaves out, so `repo hits judge` removes its file) and a file holding a pair fewer than the grade credits, at the same recall, are stale rather than read or an error. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
fd7cd1e wrote `HINT ="run ...`; put the missing space before the string. No behaviour changes. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
@vlad-perevezentsev (#395 (review)): Both points from this review are fixed: the reworded regrade in 1b38f3d and the rerun hint in fd7cd1e. Details are in the two line threads. |
@napetrov (#395 (review)): The row this pass found (the rerun hint when every cell left out has no gold thread) is the same point as the thread on compare_judged.py:144. fd7cd1e fixes it. The hint is now added only when a cell is left out for a reason a rerun can change. |

Summary
Closes #291. This is its last part: "
repo comparereadsrecall_judged". The first half,repo hits judgewriting<cell>/hits_judged.jsonwithrecall_judged, merged as #357. This half waited on #332, which moved the per-task values out ofrepos/compare.py(at its 800-line cap) intocompare_values.py. In the decision comment on #291, the maintainer asked that a figure be called uncalibrated unless labels back it, and thatrecall_judgedcountsame pointonly.On
main, every--metriccomes fromgrade.jsonmetrics, so judged recall cannot be compared across arms. A batch of two tasks, both arms judged, one treatment cell regraded since judging:With this PR:
--metric judge:recall_judged, cell's file valid and currentmetric:judge:recall_judgedgets the usual statistics, andn_baseline/n_treatmentcount the cells that report itrecall≠grade.jsonmetrics.recall), another cell's (cell/task/arm/modeldiffer),recall_judgednull, not JSON / not an object / wrongschema_version/ a directory / not UTF-8 / nested too deepjudged_cellsand on the per-arm text line--metric judge:recall(any otherjudge:field)--metric judge:recall: a judge: metric reads hits_judged.json, and the only one accepted is judge:recall_judgedno finished cell reports --metric judge:recall_judged (2 no hits_judged.json, 1 stale: regraded since judging): runrepo hits judge --judge PROVIDER/MODELfirstChanges
dokimi/repos/compare_judged.pyreadsjudge:<field>metrics from each cell'shits_judged.jsonand applies the rules above.recall_judgedis the only field it accepts. It counts each cell's reason per compared arm and writes the text lines. The file name comes fromhits_judge.HITS_JUDGEDand the schema version fromartifacts.SCHEMA_VERSIONS.dokimi/repos/compare.pyonly calls into it: inload_batches, before the cells are read (refusing a badjudge:name), next to_check_metrics, in the summary, and informat_compare. The file grows to 781 lines.repo compare --jsongainsjudged_cells. It isnullwithout ajudge:metric. Otherwise it holdsmetricsandarms: per compared arm,cells(of the compared models) and the countsread,no_file,stale,other_cell,unjudgedandunreadable.--ledgercallscomparewith no metrics (ledger.py:177), so ajudge:metric does not reach the rule verdicts. The contract says so; the ledger code is unchanged.repo comparebullet: thejudge:reading, which cells count,judged_cells, the per-arm line, the refusal and the uncalibrated note.repo hits judgetext: one sentence pointing to the compare reading.reviewing.md"Reading a replay batch": the compare command, and "quote it only next to a calibration".architecture.md: the new module.--metrichelp.Risk
A plain
--metric NAMEstill readsgrade.json, and its output is unchanged.repo compare --jsonhas one new key,judged_cells, which isnullunless ajudge:metric is asked for. A--metricstarting withjudge:gave exit 2 before ("no finished cell reports") unless agrade.jsonmetric had that literal name. Nowjudge:recall_judgedis read fromhits_judged.json, agrade.jsonmetric of the same name is ignored, and every otherjudge:name exits 2 with the new message. No exit code changes otherwise. Compare cannot see whether the judge was calibrated: the text says so, and the figure stays uncalibrated unlessrepo hits judge --labelsreported agreement.Testing
On a Linux review host, at
d72aa27(onmain5ce3207):make ci-local: 30 passed, 0 failed, 7 skipped (external-catalog-smoke, which needs docker and network;test-latest,docs.yml:deployandpip-audit.yml:report, whose job conditions are false on a PR; cli-surface "Package the result", an expression the local runner does not know; conda-forge-deps, no micromamba; semgrep secrets, offline). 15633 tests passed, 64 skipped; changed-line coverage 100% (79 lines). Not covered locally: the matrix legs on Python 3.11, 3.12 and 3.14.tests/test_repo_compare_judged.py(10 tests). The fixtures are written by the real writer (hits_judge.cell_doc+save_artifact). They cover:n;--metric recall --metric judge:recall_judgedgiving both rows;schema_version, a list, deep nesting, not UTF-8, a directory), each left out and counted injudged_cellsand the text line;judge:recall,judge:n_threadsandjudge:refused with exit 2;repo hits judgehint;--tasknarrowing;--json.mainexits 2 on--metric judge:recall_judged; this branch prints the rows and the lines shown.compare_judged.pyandcompare.pyfails the test that pins it. The breaks: identity check dropped, stale check dropped, null read as 0,schema_versioncheck dropped,RecursionErrornot caught, agrade.jsonmetric of the judge name kept, otherjudge:fields accepted, hint refusal dropped, per-arm line for no arm, per-arm line for every arm, note dropped,judged_cellsalways null,judged_cellscounting cells outside--task,hits_judged.jsonnever read, note printed without a judge metric.Revert
A plain revert removes the reading,
judged_cellsand the docs. Nothing stored changes:hits_judged.jsonand its schema are only read.Checklist
pytestpassesruff,mypy, and schema checks passAGENTS.md.github/.licenserc.yaml)