Expand description
The deterministic local chart document (mission burndown-scope-analytics-01M3C9J1,
WP04 T019–T023).
§What this module is
ChartExport is the portable document form of one folded series: a versioned,
local, read-only JSON payload carrying the scope and its period, the counting mode
and unit, the ordered daily points, the optional end-date projection, the aggregate
coverage tally, the documented formula version and every finding — the document half
of data-model.md §6 and of contracts/chart-export.md. It is output, never state
(C-001): nothing here opens a store, reads a clock, writes an issue, or creates a
second analytics store.
BurndownSeries (+ optional Projection) ──ChartExport::build──▶ versioned local document§Mirrored, never re-derived (C-008)
The shared values are projected, not recomputed. ChartExport carries a copy
of the series’ own scope, mode, unit and formula version, its points, its findings
and its CoverageSummary — every number is a shared DTO field, read once (R9). The
module does no arithmetic at all: it copies, sorts and names. The one place it
could have drifted, a forecast, is a shared value too: ChartProjection mirrors
WP03’s Projection rather than drawing a second one.
AnalyticsScope (WP01’s events.rs) and BurndownSeries (WP02’s series.rs)
carry no serde derives and are owned by other packages, so the fields this document
needs are mirrored into its own serde types (ChartScope, ChartSeries) —
the same portable-document precedent crate::capacity::portable and
crate::time_tracking::portable follow. The shared leaf types that already derive
serde (Mode, Coverage, DailyPoint,
CoverageSummary, AnalyticsDiagnostic, ProjectionStatus) are reused
verbatim, so the wire vocabulary is the contract’s own and cannot be re-spelled here.
§The marker is read before the shape (T019)
ChartExport::from_json reads the format marker first, so a payload that is
not a chart document at all — a tix-capacity, tix-roadmap, tix-health,
tix-time or tix-views document, whose fields overlap structurally — is refused
by name rather than by whatever shape error its fields happened to raise. A
document of a different SCHEMA_VERSION is read, its own fields kept — including
the document-level keys this build does not interpret, which travel in
ChartExport::unknown — and the mismatch is reported as a VERSION_MISMATCH
finding (merged, never appended twice), which is the checked-and-reported third
answer between refusing a newer writer and silently dropping its fields.
§Preserved data has its own namespaces (T019)
Data this build does not interpret travels in two channels, and neither can shadow a projected field:
- the nested
extensionsobject — the channel this build writes, throughChartExport::insert_extension. Because it is its own object, a preserved key namedformat,seriesorscopelands inside it and can never duplicate — or be read in place of — the projected field beside it, for this reader or for a lenient one. Flattening an extension bag onto a projected object is what produced a duplicate JSON key (and an unloadable file) in an earlier mission. - the flattened catch-all (
ChartExport::unknown) — the channel this build only reads. serde delivers a key a newer writer placed directly on the document here, and it is re-emitted verbatim; that is what keeps the version finding’s promise (“its fields are preserved rather than refused or dropped”) true.buildalways starts it empty and there is no write side, so this build can never emit a second projected key: a reader cannot make it produce a document it then refuses.
§Absent is null, never 0 (C-005, T021)
Every unknown number is null on the wire (a serde Option), never a fabricated
zero, and a day with no recorded history keeps its known / coverage_limited /
unknown marker beside it. The document states null values plainly, so a consumer
cannot mistake “not known” for “nothing happened”.
§Ordering and precision are fixed (T020)
Days are the series’ calendar order, findings are sorted by their own Ord, and both
preserved-data channels are BTreeMaps, so key order is normalised. Every value is a
whole number (precision): count mode counts whole issues and estimate mode counts
whole converted units, because the shared conversion refuses a fractional result
rather than rounding it (crate::wip::WipConfig::convert). The only fractional
quantity in a document is a projection’s observed_rate, and the projection node
documents that beside it. Serialization is therefore a pure function of the input: the
same series, mode and period produce identical bytes, and the bytes this build emits
re-read as a byte fixpoint (ChartExport::to_json_pretty →
ChartExport::from_json → ChartExport::to_json_pretty).
§A closed snapshot is provenance, not a re-read (C-006, T022)
The scope’s snapshot_id is carried into ChartScope::snapshot_id and the fold’s
own ClosedSnapshot
finding travels with the series’ diagnostics, so a document says which closed view it
describes. Nothing is recomputed from current state: an event recorded after the close
is not part of the document’s points, exactly as it is not part of the fold.
§A recorded estimate_changed is reported, never replayed
WP03’s decision (units.rs, tracer design-decisions): an estimate change recorded
inside the period is reported — its day is coverage-limited and the finding travels
in the series’ diagnostics — and is not replayed into the levels. This module
inherits that by copying the series verbatim; it never re-values a recorded change,
which would be a second formula and would double-count against the stated estimate
every other contribution is valued at.
§Remote namespaces stay separate (T022)
tix export --target gitlab|linear mirrors issues to a remote tracker and shells
out to glab/linear-cli; this document is a local interchange form and never
reaches either tool. Where a remote target is configured, its inability to carry a
chart or time-series field is diagnosed explicitly in the document
(UNSUPPORTED_REMOTE_FIELD, naming the target and the day counts that stay local),
because a silent drop and an invented mapping are both worse than a named refusal.
src/sync/** is untouched, and chart is dispatched by name before
Tool::from_target is consulted.
§Read-only, on every path
Build, serialize and read back all take values and return values: no file is opened
for writing, no History event is appended, and no issue is mutated (C-007).
Structs§
- Chart
Diagnostic - One document-level finding, in the chart document’s own vocabulary (T021/T022).
- Chart
Export - Everything a local chart surface knows, in one versioned document (
data-model.md§6). - Chart
Period - The inclusive calendar period a document covers (
data-model.md§1). - Chart
Projection - The optional read-only end-date forecast, in the document’s own serde shape
(
data-model.md§5). - Chart
Scope - The scope a document describes, in the document’s own serde shape
(
data-model.md§1/§6). - Chart
Series - The ordered daily series, in the document’s own serde shape (
data-model.md§4).
Constants§
- FORMAT
- The document’s format marker. A consumer reads this before treating the payload as a chart document; a document with another marker is refused by name.
- SCHEMA_
VERSION - The document’s schema version. Bumped when the shape changes incompatibly.
- UNSUPPORTED_
REMOTE_ FIELD - The stable kind spelling of a “a configured remote target cannot carry chart data” finding.
- VERSION_
MISMATCH - The stable kind spelling of a “different document version” finding.
Functions§
- precision_
of - The precision of the series values under
mode(data-model.md§3: “Values use the selected mode’s precision and unit”, T020).