D02-F04-A04 / Complete engineering topic

Filing-Revision Versioning

A production-minded guide to Filing-Revision Versioning.

D02 · CORPORATE ACTIONS AND SECURIT…
D02-F04-A04Canonical / Tested / Open
D02 / D02-F04
Key concepts

The governed definitions this build depends on. Read them first if a term is unfamiliar.

A point-in-time research system should answer a simple audit question: which filing version had we actually acquired at this historical cutoff? Filing-revision versioning makes that answer reproducible by retaining every accession and separating the reporting period, regulator acceptance, and local ingestion times.

The mistake this prevents

Suppose a company files an annual report and later amends it. If a database overwrites the original values, a backtest run “as of” the earlier date sees facts that arrived months later. Filtering only on the regulator acceptance timestamp is also insufficient when the research system did not ingest the document at that instant.

The SEC says filings often appear on sec.gov one to three minutes after the EDGAR acceptance timestamp, that the lag may increase, and that no timestamp identifies when filing content first became available on the website (SEC webmaster FAQ). That is why a local ingestedAt timestamp is evidence, while an assumed dissemination time is not.

Two clocks control filing-version eligibility

Notice the two distinct boundaries: acceptance orders regulator submissions; ingestion determines what the local system could use.

Three times, three meanings

TimeWhat it meansWhat it does not prove
reportPeriodEconomic period described by the filingWhen the information became known
acceptedAtEDGAR accepted the submissionExact public or local availability
ingestedAtThe local system acquired this versionThat parsing or validation was complete unless separately recorded

For a production system, add parsedAt, validatedAt, provider publication time, source hash, and field-level lineage when those boundaries matter. Do not compress them into one generic date.

The causal selection rule

For entity ee, period pp, form family ff, and cutoff tt, first build the eligible set:

E(t)={r:r.e=e, r.p=p, r.form{f,f/A}, r.ingestedAtt}.E(t)=\{r:r.e=e,\ r.p=p,\ r.form\in\{f,f/A\},\ r.ingestedAt\le t\}.

Then choose:

r(t)=arg maxrE(t)(r.acceptedAt, r.amendmentNumber, r.accessionNo).r^*(t)=\operatorname*{arg\,max}_{r\in E(t)} \bigl(r.acceptedAt,\ r.amendmentNumber,\ r.accessionNo\bigr).

ingestedAt controls eligibility. The remaining tuple orders versions that were already eligible and resolves ties deterministically. If the eligible set is empty, the correct output is an error—not the first later filing.

Rendering system map…

The algorithm selects a source version. A fact extractor must still check whether an amendment changes the requested filing item.

Historical case: one fiscal year, two valid versions

Esterline Technologies' fiscal-2017 original Form 10-K is accession 0001564590-17-024189, accepted on 2017-11-21 at 16:32:41 UTC (original filing index). Form 10-K/A Amendment No. 1 is accession 0001564590-18-007244, accepted on 2018-03-30 at 14:43:54 UTC (amendment index).

The amendment states that it restates previously issued financial statements and lists the affected items. Its restatement summary gives fiscal-2017 basic earnings per share (EPS) of 3.94asreportedand3.94 as reported and 3.75 as restated, while net earnings attributable to Esterline changed from 117.387millionto117.387 million to 111.554 million (Form 10-K/A).

Those are sourced event facts. The package makes a separate, explicit teaching assumption:

  • original locally ingested at 2017-11-21 16:40 UTC;
  • amendment locally ingested at 2018-03-30 15:00 UTC.

The ingestion times are synthetic. They are not claims about SEC dissemination or any vendor.

Three cutoffs expose the value

The selected filing changes only after local ingestion

Research cutoffSelected accessionBasic EPSExplanation
2018-02-01 00:00 UTCOriginal 10-K$3.94The amendment did not yet exist
2018-03-30 14:50 UTCOriginal 10-K$3.94Amendment accepted, but not locally ingested under the assumption
2018-03-30 15:05 UTC10-K/A$3.75Amendment now locally available and later accepted

The derived difference is:

3.753.94=0.19 USD per share.3.75-3.94=-0.19\ \text{USD per share}.

This is a revision of the filing's representation of the same fiscal period. It is not a market return, a new fiscal-quarter observation, proof of causality, or an investment signal.

Implementation walkthrough

Both reference implementations follow the same visible stages:

  1. require a stable entity, report period, form family, and timezone-aware cutoff;
  2. validate every accession, amendment number, scope, numeric fact, and timestamp;
  3. reject direct-SEC ingestion earlier than acceptance;
  4. filter by local ingestion;
  5. order eligible records by acceptance time, amendment number, and accession;
  6. return a copy of the selected facts plus eligible/excluded accessions.

The Python and TypeScript code consumes the same shared_fixture.json. The selector does not mutate its input and compares instants rather than timestamp strings, so 15:05Z and 11:05-04:00 resolve identically.

Explore the failure boundary

