Database Schema Complete

30+ tables, 50+ indexes, 9 migrations

This is the complete catalog of every table, index, constraint, and relationship in the EnrichNode database.

Core Pipeline Tables

companies

Primary enriched data store.

ColumnTypeConstraints
idSERIALPRIMARY KEY
orgNrTEXTUNIQUE NOT NULL
nameTEXT
addressTEXT
sniTEXT[]
enriched_dataJSONBStores full EnrichmentResult including contacts (PII)
processed_atTIMESTAMPTZ
sourceTEXTDEFAULT ‘pipeline’
domainTEXT(added migration 002)
lead_scoreNUMERIC(added migration 002)
contacts_countINTEGER(added migration 002)
has_vd_contactBOOLEAN(added migration 002)
last_enriched_atTIMESTAMPTZ(added migration 002)
enrichment_tierTEXTCHECK: active/stable/dormant (migration 003)
next_enrichment_afterTIMESTAMPTZ(migration 003)
data_fingerprintTEXT(migration 003)
enrichment_run_idTEXT(migration 003)
tier_assigned_atTIMESTAMPTZ(migration 003)
turnover_sekBIGINT(migration 005)
employee_countINTEGER(migration 005)
equity_sekBIGINT(migration 005)
bv_financial_yearINTEGER(migration 005)
bv_financial_fetched_atTIMESTAMPTZ(migration 005)
embeddingVECTOR(1536)(migration 007)

Indexes:

  • idx_companies_enriched_gin — GIN on enriched_data
  • idx_companies_needs_refresh — B-tree on processed_at NULLS FIRST
  • idx_companies_lead_score — B-tree on lead_score DESC NULLS LAST
  • idx_companies_has_vd — B-tree on has_vd_contact
  • idx_companies_domain — B-tree on domain
  • idx_companies_last_enriched — B-tree on last_enriched_at DESC NULLS LAST
  • idx_companies_tier — B-tree on (enrichment_tier, next_enrichment_after NULLS FIRST)
  • idx_companies_next_enrich — B-tree on next_enrichment_after NULLS FIRST WHERE NOT NULL
  • idx_companies_fingerprint — B-tree on data_fingerprint
  • idx_companies_embedding — IVFFlat on embedding vector_cosine_ops (lists=100)

leads

Sales-ready leads with validation layers.

ColumnTypeConstraints
idUUIDPRIMARY KEY
org_nrTEXTUNIQUE
nameTEXT
legal_formTEXT
addressTEXT
sni_codeTEXT
sni_secondaryTEXT[]
business_descTEXT
employeesINTEGER
turnoverBIGINT
is_activeBOOLEAN
is_validatedBOOLEAN
confidence_scoreNUMERIC(3,2)
validated_atTIMESTAMPTZ
layer_registryBOOLEAN
layer_websiteBOOLEAN
layer_profilesBOOLEAN
layer_newsBOOLEAN
websiteTEXT
linkedin_urlTEXT
emailTEXT
phoneTEXT
sourceTEXT
imported_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

Bulk Import Tables

bolagsverket_companies

~2.9M records (651K active AB post-archive).

ColumnType
idSERIAL PK
org_nrTEXT UNIQUE
nameTEXT
legal_formTEXT
registration_dateDATE
deregistration_dateDATE
deregistration_reasonTEXT
business_descriptionTEXT
addressTEXT
postal_codeTEXT
postal_cityTEXT
countryTEXT
is_activeBOOLEAN DEFAULT true
data_sourceTEXT
imported_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

scb_foundations

~1.8M records (post-archive: only AB).

ColumnType
idSERIAL PK
org_nrTEXT UNIQUE
nameTEXT
addressTEXT
postal_codeTEXT
postal_cityTEXT
co_addressTEXT
legal_formINTEGER
statusINTEGER
entity_statusINTEGER
registration_dateDATE
sni_codesTEXT[]
sni_primaryTEXT
advertising_blockBOOLEAN
data_sourceTEXT
imported_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

Compliance Tables

OptOut_Hashes

SHA-256 hashed contacts for opt-out.

CREATE TABLE "OptOut_Hashes" (
    hashed_contact TEXT PRIMARY KEY
);

RoPA_Log

Immutable audit trail (Article 30).

