19  SEC EDGAR Point-in-Time Filing-Date Integrity Audit of FMP Fundamentals — Findings (F-319)

Status: empirical verdict recorded 2026-07-30 from a live SEC EDGAR fetch (sample AAPL / MSFT / JPM / XOM / JNJ). The first live run (2026-07-27) reported a spurious “185/185 @ ~−210 days” that was an instrument bug, not a platform leak — the loader reconstructed quarterly period-ends from a calendar-quarter assumption while gold.fact_fundamental_quarter stores fiscal calendar_year/period, so non-December-FYE names (MSFT, June FYE) mis-paired to the wrong EDGAR filing. Fixed under B-319.1 / TASK-2791 (derive period-end from the row’s own period_date_sk); the numbers below are the corrected re-run. The harness remains read-only and re-runnable; re-run after each data refresh via §5.

19.1 1. Verdict

LOOK-AHEAD DETECTED — 184 of 184 matched fundamentals carry a knowledge date earlier than their EDGAR 10-K/10-Q filing date. Panel rows evaluated: 1181 (matched 184, unmatched 997, violations 184). Median violation gap ≈ −49 days; range roughly −45 to −53 days. The leak is systematic and appears in both cadence legs, exactly as the a-priori prediction below anticipated:

  • Annual (earnings_yield / pe_ratio / roic, from fact_valuation_annual / fact_moat_annual): stamped at the F-292 Jan-1 snap (e.g. 20190102) but the 10-K was filed ~late Feb (e.g. 2019-02-20) → ~−49 days. Prediction confirmed.
  • Quarterly (earnings_growth_qoq, from fact_fundamental_quarter): stamped at period-end (e.g. 2020-12-31) with no dissemination lag, filed ~2021-02-22 → −53 days. This refutes the a-priori guess that the quarterly leg was safer: TABLE_DATE_SK_EXPR["fact_fundamental_quarter"] = COALESCE(period_date_sk, …) stamps knowledge at the fiscal period-end, and the keymap min_age_days lag does not propagate into that knowledge date_sk.

Two caveats bound how alarming this is (read before acting):

  1. Anchor = the 10-K/10-Q filed date, but preliminary figures usually reach the market ~2–3 weeks earlier via an 8-K earnings release. So −49/−53 is an upper bound on the economically-relevant look-ahead. Even measured against the earlier 8-K, though, a period-end stamp (Dec-31) and a Jan-1 snap still precede the announcement — a non-trivial leak remains, it is just smaller than the headline. F-324.1 (2026-08-01) wired this in code: the audit now fetches 8-K Item 2.02 earnings releases (EARNINGS_RELEASE_FORM / parse_earnings_ releases / pair_earnings_release), pairs the earlier of {8-K release, definitive filing} as the true first-availability date, and the report now carries BOTH gap_days (vs definitive, the upper bound) and gap_days_vs_ release (the tighter estimate). The tighter empirical numbers land on the next live --live run (a Tier-4 operator step — the code + offline fixtures are delivered and unit-tested; the live 8-K fetch has not yet been run).
  2. Small / partial coverage. 184 matched of 1181 (997 unmatched — only 5 tickers, only 10-K/10-Q forms, a 20-day pairing tolerance). The verdict is “100% of what could be verified,” not “100% of the universe.” The as-reported value-agreement leg was 0 comparable of 0 because the panel concepts audited (earnings_yield / pe_ratio / roic / earnings_growth_qoq) are DERIVED ratios with no single us-gaap XBRL tag. F-324.1 addressed both halves: (a) the sample is widened to 15 tickers mixing fiscal-year-ends (SAMPLE_TICKERS now includes MSFT-Jun / NKE-May / WMT-Jan / … alongside the Dec-FYE names) so the non-Dec-FYE pairing path is exercised; (b) a FMP↔︎us-gaap concept map (FMP_CONCEPT_TO_US_GAAP / resolve_us_gaap_concepts) is wired, two RAW as-reported sources with real twins (fact_annual.revenue → Revenues, fact_annual.net_income → NetIncomeLoss) are added to the panel loader so the value-agreement leg can produce non-zero comparable evidence, and the four derived-ratio concepts are now reported explicitly as “no us-gaap twin” rather than silently as 0-of-0. Non-zero comparable counts land on the next live run.

Reasoned a-priori prediction (retained for the record; now confirmed for the annual leg, refuted for the quarterly leg). The platform’s F-292 cadence-aware knowledge-date snap stamps annual (10-K-sourced) fundamentals at the first US market day on/after Jan-1 of the following calendar year (sbfactors.factor_value_lift_service / resolve_date_sk_expr). A 10-K is actually filed ~60–90 days after fiscal year-end — late Feb–Mar for the December-FYE population — so the annual leg was predicted to leak by ~40–90 days (confirmed at ~49). The quarterly leg was predicted safer on the assumption that the keymap min_age_days dissemination lag pushed the knowledge date past the period; the live run shows that lag never reaches the quarterly knowledge date_sk, so the quarterly leg leaks too.

