fix(family-stdout): unify family-wrapper key order with the store
The sweep / walk-forward / mc family-member stdout lines routed the embedded RunReport through serde_json::json! -> a serde_json::Value, which re-alphabetizes the manifest keys (broker-first) and, for mc, even reordered the top-level keys (seed after report). That diverged from the commit-first declaration order that RunReport::to_json and the stored families.jsonl already use. Splice the pre-serialized report into a hand-built line via family_member_line / mc_member_line so each family-member stdout line is byte-identical to its families.jsonl record (commit-first manifest, seed between family_id and report). RED-first: the two new key-order tests fail against the old json! helpers and pass after the format! rewrite. closes #99
This commit is contained in:
@@ -916,6 +916,31 @@ fn parse_walkforward_args(rest: &[&str]) -> Result<(String, bool, DataChoice), S
|
||||
Ok((name, persist, real.finish(&usage)?))
|
||||
}
|
||||
|
||||
/// Render a family-member stdout line: the assigned `family_id` plus the embedded
|
||||
/// `RunReport`. The report is emitted in its own declaration key order (manifest
|
||||
/// leads with `commit`, C18) so the line is byte-identical to the stored
|
||||
/// `families.jsonl`. `serde_json::json!` would route the report through a
|
||||
/// `serde_json::Value` and re-alphabetize the manifest keys (broker-first),
|
||||
/// diverging from the store — hence the report is spliced in pre-serialized (#99).
|
||||
fn family_member_line(id: &str, report: &RunReport) -> String {
|
||||
format!(
|
||||
r#"{{"family_id":{},"report":{}}}"#,
|
||||
serde_json::to_string(id).expect("a string id always serializes"),
|
||||
report.to_json()
|
||||
)
|
||||
}
|
||||
|
||||
/// Monte-Carlo variant of [`family_member_line`]: the per-draw line also carries the
|
||||
/// realization `seed` (between `family_id` and `report`), matching `run_mc`'s shape.
|
||||
fn mc_member_line(id: &str, seed: u64, report: &RunReport) -> String {
|
||||
format!(
|
||||
r#"{{"family_id":{},"seed":{},"report":{}}}"#,
|
||||
serde_json::to_string(id).expect("a string id always serializes"),
|
||||
seed,
|
||||
report.to_json()
|
||||
)
|
||||
}
|
||||
|
||||
/// `aura sweep [--strategy <sma|momentum>] [--name <n>|--trace <n>]`: run the
|
||||
/// selected built-in sweep, persist it as a *family* (related records sharing one
|
||||
/// `family_id`, C18/C21) via `append_family`, and print each point's record line
|
||||
@@ -935,7 +960,7 @@ fn run_sweep(strategy: Strategy, name: &str, persist: bool, data: DataSource) {
|
||||
}
|
||||
};
|
||||
for pt in &family.points {
|
||||
println!("{}", serde_json::json!({ "family_id": id, "report": pt.report }));
|
||||
println!("{}", family_member_line(&id, &pt.report));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,7 +985,7 @@ fn run_walkforward(name: &str, persist: bool, data: DataSource) {
|
||||
}
|
||||
};
|
||||
for w in &result.windows {
|
||||
println!("{}", serde_json::json!({ "family_id": id, "report": w.run.oos_report }));
|
||||
println!("{}", family_member_line(&id, &w.run.oos_report));
|
||||
}
|
||||
println!("{}", walkforward_summary_json(&result));
|
||||
}
|
||||
@@ -1175,10 +1200,7 @@ fn run_mc(name: &str, persist: bool) {
|
||||
}
|
||||
};
|
||||
for draw in &family.draws {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({ "family_id": id, "seed": draw.seed, "report": draw.report })
|
||||
);
|
||||
println!("{}", mc_member_line(&id, draw.seed, &draw.report));
|
||||
}
|
||||
println!("{}", mc_aggregate_json(&family.aggregate));
|
||||
}
|
||||
@@ -1432,6 +1454,46 @@ fn main() {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// #99: a sweep/walk-forward family-member stdout line embeds the `RunReport` in
|
||||
/// its own declaration key order (manifest leads with `commit`), byte-matching the
|
||||
/// stored `families.jsonl` — never `serde_json::Value`'s alphabetical order (which
|
||||
/// would lead the manifest with `broker`).
|
||||
#[test]
|
||||
fn family_member_line_keeps_report_in_store_key_order() {
|
||||
let report = RunReport {
|
||||
manifest: sim_optimal_manifest(vec![], (Timestamp(0), Timestamp(0)), 0, 1.0),
|
||||
metrics: summarize(&[], &[]),
|
||||
};
|
||||
let line = family_member_line("demo-1", &report);
|
||||
assert!(
|
||||
line.starts_with(r#"{"family_id":"demo-1","report":{"manifest":{"commit":"#),
|
||||
"got: {line}"
|
||||
);
|
||||
assert!(
|
||||
!line.contains(r#""manifest":{"broker":"#),
|
||||
"manifest re-alphabetized (broker-first), should be commit-first: {line}"
|
||||
);
|
||||
}
|
||||
|
||||
/// #99: the Monte-Carlo per-draw line carries the `seed` between `family_id` and
|
||||
/// `report`, and the embedded report stays in store (commit-first) key order.
|
||||
#[test]
|
||||
fn mc_member_line_keeps_report_in_store_key_order_with_seed() {
|
||||
let report = RunReport {
|
||||
manifest: sim_optimal_manifest(vec![], (Timestamp(0), Timestamp(0)), 7, 1.0),
|
||||
metrics: summarize(&[], &[]),
|
||||
};
|
||||
let line = mc_member_line("mc-1", 7, &report);
|
||||
assert!(
|
||||
line.starts_with(r#"{"family_id":"mc-1","seed":7,"report":{"manifest":{"commit":"#),
|
||||
"got: {line}"
|
||||
);
|
||||
assert!(
|
||||
!line.contains(r#""manifest":{"broker":"#),
|
||||
"manifest re-alphabetized (broker-first), should be commit-first: {line}"
|
||||
);
|
||||
}
|
||||
|
||||
// The vetted GER40 real-data window: the whole of September 2024 (UTC,
|
||||
// inclusive), the same calendar month the gated ingest `ger40_breakout_real`
|
||||
// test drives. Expressed in Unix-ms (`run_sample_real`'s window currency):
|
||||
|
||||
Reference in New Issue
Block a user