CREATE TABLE "RoPA_Log" (
    id          SERIAL PRIMARY KEY,
    timestamp   TIMESTAMPTZ DEFAULT NOW(),
    "tableName" TEXT NOT NULL,
    operation   TEXT NOT NULL,
    "recordId"  TEXT,
    description TEXT
);

Migration 002 adds: month_bucket column for efficient querying.

Article14_Notifications

GDPR Art. 14 notification log.

CREATE TABLE "Article14_Notifications" (
    id              SERIAL PRIMARY KEY,
    email           TEXT NOT NULL,
    company_org_nr  TEXT,
    sent_at         TIMESTAMPTZ DEFAULT NOW(),
    method          TEXT DEFAULT 'email',
    status          TEXT DEFAULT 'sent',  -- sent | failed | skipped
    error_message   TEXT,
    UNIQUE (email, company_org_nr)
);

Quality & Error Tracking

enrichment_errors

ColumnType
idSERIAL PK
org_nrTEXT
timestampTIMESTAMPTZ
error_typeTEXT
error_messageTEXT
stack_traceTEXT
retry_countINTEGER
resolvedBOOLEAN
metadataJSONB

enrichment_issues

ColumnTypeConstraints
idSERIAL PK
org_nrTEXT
issue_typeTEXT
severityTEXTCHECK: low/medium/high/critical
descriptionTEXT
detected_atTIMESTAMPTZ
statusTEXTCHECK: open/in_progress/resolved/flagged_manual
resolutionTEXT
resolved_atTIMESTAMPTZ
auto_remediation_attemptsINTEGER
last_auto_attempt_atTIMESTAMPTZ

remediation_actions

ColumnType
idSERIAL PK
org_nrTEXT
action_typeTEXT
issue_idINTEGER FK → enrichment_issues (ON DELETE SET NULL)
successBOOLEAN
detailsJSONB
executed_atTIMESTAMPTZ

Scale Infrastructure

enrichment_queue_state

ColumnType
idSERIAL PK
batch_idTEXT UNIQUE
dispatched_atTIMESTAMPTZ
batch_sizeINTEGER
statusTEXT
completed_atTIMESTAMPTZ
orgs_enrichedINTEGER
orgs_failedINTEGER

enrichment_run_log

ColumnTypeConstraints
idSERIAL PK
run_idTEXT UNIQUE
run_typeTEXTCHECK: canary/quarterly/manual/delta
quarterTEXT
started_atTIMESTAMPTZ
completed_atTIMESTAMPTZ
statusTEXTCHECK: running/completed/failed/aborted
companies_targetedINTEGER
companies_dispatchedINTEGER
companies_completedINTEGER
companies_errorINTEGER
companies_blockedINTEGER
companies_skippedINTEGER
p50_latency_msINTEGER
p95_latency_msINTEGER
cost_estimate_usdNUMERIC
serper_callsINTEGER
hunter_callsINTEGER
playwright_callsINTEGER
error_rate_pctNUMERIC
notesTEXT

enrichment_history

ColumnType
idSERIAL PK
org_nrTEXT
run_idTEXT FK → enrichment_run_log
enriched_atTIMESTAMPTZ
duration_msINTEGER
statusTEXT
lead_score_thenNUMERIC
contacts_thenINTEGER
fingerprintTEXT
api_calls_jsonJSONB

api_circuit_state

ColumnTypeConstraints
api_nameTEXT PK
stateTEXTCHECK: closed/open/half-open
failure_countINTEGER
last_failure_atTIMESTAMPTZ
opened_atTIMESTAMPTZ
reset_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

Archive Tables (Migration 009)

TablePurpose
bolagsverket_companies_archiveArchived inactive/non-AB BV companies
scb_foundations_archiveArchived inactive/non-AB SCB companies
archive_audit_logArchive operation audit trail

Multi-Tenancy

organizations

ColumnType
idUUID PK
nameTEXT
created_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

users

ColumnType
idUUID PK
organization_idUUID FK → organizations
emailTEXT UNIQUE
password_hashTEXT
created_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

projects

ColumnType
idUUID PK
organization_idUUID FK → organizations
nameTEXT
created_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

documents

ColumnType
idUUID PK
project_idUUID FK → projects
nameTEXT
contentTEXT
embeddingVECTOR(1536)
created_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

Contact Mapping (contact-mapping-schema.sql)

swedish_company_officials