Open the guided filing-version playground. Choose the cutoff after SEC acceptance but before the synthetic local ingestion. Under the correct policy, the original remains selected. Switch to the deliberately unsafe “acceptance only” comparison and the amendment appears too early.

That comparison is the core lesson: a regulator event and a local knowledge event answer different questions.

Failure modes

Acceptance-only selection leaks a later version

  • Latest-only storage: destroys the version that was valid at an earlier cutoff.
  • Acceptance-only eligibility: can precede local availability.
  • Filing-date eligibility: loses intraday ordering and can differ from acceptance behavior.
  • Whole-filing overwrite: ignores that an amendment may change only specified items.
  • Naive timestamps: make offsets ambiguous and comparisons unreliable.
  • Invented provider time: turns an assumption into false evidence.
  • Backfill without lineage: makes a corrected value look as if it had always existed.

Retain every accession, local acquisition evidence, source hash, extraction version, and amendment scope. Reject uncertain history rather than manufacturing precision.

What this case does—and does not—show

It shows that a real filing revision can change a historical accounting value and that a bitemporal selector can reproduce which version a local system could use under explicit availability assumptions.

It does not show when a commercial provider revised its field, how a security price reacted, why investors traded, or whether the restatement predicts anything. Those would be separate empirical claims requiring provider evidence, event-study controls, and licensing review.

Summary

Filing-revision versioning preserves both economic truth and knowledge-time truth:

  • the report period says what the filing describes;
  • the accession and acceptance timestamp identify the regulator version;
  • the local ingestion timestamp establishes the research system's evidence boundary;
  • amendment scope and field lineage explain what changed.

With those pieces, a historical query can be rerun and defended without rewriting the past.

Asset map

AssetPlacementVideo sceneSourceStatic fallbackStatus
Two-clock overviewProblem2visuals/static/article-hero.svgSame SVGReady
Causal flowFormula3visuals/mermaid/calculation-flow.mdArticle stepsReady
Three-cutoff caseWorked example5visuals/static/worked-example.svgTable aboveReady
Failure guardFailure modes7visuals/static/failure-guard.svgFailure listReady
Guided labExploration4–7visuals/animated/playground.htmlThree-cutoff SVGReady

Primary source roles and limitations are recorded in ../REFERENCES.md.

Causal filing-version query

This flow separates local availability from version ordering.

Rendering system map…

Takeaway: ingestedAt controls local knowability; acceptedAt orders the versions that were already knowable.

Filing-version lifecycle

This state model prevents an accepted amendment from entering a local point-in-time query before acquisition.

Rendering system map…

Takeaway: superseded does not mean deleted; every accession remains available for historical reconstruction.

References6 primary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

Accessed 2026-07-22. The package stores a small factual case record, not raw filing documents or provider datasets.

R1 — Esterline fiscal-2017 original Form 10-K filing index

R2 — Esterline fiscal-2017 Form 10-K/A filing index

R3 — Esterline fiscal-2017 Form 10-K/A

  • Organization: Esterline Technologies Corporation, filed with the SEC
  • Source type: Primary issuer filing
  • Filing date: 2018-03-30
  • URL: https://www.sec.gov/Archives/edgar/data/33619/000156459018007244/esl-10ka_20170929.htm
  • Jurisdiction: United States
  • Supports: Amendment purpose and scope, categories of errors, and as-reported versus restated fiscal-2017 net earnings and EPS
  • Limitations: The filing supports accounting disclosure facts, not market causality or investment significance

R4 — SEC Webmaster Frequently Asked Questions

  • Organization: U.S. Securities and Exchange Commission
  • Source type: Official regulator technical guidance
  • Version: Current page accessed 2026-07-22
  • URL: https://www.sec.gov/about/webmaster-frequently-asked-questions
  • Jurisdiction: United States
  • Supports: Acceptance-to-website lag is variable; no first-availability timestamp exists for filing content on sec.gov; accession and EDGAR timestamp definitions
  • Limitations: General operational guidance, not a historical availability timestamp for the Esterline filings

R5 — Accessing EDGAR Data

  • Organization: U.S. Securities and Exchange Commission
  • Source type: Official regulator developer guidance
  • Version: Current page accessed 2026-07-22
  • URL: https://www.sec.gov/search-filings/edgar-search-assistance/accessing-edgar-data
  • Jurisdiction: United States
  • Supports: EDGAR submissions data, filing archives, access practices, and accession-based retrieval
  • Limitations: Does not define a research firm's local ingestion or validation boundary

R6 — EDGAR glossary

  • Organization: U.S. Securities and Exchange Commission
  • Source type: Official regulator terminology
  • Version: Current page accessed 2026-07-22
  • URL: https://www.sec.gov/submit-filings/filer-support-resources/edgar-glossary
  • Jurisdiction: United States
  • Supports: Meaning of “accepted,” accession number, and EDGAR dissemination terminology
  • Limitations: A generated accession number alone does not prove acceptance or local availability
filing_revision_versioning.ts
/** Point-in-time filing-version selection for D02-F04-A04. */

