//! Fieldtest c0011 #1 — the pure transpose/normalize core (axis a). //! //! Question under test: a project author who has recorded a handful of M1 bars //! (as data-server `M1Parsed` AoS records) calls `transpose_m1`, inspects the //! resulting SoA `M1Columns`, and the C3/C7 boundary contract — field-wise //! transpose, Unix-ms -> epoch-ns normalization, volume kept as i64 — holds //! exactly as the public rustdoc promises, WITHOUT reading crates/*/src. //! //! Public-surface facts used (aura-ingest rustdoc + design ledger C3/C7): //! - `unix_ms_to_epoch_ns(time_ms) -> Timestamp` = "Normalize Unix-millisecond //! to canonical epoch-ns" (the single C3 unit normalization). The feat-commit //! surface line states `= ms * 1_000_000`. //! - `transpose_m1(&[M1Parsed]) -> M1Columns` — "Pure: identical bars yield //! identical columns (C1)". AoS -> SoA (C7). //! - `M1Columns { ts: Vec, open/high/low/close/spread: Vec, //! volume: Vec }` — public fields; "All columns share an index: ts[i] //! is the timestamp of close[i]"; derives Clone + Debug + PartialEq. //! - `M1Columns::close_stream() -> Vec<(Timestamp, Scalar)>` — close column //! zipped with normalized ts as `Scalar::F64`. //! - data-server rustdoc: `M1Parsed { time_ms: i64, open/high/low/close/spread: //! f64, volume: i64 }`, all public fields, Copy. use aura_core::{Scalar, Timestamp}; use aura_ingest::{M1Columns, transpose_m1, unix_ms_to_epoch_ns}; use data_server::records::M1Parsed; fn main() { // ---- Part 1: the unit-normalization primitive, standalone (C3). -------- // The surface claims ns = ms * 1_000_000. Check a few representative values // a project author would care about: zero, a small ms, and a realistic // 2006-era Pepperstone Unix-ms timestamp. assert_eq!(unix_ms_to_epoch_ns(0), Timestamp(0), "zero maps to zero"); assert_eq!(unix_ms_to_epoch_ns(1), Timestamp(1_000_000), "1 ms = 1e6 ns"); // 2006-08-01T00:00:00Z is 1_154_390_400_000 ms; ns must be *1e6 of that. let ms_2006 = 1_154_390_400_000_i64; assert_eq!( unix_ms_to_epoch_ns(ms_2006), Timestamp(ms_2006 * 1_000_000), "realistic 2006 ms scales by exactly 1e6" ); println!("unix_ms_to_epoch_ns: 0->0, 1->1e6, 2006 ms -> {:?}", unix_ms_to_epoch_ns(ms_2006)); // ---- Part 2: field-wise AoS -> SoA transpose (C7). --------------------- // Three hand-built bars, distinct values per field so a transposition that // swapped two columns (e.g. high<->low) would be caught. let bars = [ M1Parsed { time_ms: 1_000, open: 10.0, high: 11.0, low: 9.5, close: 10.5, spread: 0.2, volume: 100 }, M1Parsed { time_ms: 2_000, open: 10.5, high: 12.0, low: 10.1, close: 11.8, spread: 0.3, volume: 250 }, M1Parsed { time_ms: 3_000, open: 11.8, high: 12.4, low: 11.0, close: 11.2, spread: 0.25, volume: 75 }, ]; let cols: M1Columns = transpose_m1(&bars); println!("transposed M1Columns = {cols:?}"); // Each column must be exactly the corresponding field of each bar, in order. assert_eq!(cols.ts, vec![Timestamp(1_000_000_000), Timestamp(2_000_000_000), Timestamp(3_000_000_000)], "ts column = each bar's time_ms normalized to epoch-ns"); assert_eq!(cols.open, vec![10.0, 10.5, 11.8], "open column"); assert_eq!(cols.high, vec![11.0, 12.0, 12.4], "high column (not swapped with low)"); assert_eq!(cols.low, vec![9.5, 10.1, 11.0], "low column"); assert_eq!(cols.close, vec![10.5, 11.8, 11.2], "close column"); assert_eq!(cols.spread, vec![0.2, 0.3, 0.25], "spread column"); // volume stays i64 (C7: the one i64 column), never coerced to f64. assert_eq!(cols.volume, vec![100_i64, 250, 75], "volume column kept as i64"); // Index alignment: ts[i] is the timestamp of close[i] (rustdoc claim). for i in 0..bars.len() { assert_eq!(cols.ts[i], unix_ms_to_epoch_ns(bars[i].time_ms), "ts[{i}] normalized"); assert_eq!(cols.close[i], bars[i].close, "close[{i}] aligned"); } // ---- Part 3: purity (C1) — identical bars -> identical columns. -------- let cols_again = transpose_m1(&bars); assert_eq!(cols, cols_again, "transpose is pure: same input -> same output (PartialEq)"); // ---- Part 4: close_stream — the engine source-stream shape (C7). ------- // The close column zipped with normalized ts as Scalar::F64. let stream = cols.close_stream(); println!("close_stream = {stream:?}"); let expected: Vec<(Timestamp, Scalar)> = vec![ (Timestamp(1_000_000_000), Scalar::F64(10.5)), (Timestamp(2_000_000_000), Scalar::F64(11.8)), (Timestamp(3_000_000_000), Scalar::F64(11.2)), ]; assert_eq!(stream, expected, "close_stream = (normalized ts, close as Scalar::F64) per bar"); // ---- Part 5: empty edge — a window with no bars. ----------------------- let empty = transpose_m1(&[]); assert!(empty.ts.is_empty() && empty.close.is_empty(), "empty input -> empty columns"); assert!(empty.close_stream().is_empty(), "empty columns -> empty close_stream"); println!("c0011_1 OK: transpose_m1 / unix_ms_to_epoch_ns / close_stream match the rustdoc C3/C7 contract"); }