All writing

Live and final are different data products

Model in-progress and completed reporting periods separately so partial evidence cannot overwrite settled history.

A report for the current period answers a different question from a report for a completed period. The first is a changing projection based on partial evidence; the second is a settled record whose boundaries should no longer move.

Treating both as one mutable object creates subtle failures. Late events rewrite history, partial totals look complete, and a live refresh can overwrite a finalized result. The cleaner design gives live and final data distinct states and write paths.

Put completeness in the contract

This section makes report state part of the returned data rather than an implication hidden in a date comparison. Consumers can then label and cache the result according to its semantics.

A report envelope can make completeness explicit:

export type ReportPeriod = {
  start: string;
  end: string;
  timezone: string;
};

export type ReportEnvelope = {
  subjectId: string;
  period: ReportPeriod;
  status: 'partial' | 'final';
  facts: Record<string, number>;
  evidenceThrough: string;
  revision: number;
  generatedAt: string;
};

evidenceThrough describes how far input was processed. generatedAt only describes when the artifact was assembled, so substituting one for the other would misrepresent freshness.

Revision is meaningful for a partial report because its values can change. A final report should instead be immutable under its normal identity; corrections can create an explicit later revision without pretending the original never existed.

Build partial reports from current evidence

This section constructs a live view that is safe to refresh. It always advertises incompleteness, records the processing watermark used to produce it, and increments a revision when the evidence changes.

The watermark must come from the evidence source rather than the application clock. A report generated at noon may still contain evidence only through 11:40 because ingestion is behind. generatedAt describes computation time; evidenceThrough describes knowledge.

Partial reports can be updated in place because their contract says they are provisional. Cache keys or response headers should include the revision so clients do not unknowingly combine values from different live snapshots.

Finalize through a separate transition

This section creates a final record only when period closure and source completeness are both established. Closing the calendar is necessary but not sufficient: the evidence pipeline must also confirm that it has processed the complete window under the expected definition.

Make finalization idempotent. A uniqueness rule on subject and period, or an equivalent compare-and-publish operation, prevents two workers from creating competing final records. Retrying the transition should return the existing final artifact rather than recomputing a different one.

The finalizer does not promote the partial record. Recomputing from the complete evidence window avoids carrying forward omissions caused by an earlier ingestion watermark or a live-path approximation.

Route reads according to period state

This section gives callers one entry point while preserving the two underlying products. The authoritative calendar determines which path is valid; a request parameter cannot downgrade a finalized period back to live mode.

When the period is open, the reader returns the latest partial revision and its watermark. When the period is closed, it returns the final artifact or an explicit finalization-pending state. It should not continue serving a partial result with a final label while the final workflow catches up.

Tests should prove that a partial writer cannot target final storage, a live read never returns status: 'final', and finalization is idempotent. Advance the calendar across the boundary and verify that the reader stops serving the partial artifact. Then deliver late evidence and confirm that it follows an explicit correction policy rather than silently rewriting history.

Live data is valuable because it changes; final data is valuable because it does not. Naming them as separate products makes completeness, caching, correction, and user expectations much easier to reason about.