Repository navigation
Foundation: deterministic helpers and evaluation for ordered frames (#309) - #323
Conversation
#309) Add jev_cookbook.frames (sample_frames, frame_timestamps, select_frames, frame_intervals, relative_frames, window_timestamps) and evaluation.transition_metrics, with docs and hand-computed edge-case tests. vision.py is untouched (#321 is editing it). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Orchestrator adjudication: the helpers live in a new |
|
Opus reviewer (independent review 1 of 2). Resolved model: Claude Opus 5.5 ( The sampling helpers are careful work. The window, BlockingB1. The timing error includes examples where the predicted label is wrong ( The issue asks for "earliest-transition error in seconds". That means how far off the model was on the transition. The time attached to a different event is not that. In the PR's own hand-computed test, the wrong-label row (ex4, error 0.0) pulls the MAE down from 0.2 s to 0.133 s. A README that quotes Change: take the error only over resolved examples where the labels match and both times are given. B2. The documented tie rule fails when two frames lie within the slack of each other ( Frame 1 sits exactly on the target. Frame 2 is 5e-10 s away, which the slack treats as a tie, and "a tie goes to the earlier frame" says index 1. Suggestions (non-blocking)
Checks I ran (Windows 11, Python 3.14, fresh venv,
|
|
Opus reviewer (independent review 2 of 2). Resolved model: Claude Opus 5.5 ( Reviewed head SummaryThe arithmetic is careful: validation matches Blocking1. No convention says which second a detected transition is, so Jev's
So the same model answers can give 0 % or 100 % tolerance accuracy depending on a choice each builder will make independently. The gold labels have the same problem: does a labeller mark the first full-rate frame that shows the new state, or the moment of change? The issue also requires that sampling be "stated". When Change: fix one convention in code and docs. I'd suggest "a transition's time is the 2. The earliest-transition error includes predictions with the wrong label.
Change: take the timing error only over resolved examples whose label matches gold. If the all-labels view is wanted too, report it as separate fields, for example Suggestions (non-blocking, but each removes hand-rolled timestamp arithmetic a recipe would otherwise copy)
Fit verdict per recipe (from the sketch)
Overall the interface is sound for picking frames and labelling them, but as it stands it will push builders to hand-roll the timestamp arithmetic that decides the reported number: transition time, frame-at-time lookup and window enumeration. Blocking 1 and 2 are the ones that change what READMEs report. Checks run (fresh venv, Windows,
|
Timing error now covers only label-matched examples (any-label view in separate fields); the tie rule holds for frames closer than the slack; one transition-time convention (first frame showing the new state) as frames.transition_time and earliest_transition, with tolerance versus sampling interval recorded; frame_at, frame_windows, frame_index on selected copies; several review labels, JSON lists, ValueError for huge numbers, per-example outcomes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Opus reviewer (independent review 1 of 2, re-review). Resolved model: Claude Opus 5.5 ( What I reviewed: the fix commit My round-1 findingsB1 (timing error included wrong-label examples): resolved.
B2 (tie rule broken when two frames lie within the slack of each other): resolved for the case I raised. The selection now looks at
There is one residual case, which is suggestion 1 below. My round-1 suggestions:
New in this round (from the other review), checked
BlockingNone. Suggestions (non-blocking)
Checks I ran (Windows 11, Python 3.14, fresh venv reinstalled at this head, no
|
|
Opus reviewer (independent review 2 of 2, re-review). Resolved model: Claude Opus 5.5 ( Reviewed head My round-1 findings (comment 6303821403)Blocking 1 (no convention for a transition's time): resolved. The convention is now "the Blocking 2 (timing error counted wrong labels): resolved. Suggestions:
Blocking (new, in helpers added this round)1.
Change: accept 2. With frames every 0.5 s from 0.0 to 8.0 and Change (any one): add a final window aligned to Suggestions
Fit after this round (sketch re-run: ranks 85, 92 and 99, plus 87/89/95 probes; script at scratchpad
|
earliest_transition accepts None (and NumPy booleans) and returns an explicit review result rather than a later transition; frame_windows adds a final end-aligned window; docs state the actual tie guarantee, late-only for correct detections, and dropping frame_index; frame_index and uncertain_labels are validated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Opus reviewer (independent review 2 of 2, re-review 2). Resolved model: Claude Opus 5.5 ( Reviewed head My round-2 findings (comment 6304297506)Blocking 1 (
Tests: Blocking 2 (
Suggestion: drop Suggestion: Other changes since
|
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PR #323 — Opus review (independent review 2 of 2, fresh reviewer)
My brief's focus for this review: The merge itself
Nothing unintended came in with the merge. Scope and dependencies
All inside Scope observation (not a defect): #309 asks for four helpers plus one metric; the PR ships ten public Must changeM1.
|
| Command | Result |
|---|---|
ruff check . |
All checks passed (rc 0) |
ruff format --check . |
399 files already formatted (rc 0) — this is the run that covers the Python code blocks in docs/*.md, so both new blocks are formatting-clean |
python tools/check_hygiene.py |
Hygiene check passed: 608 file(s) scanned (rc 0) |
python tools/render_catalog.py --check |
rc 0, no output |
python tools/notebook_ci.py fixtures |
51 recipe folders, 0 failed (rc 0) |
python tools/check_recipe_assets.py |
Image-asset check passed: 461 file(s) under recipes/ checked (rc 0) |
pytest tests/test_docs_examples.py tests/test_frames.py tests/test_evaluation.py -q |
339 passed, 3 skipped in 4.0 s (rc 0) |
pytest -q (full, tests/ + recipes/) |
5459 passed, 49 skipped, 50 deselected, 31 xfailed, 0 failed in 2 min 51 s (rc 0) |
Notes on those runs, read rather than taken on trust:
- Nothing failed. The two Windows-only
tests/test_notebook_ci_sandbox.pyfailures the PR body
reports do not occur here (Linux), andmain'sc316712(Foundation: two notebook-CI sandbox tests use signal.SIGKILL and fail on Windows #302 / Foundation: make the sandbox kill helper and docs-example timeout portable to Windows (#302) #325) is the fix for them anyway. - The
docs/evaluation.mdblock really executed.pytest tests/test_docs_examples.py -q -rsshows
the only three skips are:280,:296and:305, all "exercises the Windows-only SIGBREAK timer
path" — not the block-execution tests. Sotest_block_executes[8]ran and passed on this head, and
the 50 deselected tests in the full run are the defaultcatalog_auditdeselection, unrelated to this
diff. ruff format --checkis the only automated check that reads thedocs/vision.mdblocks, and it
checks formatting only — which is why M2 is invisible to CI.gh pr checks 323on this exact head: 60 of 60 pass, none pending or failed (Lint, Tests py3.10
and py3.14, Catalog, Hygiene, Fixtures, Scope and every Notebook job). The GitHub check-runs API
reports 30 success and no other conclusion.
Reproductions of my own, run directly against the diff (standard library only, src on PYTHONPATH,
scripts in /tmp, nothing written into the checkout):
frame_windowscoverage sweep and thewidth=1, stride=5counterexample (M1).- The
docs/vision.mdwalkthrough against the page's ownload_framesframes, and the
every_seconds=0.5variant that works (M2). - The
countformula against exactFractionround-half-down for everynin 1..39 andkin 1..n. every_secondsagainst a brute-force nearest/earliest-within-slack reference, 4000 random sequences
x 8 rates, seed 309.- The two documented worked examples (
count=3over[0, .1, .2, .3, 10]; windows 0/2/4/5).
Git-level checks: git show --cc HEAD (empty), git merge-tree --write-tree 6b18804 c316712 against
HEAD^{tree} (identical), git log --format='%G?' -1 (G), git diff --raw -M origin/main...HEAD
(7 paths, all 100644), git grep over recipes/ for the new names (no hit, so no notebook to
execute), git merge-base --is-ancestor c3167129 HEAD (true).
I did not execute any notebook: no recipe imports jev_cookbook.frames or
evaluation.transition_metrics, and the diff touches no notebook, fixture or recipe folder.
One process note for the record: my first install invocation used uv pip install --extra dev -e .,
which uv rejected ("Requesting extras requires a ... pyproject.toml file"); re-running it as
-e ".[dev]" worked. No check was skipped and nothing failed for lack of space (/workspace was at
~500 MB free at the start and recovered to ~8 GB during the run).
Prior review comments — status at this head
Read only after forming the view above.
Comment 6303367867 (review 1, round 1, head 8ff6588c)
- B1 timing error included wrong-label examples — resolved.
evaluation.pyappends to the
headlineerrorsonly underlabel_ok; the all-labels view isn_timed_any_label/
mean_*_any_label_seconds;test_transition_metrics_wrong_label_at_the_right_second_is_not_good_timing
pins it, and the hand-computed test now expectsn_timed == 2, MAE 0.2 (I re-derived both). - B2 tie rule broken for frames within the slack — resolved for the two-frame case. The search
spanscursor-1..cursor+1and takes the earliest withinTIME_EPSILONof the closest;
test_tie_rule_holds_when_frames_are_closer_than_the_slackcovers[0, 1, 1+5e-10, 2] -> [0, 1, 3].
My 4000-trial brute-force sweep found no disagreement. - S1 two-element lists — resolved (
_transitionaccepts any non-str/bytesSequenceof length 2). - S2 several review labels — resolved (
uncertain_labels; a bare string is one label, tested). - S3 one validator,
ValueErrornotOverflowError— resolved (frames._secondsreused; see my
suggestion 2 on it being private). - S4 per-example outcomes — resolved (
outcomes,timing_errors_seconds). - S5
countis position-based, state the consequence — resolved (docs/vision.md:276with the
worked example; I verified the numbers). - S6 absolute slack, say "since the start of the sequence" — resolved in both docs, plus the note
that the slack is meaningless at epoch scale. - S7 one module spelling, record the deviation — resolved in the docs (
docs/vision.md:233-235:
jev_cookbook.framesis the one import path, no re-export planned) and in the PR body. Whether Foundation: no deterministic helpers or evaluation for ordered frames (sampling, intervals, relative times, windows, transitions) #309
itself was annotated is outside the diff; I did not check.
Comment 6303821403 (review 2, round 1, head 8ff6588c)
- Blocking 1 no transition-time convention — resolved. One convention ("the
timestamp_seconds
of the first frame in which the new state is visible") inframes.transition_time,
frames.earliest_transition, thetransition_metricsdocstring anddocs/evaluation.md:768-779,
with "do not substitute the pair's start or midpoint", plusmax_sampling_interval_seconds/
tolerance_below_sampling_intervalandtest_transition_convention_end_to_end. - Blocking 2 wrong-label timing — resolved (same change as review 1 B1).
- S frame-at-time lookup — resolved (
frame_at, before / after / nearest, ties to the earlier
frame, raises when nothing qualifies). - S sliding windows — resolved in shape, but its documented guarantee is now wrong:
frame_windows
exists and keeps empty windows visible withindices == [], which is good; see M1. - S index spaces — resolved (
frame_indexonselect_framescopies, preserved across re-selection
and now validated; "Index spaces" paragraph atdocs/vision.md:316-322). - S multi-transition sequences — resolved (
docs/evaluation.md:838-844: split per transition, "do
not invent a sequence-alignment metric"). - S two review sources, one label — resolved (
uncertain_labelsplural; the docs say to map both
the model's uncertain option and Python's gate to one of them).
Comment 6304216637 (review 1, re-review, head 2cef5c7e) — APPROVE
- S1 clusters of 3+ frames within the slack — resolved by narrowing the claim, not by code:
docs/vision.md:269-271now says the tie rule is "guaranteed for up to two such frames around a
target". That is honest, and I agree the_orderedrejection was not needed. - S2
earliest_transitionrejectednp.bool_— resolved (dtype-kind check;
test_earliest_transition_accepts_numpy_booleans). - S3
frame_windowscan leave trailing frames in no window — addressed for the tail, but the fix
introduced M1. The suggested wording ("frames after the last full window are not covered… or add a
final window") became an unconditional "no frame is left out", false forstride > width. Not
resolved as a documentation matter. - S4 "its time can only be late" — resolved ("a correct detection can only be late", plus the
negative-signed-error note for a false early flag). - S5
frame_indexreaches the state; validate an inherited one — resolved (docs say to drop it;
test_select_frames_validates_an_existing_frame_index).
Comment 6304297506 (review 2, re-review, head 2cef5c7e) — CHANGES-NEEDED
- Blocking 1
earliest_transitioncould not represent an undetermined pair — resolved.None
(and NumPy booleans) accepted; an undetermined pair before the firstTrue, or with noTrueat all,
returns{"review": True, "first_undetermined_index": i, ...}with no time; found transitions carry
"review": False; the docstring says never to coerce toFalse; both docs say to score it as one of
uncertain_labels; two dedicated tests. - Blocking 2
frame_windowsleaves the end uncovered — partially resolved: the tail is covered
and tested, but the accompanying guarantee is an overclaim forstride > width(M1). - S drop
frame_indexfrom the state — resolved (docs/vision.md:290-292). - S
uncertain_labels=Noneraised a bareTypeError— resolved (ValueErrorwith a message;
test_transition_metrics_rejects_missing_or_bad_review_labels).
Comment 6304979903 (review 2, re-review 2, head 6b18804) — APPROVE
- S the 148-character docstring line at
frames.py:289and the overlongdocs/evaluation.mdparagraph
line — not resolved.frames.py:289is still 148 characters (and is the line carrying M1's false
claim);docs/evaluation.md:778is still 134. Both were non-blocking; fold them into the M1/M2 fix. - S
earliest_transitionreturns a truthy dict for a review as well as a find, soif found:is
misleading — not addressed beyond the existing documentedreviewkey. Still non-blocking; see my
suggestion 9.
Acceptance criteria of #309, one by one
sample_frames(timestamps, *, every_seconds, count, start, end)returns selected indices
deterministically; the docs state the tie-breaking rule; edge tests cover empty, single, exact
boundaries and float drift — met in substance, atjev_cookbook.frames.sample_framesrather
thanvision.sample_frames. The deviation is deliberate, documented as the single import path with
no re-export (docs/vision.md:233-235), stated in the PR body, and accepted by both prior reviewers;
I accept it too (no Pillow import, no collision with Foundation: enforce the Images API's 280 px minimum, hash image bytes in replay keys, bound tiles (#306) #321'svision.py). Determinism, the tie rule
and all four edge classes are tested, and I re-derived thecountandevery_secondsrules against
independent references with no mismatch.frame_intervalsandrelative_frames(addsseconds_ago, order unchanged), both tested —
met (tests/test_frames.py:217-251; the input rows are provably not mutated).window_timestamps(state_frames, first_index, last_index)returns start, end and duration, with
tests — met (tests/test_frames.py:254-280). Its only worked example in the docs raises (M2),
which is a docs defect, not a helper defect.transition_metricsreporting exact and within-tolerance accuracy; an explicit uncertain/review
count with a stated denominator; earliest-transition error in seconds; documented in
docs/evaluation.md; tested with hand-computed cases — met. Both denominators are named and
computed (n_resolvedandn_examples, with*_accuracyand*_accuracy_all); the timing error is
label-matched with the any-label view kept under separate names; the documented convention makes the
number well defined rather than builder-dependent; the hand-computed tests and the executed docs
block agree with my own arithmetic.docs/vision.mddocuments sampling, that no video decoding happens (frames are pre-extracted
stills), and how a recipe records the selected timestamps infields— not met at this head.
All three subjects are present and the sampling and no-video statements are accurate, but the section
contains a false guarantee aboutframe_windows(M1) and a worked example that raises on the page's
own data (M2), and thefieldsexample drops the image path the same document requires (suggestion
4). These are exactly the claims a recipe author will copy.- No new dependency; Pillow only — met.
pyproject.tomluntouched;frames.pyis
stdlib-only;evaluation.pygains only an intra-package import.
Noticed outside this task (reported, not fixed)
docs/development.mdnames a ruff version the repository no longer pins. It says "Run
pre-commitwith the project virtual environment activated so the pinnedruff==0.16.10is first on
PATH", butpyproject.toml:24pinsdev = ["ruff==0.17.1", "pytest"], and that is what a fresh
pip install -e ".[dev]"gives (ruff 0.17.1 in my venv; both prior reviewers also reported 0.17.1).
This is onmain, not in this PR's diff, so I have not touched it; it belongs in its own foundation
issue.
Verdict
Three must-change items, all in docs or docs-adjacent text and all introduced by the most recent fix
round, so neither prior reviewer saw them: a coverage guarantee stated three times that is false for
every stride > width configuration of a helper sixteen recipes will copy (M1), a worked example that
raises on the document's own data with no test covering it (M2), and a block count the PR itself made
wrong (M3). Everything else I checked — the transition-time convention, the denominators, the count
and every_seconds arithmetic, the tie rule, the fit with the multiframe row of
docs/multimodal-track.md, consistency with #321/#322's image rules, the merge, the scope and the
dependency rule — I verified independently and found sound. The fixes are small and local to
src/jev_cookbook/frames.py, docs/vision.md, tests/test_docs_examples.py and the PR body.
OPUS-REVIEW: CHANGES-NEEDED 4f338f9
PR #323 review - reviewer 1 of 2 (fresh, independent)
The merge itself
No symlink ( The PR body still describes the earlier merge ("Merged Must changeM1.
|
| Finding | Status at 4f338f9 |
|---|---|
| B1 timing error counted wrong-label predictions | Resolved. errors.append is now inside if label_ok; the all-labels view moved to n_timed_any_label / mean_*_any_label_seconds. transition_metrics([("opened", 2.0)], [("closed", 2.0)], tolerance_seconds=0.5) gives n_timed=0, MAE NaN, n_timed_any_label=1. Pinned by test_transition_metrics_wrong_label_at_the_right_second_is_not_good_timing and by the hand-computed test (n_timed == 2, MAE 0.2). |
| B2 tie rule broken for two frames inside the slack | Resolved for the case raised. near = range(cursor-1, cursor+2) plus "earliest within slack of the closest"; sample_frames([0, 1, 1+5e-10, 2], every_seconds=1) == [0, 1, 3], tested with its mirror. Residual for clusters of three or more: S3. |
| S1 accept a 2-element list | Resolved. _transition accepts any non-str/bytes Sequence of length 2; tested with JSON-shaped lists. |
| S2 several review labels | Resolved. uncertain_labels: Collection | str; a bare string is one label; tested. |
S3 one validator, ValueError not OverflowError |
Resolved (10**400 raises ValueError for the time, the tolerance and the interval, each tested) - but through a private import, see S1 above. |
| S4 per-example outcomes | Resolved. outcomes and timing_errors_seconds. |
S5 document that count is position-based |
Resolved. docs/vision.md with the [0, .1, .2, .3, 10] example, plus test_count_spreading_is_by_position_not_by_time. |
| S6 "elapsed seconds since the start of the sequence" | Resolved in both docs and the module docstring. |
| S7 one module spelling, no re-export | Resolved in the docs; the issue text still says vision.* (S9). |
Comment 6303821403 (review 2, round 1, on 8ff6588c)
| Finding | Status at 4f338f9 |
|---|---|
| Blocking 1: no convention for which second a transition is | Resolved. transition_time / earliest_transition plus "One convention for the time" in docs/evaluation.md, with the explicit "do not substitute the pair's start or midpoint", max_sampling_interval_seconds, tolerance_below_sampling_interval, and an end-to-end test. |
| Blocking 2: timing error included wrong labels | Resolved (same as B1 above). |
| S frame-at-time lookup | Resolved. frame_at with before / after / nearest, tie to the earlier frame, raising when nothing qualifies; tested. |
| S sliding-window enumeration | Added (frame_windows), but its coverage contract is wrong - M1. |
| S index spaces | Resolved. select_frames adds and preserves frame_index, validated; "Index spaces" paragraph in docs/vision.md; tested. |
| S multi-transition sequences | Resolved. "Several transitions in one sequence" in docs/evaluation.md. |
| S two review sources, one label | Resolved. A collection of labels plus the docs sentence about mapping the model's option and Python's gate. |
Comment 6304216637 (review 1, round 2, APPROVE on 2cef5c7e)
| Finding | Status at 4f338f9 |
|---|---|
| S1 clusters of 3+ frames inside the slack | Not fixed in code; docs narrowed to "up to two such frames around a target". Defensible; residual recorded as S3. |
S2 earliest_transition rejected np.bool_ |
Resolved. dtype-kind b accepted; test_earliest_transition_accepts_numpy_booleans. |
S3 frame_windows can leave trailing frames in no window |
Addressed for the tail (end-aligned window) but the replacement claim overclaims - M1. |
| S4 "its time can only be late" | Resolved. Both docs now say a correct detection can only be late, and name the negative signed error for a false early flag. |
S5 frame_index in the state; unvalidated inherited frame_index |
Resolved. docs/vision.md:292 says to drop it; select_frames validates a non-negative integer; tested with "x", -1, 1.5, True, None. |
Comment 6304297506 (review 2, round 2, CHANGES-NEEDED on 2cef5c7e)
| Finding | Status at 4f338f9 |
|---|---|
Blocking 1: earliest_transition could not represent an undetermined pair |
Resolved. None accepted, review result with no time, "never coerce to False" in the docstring, scoring guidance in both docs, four tested branches including [None, True, True]. |
Blocking 2: frame_windows left the end uncovered |
Addressed; see M1 for the residual overclaim the fix introduced. |
S drop frame_index from the state |
Resolved. |
S uncertain_labels=None raised a bare TypeError |
Resolved. ValueError: uncertain_labels must be a label or a collection of labels, tested for None, 3, 2.5. |
Comment 6304979903 (review 2, re-review 2, APPROVE on 6b18804)
| Finding | Status at 4f338f9 |
|---|---|
S frames.py:289 is a 148-character line (and a long docs/evaluation.md paragraph line) |
Not fixed. Still 148 characters. S7. |
S earliest_transition returns a truthy dict for a review too, so if found: no longer means "found" |
Unchanged by design; the review key is documented. Worth setting the None / review / found three-branch pattern in the first recipe that uses it, as that comment suggests. |
Acceptance criteria of #309
sample_frames(timestamps, *, every_seconds, count, start, end)returns indices
deterministically, docs state the tie-breaking rule, edge tests cover empty, single, exact
boundaries and float drift - met (verified against a 20,000-case brute-force reference). It
ships asjev_cookbook.frames.sample_frames, notvision.sample_frames: a documented, justified
deviation I accept; amend the issue text (S9).frame_intervals(state_frames)returns durations;relative_frames(state_frames, now=None)adds
seconds_agowithout changing order; both tested - met (module path as above).window_timestamps(state_frames, first_index, last_index)returns start, end and duration, with
tests - met.evaluation.transition_metrics(gold, predicted, *, tolerance_seconds)reports exact and
within-tolerance accuracy, an explicit uncertain/review count with a stated denominator, and
earliest-transition error in seconds; documented indocs/evaluation.mdand tested with
hand-computed cases - met, under one documented transition-time convention, with both
denominators stated and the docs example executed in CI.docs/vision.mddocuments sampling, that no video decoding happens (frames are pre-extracted
stills), and how a recipe records the selected timestamps infields- met in substance, one
statement wrong: the sampling, no-decoding andfieldsmaterial is all there and accurate, but
theframe_windowsparagraph (docs/vision.md:302-305) states a coverage guarantee that does not
hold (M1).- No new dependency; Pillow only - met.
frames.pyis standard library only,evaluation.py
gained no import beyondcollections.abc.Collectionand.frames, andpyproject.tomlis
unchanged.
Checks run
Linux, Python 3.14.7, fresh uv venv .venv-r1 with
uv pip install --link-mode symlink --extra dev -e ., no TYPESAFE_API_KEY set, no live call, no
network use by any check. Every heavy command ran under flock .claude/heavy.lock nice -n 10, and
pytest with -p no:cacheprovider (free disk on /workspace stayed between 0.3 and 0.7 GB
throughout, so the venv used symlinked wheels and was deleted afterwards).
ruff check .(ruff 0.17.1, the pinned version from the venv) - All checks passed, rc 0.ruff format --check .- 399 files already formatted, rc 0.pytest -q -p no:cacheprovider- 5459 passed, 49 skipped, 50 deselected, 31 xfailed, 0
failed in 182 s. (The 50 deselected arepyproject.toml's defaultcatalog_audit
deselection. No failure at all here, including the twotests/test_notebook_ci_sandbox.py
signal.SIGKILLcases that fail on the author's Windows machine - this run is on Linux.)pytest -q -p no:cacheprovider -rs tests/test_frames.py tests/test_evaluation.py tests/test_docs_examples.py- 339 passed, 3 skipped. The three skips are
tests/test_docs_examples.py:280/296/305, all "exercises the Windows-only SIGBREAK timer path;
POSIX uses SIGALRM", sotest_block_executesitself ran: the newdocs/evaluation.md
transitions block really was executed and linted here, not skipped.python tools/check_hygiene.py- Hygiene check passed: 608 file(s) scanned, rc 0.python tools/render_catalog.py --check- rc 0 (README is current; the diff touches no recipe
and no catalog entry, so nothing was expected to change).python tools/notebook_ci.py fixtures- 51 recipe folders, 0 failed, rc 0.git show --cc HEAD- no diff hunks (the merge contributed nothing of its own).git diff --raw -M origin/main...HEAD- the 7100644paths listed above.- Notebook execution: nothing under
recipes/importsjev_cookbook.framesor
transition_metrics, and the diff touches no recipe, so no notebook is affected and none was
executed. - Independent probes (pure-stdlib scripts, outside the suite): the 2000/4000
frame_windows
coverage trials behind M1; the 20,000-casesample_framesbrute-force comparison; the three-frame
sub-epsilon cluster of S3; the error-message cases of S4; theseconds_agooverwrite of S5; the
wrong-argument-kind messages of S6;every_secondson an epoch-scale grid (it happens to work
here, but the docs rightly forbid it, since 1e-9 s is below the float spacing at 1.7e9).
Verdict
One must-change, M1: a false unconditional coverage guarantee in the contract of a helper that
sixteen recipes will copy, in the same place a prior round blocked, with no test in the regime where
it fails. The fix is small - a validation line, or a qualifying clause plus one test. Everything else
in the diff is careful, well tested and, as far as I can establish, correct; all nine prior blocking
findings across the two earlier rounds are resolved at this head, and the merge changed nothing
unintended.
OPUS-REVIEW: CHANGES-NEEDED 4f338f9
…he vision walkthrough run and be tested (#309) frame_windows: the docstring, the code comment and docs/vision.md claimed every frame lies in some window. That holds only when stride_seconds <= width_seconds; a wider stride gives deliberately sparse windows. State the condition and the consequence, drop the false "overlapping the previous one", reflow the 148-character docstring line, and add tests for stride == width (boundary) and stride > width (pinned uncovered indices). docs/vision.md: the ordered-frame walkthrough raised on the page's own frames (every_seconds=2.0 over a 1.5 s sequence selects one frame). Use 0.5, state the values in comments, and say it continues from the load_frames example. tests/test_docs_examples.py now executes the load_frames, walkthrough and fields blocks of docs/vision.md in page order against generated stills. tests/test_docs_examples.py: the module docstring said "eight blocks" against a nine-block assertion; fix it and add a test that keeps the two in agreement. Refs #309 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PR #323 — Opus re-review (independent review 2 of 2, round 3)
I checked out Verdict firstAll three of my must-change findings are resolved, and resolved honestly rather than papered over. The My findings from head
|
| Mutation | Guard | Result |
|---|---|---|
docs/vision.md: every_seconds=0.5 → 2.0 (the exact M2 bug) |
test_vision_frame_walkthrough_runs_on_the_pages_own_frames |
KILLED (1 failed) |
docs/vision.md: window_timestamps(sampled, 1, 2) → (0, 2) (a wrong documented window) |
same | KILLED (1 failed) |
tests/test_docs_examples.py: module docstring "nine" → "eight" (the exact M3 bug) |
test_docstring_block_count_matches_the_assertion |
KILLED (1 failed) |
frames.py: drop the end-aligned final window (if False:) |
..._cover_every_frame_when_stride_is_at_most_width + ..._cover_the_end_of_the_sequence |
KILLED (2 failed) |
frames.py: silently clamp stride to width (hiding the sparse regime) |
..._with_a_stride_wider_than_the_width_leave_frames_out |
KILLED (1 failed) |
frames.py: raise for stride > width (option 1 from my review) |
same | KILLED (1 failed) |
The last row is worth stating plainly rather than hiding: the new test now pins the documented
choice, so adopting the validation alternative I offered would require editing that test. That is
correct behaviour for a test of a documented contract, not a defect — but it means the sparse regime is
now a deliberate, tested part of the interface, which is exactly what the docs claim.
Everything that changed since 4f338f92
git diff --stat 4f338f9 a9c7922: docs/vision.md +24/-17ish, src/jev_cookbook/frames.py +17/-6,
tests/test_docs_examples.py +53/-2, tests/test_frames.py +47/-0. I read all of it. There is nothing
in the fix commit beyond the three findings: no behaviour change in frames.py (only the docstring and
one comment), no change to evaluation.py, docs/evaluation.md, tests/test_evaluation.py or any
__all__, and no new dependency. The 148-character docstring line at the old frames.py:289 was
reflowed in the process — frames.py now has no line over 100 characters, closing half of my
previous suggestion 6.
The merge of #331
git show --cc 42dc36aproduces no combined-diff hunks: the merge commit contributes nothing of
its own.- Independently reproduced:
git merge-tree --write-tree a9c7922 2601074→
3e74fa02a6cffaec12cd0914bd6a984244749235, identical togit rev-parse 42dc36a^{tree}. - Both
a9c7922and42dc36aare signed (%G?→G); the merge message carries the required
Co-Authored-By: Claude Sonnet 5.5.origin/mainis an ancestor of the head. - Scope against the new base is unchanged:
git diff --raw -M origin/main 42dc36alists the same 7
paths, all mode100644, 2 added (src/jev_cookbook/frames.py,tests/test_frames.py) and 5
modified, no symlink, submodule, mode or type change, nothing outsidesrc/,docs/,tests/.
pyproject.tomluntouched → still no new dependency. - No name collision with Foundation: Box evaluation report and gold Box label format (#311) #331:
vision.__all__gainedLocalizationReport,gold_box,
localization_reportandexpand_box; none offrames.py's ten names appears invision, and
load_framesstays invision.jev_cookbook.framesis still the one import path for the
arithmetic, as the docs claim.
Reading the merged docs/vision.md as a learner
#331 inserts four new ### sections (Gold labels, The localization report, Tiled and hierarchical
recipes, The older localization_metrics) between "Presence, confidence and evaluation" and "Tiles and
coordinate transforms". The frames material sits after them, under "Explicitly timestamped still
frames" → "### Sampling, intervals and windows" (lines 314-445). The merge boundary is clean: the
load_frames block is immediately above the sampling subsection, so the new "continues from the
frames loaded above" sentence is true of the merged page as well as of the pre-merge one.
I found no contradiction between #331's sections and the frames section, and several places where
they reinforce each other:
docs/vision.md:4promises these helpers "do not ... decode video", which the frames section
restates as "No video is decoded anywhere in the cookbook" — I re-verified that claim at this head:
nocv2/imageio/ffmpeg/VideoCaptureimport undersrc/,tools/orrecipes/.- Foundation: Box evaluation report and gold Box label format (#311) #331's "Nothing is filtered, so no example can leave a denominator" and "a mean or rate whose
denominator is zero is NaN ..., never 0" are the same contracttransition_metricsstates for
n_resolved/n_examplesand its NaN means. - Both sections fix the unit of an example explicitly and for the same reason — Foundation: Box evaluation report and gold Box label format (#311) #331: "one example is
one image, not one tile"; transitions: "the function scores one(label, time)per example. Split a
sequence into one example per transition". A learner meets the same discipline twice, worded
consistently. - All internal links in both merged documents resolve: I checked every relative
](...)link in
docs/vision.mdanddocs/evaluation.mdagainst the headings of its target — 0 broken files, 0
broken anchors. Foundation: Box evaluation report and gold Box label format (#311) #331's new links (vision.md#the-localization-report,
vision.md#tiled-and-hierarchical-recipes,fixtures.md#box-labels) all resolve.
The edited PR body against the diff
The orchestrator's edit fixes both of my body-level complaints:
- The
frame_windowsbullet now reads "Every frame lies in some window whenstride_seconds <= width_seconds; with a larger stride the windows are deliberately sparse and frames between them are
in no window (documented with an example, tested)" — the overclaim is gone and matches the code and
docs exactly. - "How it was verified" now reports this head ("Final head 42dc36a ..., Linux, fresh Python 3.14
venv") instead of the superseded6b18804, the stale Windows-failure list is gone, and the
"docs/vision.mdblocks were executed by hand" claim is replaced by "tests/test_docs_examples.py
executes thedocs/evaluation.mdblocks and thedocs/vision.mdframe walkthrough", which is true. - Every number in it reproduces on my own run:
ruff format --check399 files,check_hygiene608
files,check_recipe_assets461 files,notebook_ci fixtures51 folders / 0 failed, and pytest
5547 passed, 49 skipped, 50 deselected, 31 xfailed, 0 failed — identical to my own counts. Closes #309is correct for a PR meant to close the issue; no other closing keyword appears.
Two observations on the body, neither blocking:
- The "What this adds" tests bullet still describes only the block-count bump ("8 to 9 blocks, empty
preamble") and now understates the diff:tests/test_docs_examples.pyalso gained a test that
executesdocs/vision.md's walkthrough and a test that ties the docstring count to
len(BLOCKS), andtests/test_frames.pygained the two coverage-regime tests. The body is not
wrong — "How it was verified" mentions the walkthrough test — just thinner than the diff in the one
place a reader looks for the test story. - CONTRIBUTING.md says a description is "written once, at open ... and never edited after the final
push", with fix-round notes going in pull-request comments, because aneditedevent fires a fresh
Scoperun that can leave an ambiguous duplicate row. I checked: this head carries exactly one
Scoperow, andgh pr checks 323reports 60 of 60 pass, none pending or failed, so the edit
caused no merge-gate ambiguity in practice. Worth recording in a comment rather than repeated edits
next time; it does not affect mergeability here. (Also, folding the round-3 fixes under the "Review
round 2 resolutions" heading makes the paper trail read as if the qualification landed a round
earlier than it did. Content is accurate; only the attribution of rounds is loose.)
Status of my previous suggestions (all were non-blocking; the builder's report did not claim them)
| # | Suggestion | Status at 42dc36a |
|---|---|---|
| 1 | docs/evaluation.md:19-33 central "Undefined is NaN" table not extended for transition_metrics |
Open, and I now withdraw the framing: #331's own "Box localization" section (:739-769) likewise states its NaN rule locally without adding table rows, so "The cases:" being non-exhaustive is a repo-wide convention question, not this PR's defect. Leave it to a docs sweep. |
| 2 | docs/evaluation.md:3-4 "plain numpy, imports nothing from the answer classes or the SDK" vs evaluation.py:35 from .frames import TIME_EPSILON, _seconds; and _seconds being private |
Open. Still literally true, still worth a clause, still worth promoting _seconds. |
| 3 | docs/vision.md:3 "Import these helpers from jev_cookbook.vision" is no longer true of the whole document |
Open. The frames section's own disclaimer (:339-341) remains the mitigation. |
| 4 | The fields example (docs/vision.md:433-443) drops the relative image path that :57-60 requires in fields |
Open (only the every_seconds value changed). Now slightly more visible, because #331 added a whole shared gold-label format one screen above. |
| 5 | Say that frame_index / seconds_ago enter the replay_key |
Open (no "replay" in the frames section). |
| 6 | Two unreflowed long lines | Half resolved. frames.py now has no line over 100 chars. docs/evaluation.md:810 is still 134 ("Do not substitute the pair's start or midpoint: the three give"), and docs/vision.md:377-378 is still ragged mid-paragraph. |
| 7 | tests/test_frames.py:296 section banner reading # ----- round 2 |
Open. A review round still means nothing to a later reader; the new tests were appended without adding another round banner, which is the right instinct. |
| 8 | Anchorless cross-links | Open, and now the odd ones out: docs/vision.md:424 links [evaluation.md](evaluation.md) and docs/evaluation.md:795 links [What an evaluation must show](multimodal-track.md), while every link #331 added carries an anchor. evaluation.md#transitions and multimodal-track.md#what-an-evaluation-must-show both exist. Matters more now that docs/evaluation.md is ~900 lines. |
| 9 | earliest_transition returns a truthy dict for a review as well as a find; show the if found is None: ... elif found["review"]: ... pattern |
Open. |
New, non-blocking, surfaced only by the #331 merge
- There is now a shared gold-label format for Box recipes and none for multiframe recipes. Foundation: Box evaluation report and gold Box label format (#311) #331
addeddocs/fixtures.md"### Box labels" explicitly "so ten recipes do not invent ten formats",
withvision.gold_boxand validator enforcement. The ~10 multiframe recipes have the same problem:
docs/evaluation.md:791-879defines the gold shape as(label, seconds)"as a tuple or a
two-element list (what JSON loads)", but the Transitions section contains no link to
docs/fixtures.mdat all andfixtures.mdhas no transition-label section, so each recipe will
choose its ownlabels.jsonlencoding. Out of Foundation: no deterministic helpers or evaluation for ordered frames (sampling, intervals, relative times, windows, transitions) #309's scope — but it is the same risk Foundation: Box evaluation report and gold Box label format (#311) #331 just
spent a PR removing for Box, and it is now visibly asymmetric inside the same two documents. Worth
a follow-up issue before rank 85 is opened, not a change here. - A learner meets two different "gold labels" in
docs/vision.md. Foundation: Box evaluation report and gold Box label format (#311) #331's### Gold labels
(:160) is the Box label format; the frames section's "Gold labels use the same rule" (:422) is
the transition-time convention. Neither is wrong and the frames sentence is explicit about which
rule it means, but one clause ("gold transition labels") would stop the two from cross-wiring in a
skim. docs/vision.md:412states the coverage guarantee without the docstring's[start, end]
qualifier.frames.py:290says "every frame in[start, end]"; the bullet says "Every frame
lies in some window only whenstride_seconds <= width_seconds". With explicit bounds, frames
outside them are in no window either —frame_windows([0..4], width=1, stride=1, start=1, end=3)
returns windows covering only indices 1-3, as the docstring (correctly) implies. Two words in the
bullet would make the page match the docstring.
None of these is a false statement in the shipped text, which is why none is a must-change.
Checks I ran
Fresh venv outside the repository (uv venv --python 3.14 /home/agent/r2b-venv, then
uv pip install --link-mode symlink -e ".[dev]" → ruff 0.17.1, pytest 9.1.1), Linux, Python 3.14.7.
Install and all checks in a single flock .claude/heavy.lock nice -n 10 acquisition (no nested flock),
run in the background with log polling; pytest -p no:cacheprovider. No TYPESAFE_API_KEY, no live
call, no credential looked for. HEAD was re-printed inside the script as 42dc36a6a09be3946....
| Command | Result |
|---|---|
uv pip install -e ".[dev]" |
rc 0 |
ruff check . |
All checks passed (rc 0) |
ruff format --check . |
399 files already formatted (rc 0) — the run that covers the docs/*.md python blocks |
python tools/check_hygiene.py |
Hygiene check passed: 608 file(s) scanned (rc 0) |
python tools/check_recipe_assets.py |
Image-asset check passed: 461 file(s) under recipes/ checked (rc 0) |
python tools/render_catalog.py --check |
rc 0, no output |
python tools/notebook_ci.py fixtures |
51 recipe folders, 0 failed (rc 0) |
pytest tests/test_docs_examples.py tests/test_frames.py tests/test_evaluation.py -q -rs |
343 passed, 3 skipped in 2.1 s (rc 0) — 4 more than the 339 at 4f338f9, exactly the four new tests |
pytest -q (full, tests/ + recipes/) |
5547 passed, 49 skipped, 50 deselected, 31 xfailed, 0 failed in 2 min 35 s (rc 0) |
| Mutation probes (6, one at a time, tree restored) | all killed; baseline 5 passed |
gh pr checks 323 |
60 of 60 pass, none pending or failed; exactly one Scope row |
Read rather than taken on trust:
- The three skips are not the block-execution tests.
-rsnames them::280,:296,:305, all
"exercises the Windows-only SIGBREAK timer path". Sotest_block_executesran for every
docs/evaluation.mdblock, block 8 (Transitions) included, and the newdocs/vision.mdwalkthrough
test ran too. The 50 deselected are the defaultcatalog_auditdeselection, unrelated to this diff. docs/evaluation.mdstill has exactly 9 python blocks after Foundation: Box evaluation report and gold Box label format (#311) #331, so the module's
assert len(BLOCKS) == 9andPREAMBLE[8]still refer to the Transitions block; Foundation: Box evaluation report and gold Box label format (#311) #331's Box
localization section added prose and a table, no fence.git status --porcelainempty after the full run and after the mutation script — nothing wrote into
the checkout, and I restored every mutated file.- No notebook executed and none needed to be:
git grepoverrecipes/finds no import of
jev_cookbook.framesortransition_metrics, and the diff touches no notebook, fixture or recipe
folder.
Reproductions of my own (standard library only, src on PYTHONPATH, scripts under /tmp, nothing
written into the checkout): the frame_windows sparse-regime counterexample; the 20,000-trial
randomized coverage probe described under M1; the fenced-block marker uniqueness check on the merged
docs/vision.md; and a link/anchor resolver over every relative link in both merged documents.
Acceptance criteria of #309, one by one
sample_frames(...)deterministic, tie-breaking documented, edge tests for empty, single, exact
boundaries and float drift — met. (Unchanged this round; atjev_cookbook.frames.sample_frames,
a deviation documented as the one import path and accepted by both reviewers. In my previous review
I re-derived thecountandevery_secondsrules against independent references with no
mismatch.)frame_intervals/relative_frames(addsseconds_ago, order unchanged), tested — met.window_timestampsreturns start, end and duration, with tests — met. Its worked example in the
docs now runs and is executed by a test (M2), so the one defect I held against this criterion last
round is gone.transition_metricswith exact and within-tolerance accuracy, an explicit uncertain/review count
with a stated denominator, and earliest-transition error in seconds; documented in
docs/evaluation.md; tested with hand-computed cases — met. Untouched this round; the block is
still executed bytests/test_docs_examples.pyand still agrees with my own arithmetic.docs/vision.mddocuments sampling, that no video decoding happens, and how a recipe records the
selected timestamps infields— met. This is the criterion I marked not met at4f338f9; both
reasons (the falseframe_windowsguarantee and the non-running walkthrough) are fixed, each with a
test, and the section reads correctly beside Foundation: Box evaluation report and gold Box label format (#311) #331's new Box material.- No new dependency; Pillow only — met.
pyproject.tomluntouched (Pillow is already a core
dependency);frames.pyis standard-library only.
Verdict
Every must-change item is resolved, by the honest route in each case: the coverage claim was scoped to
the regime where it is true and the counterexample documented rather than quietly dropped; the docs
walkthrough was made to run on the page's own data and is now executed by a test; and the block count
is now derived from len(BLOCKS) instead of restated in prose. I verified each with my own
reproduction and killed every one of six mutations aimed at the new guards, including two that restore
the exact bugs I reported. The merge of #331 is a provably clean auto-merge, the scope and dependency
position are unchanged, the edited PR body now matches the diff and the head, every check I ran is
green, and all 60 CI checks pass on this exact head. The remaining items are the nine non-blocking
suggestions carried over (one of which I withdraw as a repo-wide convention question, and half of
another is now fixed) plus three small new ones surfaced by reading the merged documents; none is a
false statement in shipped text, and none should hold up a foundation interface that sixteen recipes
are waiting on.
OPUS-REVIEW: APPROVE 42dc36a
PR #323 re-review - reviewer 1 of 2 (round 3)
What changed since the head I reviewedTwo commits:
The merge is clean and contributed nothing of its own. No symlink, submodule, mode change or type change; nothing outside One merge hazard that came out right and is worth recording. Reading the merged
My findings from
|
| Mutation | Result |
|---|---|
docs/vision.md walkthrough every_seconds=0.5 -> 2.0 |
killed: test_vision_frame_walkthrough_runs_on_the_pages_own_frames fails (ValueError: first_index 1 is outside 0..0). The new docs test really does execute the page, not just parse it. |
frames.py drops the end-aligned window (if high - (starts[-1] + width) > TIME_EPSILON: -> if False:) |
killed, 3 failures: test_frame_windows_cover_the_end_of_the_sequence, test_frame_windows_cover_every_frame_when_stride_is_at_most_width, test_frame_windows_with_a_stride_wider_than_the_width_leave_frames_out. |
tests/test_docs_examples.py docstring "Two of the nine blocks" -> "eight" |
killed: test_docstring_block_count_matches_the_assertion fails. The new prose-count guard works. |
frames.py made to reject stride > width (the alternative fix) |
killed, 3 failures: test_frame_windows_keeps_empty_windows_visible, test_frame_windows_float_drift_and_errors, test_frame_windows_with_a_stride_wider_than_the_width_leave_frames_out. Evidence that allowing a sparse stride is now a tested contract, not an accident. |
Acceptance criteria of #309
sample_frames(...)deterministic, tie-breaking documented, edge tests for empty, single, exact
boundaries and float drift - met (module pathjev_cookbook.frames, a documented deviation I
accept; S9).frame_intervals/relative_frames(addsseconds_ago, order unchanged), both tested -
met, and now also executed through thedocs/vision.mdwalkthrough test.window_timestampsreturns start, end and duration, with tests - met, likewise.transition_metricswith exact and within-tolerance accuracy, an explicit uncertain/review count
with a stated denominator, and earliest-transition error in seconds, documented in
docs/evaluation.mdand tested with hand-computed cases - met.docs/vision.mddocuments sampling, that no video decoding happens (pre-extracted stills), and
how a recipe records the selected timestamps infields- met. This was the one criterion I
marked "met in substance, one statement wrong" last round; the wrong statement is now correct,
qualified and tested, and the walkthrough a recipe author copies is executed in CI.- No new dependency; Pillow only - met.
frames.pyis standard library only;
pyproject.tomlunchanged.
Checks run
Linux, Python 3.14.7, fresh uv venv /home/agent/r1b-venv (outside /workspace) with
uv pip install --link-mode symlink -r <file naming -e .[dev]>, numpy 2.5.4, Pillow 13.0.0, ruff
0.17.1 (the pinned version, from the venv). No TYPESAFE_API_KEY, no live call, no network use by
any check. Each heavy command ran under its own flock .claude/heavy.lock nice -n 10 (no nesting;
the wait for the queued install was done outside the lock), and pytest with -p no:cacheprovider.
ruff check .- All checks passed.ruff format --check .- 399 files already formatted.pytest -q -p no:cacheprovider- 5547 passed, 49 skipped, 50 deselected, 31 xfailed, 0
failed in 162 s. (The 50 deselected arepyproject.toml's defaultcatalog_auditdeselection.
No failures at all, including the twotests/test_notebook_ci_sandbox.pysignal.SIGKILLcases
that fail on the author's Windows machine - this run is on Linux.)pytest -q -rs tests/test_frames.py tests/test_evaluation.py tests/test_docs_examples.py tests/test_vision.py- 610 passed, 3 skipped. The three skips are
tests/test_docs_examples.py:280/296/305, all "exercises the Windows-only SIGBREAK timer path;
POSIX uses SIGALRM", so bothtest_block_executesand the new
test_vision_frame_walkthrough_runs_on_the_pages_own_framesactually ran here.python tools/check_hygiene.py- Hygiene check passed: 608 file(s) scanned.python tools/render_catalog.py --check- rc 0.python tools/notebook_ci.py fixtures- 51 recipe folders, 0 failed.git show --cc 42dc36a- no diff hunks.git diff 4f338f9 a9c7922- the 4-file fix above.
git diff origin/main HEAD -- docs/evaluation.md docs/vision.md- zero deletion lines.
git diff --raw -M origin/main...HEAD- the 7100644paths above.- Notebook execution: nothing under
recipes/(51 folders) importsjev_cookbook.framesor
transition_metrics, and the diff touches no recipe, so no notebook is affected and none was
executed. - Independent probes: the documented
stride > widthcounterexample and the tighter overshoot; 6000
randomisedstride <= widthconfigurations with and without explicit bounds;stride == width
across 7 widths x 6 grid sizes; a stride a hair above the width; plus the four mutations above. git status --porcelainempty at the end: the mutation runs left the tree clean.
Verdict
M1 is resolved properly - the false guarantee is replaced by an accurate, scoped one in both the
docstring and docs/vision.md, with a worked counterexample, and both regimes are now pinned by
tests that I confirmed fail when the behaviour is mutated. I accept the choice to qualify the claim
rather than reject stride > width. The vision walkthrough now runs and is executed in CI (which
also closes my S8), the long docstring line is gone (S7), the merge of #331 lost nothing in either
doc, and every check from docs/development.md is green on this exact head. My six remaining items
are non-blocking suggestions for a later tidy-up issue.
OPUS-REVIEW: APPROVE 42dc36a
Closes #309
What this adds
src/jev_cookbook/frames.py(new, pure Python, no new dependency):sample_frames,frame_timestamps,select_frames(copies carryframe_index),frame_intervals,relative_frames,window_timestamps,frame_at,frame_windows,transition_time,earliest_transition.evaluation.transition_metrics+TransitionMetrics: exact and within-tolerance accuracy, review count with stated denominators (resolved and all), label-matched earliest-transition error in seconds (plus separately named any-label fields), per-exampleoutcomesandtiming_errors_seconds,max_sampling_interval_seconds/tolerance_below_sampling_interval.docs/vision.md(sampling rules, tie-breaking, no video decoding, elapsed-seconds convention, index spaces, recording the selection infields) anddocs/evaluation.md(the transition-time convention, tolerance versus sampling gap, review labels, multi-transition sequences, result type row).tests/test_frames.py(new), additions totests/test_evaluation.py;tests/test_docs_examples.pybumped for the one new python block indocs/evaluation.md(8 to 9 blocks, empty preamble).Deliberate deviation from the issue text: the helpers live in
jev_cookbook.frames, notjev_cookbook.vision(#321 editsvision.py; it is untouched).jev_cookbook.framesis the single import path; no re-export is planned.Review round 1 resolutions (head 8ff6588)
n_timedand means now cover only label-matched examples with times on both sides; the all-labels view isn_timed_any_labeland*_any_label_*. Tests and docs updated (hand-computed: n_timed 2, MAE 0.2).[0, 1, 1+5e-10, 2]giving[0, 1, 3].timestamp_secondsof the first frame in which the new state is visible, for gold and predictions.frames.transition_timeandframes.earliest_transitionimplement it; docs say tolerance should be at least the largest sampled gap;max_sampling_interval_secondsis recorded andtolerance_below_sampling_intervalflags the opposite. A hand-computed end-to-end test pins it.uncertain_labelstakes several review labels;ValueError(notOverflowError) for huge times, one shared validator; per-example outcomes;frame_atandframe_windows;frame_indexon selected copies; docs on position-basedcount, elapsed seconds, multi-transition sequences, and mapping the model's uncertain option and Python's gate to the review labels.Merged
origin/main(7fa806e) with a signed merge commit; no conflicts (#318'sdocs/evaluation.mdedits merged cleanly alongside this section).Review round 2 resolutions (head 2cef5c7)
earliest_transitionacceptsNonefor an undetermined pair (and NumPy booleans). An undetermined pair before the firstTrue, or with noTrue, returns{"review": True, "first_undetermined_index": i, ...}with no time, never a later transition. Confirmed results now carry"review": False. Docs say to score it with anuncertain_labelslabel. Tested.frame_windowsappends a final window aligned toendwhen the last stride-spaced window stops short. Every frame lies in some window whenstride_seconds <= width_seconds; with a larger stride the windows are deliberately sparse and frames between them are in no window (documented with an example, tested). Tested with frames every 0.5 s to 8 s, width 3, stride 2 (windows start 0, 2, 4, 5) and with a small case.docs/vision.mdstates the actual tie guarantee (up to two frames within the slack around a target); "can only be late" now says a correct detection; docs say to dropframe_indextoo;select_framesvalidates a pre-existingframe_index;uncertain_labels=None(or a non-collection) raisesValueError.Recipe checklist
Skipped (foundation work).
How it was verified
Final head 42dc36a (signed merge of origin/main 2601074 into a9c7922, after two fix rounds), Linux, fresh Python 3.14 venv:
ruff check .: All checks passed.ruff format --check .: 399 files already formatted.pytest: 5547 passed, 49 skipped, 50 deselected, 31 xfailed, 0 failed.python tools/check_hygiene.py: passed, 608 files.python tools/check_recipe_assets.py: passed, 461 files.python tools/render_catalog.py --check: exit 0.python tools/notebook_ci.py fixtures: 51 recipe folders, 0 failed.tests/test_docs_examples.pyexecutes thedocs/evaluation.mdblocks and thedocs/vision.mdframe walkthrough.No live call, no key; nothing here claims anything about Jev.
🤖 Generated with Claude Code