19.2 2. Method

Read-only, stdlib-only (urllib / json / dataclasses / datetime); no ingestion, no Gold write, no new runtime dependency.

  • CIK resolution — EDGAR company_tickers.json parsed into a {TICKER: CIK} map (parse_ticker_cik_map / resolve_cik), then zero-padded to the 10-digit path token (zero_pad_cik).
  • Filing dates — the submissions API (data.sec.gov/submissions/CIK…json) filings.recent parallel arrays zipped into EdgarFilings (parse_recent_filings), carrying form / filed / acceptanceDateTime / accessionNumber / reportDate.
  • As-reported values — the XBRL companyfacts API (data.sec.gov/api/xbrl/companyfacts/CIK…json), facts → us-gaap → <Concept> → units → USD, parsed into AsReportedFacts (parse_company_facts).
  • Pairing — a panel row pairs to a filing when abs(period_of_report − period_end) ≤ 20 days; the earliest-filed match wins (first public disclosure, the conservative choice). period_of_report / period_end are used only to pair — never for the leak test itself.
  • The leak test — knowledge date_sk (decoded to a date) vs the paired filing’s EDGAR filed date. gap_days = knowledge_date − edgar_filed; a negative gap is a look-ahead violation. The leak is always anchored on the filing date, never period-end — the F-288 grain lesson.
  • As-reported agreement — the platform’s stored value vs EDGAR’s as-reported value, material when the relative difference exceeds MATERIALITY_REL_THRESHOLD = 0.01 (1%).

Sample (SAMPLE_TICKERS / AUDITED_CONCEPTS / AUDITED_FORMS): the 2026-07-30 verdict above used a 5-name cross-section (AAPL, MSFT, JPM, XOM, JNJ); F-324.1 widened SAMPLE_TICKERS to 15 mixed-FYE names (adding NKE-May, WMT/HD/COST/TGT-Jan/Feb, PG, KO, CSCO, ORCL, PEP) now that OQ-3’s “widen only if leakage is found” is satisfied. Filings audited: recent 10-K + 10-Q for the leak test, plus 8-K Item 2.02 earnings releases for the first-availability anchor (F-324.1); us-gaap concepts Revenues, NetIncomeLoss, Assets, StockholdersEquity.

19.3 3. Worst-offender table

Corrected live run, 2026-07-30 (top 20 of 184 violations, most-negative gap_days first). Every worst-offender is JNJ — a December-FYE filer that files relatively late (~Feb 20), giving the widest gap; the other sampled names also violate, with smaller gaps. knowledge_date_sk at the fiscal period-end (quarterly) or the Jan-2 snap (annual) sits ~7 weeks before edgar_filed.

F-324.1 (2026-08-01): this table pre-dates the 8-K anchor. The report now emits two additional columns — edgar_first_available (the earlier of the 8-K Item 2.02 earnings release and the definitive filing) and gap_days_vs_release (the tighter, less-negative gap) — so a JNJ row filing its 8-K ~2 weeks before its 10-K would show a gap_days_vs_release roughly ~14 days less negative than the gap_days shown here. The widened columns + the tighter numbers repopulate on the next live --live run (Tier-4). The rows below are the vs-definitive upper bound and remain valid as such.

symbol concept source_table period_end knowledge_date_sk edgar_filed gap_days
JNJ earnings_growth_qoq fact_fundamental_quarter 2020-12-31 20201231 2021-02-22 -53
JNJ earnings_growth_qoq fact_fundamental_quarter 2018-12-31 20181231 2019-02-20 -51
JNJ earnings_growth_qoq fact_fundamental_quarter 2019-12-31 20191231 2020-02-18 -49
JNJ earnings_yield fact_valuation_annual 2018-12-31 20190102 2019-02-20 -49
JNJ earnings_yield fact_valuation_annual 2020-12-31 20210104 2021-02-22 -49
JNJ pe_ratio fact_valuation_annual 2018-12-31 20190102 2019-02-20 -49
JNJ pe_ratio fact_valuation_annual 2020-12-31 20210104 2021-02-22 -49
JNJ roic fact_moat_annual 2020-12-31 20210104 2021-02-22 -49
JNJ roic fact_moat_annual 2018-12-31 20190102 2019-02-20 -49
JNJ earnings_growth_qoq fact_fundamental_quarter 2021-12-31 20211231 2022-02-17 -48
JNJ earnings_growth_qoq fact_fundamental_quarter 2022-12-31 20221231 2023-02-16 -47
JNJ earnings_growth_qoq fact_fundamental_quarter 2023-12-31 20231231 2024-02-16 -47
JNJ earnings_yield fact_valuation_annual 2019-12-31 20200102 2020-02-18 -47
JNJ pe_ratio fact_valuation_annual 2019-12-31 20200102 2020-02-18 -47
JNJ roic fact_moat_annual 2019-12-31 20200102 2020-02-18 -47
JNJ earnings_growth_qoq fact_fundamental_quarter 2024-12-29 20241229 2025-02-13 -46
JNJ earnings_growth_qoq fact_fundamental_quarter 2025-12-28 20251228 2026-02-11 -45
JNJ earnings_yield fact_valuation_annual 2021-12-31 20220103 2022-02-17 -45
JNJ earnings_yield fact_valuation_annual 2023-12-31 20240102 2024-02-16 -45
JNJ pe_ratio fact_valuation_annual 2021-12-31 20220103 2022-02-17 -45

