rabbit-brain 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rabbit_brain-0.1.0/.gitignore +8 -0
- rabbit_brain-0.1.0/AGENTS.md +139 -0
- rabbit_brain-0.1.0/CHANGELOG.md +10 -0
- rabbit_brain-0.1.0/LICENSE +202 -0
- rabbit_brain-0.1.0/PKG-INFO +58 -0
- rabbit_brain-0.1.0/README.md +26 -0
- rabbit_brain-0.1.0/pyproject.toml +55 -0
- rabbit_brain-0.1.0/src/rabbit_brain/__init__.py +12 -0
- rabbit_brain-0.1.0/src/rabbit_brain/checks.py +107 -0
- rabbit_brain-0.1.0/src/rabbit_brain/cli.py +552 -0
- rabbit_brain-0.1.0/src/rabbit_brain/errors.py +68 -0
- rabbit_brain-0.1.0/src/rabbit_brain/example.py +73 -0
- rabbit_brain-0.1.0/src/rabbit_brain/fmt.py +39 -0
- rabbit_brain-0.1.0/src/rabbit_brain/importer.py +162 -0
- rabbit_brain-0.1.0/src/rabbit_brain/models.py +390 -0
- rabbit_brain-0.1.0/src/rabbit_brain/prose.py +56 -0
- rabbit_brain-0.1.0/src/rabbit_brain/recorder.py +55 -0
- rabbit_brain-0.1.0/src/rabbit_brain/report.py +91 -0
- rabbit_brain-0.1.0/src/rabbit_brain/runs.py +204 -0
- rabbit_brain-0.1.0/src/rabbit_brain/stability.py +142 -0
- rabbit_brain-0.1.0/tests/conftest.py +19 -0
- rabbit_brain-0.1.0/tests/fixtures/v5-expected.json +1219 -0
- rabbit_brain-0.1.0/tests/test_checks.py +48 -0
- rabbit_brain-0.1.0/tests/test_cli.py +149 -0
- rabbit_brain-0.1.0/tests/test_docs.py +49 -0
- rabbit_brain-0.1.0/tests/test_import.py +81 -0
- rabbit_brain-0.1.0/tests/test_stability.py +82 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Rabbit Brain: AGENTS.md
|
|
2
|
+
|
|
3
|
+
Rabbit Brain (`rb`) reviews a candidate checkpoint of an iterative perception model against the current one: it ranks the cases that regressed on error or never settled during refinement, explains each one, and keeps checks so the next checkpoint gets the same review. It runs locally; nothing leaves this machine.
|
|
4
|
+
|
|
5
|
+
Read this whole file before running anything. **Compute per-case errors in your own evaluator; that is your metric. Do not compute regressions, rankings, stability or verdicts yourself.** `rb` defines them, applies the same definitions on every run, and records how, which is what makes a result comparable across checkpoints and people. If you find yourself writing a comparison script, stop and run `rb import`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install rabbit-brain # or: uvx --from rabbit-brain rb
|
|
11
|
+
rb version # rabbit-brain 0.1.x
|
|
12
|
+
rb docs # prints this file
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Python 3.10 or later. The only dependency is pydantic. Nothing here needs a GPU: version 0.1 reads results your evaluator already produced.
|
|
16
|
+
|
|
17
|
+
## When to use it, and when not
|
|
18
|
+
|
|
19
|
+
Use it when there are two checkpoints (or two models for the same task), one lower-is-better error per case for each, and a release decision to make. If the model refines its answer iteratively (RAFT-family optical flow, RAFT-Stereo-style depth), also export one number per refinement iteration per case and `rb` will flag the answers the candidate never settled on, which needs no labels.
|
|
20
|
+
|
|
21
|
+
Do not use it for training, hyperparameter search, certifying a model, or metrics where higher is better (convert those to an error first).
|
|
22
|
+
|
|
23
|
+
## What is in 0.1, and what is not
|
|
24
|
+
|
|
25
|
+
In 0.1 you bring the numbers: `rb import` reads a results file (JSON, or a CSV of per-case metrics) and everything downstream works on it. `rb run`, the runner that executes both checkpoints and records trajectories itself, arrives in 0.2 together with `rb init`, `rb doctor`, `rb verify-hook`, `rb rerun` and `rb open`. Calling any of them in 0.1 returns `E_NOT_AVAILABLE` and explains this; do not try to work around it.
|
|
26
|
+
|
|
27
|
+
## The workflow
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
rb example # a run from built-in example data, to see what the output looks like
|
|
31
|
+
rb import results.json # your results (version-1 JSON; format below) → a new run under rb-runs/
|
|
32
|
+
rb import metrics.csv --project <p> --baseline-name <a> --candidate-name <b> --dataset <d> --metric <m> --unit <u>
|
|
33
|
+
rb findings <run> --top 5 # the ranked queue, the summary, the verdict
|
|
34
|
+
rb case <run> <case_id> # one case: numbers, trajectory statistics, the reasoning
|
|
35
|
+
rb check save <run> <case_id> # keep this case for the next checkpoint (limit = current error + max regression)
|
|
36
|
+
rb check run <run> --checks checks.json # next time: exit 1 if a saved check fails or a case is flagged
|
|
37
|
+
rb report <run> --print # the receipt a human reads (also written to the run directory)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Other commands: `rb runs` (list runs), `rb check list|rm`, `rb schema <name>`, `rb docs --errors`, `rb version`. `<run>` is a run id (`rb runs`), a run directory, a `bundle.json`, or a version-1 results file read in place.
|
|
41
|
+
|
|
42
|
+
Limits, on any command that reads a run: `--max-regression 0.3 --max-late-share 0.25 --max-reversals 2` (these are the defaults; they are generic heuristics, and a scorer fitted to the model is a separate, paid step). `rb findings` also takes `--filter flagged|all|regressions|unstable|improved-unstable|settled-regressions|improved|stable` (default `flagged`), `--sort priority|error-change|late-share|name` and `--top N`.
|
|
43
|
+
|
|
44
|
+
## The results file (version 1)
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"version": 1,
|
|
49
|
+
"project": "My perception project",
|
|
50
|
+
"baseline": "model-v1", "candidate": "model-v2",
|
|
51
|
+
"dataset": "Regression set", "metric": "mean_endpoint_error", "unit": "px",
|
|
52
|
+
"cases": [
|
|
53
|
+
{ "id": "seq-001", "name": "First sequence", "baseline_error": 2.1, "candidate_error": 2.4,
|
|
54
|
+
"candidate_trajectory": [2.3, 1.4, 0.9, 0.6, 0.4, 0.3, 0.2, 0.15, 0.1, 0.08, 0.06, 0.05] }
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Rules: 1–500 cases; case ids match `^[a-zA-Z0-9_.-]{1,80}$` and stay the same across checkpoints (saved checks follow them); `baseline_error` and `candidate_error` are the same lower-is-better metric per case for the current and the candidate model, computed against the same ground truth and valid mask; `baseline_trajectory` / `candidate_trajectory` are optional, 2–64 numbers each, the mean |update| per refinement iteration (record them with the hook below); `baseline_frames` / `candidate_frames` are optional paired per-frame error series of equal length; `tags` and `notes` are optional. `rb schema example` prints this file; `rb schema comparison-v1` prints the JSON Schema. A CSV works too: columns `case_id, baseline_error, candidate_error` and optionally `name, tags, baseline_trajectory, candidate_trajectory, baseline_frames, candidate_frames` (series as semicolon-separated numbers), with the project, model names, dataset, metric and unit given as flags.
|
|
60
|
+
|
|
61
|
+
## Recording a trajectory (the one line you add to the model)
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from rabbit_brain import TrajectoryRecorder
|
|
65
|
+
rec = TrajectoryRecorder() # optional: TrajectoryRecorder(mask=valid_pixels)
|
|
66
|
+
for k in range(iters):
|
|
67
|
+
delta_flow = update_block(...) # the existing update
|
|
68
|
+
flow = flow + delta_flow
|
|
69
|
+
rec.step(delta_flow) # one number per iteration: mean |update| over the valid region
|
|
70
|
+
case["candidate_trajectory"] = rec.values # one recorder per case per model
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Works with torch tensors (shape `(B, 2, H, W)` is reduced to a per-pixel L1 magnitude), numpy arrays and nested lists. Record the current model the same way so both trajectories are compared under the same definition. If the update loop cannot be reached, import without trajectories: stability is then reported as "not assessed", never as "settled".
|
|
74
|
+
|
|
75
|
+
## Output
|
|
76
|
+
|
|
77
|
+
Every command accepts `--json` and prints exactly one JSON object on stdout; logs and warnings go to stderr.
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{"ok": true, "rb_version": "0.1.0", "command": "import", "run_id": "20260925-1412-model-v2",
|
|
81
|
+
"data": {"run_dir": "rb-runs/20260925-1412-model-v2",
|
|
82
|
+
"summary": {"cases": 12, "regressions": 3, "unstable": 4, "improved_unstable": 2, "settled_regressions": 1, "flagged": 5},
|
|
83
|
+
"verdict": {"status": "investigate", "ready": false, "line": "Not ready: 3 error regressions, 2 unstable cases that pass on error. Start with aisle-042.", "start": "aisle-042"}},
|
|
84
|
+
"errors": [],
|
|
85
|
+
"next": ["rb findings 20260925-1412-model-v2 --top 5", "rb case 20260925-1412-model-v2 aisle-042"]}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`next` lists the commands that usually follow. A run directory `rb-runs/<run_id>/` holds `bundle.json` (every case's numbers, trajectories, stability statistics and flags), `record.json` (the receipt: rb version, the exact command, input file and its sha256, environment, limits), `findings.json` (the ranked queue, the summary, the verdict under those limits) and `report.md` (the human receipt). `bundle.json`, `record.json`, `findings.json` and `report.md` are small and committable; `checks.json` belongs in the repo next to the model code. Schemas: `rb schema bundle|record|findings|checks|envelope`.
|
|
89
|
+
|
|
90
|
+
Definitions `rb` applies, and restates in every report: a case is a **regression** when the candidate's error exceeds the current model's by more than `max_regression`; **late share** is the fraction of all refinement that happened in the last third of the iterations; a **reversal** is an iteration where the update grew by more than 5% over the previous one; a case is **unstable** when late share exceeds `max_late_share` or reversals exceed `max_reversals`. Cases are ranked regression+unstable, then regression, then **improved but unstable** (they pass on error and still need a look), then the rest, by error change. A **saved check** is an absolute limit for a case id in a project: candidate error ≤ `max_error`, optionally late share ≤ `max_late_share` and reversals ≤ `max_reversals`; a saved case missing from a later run fails the check.
|
|
91
|
+
|
|
92
|
+
## Exit codes
|
|
93
|
+
|
|
94
|
+
`0` ok · `1` a saved check failed or is missing, or a case is flagged (`rb check run`; choose with `--fail-on any|checks|flags`, default `any`) · `2` invalid input or config · `3` environment failure. Read `errors[].fix`.
|
|
95
|
+
|
|
96
|
+
## Errors and what to do
|
|
97
|
+
|
|
98
|
+
| code | meaning | do this |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `E_IMPORT_INVALID` | the results file is not a valid comparison | fix every problem listed in `errors[0].problems`; start from `rb schema example` |
|
|
101
|
+
| `E_IMPORT_NOT_JSON` | the file is not JSON | check for a missing comma or bracket |
|
|
102
|
+
| `E_IMPORT_TOO_LARGE` | over 2 MB / 500 cases | split the case set or drop per-frame series |
|
|
103
|
+
| `E_IMPORT_CONFIG` | a CSV import is missing names | pass `--project --baseline-name --candidate-name --dataset --metric --unit` |
|
|
104
|
+
| `E_CSV_INVALID` | the CSV could not be converted | required columns `case_id, baseline_error, candidate_error`; series are semicolon-separated |
|
|
105
|
+
| `E_FILE_NOT_FOUND` | a path does not exist | paths are relative to the current directory |
|
|
106
|
+
| `E_RUN_NOT_FOUND` | no such run | `rb runs`; or pass a `bundle.json` / results file path |
|
|
107
|
+
| `E_CASE_NOT_FOUND` | no such case id in this run | `rb findings <run> --filter all` lists every id |
|
|
108
|
+
| `E_CHECKS_INVALID` | the checks file is not valid | `rb schema checks` |
|
|
109
|
+
| `E_CHECKS_PROJECT_MISMATCH` | the named checks file is for another project | use the same project name, or another `--checks` file (an unnamed `checks.json` for another project is ignored with a warning) |
|
|
110
|
+
| `E_LIMITS_INVALID` | a limit is out of range | `--max-regression ≥ 0`, `0 ≤ --max-late-share ≤ 1`, `0 ≤ --max-reversals ≤ 64` |
|
|
111
|
+
| `E_NOT_AVAILABLE` | the command is not in this version | see "What is in 0.1"; import results instead |
|
|
112
|
+
| `E_WRITE_FAILED` | a file could not be written | check permissions, or `--runs-dir` |
|
|
113
|
+
| `E_INTERNAL` | unexpected failure | re-run with `--json` and report it |
|
|
114
|
+
|
|
115
|
+
## Worked example
|
|
116
|
+
|
|
117
|
+
Prompt from the human: *"Review candidate checkpoint v2 against v1 on the warehouse set with Rabbit Brain and tell me what to look at."*
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
python evaluate.py --checkpoint v1 --out v1.json # your evaluator, unchanged: per-case errors (+ trajectories via the hook)
|
|
121
|
+
python evaluate.py --checkpoint v2 --out v2.json
|
|
122
|
+
python merge.py v1.json v2.json > results.json # one results file in the version-1 format above (ids identical in both)
|
|
123
|
+
rb import results.json --json
|
|
124
|
+
rb findings <run_id> --top 5 --json
|
|
125
|
+
rb case <run_id> <top case id> --json
|
|
126
|
+
rb report <run_id>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Then report to the human, in this order: the verdict line; the top cases with their `why` text; the run id; the path to `report.md`; the command that reproduces the queue (`rb findings <run_id>` with the limits used). Quote numbers only from `findings.json`. If there were no trajectories, say that stability was not assessed.
|
|
130
|
+
|
|
131
|
+
When a later checkpoint arrives, import it under the same project with the same case ids and run `rb check run <new run> --checks checks.json` first: it answers "are the cases we cared about still fine?" and exits 1 in CI if not. Then `rb findings` for the full review.
|
|
132
|
+
|
|
133
|
+
## Boundaries
|
|
134
|
+
|
|
135
|
+
`rb` does not run inference, does not modify model code (the one recorder line is the exception, and it only reads the update), and does not certify a model. Stability limits are generic heuristics and a starting point; a scorer fitted to the model is a separate, paid step. Nothing leaves the machine.
|
|
136
|
+
|
|
137
|
+
## For humans: verify what your agent did
|
|
138
|
+
|
|
139
|
+
Open `rb-runs/<run_id>/report.md`. The header lists the run id, the `rb` version, the exact command, the input file with its sha256, the environment and whether trajectories were present; the body restates the definitions with the limits in force and lists every case. Re-run the command from the header, or `rb findings <run_id>` with the same limits, and compare. If what the agent told you differs from the report, the report is right.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-09-19)
|
|
4
|
+
First release: the checker and importer from the Rabbit Brain v5 integration kit as a package.
|
|
5
|
+
- `rb import` (version-1 JSON or metrics CSV) → a run directory with `bundle.json` (version 2), `record.json`, `findings.json`, `report.md`.
|
|
6
|
+
- `rb findings`, `rb case`, `rb report`, `rb check save|run|list|rm`, `rb example`, `rb runs`, `rb docs`, `rb schema`, `rb version`.
|
|
7
|
+
- `--json` envelope on every command; exit codes 0/1/2/3; error codes with fixes.
|
|
8
|
+
- `TrajectoryRecorder` (the one-line hook) as a Python API.
|
|
9
|
+
- Same arithmetic as the web workspace and the v5 `check.py`: late share, reversals, regression threshold, ranking, verdict, report.
|
|
10
|
+
Not yet: `rb run` (the runner), adapters, evidence rendering, `rb open`. Those are 0.2.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rabbit-brain
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Release review for iterative perception models: rank the cases that regressed or never settled, keep checks for the next checkpoint.
|
|
5
|
+
Project-URL: Homepage, https://rabbit-brain-evaluation.jhetchan.chatgpt.site
|
|
6
|
+
Project-URL: Documentation, https://github.com/rabbit-brain/rb/blob/main/AGENTS.md
|
|
7
|
+
Project-URL: Source, https://github.com/rabbit-brain/rb
|
|
8
|
+
Project-URL: Paper, https://jhetchan.com
|
|
9
|
+
Author: Jhet Chan
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: RAFT,agents,ci,depth,evaluation,model-release,optical-flow,perception,regression-testing,stereo
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Topic :: Software Development :: Testing
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: pydantic<3,>=2.5
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: twine>=5; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Rabbit Brain
|
|
34
|
+
|
|
35
|
+
Release review for iterative perception models. Give it the per-case errors of your current and candidate checkpoints (and, if the model refines its answer iteratively, one number per refinement iteration) and it ranks the cases to look at: the ones that regressed on error, and the ones that pass on error but never settled. Then it keeps checks for the next checkpoint.
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
pip install rabbit-brain
|
|
39
|
+
rb example # see it on built-in example data
|
|
40
|
+
rb import results.json # your evaluator's per-case results (format: rb schema example)
|
|
41
|
+
rb findings <run> --top 5 # the ranked queue and a verdict
|
|
42
|
+
rb check save <run> <case> # keep a case for the next checkpoint
|
|
43
|
+
rb check run <run> # exit 1 in CI when something regressed
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Everything runs locally and nothing leaves your machine. Every run leaves a receipt (`report.md`, `record.json`) with the exact command, the input's hash and the definitions in force, so a colleague, or you after your coding agent ran it, can verify the numbers.
|
|
47
|
+
|
|
48
|
+
**What it does.** Compares two model versions case by case; flags regressions above a limit you choose; reads each model's own refinement trajectory (label-free) to flag answers that were still moving late or reversed direction; ranks what needs a decision first; explains each case in plain language; saves checks that follow case ids across checkpoints.
|
|
49
|
+
|
|
50
|
+
**What it needs.** One lower-is-better error per case for both models (any metric with a unit: endpoint error in px, depth error in cm, …), computed by your evaluator against the same ground truth and valid mask. Optionally the trajectory, recorded with the one-line hook (`from rabbit_brain import TrajectoryRecorder`).
|
|
51
|
+
|
|
52
|
+
**What it doesn't do.** Run inference (that arrives with the runner in 0.2), train anything, or certify a model. The stability limits are generic heuristics; a scorer fitted to your model is a separate, paid evaluation.
|
|
53
|
+
|
|
54
|
+
**For coding agents.** `rb docs` prints [AGENTS.md](AGENTS.md): the workflow, the file format, the JSON output (`--json` on every command), exit codes and every error code with its fix. Tell your agent: *"Review candidate checkpoint B against A on this case set with Rabbit Brain."*
|
|
55
|
+
|
|
56
|
+
The trajectory diagnostic comes from *Beyond Endpoint Sufficiency* (Jhet Chan, NeurIPS 2026 submission). The web workspace that reads the same files is at https://rabbit-brain-evaluation.jhetchan.chatgpt.site.
|
|
57
|
+
|
|
58
|
+
Apache-2.0.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Rabbit Brain
|
|
2
|
+
|
|
3
|
+
Release review for iterative perception models. Give it the per-case errors of your current and candidate checkpoints (and, if the model refines its answer iteratively, one number per refinement iteration) and it ranks the cases to look at: the ones that regressed on error, and the ones that pass on error but never settled. Then it keeps checks for the next checkpoint.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pip install rabbit-brain
|
|
7
|
+
rb example # see it on built-in example data
|
|
8
|
+
rb import results.json # your evaluator's per-case results (format: rb schema example)
|
|
9
|
+
rb findings <run> --top 5 # the ranked queue and a verdict
|
|
10
|
+
rb check save <run> <case> # keep a case for the next checkpoint
|
|
11
|
+
rb check run <run> # exit 1 in CI when something regressed
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Everything runs locally and nothing leaves your machine. Every run leaves a receipt (`report.md`, `record.json`) with the exact command, the input's hash and the definitions in force, so a colleague, or you after your coding agent ran it, can verify the numbers.
|
|
15
|
+
|
|
16
|
+
**What it does.** Compares two model versions case by case; flags regressions above a limit you choose; reads each model's own refinement trajectory (label-free) to flag answers that were still moving late or reversed direction; ranks what needs a decision first; explains each case in plain language; saves checks that follow case ids across checkpoints.
|
|
17
|
+
|
|
18
|
+
**What it needs.** One lower-is-better error per case for both models (any metric with a unit: endpoint error in px, depth error in cm, …), computed by your evaluator against the same ground truth and valid mask. Optionally the trajectory, recorded with the one-line hook (`from rabbit_brain import TrajectoryRecorder`).
|
|
19
|
+
|
|
20
|
+
**What it doesn't do.** Run inference (that arrives with the runner in 0.2), train anything, or certify a model. The stability limits are generic heuristics; a scorer fitted to your model is a separate, paid evaluation.
|
|
21
|
+
|
|
22
|
+
**For coding agents.** `rb docs` prints [AGENTS.md](AGENTS.md): the workflow, the file format, the JSON output (`--json` on every command), exit codes and every error code with its fix. Tell your agent: *"Review candidate checkpoint B against A on this case set with Rabbit Brain."*
|
|
23
|
+
|
|
24
|
+
The trajectory diagnostic comes from *Beyond Endpoint Sufficiency* (Jhet Chan, NeurIPS 2026 submission). The web workspace that reads the same files is at https://rabbit-brain-evaluation.jhetchan.chatgpt.site.
|
|
25
|
+
|
|
26
|
+
Apache-2.0.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rabbit-brain"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Release review for iterative perception models: rank the cases that regressed or never settled, keep checks for the next checkpoint."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "Jhet Chan" }]
|
|
14
|
+
keywords = ["optical-flow", "stereo", "depth", "evaluation", "regression-testing", "model-release", "RAFT", "perception", "ci", "agents"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
27
|
+
"Topic :: Software Development :: Testing",
|
|
28
|
+
]
|
|
29
|
+
dependencies = ["pydantic>=2.5,<3"]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = ["pytest>=8", "build>=1.2", "twine>=5"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://rabbit-brain-evaluation.jhetchan.chatgpt.site"
|
|
36
|
+
Documentation = "https://github.com/rabbit-brain/rb/blob/main/AGENTS.md"
|
|
37
|
+
Source = "https://github.com/rabbit-brain/rb"
|
|
38
|
+
Paper = "https://jhetchan.com"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
rb = "rabbit_brain.cli:main"
|
|
42
|
+
rabbit-brain = "rabbit_brain.cli:main"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/rabbit_brain"]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
48
|
+
"AGENTS.md" = "rabbit_brain/docs/AGENTS.md"
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.sdist]
|
|
51
|
+
include = ["src/rabbit_brain", "tests", "AGENTS.md", "README.md", "LICENSE", "CHANGELOG.md", "pyproject.toml"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
addopts = "-q"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Rabbit Brain: release review for iterative perception models.
|
|
2
|
+
|
|
3
|
+
Rank the cases that regressed or never settled, keep checks for the next checkpoint.
|
|
4
|
+
CLI: `rb`. Docs for agents and humans: `rb docs` (AGENTS.md).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from .recorder import TrajectoryRecorder # noqa: E402
|
|
11
|
+
|
|
12
|
+
__all__ = ["__version__", "TrajectoryRecorder"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Saved checks: absolute limits per case id for one project, kept in the repo (`checks.json`) and run against the next candidate."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from datetime import date
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, Union
|
|
8
|
+
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
11
|
+
from .errors import RBError
|
|
12
|
+
from .fmt import pct, to_fixed
|
|
13
|
+
from .models import Bundle, CheckResult, ChecksV1, ChecksV2, CheckV1, CheckV2, ComparisonV1
|
|
14
|
+
from .stability import EPS, trajectory_stats
|
|
15
|
+
|
|
16
|
+
DEFAULT_CHECKS_PATH = Path("checks.json")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_checks(path: Path, project: Optional[str] = None, strict: bool = True) -> ChecksV2:
|
|
20
|
+
"""Read a version-1 (workspace / kit) or version-2 checks file. Returns version 2, filtered to `project` when given.
|
|
21
|
+
|
|
22
|
+
A file for another project raises E_CHECKS_PROJECT_MISMATCH when `strict`, otherwise returns no checks (the caller may warn)."""
|
|
23
|
+
if not path.exists():
|
|
24
|
+
return ChecksV2(version=2, project=project or "", checks=[])
|
|
25
|
+
try:
|
|
26
|
+
raw = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
27
|
+
except json.JSONDecodeError:
|
|
28
|
+
raise RBError("E_CHECKS_INVALID", message=f"{path} is not valid JSON.")
|
|
29
|
+
if not isinstance(raw, dict):
|
|
30
|
+
raise RBError("E_CHECKS_INVALID")
|
|
31
|
+
try:
|
|
32
|
+
if raw.get("version") == 2:
|
|
33
|
+
v2 = ChecksV2.model_validate(raw)
|
|
34
|
+
if project and v2.checks and v2.project != project:
|
|
35
|
+
if strict:
|
|
36
|
+
raise RBError("E_CHECKS_PROJECT_MISMATCH", message=f"{path} holds checks for '{v2.project}', not '{project}'.")
|
|
37
|
+
return ChecksV2(version=2, project=project, checks=[])
|
|
38
|
+
return v2
|
|
39
|
+
if raw.get("version") == 1:
|
|
40
|
+
v1 = ChecksV1.model_validate(raw)
|
|
41
|
+
checks = [c for c in v1.checks if project is None or c.project == project]
|
|
42
|
+
if project and v1.checks and not checks:
|
|
43
|
+
if strict:
|
|
44
|
+
raise RBError("E_CHECKS_PROJECT_MISMATCH", message=f"No saved checks in {path} match project '{project}'.")
|
|
45
|
+
return ChecksV2(version=2, project=project, checks=[])
|
|
46
|
+
proj = project or (checks[0].project if checks else "")
|
|
47
|
+
return ChecksV2(version=2, project=proj, checks=[CheckV2(case_id=c.id, name=c.name, max_error=c.max_error, max_late_share=c.max_late_share) for c in checks])
|
|
48
|
+
except ValidationError as exc:
|
|
49
|
+
raise RBError("E_CHECKS_INVALID", message=f"{path}: {exc.errors()[0].get('msg', 'invalid')}")
|
|
50
|
+
raise RBError("E_CHECKS_INVALID", message=f"{path}: expected version 1 or 2.")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def save_checks(path: Path, checks: ChecksV2) -> None:
|
|
54
|
+
try:
|
|
55
|
+
path.write_text(json.dumps(checks.model_dump(exclude_none=True), indent=2) + "\n", encoding="utf-8")
|
|
56
|
+
except OSError as exc:
|
|
57
|
+
raise RBError("E_WRITE_FAILED", message=f"Could not write {path}: {exc}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def evaluate_check(check: CheckV2, comparison: Union[ComparisonV1, Bundle], unit: str) -> CheckResult:
|
|
61
|
+
"""Same semantics as the workspace's checkResult() and the kit's check.py."""
|
|
62
|
+
found = next((c for c in comparison.cases if c.id == check.case_id), None)
|
|
63
|
+
base = dict(case_id=check.case_id, max_error=check.max_error, max_late_share=check.max_late_share, max_reversals=check.max_reversals)
|
|
64
|
+
if found is None:
|
|
65
|
+
return CheckResult(status="missing", reason="Case not present in this comparison.", **base)
|
|
66
|
+
base["candidate_error"] = found.candidate_error
|
|
67
|
+
if found.candidate_error > check.max_error + EPS:
|
|
68
|
+
return CheckResult(status="failing", reason=f"Candidate error {to_fixed(found.candidate_error)} {unit} exceeds the {to_fixed(check.max_error)} {unit} limit.", **base)
|
|
69
|
+
if check.max_late_share is not None or check.max_reversals is not None:
|
|
70
|
+
if not found.candidate_trajectory:
|
|
71
|
+
return CheckResult(status="failing", reason="This check requires a settled trajectory, but none was exported for the candidate.", **base)
|
|
72
|
+
stats = trajectory_stats(found.candidate_trajectory)
|
|
73
|
+
if check.max_late_share is not None and stats.late_share > check.max_late_share + EPS:
|
|
74
|
+
return CheckResult(status="failing", reason=f"Late revision {pct(stats.late_share)} exceeds the {pct(check.max_late_share)} limit.", **base)
|
|
75
|
+
if check.max_reversals is not None and stats.reversals > check.max_reversals:
|
|
76
|
+
return CheckResult(status="failing", reason=f"Reversals {stats.reversals} exceed the {check.max_reversals} limit.", **base)
|
|
77
|
+
return CheckResult(status="passing", reason="Within limits.", **base)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def evaluate_all(checks: ChecksV2, comparison: Union[ComparisonV1, Bundle], unit: str, case_id: Optional[str] = None) -> list[CheckResult]:
|
|
81
|
+
selected = [c for c in checks.checks if case_id is None or c.case_id == case_id]
|
|
82
|
+
return [evaluate_check(c, comparison, unit) for c in selected]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def upsert_check(checks: ChecksV2, new: CheckV2) -> ChecksV2:
|
|
86
|
+
others = [c for c in checks.checks if c.case_id != new.case_id]
|
|
87
|
+
return ChecksV2(version=2, project=checks.project, checks=[*others, new])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def default_check(bundle: Bundle, case_id: str, *, max_error: Optional[float], max_late_share: Optional[float], max_reversals: Optional[int], require_settled: bool, note: str) -> CheckV2:
|
|
91
|
+
case = next((c for c in bundle.cases if c.id == case_id), None)
|
|
92
|
+
if case is None:
|
|
93
|
+
raise RBError("E_CASE_NOT_FOUND", message=f"Case '{case_id}' is not in run {bundle.run_id}.")
|
|
94
|
+
if max_error is None:
|
|
95
|
+
max_error = float(to_fixed(case.baseline_error + bundle.limits.max_regression, 2))
|
|
96
|
+
if max_late_share is None and require_settled and case.candidate_trajectory:
|
|
97
|
+
max_late_share = bundle.limits.max_late_share
|
|
98
|
+
return CheckV2(case_id=case.id, name=case.name, max_error=max_error, max_late_share=max_late_share, max_reversals=max_reversals, from_run=bundle.run_id, created=date.today().isoformat(), note=note or "")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def describe_check(check: CheckV2, unit: str) -> str:
|
|
102
|
+
parts = [f"candidate error ≤ {to_fixed(check.max_error)} {unit}"]
|
|
103
|
+
if check.max_late_share is not None:
|
|
104
|
+
parts.append(f"late revision ≤ {pct(check.max_late_share)}")
|
|
105
|
+
if check.max_reversals is not None:
|
|
106
|
+
parts.append(f"reversals ≤ {check.max_reversals}")
|
|
107
|
+
return ", ".join(parts)
|