This section is the human-readable schema review area for pyvalue.
Use it in this order:
- Start with Table Inventory for a fast scan of every table, its key, and its review priority.
- Open the relevant file under tables/ to inspect columns, primary keys, foreign keys, unique constraints, secondary indexes, and first-five sample rows.
- Use Indexes and Query Hotspots to judge whether the schema matches the real pipeline access patterns.
- Fall back to schema.snapshot.sql when you need the exact live DDL from
data/pyvalue.db.
Snapshot caveat:
- The documented schema target is version
66. The livedata/pyvalue.dbcarries a knownfundamentals_rawpreservation discrepancy from a pre-043migration (75,848current rows vs.77,045in the pre-migration backup). - Treat the
fundamentals_rawcounts and first-five samples in this section as documentation of the current live file, not as proof that the migration preserved every raw payload.
Important structural notes:
- Referential integrity is enforced at the database level, not in application code. Migrations 041, 043, and 046–050 added the previously-missing physical FKs on
metrics,metric_compute_status,financial_facts,financial_facts_refresh_state,market_data, and the FX state tables, on top of the FKs the catalog layer (provider,exchange,provider_exchange,issuer,listing,provider_listing) already carried. Migrations 081/083 added the provider-layer observation tablesprovider_market_data(FK toprovider_listing) andprovider_fx_rates(FK toprovider). - Domain invariants are enforced by CHECK constraints. Migration 041 added
metrics.unit_kind+ currency pairing, migrations 055–059 added enum/format/non-empty CHECKs acrossmetric_compute_status.status,*_fetch_state.last_status, currency-code formats, listing symbol formats,financial_facts.unitnon-empty, and the boolean INTEGER columns (is_alias,is_refreshable,full_history_backfilled), and migration 061 added the row-levelmarket_data_fetch_stateerror-row invariant. (The 058-erafx_rates.source_kindCHECK is gone with the column: it allowed a single value and carried no information — migration 083.) - Market data and FX rates follow the provider/canonical split (migrations 081–084, mirroring
provider_exchange/exchangeandprovider_listing/listing): ingestion dual-writesprovider_market_data→market_dataandprovider_fx_rates→fx_ratesin one transaction, downstream readers consume only the provider-free canonical tables, and refresh purges touch only the provider layer. issuer (name, country)is now UNIQUE (migration 060) after a one-time dedup that collapsed ~4,696 duplicate groups (~13,121 rows) and remapped ~8,425 listings to canonical issuers. Rows with NULL name or NULL country remain non-colliding because SQLite UNIQUE indexes treat NULLs as distinct.listingis the canonical identity root for downstream facts, market data, metrics, and listing status.provider_listingis the operational root for provider-scoped ingestion and market-data workflows. Migration 054 dropped the denormalisedprovider_listing.provider_id; the owning provider is reachable viaprovider_exchange.provider_idthroughprovider_exchange_id.fx_rates.rateisREALunder the project REAL-everywhere policy (migration 045 converted the legacy TEXTrate_textcolumn).fundamentals_raw,metrics, andmetric_compute_statuseach store the latest row per logical key, not a full history.- Migrations are the single source of truth for schema (tables, indexes, and views). Migration 042 added
provider_listing_catalogandsupported_tickers, migration 044 added thesecurities,providers, andexchange_providercompat views, migration 062 addedprimary_provider_listing_catalog. Runtime code in thepersistence/storage/package does not issueCREATE TABLE/CREATE VIEWoutside the migration framework. schema_migrationsis single-row by construction (migration 063): the column shape is(id INTEGER PRIMARY KEY CHECK (id = 1), version INTEGER NOT NULL)so stray or duplicate version rows are impossible.
Table groups:
- Identity and catalog
- Raw ingestion and state
- Canonical analytics
- FX
- Housekeeping
Views (all persisted in the schema and owned by migrations, never by runtime code):
provider_listing_catalog— joinsprovider_listingtoprovider,provider_exchange,listing,issuer, andexchangeto expose the canonical provider-scoped catalog used by ingestion, screening, and FX paths. Owned by migration 042.supported_tickers— projection ofprovider_listing_catalogretained for compatibility with code paths that read the historical name. Owned by migration 042.primary_provider_listing_catalog—provider_listing_catalogfiltered tolisting.primary_listing_status <> 'secondary'. Replaces the inline_primary_listing_predicate()filter that previously appeared at 11+ query sites instorage.py. Owned by migration 062.securities,providers,exchange_provider— backwards-compatibility views over the canonical identity tables. Owned by migration 044.
Supporting review pages:
Sample-row refresh notes:
- Sample rows are deterministic snapshots: first 5 rows ordered by primary key columns where available,
version ASCforschema_migrations, androwid ASConly as a fallback. - Wide sample rows keep payload-sized fields readable by omitting the full payload and recording size metadata instead.
- Use
python scripts/generate_database_review_docs.py --sample-rows-onlyto refresh sample rows without recomputing live table stats.