ColumnTypeConstraints
idSERIAL PK
org_nrTEXT
person_nameTEXT
role_typeTEXT
role_titleTEXT
sourceTEXT
valid_fromDATE
valid_toDATE
UNIQUE(org_nr, person_name, role_type)

contact_discovery_log

ColumnTypeConstraints
idSERIAL PK
org_nrTEXT
person_nameTEXT
discovery_sourceTEXT
contact_typeTEXT
contact_valueTEXT
confidence_scoreINTEGERCHECK: 0-100
verifiedBOOLEAN
discovered_atTIMESTAMPTZ
UNIQUE(org_nr, person_name, discovery_source, contact_type)

domain_email_patterns

ColumnTypeConstraints
domainTEXT PK
detected_patternTEXT
sample_sizeINTEGER
confidence_scoreINTEGERCHECK: 0-100
first_seen_atTIMESTAMPTZ
last_verified_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

person_phone_mappings

ColumnTypeConstraints
idSERIAL PK
person_nameTEXT
org_nrTEXT
phone_numberTEXT
phone_typeTEXTCHECK: mobile/landline/company_main/unknown
sourceTEXT
confidence_scoreINTEGER
is_primaryBOOLEAN
UNIQUE(org_nr, person_name, phone_number)

mapped_contacts

ColumnTypeConstraints
idSERIAL PK
org_nrTEXT FK → companies(“orgNr”)
person_nameTEXT
first_nameTEXT
last_nameTEXT
position_titleTEXT
position_role_typeTEXTCHECK: VD/CFO/CMO/CTO/Försäljningschef/Marknadschef/Styrelseordförande/Styrelseledamot/Medarbetare/Övrig
position_seniorityTEXTCHECK: C-Level/VP/Director/Manager/Staff
emailTEXT
phoneTEXT
linkedin_urlTEXT
data_sourcesTEXT[]
enrichment_confidenceNUMERIC
UNIQUE(org_nr, person_name)

eniro_cache

ColumnTypeConstraints
idSERIAL PK
search_queryTEXT
search_typeTEXTCHECK: company/person
resultsJSONB
result_countINTEGER
cached_atTIMESTAMPTZ
expires_atTIMESTAMPTZ
UNIQUE(search_query, search_type)

Webhooks

webhooks

ColumnType
idUUID PK
urlTEXT
eventswebhook_event[]
secretTEXT
activeBOOLEAN
created_atTIMESTAMPTZ
last_triggeredTIMESTAMPTZ
failure_countINTEGER

webhook_deliveries

ColumnTypeConstraints
idUUID PK
webhook_idUUID FK → webhooks (ON DELETE CASCADE)
eventTEXT
payloadJSONB
statusTEXTCHECK: pending/delivered/failed
response_statusINTEGER
response_bodyTEXT
error_messageTEXT
retry_countINTEGER
created_atTIMESTAMPTZ
delivered_atTIMESTAMPTZ

Merge & Migration

merged_companies

Smart merge of SCB + Bolagsverket data.

schema_migrations

ColumnType
versionTEXT PK
nameTEXT
applied_atTIMESTAMPTZ DEFAULT NOW()

domain_registry

ColumnType
domainTEXT PK
normalized_nameTEXT
loaded_atTIMESTAMPTZ

prv_trademarks

ColumnType
idSERIAL PK
org_nrTEXT
trademark_nameTEXT
statusTEXT
registration_noTEXT
registration_dateDATE
nice_classesINTEGER[]
imported_atTIMESTAMPTZ

Migration History

VersionFileWhat Was Added
000000_schema_migrations.sqlMigration tracking table
001001_add_quality_tracking.sqlenrichment_issues, remediation_actions, quality fields
002002_enrichment_scale.sqlcompanies.domain, lead_score, enrichment_queue_state, 6 indexes
003003_quarterly_scale.sqlenrichment_tier, enrichment_run_log, enrichment_history, 3 indexes
004004_circuit_state.sqlapi_circuit_state table
005005_financial_fields.sqlturnover_sek, employee_count, equity_sek
006006_domain_registry.sqlpg_trgm, domain_registry table
007007_pgvector_company_embeddings.sqlembedding VECTOR(1536), IVFFlat index
008008_prv_trademarks.sqlprv_trademarks table
009009_add_archive_tables.sqlArchive tables for inactive companies