Fix scoring lifecycle and evaluator validation gaps - #66
Conversation
Decouple run lifecycle from artifact grading, add real-path EEG smoke coverage, chance-center TOPS AUC scoring, and correct the Sleep-EDF single-epoch guidance.
bondingelectron
left a comment
There was a problem hiding this comment.
Thorough review with local verification — approving. All four fixes are correct, well-tested, and conservative with respect to existing behavior.
#60 — lifecycle decoupling (src/)
The three-axis model (artifactState / runState / graderState) is clean, and the edge semantics are right:
lifecycleFromSignalsgivesreasonprecedence over a contradictorycompleted: true(fail-closed), tolerates external bundles without signals (unknown/null), and unknown lifecycle stays leaderboard-eligible — preserving backward compatibility for externally producedscores.json.classifyRunScoresrequiresgraderState === "scored"for eligibility;scoring_failed/unscoredruns remain in the leaderboard coverage denominator exactly as before (verified by the new leaderboard test).buildLeaderboardfilters onlyrunCompleted === false; legacyscores.json(field absent) is unaffected. The legacy aggregatestatefield is preserved for existing consumers.- CLI tests cover both branches end-to-end (valid artifacts from a timed-out run still graded + marked ineligible; missing artifacts stay
submission_invalidwithgraderState: "not_run").
#61 — real-path smoke tests (EEG evaluators)
I was the author of the original v0.1/v0.2 scorers, so I checked the refactoring against the pinned real data, not just the synthetic fixtures:
- bciciv-2a: refactored
load_epochs→preprocess_rawon the realA01T.gdf/A01E.gdffrom the content-addressed release reproduces v0.2 exactly:(288, 22, 512)float32, std 6.867 µV, 72 trials/class, E-session labelsNone. Theexpected_trialsparameterization (with the divisible-by-4 check) is strictly additive. - sleep-edf: refactored
load_subject_recording→preprocess_recordingon the realSC4001E0pair reproduces v0.2 exactly:(841, 1, 3000)float32, std 22.883 µV, label distribution 188/58/250/220/125.fit_model_from_arrays/predict_arraypreserve the exact training math (Adam lr=1e-3, weighted CE, early-stop threshold1e-4, batched val loss) — confirmed line by line. - The smoke fixtures genuinely exercise the production path (filter, resample, epoching, µV conversion, model import, a real optimizer step, inference, real metric computation) rather than a copy of it, embed no real EEG or private labels, and the Docker isolation tests are correctly gated behind
BPB_DOCKER_TEST/BPB_TEST_INFERENCE_IMAGEwithuse_gpu=Falseso CPU-only CI runners work. - The
sys.modulesrestore in both contract tests fixes a real pollution bug from the original versions — thank you. - CI additions are sane: CPU-only torch wheel for the smoke job, and
eeg-real-path-smokecorrectly joins therequiredgate.
#62 — TOPS-fMRI AUC chance-centering (v0.2)
chance_centered_auc/aggregate_scoresimplementmax(0, 2·AUC−1)correctly; study4 clipping semantics unchanged; raw r/AUC diagnostics preserved untouched.- I independently recomputed the frozen v0.2 baseline from the preserved raw metrics:
study4 = 0.14077015583589433,study5 = 0.514801999231065,score = 0.32778607753347966— matchestest_metrics.pyto the last digit. output_schema.json, both READMEs, andtask.yamlare consistent, and the updated Oracle note (chance submission now headlines0.0instead of ~0.25) is accurate. Localtest_metrics.py: 4/4 pass.
#64 — sleep-edf prompt (v0.3)
The rewritten hint is scientifically precise: it keeps the useful part (dilated convs / TCN expand the receptive field within the 30 s waveform) while explicitly stating the single-epoch interface cannot encode cross-epoch stage transitions. Prompt-only change with the scorer untouched, so v0.2 score comparability is preserved, and task.yaml + README version notes are consistent.
Local verification on this branch
tscbuild, 216 Node tests (0 fail), docs smoke,bp-bench validate all→ 5/5 pass (Oracle+NOP gates)- Both contract suites (5+4), both real-path smoke suites,
tops-fmri/checks/test_metrics.py— all pass - Real-data regression on both EEG loaders as described above
Two non-blocking nits
src/cli.ts:execution.result!.reasonprintsrun=undefinedif a bundle ever lacks a reason; consider?? "unknown"for consistent display. Cosmetic only.- Operator note (not this PR's bug): the tops-fmri Oracle runs the evaluator's
apply_signature.pychild withPYTHONNOUSERSITE=1, so a machine whose numpy lives only in user site-packages fails the Oracle withModuleNotFoundError. Putting the task venv first onPATH(asenv/setup-python.shintends) fixes it. May be worth one line in the docs for maintainers runningbp-bench validateoutside CI.
Summary
Validation
npm run typechecknpm run buildnpm test— 216 tests, zero failuresnpm run test:docsbp-bench validate all— all five task definitions and Oracle/NOP gates passCompatibility
max(0, 2*AUC-1)for Study5 subgroup aggregation; raw AUC diagnostics are unchanged and v0.1 scores remain historicalCloses #60
Closes #61
Closes #62
Closes #64