Repository navigation
Serve filings from the local SEC corpus when edgar_search found them - #2990
Conversation
- `_lookup_dump` was unreachable after `edgar_search`, because it rebuilds paths from `_filings_cache`, which only `sec_filing_search` fills. - `edgar_search` now records each hit's `source_path` from the index, and filing reads use it before falling back to the filing-layout convention. - The convention can only name `primary-document.html`, so on exhibits it returned the filing's primary document and reported success: 56 of 62 exhibits across 8 demo queries. - Counts filing reads by source (cache, sec-corpus, live) and logs the totals on the first read, then at most once every 30 minutes, and at process exit, since every step in that chain falls back silently. Signed-off-by: Ushnish De <ude@nvidia.com>
c2e75f1 to
dce9d45
Compare
|
/ok to test dce9d45 |
|
/claude review |
|
SHIP β LGTM, no reliability concerns. Reviewed all 4 changed files ( Why this is low-risk:
One soft coupling worth noting (not blocking): |
| if text_content is not None: | ||
| return text_content | ||
|
|
||
| # Otherwise rebuild the path from filing metadata that sec_filing_search |
There was a problem hiding this comment.
If a recorded document path exists but canβt be read, should we return None here instead of falling back? For exhibit URLs, the fallback below reads primary-document.html, so a missing exhibit can silently return the wrong document.
β¦annot name (#3046) Follow-up to #2990. Thanks @cmunley1 for catching this in review β the answer is yes, and the same gap turns out to be reachable without any recorded path at all. ### The two ways a URL becomes a file A filing on EDGAR is a folder of documents: one primary document, plus exhibits such as an `EX-99.1` press release, each with its own URL. To serve a read from the local corpus, `_lookup_dump` has two mechanisms. The **recorded path** comes from the index, which holds a row per document, so it can name an exhibit. #2990 added it and gave it precedence. The **layout convention** rebuilds `ticker/form/year/accession/primary-document.html` from filing-level metadata. Because that metadata is per filing rather than per document, this path can only ever name the primary document β but it cannot tell whether the URL it was handed is that document, so it returns text and reports success either way. The convention predates the index by three months (#1304, May) and was written when `sec_filing_search` was the only search. Every URL that tool emits is built from the submissions API's `primaryDocument`, so the convention was correct by construction: a filing-level search feeding filing-level URLs into a filing-level path rebuild. `edgar_search` (#2722) widened the input domain from filings to documents, and the fallback kept the old assumption. ### Why the fallback cannot just check where the URL came from `_lookup_dump` receives a URL and nothing else. Holding a recorded path for it is the only provenance signal available, and it is a proxy that goes missing while the URL really did come from `edgar_search`: - `_record_dump_paths` is deliberately non-fatal, so results reach the model while the paths silently go unrecorded - an index built before the `source_path` / `canonical_url_key` columns returns `{}` for every lookup - `canonical_url_key` drifting from the builder's normalization misses silently - nothing constrains the read tool's input to a URL that appeared in a search result In each case the URL is indistinguishable from one that was never searched, and the convention resolves if `sec_filing_search` ran for that CIK earlier. Note `_filings_cache` is process-level and disk-backed, so "earlier" spans rollouts, not just the current one. ### Changes - Decline the convention when the URL names a document other than the filing's `primary_document`. This reasons about the URL rather than its provenance, so it holds in all four cases above. - Stop falling through when a recorded path exists but no readable file does, and warn: the index and the corpus have drifted apart. - Both return `None`, which routes to the live fetch β the correct document, one request slower. Neither guard fails a read. One deliberate limit: when the metadata carries no primary-document name, the first guard does not fire. The URL's document cannot be classified then, and declining on a guess would push reads back to sec.gov, which is what the corpus exists to avoid. ### Evidence Three tests, run against `main` with only `app.py` reverted: | test | on c50eb20 | with this change | | --- | --- | --- | | exhibit whose recorded file is missing | fails β returns the filing's primary document, counted `sec-corpus=1 live=0` | passes β live fetch, `sec-corpus=0 live=1` | | exhibit URL with no recorded path | fails β returns the filing's primary document | passes β declines, live fetch | | the filing's own document via the convention | passes | passes | The third is the no-regression case, and it passing on both sides is what shows the other two are testing behaviour rather than restating the new code. Full component suite: 106 passed. Signed-off-by: Ushnish De <ude@nvidia.com>
β¦2990) ## Serve filings from the local SEC corpus when edgar_search found them `edgar_search` queries a local full-text index built from a downloaded SEC corpus, but reads of its results did not come from that corpus. To turn a filing URL into a file on disk, `_lookup_dump` rebuilt a path out of `_filings_cache`, which only `sec_filing_search` populates. After an `edgar_search` hit there was nothing to rebuild from, so the read went to sec.gov. When `sec_filing_search` had run earlier in the same rollout, the rebuilt path was worse than useless for exhibits β the separate documents filed alongside a filing, such as an `EX-99.2` press release. A rebuilt path can only ever name a filing's main document, so the read returned that instead of the exhibit, and reported success. ### Changes - `edgar_search` records each hit's `source_path` from the index, keyed by canonical URL, and filing reads consult it before falling back to the rebuilt path. The index holds a row per document, so this is the only route that can name an exhibit. - Filing reads are counted by source (`cache`, `sec-corpus`, `live`). The totals are logged on the first read, then at most once every 30 minutes, and on shutdown, at warning level so they appear in job logs with no logging configuration. ### Metrics The fix changes how a filing URL is turned into a file path. There are two ways to do that, and the table compares them on the same documents: - **the exact path the index recorded** for the document the search returned - **a path rebuilt from filing metadata**, which can only name a filing's main document 8 queries against a 3-year, 5-ticker demo index returned 138 distinct documents (62 exhibits, 76 main documents). Each was read both ways, and the bytes that came back were compared against the document the search had actually returned: | how the file path is chosen | right document | wrong document | no file found | | --- | --- | --- | --- | | exact path from the index β after this change | 138 (100%) | 0 | 0 | | rebuilt from filing metadata β before this change | 64 (46%) | 56 (41%) | 18 (13%) | All 56 wrong reads were requests for an exhibit, where we previously returned the filing's primary document instead of the exhibit itself. None of the 76 primary-document reads is wrong, which is the shape the mechanism predicts: a rebuilt path can only ever name primary-document.html, so it is right for a filing's own document and wrong for every exhibit in that filing. Signed-off-by: Ushnish De <ude@nvidia.com> Signed-off-by: Junkeun Yi <jkyi@nvidia.com>
β¦annot name (#3046) Follow-up to #2990. Thanks @cmunley1 for catching this in review β the answer is yes, and the same gap turns out to be reachable without any recorded path at all. ### The two ways a URL becomes a file A filing on EDGAR is a folder of documents: one primary document, plus exhibits such as an `EX-99.1` press release, each with its own URL. To serve a read from the local corpus, `_lookup_dump` has two mechanisms. The **recorded path** comes from the index, which holds a row per document, so it can name an exhibit. #2990 added it and gave it precedence. The **layout convention** rebuilds `ticker/form/year/accession/primary-document.html` from filing-level metadata. Because that metadata is per filing rather than per document, this path can only ever name the primary document β but it cannot tell whether the URL it was handed is that document, so it returns text and reports success either way. The convention predates the index by three months (#1304, May) and was written when `sec_filing_search` was the only search. Every URL that tool emits is built from the submissions API's `primaryDocument`, so the convention was correct by construction: a filing-level search feeding filing-level URLs into a filing-level path rebuild. `edgar_search` (#2722) widened the input domain from filings to documents, and the fallback kept the old assumption. ### Why the fallback cannot just check where the URL came from `_lookup_dump` receives a URL and nothing else. Holding a recorded path for it is the only provenance signal available, and it is a proxy that goes missing while the URL really did come from `edgar_search`: - `_record_dump_paths` is deliberately non-fatal, so results reach the model while the paths silently go unrecorded - an index built before the `source_path` / `canonical_url_key` columns returns `{}` for every lookup - `canonical_url_key` drifting from the builder's normalization misses silently - nothing constrains the read tool's input to a URL that appeared in a search result In each case the URL is indistinguishable from one that was never searched, and the convention resolves if `sec_filing_search` ran for that CIK earlier. Note `_filings_cache` is process-level and disk-backed, so "earlier" spans rollouts, not just the current one. ### Changes - Decline the convention when the URL names a document other than the filing's `primary_document`. This reasons about the URL rather than its provenance, so it holds in all four cases above. - Stop falling through when a recorded path exists but no readable file does, and warn: the index and the corpus have drifted apart. - Both return `None`, which routes to the live fetch β the correct document, one request slower. Neither guard fails a read. One deliberate limit: when the metadata carries no primary-document name, the first guard does not fire. The URL's document cannot be classified then, and declining on a guess would push reads back to sec.gov, which is what the corpus exists to avoid. ### Evidence Three tests, run against `main` with only `app.py` reverted: | test | on c50eb20 | with this change | | --- | --- | --- | | exhibit whose recorded file is missing | fails β returns the filing's primary document, counted `sec-corpus=1 live=0` | passes β live fetch, `sec-corpus=0 live=1` | | exhibit URL with no recorded path | fails β returns the filing's primary document | passes β declines, live fetch | | the filing's own document via the convention | passes | passes | The third is the no-regression case, and it passing on both sides is what shows the other two are testing behaviour rather than restating the new code. Full component suite: 106 passed. Signed-off-by: Ushnish De <ude@nvidia.com> Signed-off-by: Junkeun Yi <jkyi@nvidia.com>
Serve filings from the local SEC corpus when edgar_search found them
edgar_searchqueries a local full-text index built from a downloaded SECcorpus, but reads of its results did not come from that corpus. To turn a filing
URL into a file on disk,
_lookup_dumprebuilt a path out of_filings_cache,which only
sec_filing_searchpopulates. After anedgar_searchhit there wasnothing to rebuild from, so the read went to sec.gov.
When
sec_filing_searchhad run earlier in the same rollout, the rebuilt pathwas worse than useless for exhibits β the separate documents filed alongside a
filing, such as an
EX-99.2press release. A rebuilt path can only ever name afiling's main document, so the read returned that instead of the exhibit, and
reported success.
Changes
edgar_searchrecords each hit'ssource_pathfrom the index, keyed bycanonical URL, and filing reads consult it before falling back to the rebuilt
path. The index holds a row per document, so this is the only route that can
name an exhibit.
cache,sec-corpus,live). The totalsare logged on the first read, then at most once every 30 minutes, and on
shutdown, at warning level so they appear in job logs with no logging
configuration.
Metrics
The fix changes how a filing URL is turned into a file path. There are two ways
to do that, and the table compares them on the same documents:
document
8 queries against a 3-year, 5-ticker demo index returned 138 distinct documents
(62 exhibits, 76 main documents). Each was read both ways, and the bytes that
came back were compared against the document the search had actually returned:
All 56 wrong reads were requests for an exhibit, where we previously returned the filing's primary document instead of the exhibit itself. None of the 76 primary-document reads is wrong, which is the shape the mechanism predicts: a rebuilt path can only ever name primary-document.html, so it is right for a filing's own document and wrong for every exhibit in that filing.