docs/reference/DATA_INVENTORY.md. Live gap register: docs/reference/DATABASE_MAINTENANCE.md §11.
1. What we ingest
Sources are CVM, B3, BACEN, IBGE and ANBIMA, all public except the ETF market feed.CVM funds (dados.cvm.gov.br)
CVM listed companies (CIA Aberta)
B3 and global series
BACEN, IBGE, ANBIMA
Plus
cvm_ingest_log: one row per (entity, doc_type, period) attempt, written exactly once by every ingest.
2. What we could ingest and don’t
Each line is a decision, not an oversight; measurements are in the repository inventory §2.CVM CDA blocks
The monthly CDA archive holds eight blocks; we read four. Adding one costs a field map, a migration and oneingest_* method.
Other gaps
Known defects in what is ingested
Both are written up with measurements in the repository inventory.cvm_fi_cdakeeps one bond in five and calls it the class total. BLC_1 is one row per security, but the shipped key carries none ofCD_SELIC/CD_ISIN/DT_VENC, so 80.4% of rows are lost on upsert. The fix is additive: acvm_fi_cda_titpubtable keyed on the security, leavingcvm_fi_cdaas the class roll-up the dashboards read.cia_eventcannot key an IPE filing with no protocol, and before 2015 none have one. The natural key is(protocolo, versao), so those rows are dropped, not given a synthesized key. 2010–2014 cannot be loaded; 2015 silently loses 12% of its filings. The fix is a second key era (arow_hashfor protocol-less rows), which changes the grain and waits for the owner’s call.
3. What we ingest and don’t serve
The warehouse is wider than the API. Checked against19_api_contract.sql on 2026-09-15. “Indirect” means the table feeds dim_fund / fact_fund_monthly and reaches funds, fund_nav and panel without an endpoint of its own.
Served
Each endpoint’s page has the detail; this maps table to endpoint.Held and not served
Real gaps a caller could want; none has an endpoint.Not served by design
4. How it is served
Five objects do the work; everything else is a typed view overb3_cotahist.
panel is the primitive: correlation, ranking and spreads are reductions of it, done in the client.
Notes
- FIP reads as annual and is not: a fund files quarterly or three times a year, one row per share class each time. Until 2026-08-31 the key discarded 72–77% of every published file on upsert. The key is now
(cnpj, doc_type, period, classe_cota, row_hash). - No fabricated observations. A missing month is absent, never interpolated or carried forward.
as_of≠complete_through. The first is the newest row held, the second the last period believed complete.coverage()returns both.- Prices are unadjusted. A split reads as a real ~50% jump;
adjustedisfalseon every row. Exceptions:quote_history’sclose_adj, continuous across splits and refused where unproven, and the selectableclose_total_return, NULL with a reason where unproven. - 1,000 rows per response, for everyone. Signing in raises ids, page sizes and the query budget, never this.
coverage()has no row forfund_holdingsorfund_debenturesyet; take freshness from the rows (ORDER BY period DESC LIMIT 1).- Keeping this honest: a new dataset is a row in §1; a decision against one is a row in §2 with the reason; ingested but unserved belongs in §3. A table in none of the three is what this page exists to prevent.