a982b96ecc
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
95 lines
5.0 KiB
Rust
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");
|
|
}
|