docs(aura-ingest): precise None contract for load_m1_window

The rustdoc claimed 'None if the symbol has no data in [from_ms, to_ms]',
implying a bar-level None. Empirically (cycle-0011 fieldtest) None is
file-level: it propagates data-server's own None, returned only when no
archived file overlaps the window (far-future window or unknown symbol).
A window that overlaps a loaded file but holds zero bars returns
Some(M1Columns) with empty columns, not None.

Document the distinction precisely (chosen over collapsing in-coverage-
empty into None): None propagates data-server's one file-level meaning
unchanged -- 'no data source to read from' -- rather than overloading it
with a second aura-level 'source present but empty here' meaning. So
Some/None means 'data source present' vs 'nothing to read from', not
'has bars' vs 'has none'; a consumer testing for an empty window must
check cols.close.is_empty(). Doc-only, no behaviour change; ledger
untouched (the contract is not referenced there).

closes #18
This commit is contained in:
2026-06-04 23:44:56 +02:00
parent 8bd5d0a3f9
commit ac4a8c68cf
+18 -9
View File
@@ -87,16 +87,25 @@ pub fn transpose_m1(bars: &[M1Parsed]) -> M1Columns {
/// Drain a data-server M1 window into transposed SoA columns. Reads the
/// chronological chunk iterator to exhaustion into one AoS buffer, then
/// transposes once at the boundary (C3 — one merge point, not per chunk).
/// `None` if the symbol has no data in the window (data-server's own `None`);
/// the inclusive Unix-ms window bounds are data-server's contract.
///
/// Each bound is an `Option<i64>`: `None` means unbounded on that side
/// (data-server skips the coarse `unix_ms_to_year_month` file filter rather
/// than reaching for a sentinel). `from_ms = None` reads from the start of
/// history; `to_ms = None` reads to the end. This threads data-server's own
/// per-bound `Option` contract through unchanged, so "to the end of history"
/// is expressible without an `i64::MAX` sentinel (which panics upstream in
/// chrono's `from_timestamp_millis`).
/// Each bound is an `Option<i64>` of inclusive Unix-ms (data-server's window
/// contract): `None` means unbounded on that side (data-server skips the
/// coarse `unix_ms_to_year_month` file filter rather than reaching for a
/// sentinel). `from_ms = None` reads from the start of history; `to_ms = None`
/// reads to the end. This threads data-server's own per-bound `Option`
/// contract through unchanged, so "to the end of history" is expressible
/// without an `i64::MAX` sentinel (which panics upstream in chrono's
/// `from_timestamp_millis`).
///
/// The returned `Option` is **file-level**, propagating data-server's own
/// `None`: it is `None` only when no archived file overlaps the window (e.g. a
/// far-future window, or an unknown symbol) — i.e. there is no data source to
/// read from. A window that *does* overlap a loaded file but happens to hold
/// zero bars (a narrow gap inside coverage) returns `Some(M1Columns)` with
/// empty columns, **not** `None`. So `Some`/`None` distinguishes "data source
/// present" from "nothing to read from", not "has bars" from "has none": a
/// consumer testing for "no bars in this window" must check
/// `cols.close.is_empty()`, not the `Option` alone.
pub fn load_m1_window(
server: &Arc<DataServer>,
symbol: &str,