Skip to content

fix(xbrl,find): four structural defects — dead stitching path, dropped query option, mutated fact cache, X-ticker routing - #1183

Merged
dgunning merged 4 commits into
mainfrom
fix/xbrl-stitching-and-ticker-dispatch
Aug 30, 2026
Merged

fix(xbrl,find): four structural defects — dead stitching path, dropped query option, mutated fact cache, X-ticker routing#1183
dgunning merged 4 commits into
mainfrom
fix/xbrl-stitching-and-ticker-dispatch

Conversation

@dgunning

Copy link
Copy Markdown
Owner

Fixes #1173, #1172, #1175 and #1178 — four reproducible defects, each verified against the source before fixing and each gated by a regression test confirmed to fail without its fix.

The fixes

#1173XBRLS.get_statement(use_optimal_periods=False) crashed. The non-optimal branch appended the (statements, role, statement_type) tuple from XBRL.find_statement() to the list handed to StatementStitcher, which then called .get() on it. Those tuple entries are index records carrying neither periods nor data, so the branch was dead code that raised AttributeError: 'tuple' object has no attribute 'get' before stitching a single period. It now calls get_statement_by_type() — the accessor the optimal branch already uses, which itself wraps find_statement() — with the StatementNotFoundError skip added for #683.

#1172XBRLS.query(standardize=False) still standardized. XBRLS.query() stored the option as standardize; StitchedFactQuery.__init__ read standard. The value never reached get_statement(), so standardization stayed on regardless. Both spellings are accepted now, keeping the base-class standard= callers working.

#1175.transform() / .scale() mutated the shared fact cache. Both FactQuery and StitchedFactQuery wrote transformed values back into the row dictionaries returned by get_facts(), which come from the facts view's cache. .scale(1000) therefore scaled the cache itself: a second identical query returned values divided by a million, and an unrelated untransformed query on the same view saw the scaled numbers. Rows are copied before transformation.

#1178find() did not recognize tickers containing X. The ordinary-ticker pattern ^[A-WYZ]{1,5}([.-][A-Z])?$ excluded X in every position, but the thing it was standing in for is the mutual-fund convention of a trailing X. find("XOM"), find("AXP") and find("FIX") fell through to find_company() and returned CompanySearchResults instead of the Company that Company(ticker) resolves. The ^[A-Z]{4}X$ fund pattern is now tested first and the ticker class admits the full alphabet.

Verification

Notes for review

🤖 Generated with Claude Code

dgunning and others added 4 commits August 30, 2026 15:22
…ement dict was expected (#1173)

`XBRLS.get_statement(use_optimal_periods=False)` appended the
(statements, role, statement_type) tuple returned by `XBRL.find_statement()`
to the list handed to `StatementStitcher`, which then called `.get()` on it.
The whole branch was dead: it raised `AttributeError: 'tuple' object has no
attribute 'get'` before stitching a single period.

It now calls `get_statement_by_type()` — the accessor the optimal-periods
branch already uses, and the one that returns a dict carrying 'periods' and
'data' — and skips filings that lack the statement type, matching the
StatementNotFoundError guard added for #683.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LN2NaNcXEuv5YvcKFntcaZ
…fact cache (#1172, #1175)

#1172: `XBRLS.query()` stored the option as `standardize` while
`StitchedFactQuery.__init__` read `standard`, so the value never reached
`get_statement()` and standardization stayed on no matter what the caller
asked for. Both spellings are now accepted.

#1175: `FactQuery.transform()` and `.scale()` wrote transformed values back
into the row dictionaries returned by `get_facts()`. Those rows come from the
facts view's shared cache, so scaling scaled the cache: a second identical
query returned values divided by a million, and an unrelated untransformed
query saw the scaled numbers. Rows are copied before transformation now.
`StitchedFactQuery` carried its own copy of the same loop and is fixed with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LN2NaNcXEuv5YvcKFntcaZ
…1178)

The ordinary-ticker branch of `find()` matched `^[A-WYZ]{1,5}([.-][A-Z])?$`.
The character class excludes `X` in every position, but the thing it was
guarding is the mutual-fund convention of a *trailing* X — so XOM, AXP and FIX
fell through to `find_company()` and came back as `CompanySearchResults`
instead of the `Company` that `Company(ticker)` resolves, changing the return
type and the operations available to the caller.

The `^[A-Z]{4}X$` fund pattern is now tested first, which is what the exclusion
was standing in for, and the ticker pattern admits the full alphabet. Fund
routing is unchanged: no X-containing input reached the ticker branch before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LN2NaNcXEuv5YvcKFntcaZ
The regression-test gate requires an explicit issue, PR or bead link in the
module docstring so a test can always be traced to the bug it guards. The
three new modules referenced their issues by number only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LN2NaNcXEuv5YvcKFntcaZ
@dgunning
dgunning merged commit 763cb98 into main Aug 30, 2026
11 checks passed
@dgunning
dgunning deleted the fix/xbrl-stitching-and-ticker-dispatch branch August 30, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XBRLS.get_statement(use_optimal_periods=False) crashes because a tuple is passed to StatementStitcher

1 participant