Migrated PostgreSQL schema
This snapshot is introspected from a disposable PostgreSQL 16 database after alembic upgrade head. All 24 application tables are shown in four domain diagrams. Relationship lines stay within a domain; the column listings retain cross-domain foreign keys.
Download the schema snapshot. Run pnpm docs:build --schema after migrations. The integration suite compares it with a fresh migrated database.
Draft tables support deferred accounts, documents and legacy content pages; their presence in the schema does not imply a shipped feature. PII collection is limited to leads and lead consent records.
admin_audit_log
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
actor | VARCHAR(255) | No | | |
action | VARCHAR(100) | No | | |
entity_type | VARCHAR(100) | No | | |
entity_id | VARCHAR(255) | No | | |
payload | JSONB | Yes | | |
created_at | TIMESTAMP | No | now() |
articles
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
slug | VARCHAR(160) | No | | |
vertical | VARCHAR(19) | No | | |
title | VARCHAR(255) | No | | |
summary | TEXT | No | | |
image_url | VARCHAR(2048) | Yes | | |
author | VARCHAR(120) | No | | |
published_at | TIMESTAMP | No | | |
updated_at | TIMESTAMP | No | now() | |
tags | ARRAY | No | ARRAY[]::article_tag[] | |
related_product_ids | ARRAY | No | ARRAY[]::uuid[] | |
status | VARCHAR(20) | No | 'draft'::character varying | |
body_md | TEXT | Yes | | |
excerpt | TEXT | Yes | |
consent_records Draft
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
user_id | UUID | No | | user_profiles(id) · RESTRICT |
consent_type | VARCHAR(16) | No | | |
policy_version | VARCHAR(20) | No | | |
granted | BOOLEAN | No | | |
recorded_at | TIMESTAMP | No | now() | |
ip_address_hash | VARCHAR(64) | No | |
content_pages Draft
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
slug | VARCHAR(255) | No | | |
vertical | VARCHAR(19) | Yes | | |
page_type | VARCHAR(8) | No | | |
title | VARCHAR(255) | No | | |
meta_description | TEXT | Yes | | |
body_html | TEXT | Yes | | |
structured_data | JSONB | Yes | | |
published_at | TIMESTAMP | Yes | | |
updated_at | TIMESTAMP | No | now() | |
author | VARCHAR(255) | Yes | |
credit_cards
Primary key: product_id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
product_id | UUID | No | | financial_products(id) · CASCADE |
annual_fee_crc | NUMERIC(12, 2) | No | 0 | |
annual_fee_waivable | BOOLEAN | No | false | |
interest_rate_annual_pct | NUMERIC(6, 4) | No | | |
network | VARCHAR(10) | No | | |
reward_type | VARCHAR(8) | No | 'none'::reward_type | |
cashback_rate_pct | NUMERIC(6, 4) | Yes | | |
min_income_crc | NUMERIC(14, 2) | Yes | | |
benefits_summary | TEXT | Yes | | |
currency | VARCHAR(5) | No | 'CRC'::card_currency | |
has_travel_benefits | BOOLEAN | No | false |
cta_engagement_events
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
session_id | VARCHAR(64) | No | | |
product_id | UUID | No | | financial_products(id) · CASCADE |
vertical | VARCHAR(19) | No | | |
source_surface | VARCHAR(11) | No | | |
event_type | VARCHAR(16) | No | | |
metadata | JSONB | Yes | | |
created_at | TIMESTAMP | No | now() |
document_versions Draft
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
document_id | UUID | No | | static_documents(id) · CASCADE |
version_number | INTEGER | No | | |
storage_path | VARCHAR(1024) | No | | |
superseded_at | TIMESTAMP | No | now() | |
replaced_by | UUID | Yes | | static_documents(id) · SET NULL |
editorial_reviews
Primary key: product_id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
product_id | UUID | No | | financial_products(id) · CASCADE |
pros | ARRAY | No | ARRAY[]::text[] | |
cons | ARRAY | No | ARRAY[]::text[] | |
best_use_case | TEXT | Yes | | |
review_body_md | TEXT | Yes | | |
author | VARCHAR(120) | Yes | | |
published_at | TIMESTAMP | Yes | | |
updated_at | TIMESTAMP | No | now() | |
status | VARCHAR(20) | No | 'draft'::character varying |
financial_products
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
vertical | VARCHAR(19) | No | | |
product_name | VARCHAR(255) | No | | |
issuer_id | VARCHAR(100) | No | | issuers(id) · RESTRICT |
status | VARCHAR(8) | No | 'draft'::product_status | |
editorial_score | SMALLINT | No | 0 | |
editorial_summary | TEXT | Yes | | |
updated_at | TIMESTAMP | No | now() | |
source_url | VARCHAR(2048) | Yes | |
guide_clusters
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | VARCHAR(21) | No | | |
title | VARCHAR(120) | No | | |
description | TEXT | No | | |
order_index | SMALLINT | No | |
guides
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
slug | VARCHAR(160) | No | | |
cluster_id | VARCHAR(21) | No | | guide_clusters(id) · RESTRICT |
title | VARCHAR(255) | No | | |
summary | TEXT | No | | |
body_md | TEXT | No | | |
author | VARCHAR(120) | No | | |
updated_at | TIMESTAMP | No | now() | |
order_in_cluster | SMALLINT | No | 0 | |
related_guide_slugs | ARRAY | No | (ARRAY[]::character varying[])::character varying(160)[] | |
related_article_slugs | ARRAY | No | (ARRAY[]::character varying[])::character varying(160)[] | |
related_product_ids | ARRAY | No | ARRAY[]::uuid[] | |
status | VARCHAR(20) | No | 'draft'::character varying | |
published_at | TIMESTAMP | Yes | |
home_loans
Primary key: product_id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
product_id | UUID | No | | financial_products(id) · CASCADE |
rate_type | VARCHAR(8) | No | | |
interest_rate_annual_pct | NUMERIC(8, 4) | No | | |
max_ltv_pct | NUMERIC(6, 4) | No | | |
max_term_years | SMALLINT | No | | |
currency | VARCHAR(3) | No | | |
min_amount | NUMERIC(18, 2) | Yes | | |
max_amount | NUMERIC(18, 2) | Yes | | |
eligibility_notes | TEXT | Yes | |
issuers
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | VARCHAR(100) | No | | |
name | VARCHAR(255) | No | | |
short_name | VARCHAR(50) | No | | |
vertical | VARCHAR(19) | Yes | | |
logo_url | VARCHAR(2048) | Yes | | |
website_url | VARCHAR(2048) | Yes | | |
is_active | BOOLEAN | No | true |
journey_steps
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
journey_id | UUID | No | | learning_journeys(id) · CASCADE |
guide_id | UUID | No | | guides(id) · RESTRICT |
step_title | VARCHAR(255) | No | | |
position | SMALLINT | No | |
lead_consent_records
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
lead_id | UUID | No | | leads(id) · CASCADE |
policy_version | VARCHAR(20) | No | | |
granted_at | TIMESTAMP | No | now() | |
ip_hash | VARCHAR(64) | No | |
lead_deletion_requests
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
email | VARCHAR(320) | No | | |
requested_at | TIMESTAMP | No | now() | |
status | VARCHAR(9) | No | 'pending'::deletion_request_status | |
processed_at | TIMESTAMP | Yes | | |
processing_notes | TEXT | Yes | |
leads
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
name | VARCHAR(200) | Yes | | |
email | VARCHAR(320) | Yes | | |
vertical | VARCHAR(19) | No | | |
product_id | UUID | No | | financial_products(id) · RESTRICT |
product_name_snapshot | VARCHAR(255) | No | | |
source_surface | VARCHAR(11) | No | | |
session_id | VARCHAR(64) | No | | |
status | VARCHAR(9) | No | 'new'::lead_status | |
notes | TEXT | Yes | | |
created_at | TIMESTAMP | No | now() | |
contacted_at | TIMESTAMP | Yes | | |
is_anonymized | BOOLEAN | No | false |
learning_journeys
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
slug | VARCHAR(160) | No | | |
title | VARCHAR(255) | No | | |
description | TEXT | No | | |
guide_slugs | ARRAY | No | | |
end_cta_kind | VARCHAR(40) | No | | |
end_cta_label | VARCHAR(120) | No | |
offers
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
product_id | UUID | No | | financial_products(id) · CASCADE |
headline | VARCHAR(160) | No | | |
terms_snippet | TEXT | Yes | | |
bonus_crc | NUMERIC(12, 2) | Yes | | |
intro_apr_pct | NUMERIC(6, 4) | Yes | | |
intro_period_months | SMALLINT | Yes | | |
active_from | TIMESTAMP | Yes | | |
active_until | TIMESTAMP | Yes | | |
created_at | TIMESTAMP | No | now() |
pension_funds
Primary key: product_id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
product_id | UUID | No | | financial_products(id) · CASCADE |
fund_type | VARCHAR(3) | No | | |
operadora | VARCHAR(255) | No | | |
return_1yr_pct | NUMERIC(8, 4) | Yes | | |
return_3yr_pct | NUMERIC(8, 4) | Yes | | |
return_5yr_pct | NUMERIC(8, 4) | Yes | | |
management_fee_pct | NUMERIC(6, 4) | No | | |
risk_profile | TEXT | Yes | | |
supen_rating | VARCHAR(10) | Yes | | |
prospectus_date | DATE | Yes | |
saved_comparisons Draft
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
user_id | UUID | No | | user_profiles(id) · CASCADE |
vertical | VARCHAR(19) | No | | |
product_ids | ARRAY | No | | |
label | VARCHAR(120) | Yes | | |
created_at | TIMESTAMP | No | now() |
static_documents Draft
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
vertical | VARCHAR(19) | Yes | | |
issuer_id | VARCHAR(100) | No | | issuers(id) · RESTRICT |
document_type | VARCHAR(12) | No | | |
title | VARCHAR(255) | No | | |
storage_path | VARCHAR(1024) | No | | |
storage_backend | VARCHAR(6) | No | 'volume'::storage_backend | |
access_level | VARCHAR(10) | No | 'public'::document_access | |
effective_date | DATE | Yes | | |
is_current_version | BOOLEAN | No | true | |
uploaded_at | TIMESTAMP | No | now() | |
uploaded_by | UUID | Yes | | user_profiles(id) · SET NULL |
user_alerts Draft
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
user_id | UUID | No | | user_profiles(id) · CASCADE |
product_id | UUID | No | | financial_products(id) · CASCADE |
alert_type | VARCHAR(11) | No | | |
threshold_value | NUMERIC(10, 4) | Yes | | |
is_active | BOOLEAN | No | true | |
last_triggered_at | TIMESTAMP | Yes | | |
created_at | TIMESTAMP | No | now() |
user_profiles Draft
Primary key: id
| Column | Type | Nullable | Default | Foreign key |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | |
clerk_user_id | VARCHAR(255) | No | | |
email | VARCHAR(320) | No | | |
display_name | VARCHAR(255) | Yes | | |
role | VARCHAR(15) | No | 'registered_user'::user_role | |
preferred_currency | VARCHAR(3) | No | 'CRC'::character varying | |
preferred_verticals | JSONB | No | '[]'::jsonb | |
default_sort_credit_cards | VARCHAR(50) | Yes | | |
default_sort_pension_funds | VARCHAR(50) | Yes | | |
default_sort_home_loans | VARCHAR(50) | Yes | | |
notification_frequency | VARCHAR(20) | Yes | 'immediate'::character varying | |
created_at | TIMESTAMP | No | now() | |
deleted_at | TIMESTAMP | Yes | | |
is_anonymized | BOOLEAN | No | false |