feat(chart): decimate served pages + run-context header (visual World cut 2)
Hardens the families-comparison chart page (#107) so it is openable and legible on real multi-year data. Two halves, both confined to the CLI render path (engine + registry untouched, C14): #108 — serve-time min-max decimation. A pure `decimate(ChartData, buckets)` transform partitions the shared union-ts spine into <=~2000 buckets and emits each series' per-bucket min+max (nulls preserved), bounding the served page to a few thousand points regardless of the underlying M1 point count. Applied in emit_chart on both the single-run and family paths; a no-op under budget (every existing fixture). Full recorded data stays on disk; only the page is thinned. For a walk-forward family the null-fill blow-up collapses for free (an all-null bucket stays null). Deterministic (C1). #102 — a run-context header. A `ChartMeta` (name/commit/window/broker/seed/taps, + member count for a family, + bound params for a single run) is built from the RunManifest in build_chart_data / build_comparison_chart_data (which gain a `name` arg), injected into window.AURA_TRACES.meta, and rendered by a new pure buildHeader in chart-viewer.js (headless .mjs-guarded). The family window is the SPAN across members (min from, max to), not the first member's window — the only reading that labels a disjoint walk-forward family's true OOS coverage (it collapses to the shared window for sweep/MC). Reuses the existing render_value for param display (no new Scalar Display, keeping C7's tag-only param plane). The earlier "manifest is NOT carried into the page" render pin is flipped to a positive one. Verified: cargo test --workspace = 446 passed / 0 failed; cargo clippy --workspace --all-targets -D warnings clean. New coverage: 5 decimate unit tests, single-run + family meta wiring (incl. the span-window invariant), a buildHeader headless guard, and two end-to-end cli_run tests pinning the served-page meta at the binary boundary. closes #108 closes #102
This commit is contained in:
@@ -1377,3 +1377,77 @@ fn chart_family_with_tap_overlays_the_named_tap_per_member() {
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// Property (#102, single-run run-context reaches the served page): `aura chart
|
||||
/// <run>` injects the run's `RunManifest` context into `window.AURA_TRACES.meta`
|
||||
/// at the binary boundary — `kind:"run"`, the chart name, the manifest broker and
|
||||
/// data window, the charted taps, and `members:null` (a single run carries no
|
||||
/// member count). The render-layer unit (`render_chart_html_injects_run_context`)
|
||||
/// proves the struct serializes, but only an end-to-end run pins that the
|
||||
/// *built-and-read-back* manifest threads through `build_chart_data` into the
|
||||
/// served HTML; a regression dropping the `meta` build (charting bare series with a
|
||||
/// `ChartMeta::default()`) would leave every render/unit test green while serving a
|
||||
/// header-less page. Asserts on the stable manifest fields (the synthetic window is
|
||||
/// `[1,7]`, the broker is the FX sim-optimal label, kind/taps/members are fixed),
|
||||
/// NEVER on the volatile `commit` (the dirty git HEAD), so it stays deterministic.
|
||||
#[test]
|
||||
fn chart_run_serves_manifest_run_context_in_meta() {
|
||||
let cwd = temp_cwd("chart-run-meta");
|
||||
|
||||
let traced = Command::new(BIN).args(["run", "--trace", "demo"]).current_dir(&cwd).output().expect("spawn run --trace");
|
||||
assert!(traced.status.success(), "run --trace exit: {:?}", traced.status);
|
||||
|
||||
let chart = Command::new(BIN).args(["chart", "demo"]).current_dir(&cwd).output().expect("spawn chart");
|
||||
assert!(chart.status.success(), "chart exit: {:?}", chart.status);
|
||||
let html = String::from_utf8(chart.stdout).expect("utf-8 html");
|
||||
|
||||
// The run-context meta is injected, tagged as a single run named `demo`.
|
||||
assert!(html.contains("\"meta\":{"), "run-context meta not injected into the served page: {html:?}");
|
||||
assert!(html.contains("\"kind\":\"run\""), "served meta must be kind run");
|
||||
assert!(html.contains("\"name\":\"demo\""), "served meta must carry the run name");
|
||||
// The manifest's broker + data window thread through to the served page.
|
||||
assert!(html.contains("\"broker\":\"sim-optimal(pip_size=0.0001)\""), "manifest broker missing from served meta");
|
||||
assert!(html.contains("\"window\":[1,7]"), "manifest window missing from served meta");
|
||||
// The charted taps + the single-run sentinel.
|
||||
assert!(html.contains("\"taps\":[\"equity\",\"exposure\"]"), "charted taps missing from served meta");
|
||||
assert!(html.contains("\"members\":null"), "a single run must carry no member count");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// Property (#102, family run-context + SPANNING window at the served page): `aura
|
||||
/// chart <family>` injects family-shaped `meta` — `kind:"family"`, a `members`
|
||||
/// count, the one compared `taps`, and crucially a `window` that is the SPAN across
|
||||
/// all members, NOT a single member's window. The built-in sweep grid has 4
|
||||
/// members, each over the synthetic `[1,7]` window, but the family's recorded
|
||||
/// timestamps union to a wider `[1,18]` span; the served `window` must report that
|
||||
/// span (the ledger's families-comparison invariant: only the span labels a
|
||||
/// walk-forward family's true OOS coverage, and for sweep/MC it is still the union
|
||||
/// of member windows). A regression that took the first member's window, or dropped
|
||||
/// the family `members` count, would still pass the family-overlay series test
|
||||
/// (which only checks series labels); this pins the header context. Deterministic:
|
||||
/// asserts the fixed kind/members/taps/span, never the volatile commit.
|
||||
#[test]
|
||||
fn chart_family_serves_member_count_and_spanning_window_in_meta() {
|
||||
let cwd = temp_cwd("chart-family-meta");
|
||||
|
||||
let swept = Command::new(BIN).args(["sweep", "--trace", "fam"]).current_dir(&cwd).output().expect("spawn sweep --trace");
|
||||
assert!(swept.status.success(), "sweep --trace exit: {:?}", swept.status);
|
||||
|
||||
let chart = Command::new(BIN).args(["chart", "fam"]).current_dir(&cwd).output().expect("spawn chart fam");
|
||||
assert!(chart.status.success(), "chart family exit: {:?}", chart.status);
|
||||
let html = String::from_utf8(chart.stdout).expect("utf-8 html");
|
||||
|
||||
assert!(html.contains("\"meta\":{"), "family run-context meta not injected: {html:?}");
|
||||
assert!(html.contains("\"kind\":\"family\""), "served meta must be kind family");
|
||||
assert!(html.contains("\"name\":\"fam\""), "served meta must carry the family name");
|
||||
// The 4-point built-in grid -> a member count of 4 (the single-run sentinel is gone).
|
||||
assert!(html.contains("\"members\":4"), "family meta must carry the member count, got: {html}");
|
||||
assert!(!html.contains("\"members\":null"), "a family must not carry the single-run null sentinel");
|
||||
// The default compared tap (equity), one entry — not the per-member labels.
|
||||
assert!(html.contains("\"taps\":[\"equity\"]"), "family meta must carry the single compared tap");
|
||||
// The SPANNING window across members ([1,18]), wider than any single member's [1,7].
|
||||
assert!(html.contains("\"window\":[1,18]"), "family meta must carry the SPAN across members, not one member's window; got: {html}");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user