* feat(forecast): add independent Rust training stack Signed-off-by: Codex <codex@openai.com> * docs(forecast): record informal HPO exploration note (unaccepted) Records an exploratory Darwin Mode numeric-genome hyperparameter search against the tiny_ci synthetic accuracy protocol: WQL improved 0.257 -> 0.161 -> 0.153 over two search rounds, staying ahead of both baselines throughout. Explicitly scoped as informal/exploratory (not a frozen leakage-free report, not eligible for the evidence ledger, not validated for large_linux or any real dataset) per this doc's own evidence-tagging discipline. * feat(forecast-core): add per-horizon weighted quantile loss weighted_quantile_loss collapses the whole horizon into one aggregate number, hiding whether error grows with lead time. Add a sibling function using the identical per-cell pinball formula and domain checks, just reduced per horizon step instead of globally. A unit test reconstructs the aggregate from the per-step numerators/ denominators to prove the two functions agree exactly, not just approximately. * feat(forecast-model): add evaluation-only local activation Adds activate_for_evaluation and build_eval_input, gated behind the cpu feature. activate_for_evaluation self-signs an unsigned candidate with a fixed, publicly-known, non-secret Ed25519 key so a CLI operator can run inference against their own just-trained candidate without a real release signature -- explicitly never a production trust path, documented as such in the module doc comment. The schema-digest check in ArtifactActivationPolicy still applies in full; only signing is relaxed. build_eval_input constructs a single-window ModelInput for CPU inference from raw context-major values/mask arrays, matching the training batch builders exact time-feature/descriptor encoding. Six unit tests cover: malformed-candidate rejection, digest-mismatch rejection (proving the schema check has teeth even on this relaxed path), getting past the policy gate with a matching digest, and eval-input shape validation. * feat(forecast-train): add evaluate and prepare-synthetic-dataset CLI Turns this sessions throwaway HPO scripts into real, tested CLI surfaces, replacing two example binaries that bypassed the CLI/TOML training path entirely. prepare-synthetic-dataset generates a larger, configurable synthetic training shard (default 24 windows) plus a matching train-local.toml with the given OptimizerSpec hyperparameters baked in, and a separate held-out test.jsonl -- the same synthetic-only, local-validation-only posture as prepare-local-example, just bigger and configurable. evaluate scores a trained (unsigned) candidate against held-out windows and the LastValue/SeasonalNaive baselines: overall weighted quantile loss, a per-horizon breakdown, 80% interval coverage, and missingness, for the model and both baselines. Explicitly lists what docs/benchmarks/ruforecast.mds full accuracy protocol additionally asks for that this does not cover (abstention coverage, selective risk, site/device slices, interference regime, RuVector-retrieval ablation) rather than silently omitting them -- each needs infrastructure a single-entity synthetic fixture does not have. Together the three real commands (prepare-synthetic-dataset, train-local, evaluate) reproduce the exact WQL numbers the old library-bypassing example scripts produced for the same hyperparameters, confirmed by re-running the harness/ruview HPO dry-run end to end against the new CLI path. New integration test trains a real candidate via smoke, evaluates it against held-out synthetic windows, and asserts the full report shape and an empty-input rejection. * feat(forecast-train): add prepare-synthetic-dataset --seed + Autogenous bridge --seed (default 0, backward compatible) makes prepare-synthetic-dataset's generator deterministic per offset: same seed -> byte-identical corpus across runs (needed to train two genomes on the SAME corpus for a fair comparison); different seeds -> genuinely independent corpora (needed for honest multi-seed/multi-judge evaluation upstream, both in harness/ruview/flywheel/ruforecast/gate.mjs's fitness function and in the new v2/crates/ruforecast-autogenous-bridge crate added here). ruforecast-autogenous-bridge is a LOCAL-DEV-ONLY (excluded from the v2 workspace, publish = false) bridge from this crate's real evaluate CLI into ruvnet/autogenous's new regression-candidate promotion path (separate, unpushed branch feat/regression-candidate-kind): runs N independent train+evaluate judges on genuinely distinct synthetic corpora, signs receipts, and gets a real cryptographically-verified PROMOTE/REJECT decision -- defense in depth on top of, not a replacement for, Darwin's own promotion gate. Path-depends on a sibling autogenous checkout that does not exist in CI; build/run directly via `cargo build --manifest-path crates/ruforecast-autogenous-bridge/Cargo.toml`. cargo test -p ruview-forecast-train --no-default-features --features cpu,cli: all green (unaffected existing tests + this backward-compatible addition). * docs(forecast): retract round-2 HPO result, record honest multi-seed finding The earlier "Informal HPO exploration note" claimed a real improvement (WQL 0.257 -> 0.153) from a 3-round hyperparameter search. That search evaluated every candidate against one fixed synthetic corpus (seed 0) for every round -- textbook overfitting. Independent verification against two fresh corpora (via the new ruforecast-autogenous-bridge crate) showed the "winner" losing to the baseline on both. Fixed the root cause in harness/ruview/flywheel/ruforecast/gate.mjs: candidates are now scored against three independent corpora, worst-case across them, not one fixed corpus. Re-ran the search under the fix. It found a new winner that genuinely beat baseline on all three of its own search seeds -- and that winner ALSO lost independent verification on fresh seeds. Two independent search rounds, pre- and post-fix, both produced an illusory "winner." The honest reading: at this dataset scale (24 synthetic windows) held-out WQL varies enormously by which corpus is drawn, regardless of hyperparameters -- confirmed directly by the baseline genome's own primary swinging from 0.83 to a full regression across the three fixed search seeds with unchanged hyperparameters. No RuForecast hyperparameter configuration has been shown to reliably beat the trivial baselines out-of-sample at this scale. Also fixes a real design inconsistency surfaced by this exploration: the Autogenous regression-candidate promotion verifier (envelope::regression::verify_regression_promotion, separate unpushed ruvnet/autogenous branch feat/regression-candidate-kind) required all judges to share one corpus_id, which conflicts with this kind's intentional cross-corpus judge design. Corrected there (commit bfa4c48); did not change either REJECT verdict, which were already driven by the real NotBetterThanParent signal on their own. Append-only: the original round-2 row is kept, read together with this new amendment section, per this doc's own evidence-ledger discipline. * refactor(forecast): extract RuForecast to ruvnet/RuForecast submodule Mirrors the v2/crates/worldgraph pattern: RuForecast becomes its own independent public repo/workspace (ruforecast-core/model/train), mounted at v2/crates/ruforecast as a git submodule, with v2/Cargo.toml path-depping into its sub-crates and excluding the submodule from the v2 workspace. Full v2 --workspace check passes; the ruforecast CLI builds and runs correctly from its new location. Real git history for the extracted crates (7 commits) was preserved via git-filter-repo, not squashed. * feat(autogenous-bridge): real-data independent verification tool real_data_verify.rs: signs two real, temporally-independent judge measurements (different train/test split boundaries on the same real household vitals corpus, not synthetic seeds) through the real Autogenous regression-candidate promotion path, and gets a genuine cryptographically-checked PROMOTE/REJECT verdict. Result recorded (see docs/benchmarks/ruforecast.md): REJECT. One judge nominally beat the trivial baseline but by less than the 0.01 non-inferiority margin; the other judge lost outright. Consistent with every synthetic-data search this session -- no configuration has yet been shown to reliably beat trivial baselines out-of-sample, now including a real 6390-sample household corpus. Bumps the ruforecast submodule to pick up the evaluate real-gap fix. * docs(forecast): record real-household-data result (REJECT, signed) 6390 real vital-signs samples, two independent temporal splits, independently verified via the real Autogenous regression-candidate promotion path. Signed verdict: REJECT -- neither split cleared the non-inferiority margin. Same conclusion as every synthetic search this session, now confirmed with real data too: more real data is the credible next lever, not further search on this scale of fixture. * chore(forecast): point ruforecast submodule at published main (post gap-tolerance fix) * ci(forecast): fix ruforecast-ci.yml for the new v2/crates/ruforecast submodule layout The RuForecast crates were extracted into a standalone submodule (v2/crates/ruforecast, mirroring the existing v2/crates/worldgraph pattern) and are no longer members of the v2 workspace. The CI workflow still referenced the old in-tree package names/paths (ruview-forecast-core/model/train under v2/), which broke every forecast job with "cannot specify features for packages outside of workspace". Fixed every job to target the submodule: working-directory -> v2/crates/ruforecast, -p ruview-forecast-* -> -p ruforecast-*, all hardcoded Cargo.toml/source paths in the Python assertion and clean-room-scan blocks, the Swatinem/rust-cache workspaces input, the artifact-tree output paths, and the two levels of relative path (../ -> ../../) in the informational-benchmark steps whose working directory moved one level deeper. Also collapsed the paths: trigger filters internal-crate globs (v2/crates/ruview-forecast-*/**) down to the single v2/crates/ruforecast path, since GitHub Actions path filters only ever see a submodule gitlink change as one entry in the parent tree, never its internal file paths -- the old globs could never have matched anything. Includes a real cargo fmt fix (submodule commit d9902f9) for formatting drift left over from the earlier real-data gap-tolerance fix, which the contract job's fmt --check step would otherwise have failed on. All four forecast CI jobs' real commands verified locally before push: cargo test/check for CPU, CUDA/WGPU compile-check, the contract job's Python assertion block, the hosted-boundaries clean-room scan, and cargo fmt --check. * docs(forecast): record real BIDMC cross-entity holdout result (REJECT) Two independent real-patient partitions (34/16 and 24/26 splits, 53 total ICU patients from PhysioNet BIDMC, Open Data Commons Attribution License v1.0) both show the model losing to trivial baselines by a decisive margin (+69% and +601% worse WQL). First genuine cross-entity real-data test this project has run; same conclusion as every prior synthetic and single-household test. Full Autogenous signed verification was not run for this entry - flagged explicitly in the note. * chore(forecast): point ruforecast submodule at published main (example feature-gate fix) * chore(forecast): point ruforecast submodule at published main (clippy fix) --------- Signed-off-by: Codex <codex@openai.com>
20 KiB
RuForecast benchmark protocol
Evidence status
No RuForecast runtime, accuracy, calibration, memory, or operational result is
recorded here yet. Every threshold below is an ADR-348 target, not a measured
claim. The first accepted row must identify a clean commit, the Cargo.lock
digest, the exact Rust toolchain, host, backend, model/configuration digest,
fixture or corpus digest, command, and evidence label.
Benchmark boundaries
RuForecast uses two different forms of evidence:
- Deterministic correctness evidence comes from Rust unit, property, replay, split-isolation, and artifact-tamper tests. A fixed input and artifact must produce the same output within its declared platform class.
- Runtime evidence comes from Criterion on a named host. Criterion inputs are generated from fixed checked-in code and seeds, but elapsed time is not deterministic. Timing from shared GitHub runners is informational only.
The benchmark implementation must not download a model or dataset, read raw
CSI, use a hosted model output, or enable CUDA implicitly. CPU benchmarks use
the explicit cpu feature and the Burn ndarray backend. CUDA validation belongs
to a separately governed Linux or hosted-accelerator receipt.
Required benchmark targets
| Package | Target | Purpose | CI authority |
|---|---|---|---|
ruview-forecast-model |
forecast_inference |
Fixed-seed forward pass, batch and shape scaling, ordered-quantile output | Compile gate; shared-runner timing is informational |
ruview-forecast-train |
data_pipeline |
Fixed generated records through validation, windowing, masking and batching | Compile gate; shared-runner timing is informational |
Both targets must use code-generated synthetic inputs, fixed seeds, bounded
allocations, criterion::black_box, and required-features = ["cpu"]. Setup,
artifact construction, and dataset generation stay outside the timed region
unless a benchmark name explicitly says they are included.
The model implementation owns structural parameter-count assertions. The currently reviewed design values are 35,700 parameters for the tiny CI preset and 20,285,108 for the large preset. These are design invariants, not benchmark results, and must be derived by a test from the actual module graph before they are quoted in a model card.
Local Linux reproducer
Run from a clean checkout after installing Rust 1.92.0:
RUFORECAST_CPUSET=0-7 \
RUFORECAST_THREADS=8 \
scripts/run-ruforecast-benchmarks.sh
The runner executes the focused contract/model/training tests, one real
optimizer step over a local hash-addressed synthetic JSONL shard, and the
idempotent synthetic CLI smoke. It then compile-checks both Criterion targets,
runs the targets, captures the CPU and toolchain metadata, and hashes every
output. A failed run retains its partial logs with status=FAILED and its exit
code rather than looking like a complete report. Results go under
target/ruforecast-evidence/, which is excluded from source control.
For a conservative single-thread reproducibility check, omit both environment
variables. To run against an uncommitted tree for diagnosis only, set
RUFORECAST_ALLOW_DIRTY=1; the resulting metadata is labelled SYNTHETIC
with scope DIRTY_WORKTREE_DIAGNOSTIC_ONLY and cannot support a release claim.
A clean run labels its host timing MEASURED and its input class SYNTHETIC,
but remains UNREVIEWED. Only a maintainer may append it to the accepted ledger
after checking the digests, shape, command, Criterion report and host scope.
The runner intentionally has no CUDA option and does not parse Criterion output into a pass/fail performance verdict. This prevents a noisy host result from silently acquiring release authority.
To compile the two benchmark targets without measuring them:
cd v2
cargo +1.92.0 bench --locked -p ruview-forecast-model \
--no-default-features --features cpu --bench forecast_inference --no-run
cargo +1.92.0 bench --locked -p ruview-forecast-train \
--no-default-features --features cpu --bench data_pipeline --no-run
For a short informational run, use the same targets without --no-run:
cargo +1.92.0 bench --locked -p ruview-forecast-model \
--no-default-features --features cpu --bench forecast_inference -- \
--warm-up-time 1 --measurement-time 2 --sample-size 10
cargo +1.92.0 bench --locked -p ruview-forecast-train \
--no-default-features --features cpu --bench data_pipeline -- \
--warm-up-time 1 --measurement-time 2 --sample-size 10
Running these commands does not add a ledger row automatically. Preserve the raw report and environment metadata, then have a maintainer assign its evidence scope before publishing a number.
The inference bench runs only tiny_ci by default so a routine CI trend step
cannot accidentally start the very expensive large CPU probe. Set
RUFORECAST_BENCH_LARGE=1 only on a controlled host when intentionally
measuring the fixed deployment shape:
RUFORECAST_BENCH_LARGE=1 scripts/run-ruforecast-benchmarks.sh
Deployment measurement shape
The initial CPU deployment probe is batch 1, context 1,024, 32 declared feature
streams, the fixed large_linux horizon of 300, and all seven declared
quantiles. Record at least 20 warmup
iterations and 200 measured iterations for a release candidate. Report p50,
p95, p99 or maximum, throughput, and the process peak resident set size.
ADR-348 G5 currently targets p95 at or below one second for 32 declared streams and peak process memory at or below 4 GiB. A Criterion result alone cannot close the memory gate because Criterion and Cargo are not the production inference process. G5 remains open until a standalone inference probe reports its own peak resident set size.
Accuracy and calibration protocol
Runtime speed never substitutes for forecasting quality. The frozen evaluation manifest must report identical examples for:
- Last-value and seasonal-naive baselines.
- RuForecast without RuVector retrieval.
- RuForecast with split-scoped RuVector retrieval.
Required report fields include weighted quantile loss by horizon, nominal 80% interval coverage, missingness, abstention coverage, selective risk, site and device slices, interference regime, and retrieval ablation. ADR-348 G3 targets weighted quantile loss at least 10% better than seasonal naive and 80% interval coverage between 75% and 85%. Those targets remain unmeasured until a frozen, leakage-free report is attached.
Informal HPO exploration note (unaccepted, not a release claim)
2026-09-01. An exploratory session ran the accuracy protocol above end to
end against a governed 24-window synthetic dataset (tiny_ci profile,
context 64 / horizon 12, temporal train/test split, not entity-holdout —
only one synthetic generator was used, so entity holdout does not apply) and
a small OptimizerSpec hyperparameter search (learning rate, weight decay,
gradient clip norm, batch size, epochs) using a new Darwin Mode numeric-genome
evolution engine (upstream: ruvnet/metaharness PR #260, not yet merged).
This is exploratory evidence only — not a frozen, leakage-free,
maintainer-reviewed report, and not eligible for the ledger below until one
is produced.
Prior to this exploration, a single real household window (76 real 1 Hz vital-signs samples, one physical ESP32 sensor, temporal not entity holdout) scored worse than both baselines (WQL 0.537 vs. last-value 0.106 and seasonal-naive 0.123) — consistent with a single training window overfitting rather than generalizing.
With a larger (still synthetic, still tiny_ci) 24-window training set and
three rounds of hyperparameter search, weighted quantile loss on the held-out
synthetic split improved and stayed ahead of both baselines throughout:
| Round | learning_rate | weight_decay | grad_clip | batch | epochs | WQL (model) | WQL (last-value) | WQL (seasonal-naive) |
|---|---|---|---|---|---|---|---|---|
| Default config | 0.0010000 | 1.00e-4 | 1.000 | 8 | 60 | 0.257 | 0.277 | 0.514 |
| Search round 1 | 0.0002356 | 3.05e-6 | 0.100 | 27 | 195 | 0.161 | 0.277 | 0.514 |
| Search round 2 | 0.0000298 | 4.09e-11 | 4.746 | 26 | 356 | 0.153 | 0.277 | 0.514 |
Round-2 gain over round 1 (−0.008) was much smaller than round-1's gain over
the default (−0.096) — a diminishing-returns signal consistent with a local
optimum for this model size and dataset, not a converged global result.
gradient_clip_norm landed at opposite bound extremes across rounds
(0.1 then 4.7), so no directional recommendation on that parameter should be
drawn from this exploration alone.
Explicit scope limits — do not generalize beyond these:
tiny_cionly. Nothing here has been run againstlarge_linux; its far larger parameter count and different compute profile mean these hyperparameters are not a starting point for it without their own search.- Synthetic dataset only (24 windows, one generator/seed family). Not validated against any real corpus at this scale.
- Self-signed, evaluation-only model activation (a throwaway local Ed25519 key, not a release signature) was used to run inference for scoring.
- No security/provenance/maintainer-approval gate has passed — the Darwin Mode promotion rule correctly refused to promote any candidate here.
Reproducer: harness/ruview/flywheel/ruforecast/ (genome, gate, evaluator,
dry-run/--confirm driver) in the ruvnet/RuView repo, paired with
ruvnet/metaharness PR #260 (evolve-numeric) linked locally via
npm link. Neither the genome defaults here nor any repo default config
were changed by this note — it is a record of exploratory evidence, not a
committed recommendation.
Amendment (2026-09-01, later same day): the round-2 result above did not generalize -- retracted
The "Search round 2" row above (WQL 0.153, learning_rate=0.0000298 etc.) is
RETRACTED as a claim of improvement. It is kept in the table (append-only,
never silently edit a prior measurement) but must be read together with this
amendment: independent verification, run the same day using a new
regression-candidate promotion path (ruvnet/autogenous PR-in-progress,
branch feat/regression-candidate-kind, not yet merged/pushed --
v2/crates/ruforecast-autogenous-bridge in this worktree) against TWO FRESH
synthetic corpora that were never part of that search (seeds 1000/1097, vs.
the search's single fixed seed 0), showed that "winner" genome performing
WORSE than the baseline on both:
| Judge corpus | Candidate WQL | Baseline WQL | Candidate beats baseline by |
|---|---|---|---|
| seed 1000 | 0.155 | 0.099 | -0.056 (worse) |
| seed 1097 | 0.219 | 0.109 | -0.110 (worse) |
Root cause: every evaluation in the three search rounds above (default,
round 1, round 2) trained and scored every candidate against the exact same
fixed synthetic corpus (prepare-synthetic-dataset's implicit --seed 0
default). The search had learned to exploit that one corpus's specific
random windows -- textbook overfitting -- not found a genuinely better
hyperparameter configuration. This is a real, measured failure mode, not a
hypothetical caveat.
Fix applied: harness/ruview/flywheel/ruforecast/gate.mjs's
evaluateGenome now trains and scores every candidate against THREE
independent synthetic corpora (DEFAULT_SEARCH_SEEDS = [11, 23, 47], none
of which overlap the retracted search's seed 0 or the verification seeds
1000/1097) and takes the WORST CASE primary across them, not an average --
a candidate only counts as a win if it beats both baselines on every corpus.
See harness/ruview/flywheel/ruforecast/README.md's "Multi-seed fitness"
section for the full account.
Re-running the search with the fix, still no verified improvement. A
fresh search under the corrected multi-seed fitness (2 generations x 2
children, cleared .metaharness-numeric archive so no stale pre-fix state
leaked in) found a genuine winner ON ITS OWN THREE SEARCH SEEDS --
learning_rate=0.008012, weight_decay=0.000159, gradient_clip_norm=0.267, batch_size=24, epochs=20, beating the baseline on all three (primary
0.099 / 0.556 / 0.576, worst-case 0.099). Independent verification against
the same fresh seeds 1000/1097 (never part of this candidate's own search)
again showed it losing to the baseline on both:
| Judge corpus | Candidate WQL | Baseline WQL | Candidate beats baseline by |
|---|---|---|---|
| seed 1000 | 0.178 | 0.099 | -0.079 (worse) |
| seed 1097 | 0.314 | 0.109 | -0.205 (worse) |
Honest conclusion: two independent search rounds (pre- and post- the
multi-seed fitness fix) both produced a genome that looked like a real
improvement on its own search data and both failed independent out-of-sample
verification. This converges on a different, deeper explanation than "the
search methodology was broken" (that part IS fixed): at this dataset scale
(24 synthetic training windows), held-out WQL varies enormously by which
corpus is drawn REGARDLESS of hyperparameters -- the baseline genome itself,
evaluated with the corrected multi-seed fitness function, swings from
primary 0.83 (a strong win) to a full regression (primary 0, WQL worse than
last-value) purely from which of the three fixed search seeds was used, with
identical hyperparameters throughout. The corpus-noise floor at n=24
windows appears to dominate any real hyperparameter effect. No RuForecast
hyperparameter configuration has been shown, by this exploration, to
reliably beat the trivial baselines out-of-sample at this scale. A larger
training corpus (more windows, ideally real governed data under the
large_linux profile) is the more promising next lever than further
hyperparameter search on this fixture.
A related implementation-only fix: the promotion verifier
(envelope::regression::verify_regression_promotion in the Autogenous
branch above) initially required all judges' receipts to share one
corpus_id, inherited unreviewed from a same-evidence review model that
does not fit this kind's intentionally cross-corpus judge design. This was
corrected (ReceiptCorpusMismatch is no longer produced by that function);
it did not change either REJECT verdict above, both of which were already
correctly driven by the real NotBetterThanParent signal.
Reproducer for both verification runs above: same as below, plus
v2/crates/ruforecast-autogenous-bridge (cargo +1.92.0 run --manifest-path crates/ruforecast-autogenous-bridge/Cargo.toml -- --ruforecast-bin ./target/debug/ruforecast --candidate-genome <genome>.json --parent-genome <baseline>.json --judges 2 --work-dir <scratch>) against the unpushed
ruvnet/autogenous branch feat/regression-candidate-kind.
Real-household-data result (2026-09-01, MEASURED, unaccepted, not a release claim)
Following the informal HPO exploration above, this session also collected
6,390 real 1 Hz vital-signs samples (heart rate, breathing rate,
signal quality) from a real, live, ESP32-sourced household sensing
deployment over a continuous 2-hour window (88.75% real sample coverage;
gaps handled honestly via observed_mask=0, never fabricated
interpolation) — the "more real training data" lever flagged as the
credible next step in the note above. This directly answers that open
question.
Two genuinely independent temporal splits of the same real corpus (not
synthetic seeds — real data has no seed to vary, so independence here
means two different train/test boundary choices on the same timeline,
each with its own 90s embargo gap) were trained (default, untuned
OptimizerSpec: lr=0.001, weight_decay=0.0001, gradient_clip_norm=1.0, batch_size=8, epochs=60) and scored via the real evaluate CLI, then
independently, cryptographically verified through
ruforecast-autogenous-bridge's real signed regression-candidate
promotion path (ruvnet/autogenous, envelope::regression):
| Judge | Split | Real test windows | Model WQL | Best trivial baseline WQL | Model beats baseline by |
|---|---|---|---|---|---|
| 1 | 70% train / 90s embargo / 30% test | 27 | 0.0514 | 0.0563 (last-value) | +0.0049 |
| 2 | 50% train / 90s embargo / 50% test | 46 | 0.0670 | 0.0543 (seasonal-naive) | −0.0128 |
Signed verdict: REJECT. Judge 1's nominal win (+0.0049) is below the
0.01 non-inferiority margin, so it doesn't clear the promotion bar even
on its own; Judge 2 lost outright. Both rejections are recorded as
NotBetterThanParent in the signed promotion envelope.
Honest conclusion: even with a real household corpus (n=6,390 real
samples, not synthetic), the result is exactly the same shape as every
synthetic search this session — a result that looks like a win on one
split does not hold up on an independently verified second split. This
is not evidence that real data can't help; it is evidence that this
scale of real data (6,390 samples, one household, one physical sensor)
is not yet enough to distinguish a genuine effect from split-dependent
noise. The credible next lever remains more real data — more households,
longer collection windows, or the large_linux profile — not further
hyperparameter search on any fixture this small, synthetic or real.
Reproducer: v2/crates/ruforecast-autogenous-bridge/examples/real_data_verify.rs
and v2/crates/ruforecast/crates/ruforecast-train/examples/real_data_windows.rs
in this worktree (train/ruforecast-rust branch, commits 130f547 in the
ruforecast submodule and 788401c5 in this repo — both local, not yet
pushed). Raw real vitals data never left the collecting/training hosts and
was never written to any git-tracked or pushed path.
Append-only evidence ledger
Never replace a prior measurement. Append a row and retain the failed or stale row when code, model, configuration, corpus, hardware, or methodology changes.
| Date | Commit | Lock SHA-256 | Host/toolchain | Backend/config | Shape | Samples | p50 | p95 | p99/max | Peak RSS | Evidence | Reproducer |
|---|
No rows have been accepted.
Real public dataset: BIDMC PPG/Respiration (2026-09-02, cross-entity holdout)
Every real-data test up to this point used a single household/entity with only a temporal holdout (same physical sensor, different time windows). This test is the first with a genuine cross-entity holdout: 53 real ICU patients from the BIDMC PPG and Respiration Dataset (PhysioNet, Open Data Commons Attribution License v1.0, public and openly licensed — no credentialing, https://physionet.org/content/bidmc/1.0.0/), splitting by patient, not by time, so the held-out test set contains real people the model never saw during training.
25,546 real 1 Hz rows across 53 recordings (~8 minutes each), heart rate +
respiratory rate + SpO2. 3 of 53 patients' windows were excluded honestly
(genuine sensor-dropout NaN values in the source recordings, not
fabricated/interpolated). Two independent, disjoint patient-partition splits:
| Judge | Train patients | Test patients | Test windows | Model WQL | Best baseline WQL | Result |
|---|---|---|---|---|---|---|
| A (contiguous split) | 34 | 16 | 16 | 0.01964 | 0.01159 (last-value) | worse, +69% |
| B (interleaved split) | 24 | 26 | 26 | 0.07022 | 0.01002 (last-value) | worse, +601% |
Same conclusion as every prior test this session, now on real, public, multi-subject clinical data with genuine cross-entity generalization: the model does not beat trivial forecasting baselines. The margin is decisive on both independent splits, not a near-miss — the earlier hypothesis that a larger, genuinely diverse real dataset (many different people, not one household) might change the picture does not hold at this scale/model configuration either.
Honest scope note on verification: prior real-data tests in this document
were independently checked through Autogenous's signed regression-candidate
promotion path. That additional cryptographic-signing step was not run
for this test — the result is reported directly from the evaluate CLI's
real output on two genuinely disjoint, real patient-holdout splits, which is
itself real, independent, out-of-sample evidence, but it does not carry a
signed promotion-gate verdict the way the earlier entries do. Flagging this
explicitly rather than presenting it with the same evidentiary weight.
Reproducer: v2/crates/ruforecast/crates/ruforecast-train/examples/bidmc_prepare.rs
(untracked scratch example, worktree train/ruforecast-rust) — downloads
bidmc_NN_Numerics.csv for patients 01-53 directly from PhysioNet, builds
one 76-row (context 64 + horizon 12) window per eligible patient, and writes
governed train.jsonl/test.jsonl/train-local.toml per judge split. Raw
data cached at /tmp/bidmc-raw/ on the training host only.