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, fromfact_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, fromfact_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 keymapmin_age_dayslag does not propagate into that knowledgedate_sk.
Two caveats bound how alarming this is (read before acting):
- Anchor = the 10-K/10-Q
fileddate, 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 BOTHgap_days(vs definitive, the upper bound) andgap_days_vs_ release(the tighter estimate). The tighter empirical numbers land on the next live--liverun (a Tier-4 operator step — the code + offline fixtures are delivered and unit-tested; the live 8-K fetch has not yet been run). - 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_TICKERSnow 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.jsonparsed 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.recentparallel arrays zipped intoEdgarFilings (parse_recent_filings), carryingform/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 intoAsReportedFacts (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_endare 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 EDGARfileddate.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) andgap_days_vs_release(the tighter, less-negative gap) — so a JNJ row filing its 8-K ~2 weeks before its 10-K would show agap_days_vs_releaseroughly ~14 days less negative than thegap_daysshown here. The widened columns + the tighter numbers repopulate on the next live--liverun (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):
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 tightergap_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--livere-run (Tier-4) to populate §1/§3 with the tighter empirical numbers + non-zero comparable counts. The B-319.1 fiscalperiod_date_skperiod-end derivation is preserved.Scope the knowledge-date dissemination-lag remediation — F-324.2 / TASK-2803 (design brief pending). The honest fix is to push the knowledge
date_skto 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 FINRAmin_age_dayssnap. 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 tofact_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 forksmodel_id). Tier-4 re-lift CLIpython -m sbfactors.cli relift-fundamentals-factors [--all]. OFF is byte-identical. Brief:docs/backlog/feature-fundamentals-dissemination-lag-design-brief.md.