const textValue = (value, name) => {
  if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be a non-empty string`);
  return value;
};

const instant = (value, name) => {
  const text = textValue(value, name);
  if (!/(?:Z|[+-]\d{2}:\d{2})$/.test(text)) throw new RangeError(`${name} must include a UTC offset`);
  const milliseconds = Date.parse(text);
  if (!Number.isFinite(milliseconds)) throw new RangeError(`${name} must be a valid ISO 8601 instant`);
  return milliseconds;
};

const nonnegativeInteger = (value, name) => {
  if (!Number.isInteger(value) || value < 0) throw new TypeError(`${name} must be a non-negative integer`);
  return value;
};

const factsValue = (value, name) => {
  if (!value || typeof value !== "object" || Array.isArray(value) || !Object.keys(value).length) {
    throw new TypeError(`${name} must be a non-empty object`);
  }
  for (const [key, number] of Object.entries(value)) {
    textValue(key, `${name} key`);
    if (typeof number !== "number" || !Number.isFinite(number)) throw new TypeError(`${name}.${key} must be finite`);
  }
  return structuredClone(value);
};

const validateRecord = (record, index, entityId, reportPeriod, formFamily) => {
  if (!record || typeof record !== "object" || Array.isArray(record)) throw new TypeError(`records[${index}] must be an object`);
  const prefix = `records[${index}]`;
  const recordEntity = textValue(record.entityId, `${prefix}.entityId`);
  const recordPeriod = textValue(record.reportPeriod, `${prefix}.reportPeriod`);
  const form = textValue(record.form, `${prefix}.form`);
  const accessionNo = textValue(record.accessionNo, `${prefix}.accessionNo`);
  const amendmentNumber = nonnegativeInteger(record.amendmentNumber, `${prefix}.amendmentNumber`);
  const acceptedInstant = instant(record.acceptedAt, `${prefix}.acceptedAt`);
  const ingestedInstant = instant(record.ingestedAt, `${prefix}.ingestedAt`);
  const facts = factsValue(record.facts, `${prefix}.facts`);
  if (recordEntity !== entityId || recordPeriod !== reportPeriod) throw new RangeError(`${prefix} does not match the requested entity and report period`);
  if (![formFamily, `${formFamily}/A`].includes(form)) throw new RangeError(`${prefix}.form is outside the requested form family`);
  if (amendmentNumber === 0 && form !== formFamily) throw new RangeError(`${prefix} marks an amendment as an original filing`);
  if (amendmentNumber > 0 && form !== `${formFamily}/A`) throw new RangeError(`${prefix} amendmentNumber requires an amended form`);
  if (ingestedInstant < acceptedInstant) throw new RangeError(`${prefix}.ingestedAt cannot precede acceptedAt for direct SEC ingestion`);
  if (!Array.isArray(record.amendmentScope) || !record.amendmentScope.length || record.amendmentScope.some(item => typeof item !== "string" || !item)) {
    throw new TypeError(`${prefix}.amendmentScope must be a non-empty list of strings`);
  }
  return { ...record, accessionNo, amendmentNumber, acceptedInstant, ingestedInstant, facts };
};

export function calculate(data) {
  if (!data || typeof data !== "object" || Array.isArray(data)) throw new TypeError("data must be an object");
  const entityId = textValue(data.entityId, "entityId");
  const reportPeriod = textValue(data.reportPeriod, "reportPeriod");
  const formFamily = textValue(data.formFamily, "formFamily");
  const asOf = textValue(data.asOf, "asOf");
  const asOfInstant = instant(asOf, "asOf");
  if (!Array.isArray(data.records) || !data.records.length) throw new TypeError("records must be a non-empty list");
  const checked = data.records.map((record, index) => validateRecord(record, index, entityId, reportPeriod, formFamily));
  const accessions = checked.map(record => record.accessionNo);
  if (new Set(accessions).size !== accessions.length) throw new RangeError("accessionNo values must be unique");
  const eligible = checked.filter(record => record.ingestedInstant <= asOfInstant);
  if (!eligible.length) throw new RangeError("no filing version was locally available by asOf");
  const selected = eligible.toSorted((left, right) =>
    left.acceptedInstant - right.acceptedInstant ||
    left.amendmentNumber - right.amendmentNumber ||
    left.accessionNo.localeCompare(right.accessionNo)
  ).at(-1);
  return {
    entityId,
    reportPeriod,
    asOf,
    selectionBasis: "latest accepted filing among locally ingested versions",
    accessionNo: selected.accessionNo,
    form: selected.form,
    amendmentNumber: selected.amendmentNumber,
    acceptedAt: selected.acceptedAt,
    ingestedAt: selected.ingestedAt,
    amendmentScope: [...selected.amendmentScope],
    facts: structuredClone(selected.facts),
    eligibleAccessions: eligible.map(record => record.accessionNo).sort(),
    excludedNotYetIngested: checked.filter(record => record.ingestedInstant > asOfInstant).map(record => record.accessionNo).sort(),
  };
}
Full-height labplaygroundOpen full screen