Skip to main content
The warehouse in three lists: what we ingest, what we don’t, what we hold but don’t serve, then how it is served. Coverage figures are as of 2026-08-31. Full audit: 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 one ingest_* method.

Other gaps

Known defects in what is ingested

Both are written up with measurements in the repository inventory.
  • cvm_fi_cda keeps one bond in five and calls it the class total. BLC_1 is one row per security, but the shipped key carries none of CD_SELIC / CD_ISIN / DT_VENC, so 80.4% of rows are lost on upsert. The fix is additive: a cvm_fi_cda_titpub table keyed on the security, leaving cvm_fi_cda as the class roll-up the dashboards read.
  • cia_event cannot 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 (a row_hash for 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 against 19_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 over b3_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; adjusted is false on every row. Exceptions: quote_history’s close_adj, continuous across splits and refused where unproven, and the selectable close_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 for fund_holdings or fund_debentures yet; 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.