Unified power-user entry point for edgarjure. Require as: (require '[edgar.api :as e])
Design principles:
Unified power-user entry point for edgarjure.
Require as: (require '[edgar.api :as e])
Design principles:
- Every function accepts ticker or CIK interchangeably
- Keyword args throughout; no positional form/type args — a single options
map also works everywhere: (e/income "AAPL" {:form "10-Q"})
- nth-style lookups (e/filing :n ...) return nil out of range, never throw
- Sensible defaults for taxonomy (us-gaap), unit (USD), form (10-K)
- :concept accepts a string or a collection of strings
- Functions that return datasets always return tech.ml.dataset, never seq-of-mapsFinancial statement extraction with normalization and standardization.
Four output views for each statement (:view option): :as-reported - raw XBRL observations for the statement's concepts, exactly as filed; no deduplication, no label mapping :normalized - (default) fallback chains map variant tags to canonical labels; restatements deduplicated by most-recent-filed :standardized - :normalized plus derived line items imputed from arithmetic identities (e.g. Gross Profit = Revenue - Cost of Revenue). Derived rows carry :method :derived and :derived-from for auditability. :compustat - :standardized plus line items reclassified to approximate Compustat definitions (D&A out of COGS, R&D folded into XSGA, special items added back to operating income, ...). Reclassified rows carry :method :reclassified, :rule and :derived-from. Rules live in resources/edgar/reclass/ (edgar.reclass); income statement and balance sheet (cash flow equals :standardized). With the FSDS cache enabled (edgar.fsds/enable-cache!), 10-K income periods gain statement-placement guards and extension-tag operands.
Concept fallback chains: each line item is a vector of concept names tried in order; the first one present in the facts data wins. Chains are loaded from EDN files under resources/edgar/concepts/ and exposed as public vars, so power users can inspect them or pass their own via :concepts.
Industry routing: banks (SIC 6000-6199, 6712) and insurers (SIC 6300-6399, 6411) use fundamentally different income statement line items. When no explicit :industry or :concepts is given, income-statement auto-routes on the company's SIC code. Pass :industry :standard to force generic chains.
Duration vs instant: Income statement + cash flow -> duration observations (row has :start date) Balance sheet -> instant observations (row has no :start date)
Point-in-time / look-ahead-safe mode: Pass :as-of "YYYY-MM-DD" to any public function to restrict to filings where :filed <= as-of-date. Without :as-of the latest restated value is returned (always-latest behaviour).
Quarterly and LTM derivation (10-Q only, flow variables only): :duration-months - 3/6/9/12 classification of the observation window :val-q - single-quarter value. Derived from actual period dates: a ~3-month row is used as-is; a YTD row minus the YTD row one quarter shorter (same fiscal-year start) yields the quarter ending at the longer row's end. The SEC :fy/:fp fields are deliberately NOT used — they describe the filing, not the observation, and collide across comparative periods. :val-ltm - last-twelve-months: sum of four consecutive derived quarters matched on period-end dates (±14 days tolerance for 52/53-week fiscal calendars). 10-K annual rows participate in the derivation so that Q4 = FY - 9M YTD.
Unmapped-concept logging: every statement call records us-gaap concepts in the company's facts that no active chain matched. See unmapped-concepts, clear-unmapped-concepts!, save-unmapped-concepts!.
Financial statement extraction with normalization and standardization.
Four output views for each statement (:view option):
:as-reported - raw XBRL observations for the statement's concepts,
exactly as filed; no deduplication, no label mapping
:normalized - (default) fallback chains map variant tags to canonical
labels; restatements deduplicated by most-recent-filed
:standardized - :normalized plus derived line items imputed from
arithmetic identities (e.g. Gross Profit = Revenue -
Cost of Revenue). Derived rows carry :method :derived
and :derived-from for auditability.
:compustat - :standardized plus line items reclassified to
approximate Compustat definitions (D&A out of COGS,
R&D folded into XSGA, special items added back to
operating income, ...). Reclassified rows carry
:method :reclassified, :rule and :derived-from.
Rules live in resources/edgar/reclass/ (edgar.reclass);
income statement and balance sheet (cash flow equals
:standardized). With the FSDS cache enabled
(edgar.fsds/enable-cache!), 10-K income periods gain
statement-placement guards and extension-tag operands.
Concept fallback chains: each line item is a vector of concept names tried
in order; the first one present in the facts data wins. Chains are loaded
from EDN files under resources/edgar/concepts/ and exposed as public vars,
so power users can inspect them or pass their own via :concepts.
Industry routing: banks (SIC 6000-6199, 6712) and insurers (SIC 6300-6399,
6411) use fundamentally different income statement line items. When no
explicit :industry or :concepts is given, income-statement auto-routes on
the company's SIC code. Pass :industry :standard to force generic chains.
Duration vs instant:
Income statement + cash flow -> duration observations (row has :start date)
Balance sheet -> instant observations (row has no :start date)
Point-in-time / look-ahead-safe mode:
Pass :as-of "YYYY-MM-DD" to any public function to restrict to filings
where :filed <= as-of-date. Without :as-of the latest restated value is
returned (always-latest behaviour).
Quarterly and LTM derivation (10-Q only, flow variables only):
:duration-months - 3/6/9/12 classification of the observation window
:val-q - single-quarter value. Derived from actual period dates:
a ~3-month row is used as-is; a YTD row minus the YTD row one
quarter shorter (same fiscal-year start) yields the quarter
ending at the longer row's end. The SEC :fy/:fp fields are
deliberately NOT used — they describe the filing, not the
observation, and collide across comparative periods.
:val-ltm - last-twelve-months: sum of four consecutive derived quarters
matched on period-end dates (±14 days tolerance for 52/53-week
fiscal calendars). 10-K annual rows participate in the
derivation so that Q4 = FY - 9M YTD.
Unmapped-concept logging: every statement call records us-gaap concepts in
the company's facts that no active chain matched. See unmapped-concepts,
clear-unmapped-concepts!, save-unmapped-concepts!.Central loader for all built-in edgarjure form parsers.
Requiring this namespace registers filing-obj methods for all supported form types. Individual parsers can also be required directly.
Usage: (require '[edgar.forms]) ; loads all built-in parsers (edgar.filing/filing-obj filing) ; dispatches to the appropriate parser
Supported form types after loading: "3" "3/A" — Form 3: initial beneficial ownership (edgar.forms.ownership) "4" "4/A" — Form 4: insider trades (edgar.forms.ownership) "5" "5/A" — Form 5: annual beneficial ownership (edgar.forms.ownership) "13F-HR" — Form 13F-HR: institutional holdings (edgar.forms.form13f) "13F-HR/A" — Form 13F-HR/A: amended institutional holdings (edgar.forms.form13f) "SCHEDULE 13D" "SCHEDULE 13D/A" "SCHEDULE 13G" "SCHEDULE 13G/A" — beneficial ownership >5% (edgar.forms.schedule13; XML era, filed Dec 2024 onward — legacy SC 13D/G fall through to the default raw-HTML result)
Central loader for all built-in edgarjure form parsers.
Requiring this namespace registers filing-obj methods for all supported
form types. Individual parsers can also be required directly.
Usage:
(require '[edgar.forms]) ; loads all built-in parsers
(edgar.filing/filing-obj filing) ; dispatches to the appropriate parser
Supported form types after loading:
"3" "3/A" — Form 3: initial beneficial ownership (edgar.forms.ownership)
"4" "4/A" — Form 4: insider trades (edgar.forms.ownership)
"5" "5/A" — Form 5: annual beneficial ownership (edgar.forms.ownership)
"13F-HR" — Form 13F-HR: institutional holdings (edgar.forms.form13f)
"13F-HR/A" — Form 13F-HR/A: amended institutional holdings (edgar.forms.form13f)
"SCHEDULE 13D" "SCHEDULE 13D/A" "SCHEDULE 13G" "SCHEDULE 13G/A"
— beneficial ownership >5% (edgar.forms.schedule13; XML era,
filed Dec 2024 onward — legacy SC 13D/G fall through to the
default raw-HTML result)No vars found in this namespace.
Form 13F-HR parser — Institutional Investment Manager Holdings Report. Registers a filing-obj method for form type "13F-HR".
Only handles XML-era filings (post-2013 Q2). Earlier filings use heterogeneous plain-text infotables and are not supported.
Usage: (require '[edgar.forms.form13f]) ; side-effectful load registers the method (edgar.filing/filing-obj filing) ; => {:form "13F-HR" :holdings <dataset> ...}
Form 13F-HR parser — Institutional Investment Manager Holdings Report.
Registers a filing-obj method for form type "13F-HR".
Only handles XML-era filings (post-2013 Q2). Earlier filings use
heterogeneous plain-text infotables and are not supported.
Usage:
(require '[edgar.forms.form13f]) ; side-effectful load registers the method
(edgar.filing/filing-obj filing) ; => {:form "13F-HR" :holdings <dataset> ...}Form 4 parser — Statement of Changes in Beneficial Ownership.
Forms 3, 4, and 5 share the ownershipDocument XML schema; the actual parser and the filing-obj registrations for all of them live in edgar.forms.ownership. This namespace remains as the historical entry point for Form 4.
Usage: (require '[edgar.forms.form4]) ; side-effectful load registers the methods (edgar.filing/filing-obj filing) ; => {:form "4" :reporting-owner {...} ; :transactions [...] :holdings [...]}
Form 4 parser — Statement of Changes in Beneficial Ownership.
Forms 3, 4, and 5 share the ownershipDocument XML schema; the actual
parser and the filing-obj registrations for all of them live in
edgar.forms.ownership. This namespace remains as the historical entry
point for Form 4.
Usage:
(require '[edgar.forms.form4]) ; side-effectful load registers the methods
(edgar.filing/filing-obj filing) ; => {:form "4" :reporting-owner {...}
; :transactions [...] :holdings [...]}Generic parser for SEC Section 16 ownership forms — Forms 3, 4, and 5 (plus their /A amendments). All three share the ownershipDocument XML schema:
Form 3 - initial statement: holdings only (nonDerivativeHolding / derivativeHolding), no transactions Form 4 - statement of changes: transactions (may also carry holdings) Form 5 - annual statement: transactions and holdings
Registers filing-obj methods for "3" "3/A" "4" "4/A" "5" "5/A".
Usage: (require '[edgar.forms.ownership]) ; side-effectful load registers methods (edgar.filing/filing-obj filing) ;=> {:form "3" :issuer {...} :reporting-owner {...} ; :transactions [...] :holdings [...]}
Generic parser for SEC Section 16 ownership forms — Forms 3, 4, and 5
(plus their /A amendments). All three share the ownershipDocument XML
schema:
Form 3 - initial statement: holdings only (nonDerivativeHolding /
derivativeHolding), no transactions
Form 4 - statement of changes: transactions (may also carry holdings)
Form 5 - annual statement: transactions and holdings
Registers filing-obj methods for "3" "3/A" "4" "4/A" "5" "5/A".
Usage:
(require '[edgar.forms.ownership]) ; side-effectful load registers methods
(edgar.filing/filing-obj filing)
;=> {:form "3" :issuer {...} :reporting-owner {...}
; :transactions [...] :holdings [...]}Schedule 13D / 13G parser — beneficial ownership reports of >5% positions.
Handles the structured-XML era (SEC mandated XML for Schedules 13D/G filed from December 2024; form types "SCHEDULE 13D" / "SCHEDULE 13G" and their /A amendments, with a primary_doc.xml). Legacy "SC 13D" / "SC 13G" filings are HTML/text-only and intentionally NOT registered — they fall through to filing-obj's :default raw-HTML result.
The 13D and 13G XML schemas differ in field naming (issuerCIK vs issuerCik, dateOfEvent vs eventDateRequiresFilingThisStatement, aggregateAmountOwned vs reportingPersonBeneficiallyOwnedAggregateNumber- OfShares, ...); this parser normalizes both into one result shape.
Usage: (require '[edgar.forms.schedule13]) ; side-effectful load registers methods (edgar.filing/filing-obj filing) ;=> {:form "SCHEDULE 13D" :schedule :13d ; :issuer {:cik ... :name ... :cusip ...} ; :reporting-persons [{:name ... :aggregate-owned ... :percent-of-class ...} ...]}
Schedule 13D / 13G parser — beneficial ownership reports of >5% positions.
Handles the structured-XML era (SEC mandated XML for Schedules 13D/G
filed from December 2024; form types "SCHEDULE 13D" / "SCHEDULE 13G"
and their /A amendments, with a primary_doc.xml). Legacy "SC 13D" /
"SC 13G" filings are HTML/text-only and intentionally NOT registered —
they fall through to filing-obj's :default raw-HTML result.
The 13D and 13G XML schemas differ in field naming (issuerCIK vs
issuerCik, dateOfEvent vs eventDateRequiresFilingThisStatement,
aggregateAmountOwned vs reportingPersonBeneficiallyOwnedAggregateNumber-
OfShares, ...); this parser normalizes both into one result shape.
Usage:
(require '[edgar.forms.schedule13]) ; side-effectful load registers methods
(edgar.filing/filing-obj filing)
;=> {:form "SCHEDULE 13D" :schedule :13d
; :issuer {:cik ... :name ... :cusip ...}
; :reporting-persons [{:name ... :aggregate-owned ... :percent-of-class ...} ...]}SEC Financial Statement Data Sets (DERA) access. https://www.sec.gov/dera/data/financial-statement-data-sets
Quarterly ZIPs containing four tab-delimited tables: sub — one row per submission: adsh, cik, name, sic, form, period, fy, fp, filed, and filer metadata num — one row per numeric fact: adsh, tag, version, ddate, qtrs, uom, segments, value pre — statement placement: adsh, stmt (BS/IS/CF/EQ/CI), report, line, tag — i.e. which statement each tag appeared on and in what order tag — tag metadata: version, custom flag, datatype, iord (instant/ duration), crdr (credit/debit), label, documentation
Why this matters for standardization: unlike the companyfacts API, these sets include company extension tags and statement placement — the two ingredients Compustat-style cross-company standardization needs most. One bulk download covers every filer for a quarter, instead of one HTTP call per company.
Two access styles:
Managed (what the :compustat statement view uses): enable the local quarter cache and let per-filing contexts stream what they need — (fsds/enable-cache!) ; or (e/enable-fsds!) (fsds/annual-filing-context "1018724" "2026-02-06") ;=> {:adsh ... :placement {tag #{"IS" ...}} :is-extension-values [...]}
Manual table access for ad-hoc analysis: (require '[edgar.fsds :as fsds]) (def zip (fsds/download-quarter! 2026 1 "/data/fsds")) (def sub (fsds/load-table zip :sub)) (def pre (fsds/load-table zip :pre)) (def num (fsds/load-table zip :num)) ; millions of rows — needs heap
;; Find a company's filing in the quarter: (def adsh (-> (fsds/find-submissions sub :cik "1018724" :form "10-K") (ds/column :adsh) first))
;; Income statement exactly as presented — extension tags included ;; (their :version is the filing's adsh instead of a us-gaap release): (fsds/presentation pre adsh :stmt "IS")
;; Which statement does each tag sit on? The Compustat-reclass guard ;; question: D&A on "CF" only means it is embedded in the income ;; statement expense lines (strip it); D&A presented on "IS" means ;; the expense lines already exclude it: (get (fsds/statement-placement pre adsh) "DepreciationDepletionAndAmortization") ;=> #{"CF"} for AMZN
;; Values for extension tags the companyfacts API cannot see (e.g. ;; AMZN's Fulfillment / Technology and infrastructure, which Compustat ;; folds into XSGA/XRD — FY2025 TechnologyAndInfrastructureExpense ;; equals Compustat XRD exactly): (fsds/facts-for num adsh :qtrs 4 :tag "FulfillmentExpense")
SEC Financial Statement Data Sets (DERA) access.
https://www.sec.gov/dera/data/financial-statement-data-sets
Quarterly ZIPs containing four tab-delimited tables:
sub — one row per submission: adsh, cik, name, sic, form, period,
fy, fp, filed, and filer metadata
num — one row per numeric fact: adsh, tag, version, ddate, qtrs,
uom, segments, value
pre — statement placement: adsh, stmt (BS/IS/CF/EQ/CI), report, line,
tag — i.e. which statement each tag appeared on and in what order
tag — tag metadata: version, custom flag, datatype, iord (instant/
duration), crdr (credit/debit), label, documentation
Why this matters for standardization: unlike the companyfacts API, these
sets include company extension tags and statement placement — the two
ingredients Compustat-style cross-company standardization needs most.
One bulk download covers every filer for a quarter, instead of one HTTP
call per company.
Two access styles:
1. Managed (what the :compustat statement view uses): enable the local
quarter cache and let per-filing contexts stream what they need —
(fsds/enable-cache!) ; or (e/enable-fsds!)
(fsds/annual-filing-context "1018724" "2026-02-06")
;=> {:adsh ... :placement {tag #{"IS" ...}} :is-extension-values [...]}
2. Manual table access for ad-hoc analysis:
(require '[edgar.fsds :as fsds])
(def zip (fsds/download-quarter! 2026 1 "/data/fsds"))
(def sub (fsds/load-table zip :sub))
(def pre (fsds/load-table zip :pre))
(def num (fsds/load-table zip :num)) ; millions of rows — needs heap
;; Find a company's filing in the quarter:
(def adsh (-> (fsds/find-submissions sub :cik "1018724" :form "10-K")
(ds/column :adsh) first))
;; Income statement exactly as presented — extension tags included
;; (their :version is the filing's adsh instead of a us-gaap release):
(fsds/presentation pre adsh :stmt "IS")
;; Which statement does each tag sit on? The Compustat-reclass guard
;; question: D&A on "CF" only means it is embedded in the income
;; statement expense lines (strip it); D&A presented on "IS" means
;; the expense lines already exclude it:
(get (fsds/statement-placement pre adsh)
"DepreciationDepletionAndAmortization") ;=> #{"CF"} for AMZN
;; Values for extension tags the companyfacts API cannot see (e.g.
;; AMZN's Fulfillment / Technology and infrastructure, which Compustat
;; folds into XSGA/XRD — FY2025 TechnologyAndInfrastructureExpense
;; equals Compustat XRD exactly):
(fsds/facts-for num adsh :qtrs 4 :tag "FulfillmentExpense")Reclassification rule engine (roadmap 4.1c).
Approximates commercial-database item definitions (currently Compustat) by reclassifying standardized line items: stripping D&A out of COGS, folding R&D into SG&A, adding special-item charges back to operating income, netting excise taxes from revenue, etc.
Rules are data, loaded from EDN under resources/edgar/reclass/ and applied per [unit start end] period group on top of the :standardized row set. Reclassified rows are ADDED alongside the original items (never replacing them) and carry provenance: :method :reclassified :rule the rule :id that produced the row :derived-from labels of the operand line items actually used
Rule format: {:id :cogs-ex-da ; unique keyword :target "COGS (Compustat)" ; emitted line-item label :formula [:- "Cost of Revenue" "D&A"] :guards [[:lt "D&A" "Cost of Revenue"]] :compustat "COGS" ; documentation only :notes "..."} ; documentation only
Formula operators: [:= a] - copy operand a [:+ a b ...] - sum [:- a b ...] - first minus the rest [:neg-sum a ..] - negated sum of the operands present with a non-zero value (nil when none are) Operands are line-item labels; wrap as [:opt label] to treat a missing operand as 0 instead of blocking the rule. Targets of earlier rules can be operands of later ones (applied iteratively, capped at 4 passes).
Guards (all must pass; operands referenced by label): [:lt a b] - value of a strictly less than value of b [:gt a b] - value of a strictly greater than value of b [:concept-not-in a #{c ...}] - a's winning XBRL concept is not in the set (rows without a concept, e.g. derived rows, pass) [:present a] - a line item named a exists in the period (marker rows, e.g. FSDS placement signals) [:absent a] - no line item named a exists in the period
Rules are tried in order; for a given :target the first rule whose operands and guards are satisfied wins (the target is skipped once present).
Reclassification rule engine (roadmap 4.1c).
Approximates commercial-database item definitions (currently Compustat)
by reclassifying standardized line items: stripping D&A out of COGS,
folding R&D into SG&A, adding special-item charges back to operating
income, netting excise taxes from revenue, etc.
Rules are data, loaded from EDN under resources/edgar/reclass/ and applied
per [unit start end] period group on top of the :standardized row set.
Reclassified rows are ADDED alongside the original items (never replacing
them) and carry provenance:
:method :reclassified
:rule the rule :id that produced the row
:derived-from labels of the operand line items actually used
Rule format:
{:id :cogs-ex-da ; unique keyword
:target "COGS (Compustat)" ; emitted line-item label
:formula [:- "Cost of Revenue" "D&A"]
:guards [[:lt "D&A" "Cost of Revenue"]]
:compustat "COGS" ; documentation only
:notes "..."} ; documentation only
Formula operators:
[:= a] - copy operand a
[:+ a b ...] - sum
[:- a b ...] - first minus the rest
[:neg-sum a ..] - negated sum of the operands present with a non-zero
value (nil when none are)
Operands are line-item labels; wrap as [:opt label] to treat a missing
operand as 0 instead of blocking the rule. Targets of earlier rules can be
operands of later ones (applied iteratively, capped at 4 passes).
Guards (all must pass; operands referenced by label):
[:lt a b] - value of a strictly less than value of b
[:gt a b] - value of a strictly greater than value of b
[:concept-not-in a #{c ...}] - a's winning XBRL concept is not in the set
(rows without a concept, e.g. derived rows,
pass)
[:present a] - a line item named a exists in the period
(marker rows, e.g. FSDS placement signals)
[:absent a] - no line item named a exists in the period
Rules are tried in order; for a given :target the first rule whose operands
and guards are satisfied wins (the target is skipped once present).Malli schemas and validation helpers for the edgarjure public API.
Usage (in edgar.api): (schema/validate! ::filings-args {:ticker-or-cik toc :form form ...})
All public validate! calls throw ex-info with :type ::invalid-args on failure. The error message includes the human-readable Malli explanation.
Malli schemas and validation helpers for the edgarjure public API.
Usage (in edgar.api):
(schema/validate! ::filings-args {:ticker-or-cik toc :form form ...})
All public validate! calls throw ex-info with :type ::invalid-args on failure.
The error message includes the human-readable Malli explanation.HTML table extraction from SEC filings.
Extracts <table> elements from filing HTML and converts them to tech.ml.dataset objects. Handles iXBRL inline-tagged content, colspan expansion, and mixed numeric/string columns.
Usage: (require '[edgar.tables :as tables]) (tables/extract-tables filing) ; => seq of datasets (tables/extract-tables filing :nth 0) ; => first table as dataset (tables/extract-tables filing :min-rows 5) ; => only tables with >=5 data rows
HTML table extraction from SEC filings. Extracts <table> elements from filing HTML and converts them to tech.ml.dataset objects. Handles iXBRL inline-tagged content, colspan expansion, and mixed numeric/string columns. Usage: (require '[edgar.tables :as tables]) (tables/extract-tables filing) ; => seq of datasets (tables/extract-tables filing :nth 0) ; => first table as dataset (tables/extract-tables filing :min-rows 5) ; => only tables with >=5 data rows
Validation harness — quantify how close edgarjure's statement output is to a benchmark (Compustat/Capital IQ extract, hand-collected figures, academic reconciliation datasets).
Usage: (require '[edgar.validation :as validation]) (validation/compare-to-benchmark "AAPL" [{:line-item "Revenue" :end "2023-09-30" :val 383285000000} {:line-item "Net Income" :end "2023-09-30" :val 96995000000}]) ;=> {:match-rate 1.0 :matched [...] :mismatched [] :missing []}
Track :match-rate over time as concept files and identities improve.
Validation harness — quantify how close edgarjure's statement output is to
a benchmark (Compustat/Capital IQ extract, hand-collected figures, academic
reconciliation datasets).
Usage:
(require '[edgar.validation :as validation])
(validation/compare-to-benchmark "AAPL"
[{:line-item "Revenue" :end "2023-09-30" :val 383285000000}
{:line-item "Net Income" :end "2023-09-30" :val 96995000000}])
;=> {:match-rate 1.0 :matched [...] :mismatched [] :missing []}
Track :match-rate over time as concept files and identities improve.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |