feat(xbrl): ISO date transforms + CIK/dimension query coverage (#154)#195
Merged
Conversation
Hardening: register the ixt/ixt-sec date transforms so non-numeric date facts (e.g. dei:DocumentPeriodEndDate tagged ixt:date-monthname-day-year-en) normalize to ISO-8601 instead of being left as locale strings. Both the TR1 concatenated (datemonthdayyearen, dateslashus/dateslasheu) and TR3/TR4 hyphenated spellings are handled; an unparseable date keeps its trimmed raw text rather than blanking the fact. Query coverage: expose `sec query xbrl --cik <cik>` to read a concept's series across all of an issuer's filings (the query function already supported CIK; the CLI only took an accession), ordered by (accession, fact_index) with an Accession column. Surface each fact's dimensional qualifiers as a compact Axis=Member column. Tests: date-transform cases (all spellings + unparseable fallback), a dei date fact normalized end to end, formatXbrlDimensions rendering and malformed-JSON degradation, and CIK-path ordering (unfiltered and concept-filtered). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112rEbCs1VKZQ4DJ6CfQojT
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens iXBRL ingestion by registering SEC date ixt transforms that normalize common locale date formats into ISO-8601 strings, and expands sec query xbrl to support cross-filing queries by CIK plus display of dimensional qualifiers.
Changes:
- Add ixt date transform registrations and parsing logic to normalize supported date spellings to ISO-8601, with “unparseable -> trimmed raw text” fallback.
- Extend XBRL querying/CLI output:
sec query xbrl --cik <cik>support, deterministic cross-filing ordering, and aDimensions(Axis=Member) column. - Add/expand tests for date normalization, CIK query ordering, and dimension formatting.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/sec/xbrl/parseInlineXbrl.test.ts | Adds end-to-end test asserting a DEI date fact is normalized to ISO-8601 via ixt transforms. |
| src/sec/xbrl/ixtTransforms.ts | Implements and registers SEC date transforms (month-name, slash, hyphen variants) to produce ISO dates. |
| src/cli/queries/XbrlQuery.ts | Updates query ordering logic and adds formatXbrlDimensions() for compact Axis=Member display. |
| src/cli/queries/XbrlQuery.test.ts | Adds tests for CIK-path ordering and dimension formatting behavior/fallbacks. |
| src/cli/groups/query.ts | Extends sec query xbrl command to accept --cik, validates accession vs CIK usage, and renders new columns. |
| CLAUDE.md | Documents ISO date normalization behavior and the new query surface (--cik, dimensions). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+61
to
+67
| function toIsoDate(year: number, month: number, day: number): string | null { | ||
| if (!Number.isInteger(month) || month < 1 || month > 12) return null; | ||
| if (!Number.isInteger(day) || day < 1 || day > 31) return null; | ||
| if (!Number.isInteger(year) || year < 1) return null; | ||
| const pad = (n: number, width: number): string => String(n).padStart(width, "0"); | ||
| return `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`; | ||
| } |
Comment on lines
+45
to
+47
| const total = await repo.count(criteria); | ||
| const rows = (await repo.query(criteria, { limit, offset })) ?? []; | ||
| return { rows: rows.sort((a, b) => a.fact_index - b.fact_index), total }; | ||
| return { rows: rows.sort(byFilingOrder), total }; |
Comment on lines
+414
to
+420
| const cikRaw = options.cik as string | undefined; | ||
| let cik: number | undefined; | ||
| if (cikRaw !== undefined) { | ||
| cik = parseInt(cikRaw, 10); | ||
| if (!Number.isFinite(cik)) | ||
| throw new Error(`Invalid --cik "${cikRaw}". Must be a number.`); | ||
| } |
…R BY pushdown - toIsoDate: reject impossible calendar dates (Feb 30, Apr 31) via a UTC round-trip check instead of emitting an invalid ISO string like "2024-02-30"; unparseable dates keep their raw text. - query xbrl --cik: require an all-digit string (matches parseCikArg in the fetch group) so "123abc" errors instead of being silently read as 123. - queryXbrlFacts unfiltered path: push ORDER BY (accession_number, fact_index) down to storage so offset/limit pagination is consistent across pages, rather than sorting only the returned page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112rEbCs1VKZQ4DJ6CfQojT
sroussey
added a commit
that referenced
this pull request
Jul 16, 2026
…#195) * feat(xbrl): ISO date transforms + CIK/dimension query coverage (#154) Hardening: register the ixt/ixt-sec date transforms so non-numeric date facts (e.g. dei:DocumentPeriodEndDate tagged ixt:date-monthname-day-year-en) normalize to ISO-8601 instead of being left as locale strings. Both the TR1 concatenated (datemonthdayyearen, dateslashus/dateslasheu) and TR3/TR4 hyphenated spellings are handled; an unparseable date keeps its trimmed raw text rather than blanking the fact. Query coverage: expose `sec query xbrl --cik <cik>` to read a concept's series across all of an issuer's filings (the query function already supported CIK; the CLI only took an accession), ordered by (accession, fact_index) with an Accession column. Surface each fact's dimensional qualifiers as a compact Axis=Member column. Tests: date-transform cases (all spellings + unparseable fallback), a dei date fact normalized end to end, formatXbrlDimensions rendering and malformed-JSON degradation, and CIK-path ordering (unfiltered and concept-filtered). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112rEbCs1VKZQ4DJ6CfQojT * fix(xbrl): address review — reject impossible dates, strict CIK, ORDER BY pushdown - toIsoDate: reject impossible calendar dates (Feb 30, Apr 31) via a UTC round-trip check instead of emitting an invalid ISO string like "2024-02-30"; unparseable dates keep their raw text. - query xbrl --cik: require an all-digit string (matches parseCikArg in the fetch group) so "123abc" errors instead of being silently read as 123. - queryXbrlFacts unfiltered path: push ORDER BY (accession_number, fact_index) down to storage so offset/limit pagination is consistent across pages, rather than sorting only the returned page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112rEbCs1VKZQ4DJ6CfQojT --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #154 (iXBRL ingestion hardening and query coverage). Based on
harness.The issue is broad — "harden edge cases, grow fixture coverage, and expand
sec query xbrl". This PR delivers a coherent, tested slice across all three prongs.Hardening — ixt date transforms
Date-formatted non-numeric facts (e.g.
dei:DocumentPeriodEndDatetaggedixt:date-monthname-day-year-en= "March 31, 2026") were left as locale strings because unregistered transforms fall through to raw text. Registered the SEC date-transform family so they normalize to ISO-8601 (2026-03-31):datemonthdayyearen,dateslashus/dateslasheu) and TR3/TR4 hyphenated (date-monthname-day-year-en,date-day-monthname-year-en,date-month-day-year,date-day-month-year,date-year-month-day) spellings.Query coverage
sec query xbrl --cik <cik>to read a concept's series across all of an issuer's filings (e.g. trust balance over time). The query function already accepted a CIK; only the CLI was accession-only. Cross-issuer results carry an Accession column and order by(accession, fact_index).Axis=Membercolumn (e.g.StatementClassOfStock=CommonClassA), previously invisible in the table.--cikchoice (neither / both / non-numeric CIK all error cleanly).Tests
deidate fact normalized to ISO end to end throughparseInlineXbrl.formatXbrlDimensions:Axis=Memberrendering (prefix + Axis/Member/Domain suffix stripping) and graceful""degradation on null / malformed JSON.38 tests pass (was 28).
Verification
bun teston all XBRL + query tests: 38 pass, 0 failtsc --noEmit: cleanprettier --check: cleanDimensionscolumn renders on accession queries, theAccessioncolumn + cross-filing ordering render on--cikqueries, and the validation errors surface cleanly.Docs
Updated
CLAUDE.md's iXBRL section to document the ISO date normalization and the new--cik/ dimensions query surface.🤖 Generated with Claude Code
https://claude.ai/code/session_0112rEbCs1VKZQ4DJ6CfQojT
Generated by Claude Code