19.4 4. Coordination boundary (F-313 / SBM-7)

F-319 owns only the point-in-time filing-date + as-reported-value integrity leg — is a fundamental usable before it was filed, and does its stored value match EDGAR’s? The EDGAR delisting-confirmation leg (positive confirmation of a performance vs benign delist via EDGAR) is a separate concern owned by survivorship-bias-measurement F-313 / SBM-7. F-313 was unbuilt at F-319’s authoring, so this script carries its own stdlib EDGAR fetcher + CIK-resolution surface; any shared EDGAR fetch / CIK-resolution surface must be reused, not forked — if/when F-313 lands, the two fetchers should be consolidated into one shared EDGAR access module rather than maintained in parallel.

19.5 5. How to run

Read-only, re-runnable, no gate:

# Live fetch (Tier-4 operator step; caches snapshots for reproducibility):
.venv\Scripts\python.exe scripts\research\edgar_pit_audit.py --live --out docs\research\edgar-pit-integrity-findings.md

# Default (re-runs against the already-cached local snapshot):
.venv\Scripts\python.exe scripts\research\edgar_pit_audit.py

--sample AAPL,MSFT,... overrides the audited tickers; --snapshot-dir overrides the local cache (default data/reference/edgar_snapshots); --out writes the report markdown (default stdout). SEC requires a declared User-Agent on every request — already set in EDGAR_USER_AGENT. The DuckDB connection is opened read_only=True; if the DB or the live fetch is unavailable the script prints a clear INDETERMINATE report and exits 0 (it is a diagnostic, not a gate). Transcribe the verdict + worst-offender rows from the live run into §1 / §3 above.

19.6 6. Follow-ups (raised by the 2026-07-30 result)

The corrected run turns the leak from a hypothesis into a measured fact, but two things must happen before it can be acted on with confidence — tracked under the follow-up milestone F-324 (m-357):

  1. Widen the anchor to 8-K earnings-release dates — DELIVERED in code (F-324.1 / TASK-2802, 2026-08-01). The audit now fetches 8-K Item 2.02 earnings releases, pairs the earlier of {8-K release, definitive filing} as the true economic first-availability date, and reports both the conservative gap_days (vs definitive) and the tighter gap_days_vs_release. The sample was widened to 15 mixed-FYE tickers and the FMP↔︎us-gaap concept map wired (two raw as-reported sources added) so the value-agreement leg produces evidence. Remaining: the live --live re-run (Tier-4) to populate §1/§3 with the tighter empirical numbers + non-zero comparable counts. The B-319.1 fiscal period_date_sk period-end derivation is preserved.

  2. Scope the knowledge-date dissemination-lag remediation — F-324.2 / TASK-2803 (design brief pending). The honest fix is to push the knowledge date_sk to on-or-after the actual filing/announcement date — a dissemination lag on the quarterly period-end stamp and a move of the annual Jan-1 snap onto the real filing date, mirroring F-299’s FINRA min_age_days snap. This is not a quick patch: changing the knowledge date rewrites every downstream PIT join and forks factor history, so it needs its own design brief + report-then-enforce switch + Tier-4 bring-up, not an inline change. The lag size should be chosen from F-324.1’s 8-K-anchored numbers once the live re-run lands.

    Remediation delivered as F-442 / LH-29 (default-OFF switch SB_FUNDAMENTALS_DISSEMINATION_LAG_ENABLED, report-then-enforce): a fixed proxy lag = period-end + N calendar days snapped forward to the first US market day — quarterly 60d / annual 90d — applied to fact_fundamental_quarter, fact_fundamental_annual, fact_valuation_annual, fact_moat_annual (superseding the F-292 Dec-31/Jan-1 annual snap when ON), with the signal panel + coverage rerouted through the switched expression (flipping ON forks model_id). Tier-4 re-lift CLI python -m sbfactors.cli relift-fundamentals-factors [--all]. OFF is byte-identical. Brief: docs/backlog/feature-fundamentals-dissemination-lag-design-brief.md.