fix(sec): derive fiscal year end from the issuer's own 10-K
Regression found by the post-reparse collision check. _period_identity trusted submissions.fiscalYearEnd, which is not reliable: Franklin Resources (BEN) declares 1231 while every one of its 10-Ks ends 09-30. The effect was data loss, not just a bad label. BEN's real fiscal Q1 (Dec 31) sat 0 days from the claimed year end, matching no quarter band, so it fell back to SEC's fy/fp; its fiscal Q2 (Mar 31) computed 275 days out and was labelled Q1. Both landed on the same key, the collision discarded one, and BEN lost TTM EPS and revenue growth entirely — values it had before this branch. A 10-K's reportDate IS the fiscal year end by definition, so resolve_fiscal_ year_end() now prefers the issuer's most recent annual filing and treats the declared value as a fallback for issuers with no 10-K in the set. Scanned the full tracked universe: 2 of 506 issuers declare a year end more than 21 days from their own 10-K — BEN (91d, broken) and DELL (29d, mislabelled but functionally correct). Both now derive correctly and match the legacy provider: BEN revenue growth 3.8243 vs 3.82, DELL 38.5735 vs 38.57. Controls (AAPL, COST, PEP, DPZ, IRM, JPM, CRM, STX, AVY) byte-identical. 826 unit tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -179,15 +179,19 @@ def parse_snapshots(
|
||||
) -> ParseResult:
|
||||
"""Build snapshot rows for ``accessions`` (those with facts + filing meta).
|
||||
|
||||
``fiscal_year_end`` is the issuer's ``submissions.fiscalYearEnd`` (MMDD) and
|
||||
is what makes period identity independent of SEC's unreliable fy/fp fields
|
||||
(see ``_period_identity``). Omitting it falls back to the old fy/fp behaviour.
|
||||
``fiscal_year_end`` is the issuer's declared ``submissions.fiscalYearEnd``
|
||||
(MMDD) and seeds period identity (see ``_period_identity``), making it
|
||||
independent of SEC's unreliable fy/fp fields. It is only a hint: the issuer's
|
||||
own 10-K period ends override it (see ``resolve_fiscal_year_end``). With
|
||||
neither available, the old fy/fp behaviour is used.
|
||||
|
||||
``skipped_filings`` = no row produced (missing facts/meta or no usable period
|
||||
identity); ``field_issues`` = a row was produced but a field is null/ambiguous.
|
||||
Callers must not use field issues as failed-row coverage.
|
||||
"""
|
||||
cik = f"{int(companyfacts['cik']):010d}"
|
||||
# The declared value is only a hint; the issuer's own 10-Ks are authoritative.
|
||||
fiscal_year_end = resolve_fiscal_year_end(filings, fiscal_year_end)
|
||||
by_accn = _index_by_accession(companyfacts)
|
||||
result = ParseResult()
|
||||
for accn in accessions:
|
||||
@@ -291,6 +295,29 @@ def _parse_one(
|
||||
return row, ("ambiguous shares outstanding" if ambiguous else None)
|
||||
|
||||
|
||||
def resolve_fiscal_year_end(
|
||||
filings: dict[str, FilingMeta], declared: str | None
|
||||
) -> str | None:
|
||||
"""The issuer's fiscal-year-end MMDD, preferring its own 10-K period ends.
|
||||
|
||||
``submissions.fiscalYearEnd`` is *not* reliable: Franklin Resources (BEN)
|
||||
declares 1231 while every one of its 10-Ks ends 09-30. Trusting it put BEN's
|
||||
fiscal Q1 (Dec) 0 days from the claimed year end — matching no quarter band —
|
||||
and labelled its fiscal Q2 (Mar) as Q1, colliding two periods on one key and
|
||||
destroying the quarter chain.
|
||||
|
||||
A 10-K's reportDate **is** the fiscal year end by definition, so it wins
|
||||
whenever one is available; the declared value is only a fallback for an issuer
|
||||
with no annual filing in the set. The most recent 10-K is used, so an issuer
|
||||
that changed its year end is measured against its current calendar.
|
||||
"""
|
||||
annual = [m.report_date for m in filings.values() if m.form.startswith("10-K")]
|
||||
if annual:
|
||||
latest = max(annual)
|
||||
return f"{latest.month:02d}{latest.day:02d}"
|
||||
return declared
|
||||
|
||||
|
||||
def _period_identity(
|
||||
meta: FilingMeta, fiscal_year_end: str | None
|
||||
) -> tuple[int | None, str | None]:
|
||||
|
||||
Reference in New Issue
Block a user