diff --git a/crates/aura-engine/src/report.rs b/crates/aura-engine/src/report.rs index a525c5c..973e2d7 100644 --- a/crates/aura-engine/src/report.rs +++ b/crates/aura-engine/src/report.rs @@ -58,9 +58,25 @@ pub struct RunReport { impl RunReport { /// Render canonical, machine-readable JSON (C14). Hand-rolled — the schema /// is tiny, closed, and flat, so the deliberately zero-dependency workspace - /// stays so. Field order is fixed; `f64` uses the round-trippable `{}` - /// shortest form (finite values only — pip equity and exposure are finite - /// by construction); `params` renders as a JSON object in insertion order. + /// stays so. + /// + /// # Shape + /// + /// The JSON keys mirror the struct field names, in fixed order, nested as + /// `{"manifest": {…}, "metrics": {…}}`. `window` is a 2-element + /// `[from, to]` array of epoch-ns integers; `params` is a JSON object of + /// `name: value` pairs in insertion order. A run renders as, e.g.: + /// + /// ```text + /// {"manifest":{"commit":"abc123","params":{"sma_fast":2,"sma_slow":4},"window":[1,6],"seed":0,"broker":"sim-optimal(pip_size=0.0001)"},"metrics":{"total_pips":12,"max_drawdown":1,"exposure_sign_flips":1}} + /// ``` + /// + /// `f64` fields use the round-trippable `{}` shortest form (finite values + /// only — pip equity and exposure are finite by construction), so a + /// whole-valued float renders **without** a fractional part (`12.0` → `12`): + /// one `f64` field may appear as an integer token in one run and a decimal + /// token in another. Consumers must parse the value as a number, never key + /// off the token shape. pub fn to_json(&self) -> String { let m = &self.manifest; let mut params = String::from("{");