Files
Aura/fieldtests/cycle-0009-run-metrics/c0009_4_f64field_and_json.rs
T
Brummel a982b96ecc fieldtest: cycle-0009 — 4 examples, 6 findings
First fieldtest of the run-metrics + manifest report surface. A standalone
downstream-consumer crate (fieldtests/cycle-0009-run-metrics/) path-depends on
the engine crates and exercises the post-0009 surface from the public interface
only (rustdoc + ledger + glossary + project layout, never crates/*/src).

Primary axis empirically met: the north-star "a run emits metrics + manifest"
move is reachable from rustdoc alone — drain two recording sinks -> f64_field ->
summarize -> RunManifest -> to_json, metrics matching the hand model on the first
run, deterministic across reruns.

Findings: 0 bugs, 1 friction, 1 spec_gap, 4 working.
  - working x4: north-star reachable from rustdoc; SimBroker firing/slot docs (a
    resolved 0007 gap) now carry the example; summarize metric definitions exact
    on six degenerate inputs (incl. negative-curve drawdown + flat-as-sign-0);
    f64_field panics precise and well-located.
  - spec_gap: to_json's JSON key names + {manifest,metrics} nesting are not on
    the public surface — a consumer parsing the JSON (C18 registry, the deferred
    aura run printer) cannot author against it from rustdoc alone.
  - friction: to_json renders whole-valued f64 without a decimal point (3.0 ->
    "3"), so one f64 field appears as integer or decimal token within one schema.

Both doc-level findings are the same doc pass and matter mainly for the deferred
aura run (#8) and the C18 registry that will parse this JSON. Spec feeds the next
plan as reference.

refs #6
2026-06-04 19:06:13 +02:00

95 lines
5.0 KiB
Rust

//! Fieldtest c0009 #4 — the f64_field bridge + the to_json structured face,
//! probed from the docs alone.
//!
//! Axis (carrier, north-star sub-surface): f64_field is the documented bridge
//! from a recording sink's `Vec<Scalar>` rows to summarize. The rustdoc says:
//! "extract one f64 field of each row into (ts, f64) samples. Panics if a row
//! has no such field or the field is not an f64 scalar — a wiring bug ...
//! surfaced like the engine's other 'checked at wiring' contract violations."
//! And RunReport::to_json: "field order is fixed; f64 uses the round-trippable
//! {} shortest form; params renders as a JSON object in insertion order."
//!
//! This example exercises:
//! (a) projecting a NON-zero column out of a multi-column recorded row,
//! (b) the documented panic on a wrong-kind field (caught with catch_unwind so
//! the program reports it instead of aborting — a downstream consumer
//! wanting to validate a sink would want this),
//! (c) the exact to_json byte shape, and whether the documented "round-trippable
//! {}" claim survives a 0.0001-style pip_size and a NEGATIVE/fractional pip.
use std::panic::{self, AssertUnwindSafe};
use aura_core::{Scalar, Timestamp};
use aura_engine::{RunManifest, RunMetrics, RunReport, f64_field};
fn main() {
// (a) A recorded row may carry K columns (C7/C8 — a record is a bundle of
// base columns). f64_field(field=1) must pick the SECOND column.
let rows: Vec<(Timestamp, Vec<Scalar>)> = vec![
(Timestamp(1), vec![Scalar::F64(10.0), Scalar::F64(-1.5)]),
(Timestamp(2), vec![Scalar::F64(20.0), Scalar::F64(2.5)]),
];
let col0 = f64_field(&rows, 0);
let col1 = f64_field(&rows, 1);
println!("col0 = {col0:?}");
println!("col1 = {col1:?}");
assert_eq!(col0, vec![(Timestamp(1), 10.0), (Timestamp(2), 20.0)]);
assert_eq!(col1, vec![(Timestamp(1), -1.5), (Timestamp(2), 2.5)]);
// (b) Documented panic: a non-f64 field (here an i64 in the row) is a wiring
// bug. A real consumer building a generic "drain any sink" helper would hit
// this if it mis-declared a column kind. We confirm it PANICS (per docs)
// rather than silently coercing or dropping.
let bad_rows: Vec<(Timestamp, Vec<Scalar>)> =
vec![(Timestamp(1), vec![Scalar::I64(7)])];
let r = panic::catch_unwind(AssertUnwindSafe(|| f64_field(&bad_rows, 0)));
match r {
Err(_) => println!("(b) f64_field on an i64 field PANICKED as documented (wiring bug)"),
Ok(v) => panic!("(b) expected panic on non-f64 field, got {v:?}"),
}
// Also: field index out of range panics ("a row has no such field").
let r = panic::catch_unwind(AssertUnwindSafe(|| f64_field(&rows, 5)));
assert!(r.is_err(), "(b) out-of-range field index panics as documented");
println!("(b) f64_field on an out-of-range field index PANICKED as documented");
// (c) to_json byte shape + the "round-trippable {}" claim under a realistic
// FX pip_size (0.0001) and fractional/negative metric values.
let report = RunReport {
manifest: RunManifest {
commit: "abc123".to_string(),
params: vec![("len".to_string(), 14.0), ("k".to_string(), 2.5)],
window: (Timestamp(1_700_000_000_000_000_000), Timestamp(1_700_000_086_400_000_000)),
seed: 42,
broker: "sim-optimal(pip_size=0.0001)".to_string(),
},
metrics: RunMetrics {
total_pips: -12.5,
max_drawdown: 33.25,
exposure_sign_flips: 7,
},
};
let json = report.to_json();
println!("(c) to_json() = {json}");
// What the rustdoc lets me check WITHOUT reading src: it is one flat-ish JSON
// object, params is a nested object in insertion order, values are present in
// {} shortest form. I assert structure I can justify from the surface; the
// exact KEY NAMES are not on the public surface (recorded as a spec_gap), so
// I check the values I supplied appear, and that the ns timestamps survive as
// integers (round-trippable claim) rather than scientific notation.
assert!(json.starts_with('{') && json.ends_with('}'));
assert!(json.contains("-12.5"), "negative fractional total_pips present");
assert!(json.contains("33.25"), "fractional drawdown present");
assert!(json.contains("\"k\":2.5"), "fractional param in insertion order, object form");
// params object ordering: len appears before k (insertion order, documented).
let li = json.find("\"len\"").expect("len key");
let ki = json.find("\"k\":2.5").expect("k key");
assert!(li < ki, "params keys render in insertion order (len before k)");
// round-trip claim: the big ns window bound must be a bare integer, not 1.7e18.
assert!(json.contains("1700000000000000000"), "ns timestamp as integer, not sci-notation");
assert!(!json.to_lowercase().contains("e1"), "no scientific-notation float leaked");
println!("c0009_4 OK: f64_field column-pick + documented panics + to_json shape");
}