329 lines
22 KiB
SQL
329 lines
22 KiB
SQL
PRAGMA foreign_keys = ON;
|
|
|
|
CREATE TABLE IF NOT EXISTS organizations (
|
|
id TEXT PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id), email TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL, password_salt TEXT NOT NULL, role TEXT NOT NULL CHECK(role IN ('viewer','owner','admin','researcher')),
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_users_org ON users(organization_id);
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token_hash);
|
|
CREATE TABLE IF NOT EXISTS audit_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT REFERENCES organizations(id), user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
action TEXT NOT NULL, details TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_org ON audit_log(organization_id,created_at);
|
|
CREATE TABLE IF NOT EXISTS businesses (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL, name TEXT NOT NULL, website TEXT NOT NULL DEFAULT '', website_domain TEXT NOT NULL DEFAULT '',
|
|
email TEXT NOT NULL DEFAULT '', phone TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', score INTEGER NOT NULL DEFAULT 0,
|
|
score_version TEXT NOT NULL DEFAULT 'mvp-1', score_factors TEXT NOT NULL DEFAULT '[]', website_class TEXT NOT NULL DEFAULT 'missing',
|
|
verified INTEGER NOT NULL DEFAULT 0, verified_at TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
-- Phase 6 normalized location and reversible merge ledger.
|
|
CREATE TABLE IF NOT EXISTS merge_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id),
|
|
source_business_id INTEGER NOT NULL REFERENCES businesses(id), target_business_id INTEGER NOT NULL REFERENCES businesses(id),
|
|
source_snapshot_json TEXT NOT NULL, child_reassignment_json TEXT NOT NULL DEFAULT '{}', actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
reversible INTEGER NOT NULL DEFAULT 1, reversed_at TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_merge_history_org ON merge_history(organization_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_businesses_org ON businesses(organization_id);
|
|
CREATE INDEX IF NOT EXISTS idx_businesses_score ON businesses(organization_id,score DESC,id);
|
|
CREATE INDEX IF NOT EXISTS idx_businesses_class ON businesses(organization_id,website_class);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_business_domain ON businesses(organization_id, website_domain) WHERE website_domain <> '';
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_business_email ON businesses(organization_id, email) WHERE email <> '';
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_business_phone ON businesses(organization_id, phone) WHERE phone <> '';
|
|
CREATE TABLE IF NOT EXISTS suppressions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL, kind TEXT NOT NULL CHECK(kind IN ('email','domain','phone')),
|
|
value TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(organization_id, kind, value)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS business_identifiers (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), kind TEXT NOT NULL, value TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS contacts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), name TEXT NOT NULL DEFAULT '', email TEXT NOT NULL DEFAULT '', phone TEXT NOT NULL DEFAULT '',
|
|
title TEXT NOT NULL DEFAULT '', do_not_contact INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS domains (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), domain TEXT NOT NULL, kind TEXT NOT NULL DEFAULT 'other', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS websites (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), url TEXT NOT NULL, website_class TEXT NOT NULL DEFAULT 'business_site', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS evidence (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), kind TEXT NOT NULL, url TEXT NOT NULL DEFAULT '', claim TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS pipeline_entries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), stage TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS interactions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), kind TEXT NOT NULL, body TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE IF NOT EXISTS notes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), body TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_contacts_business ON contacts(business_id);
|
|
CREATE INDEX IF NOT EXISTS idx_contacts_org_business ON contacts(organization_id,business_id);
|
|
CREATE INDEX IF NOT EXISTS idx_identifiers_business ON business_identifiers(business_id,kind);
|
|
CREATE INDEX IF NOT EXISTS idx_domains_business ON domains(business_id);
|
|
CREATE INDEX IF NOT EXISTS idx_websites_business ON websites(business_id);
|
|
CREATE INDEX IF NOT EXISTS idx_evidence_business ON evidence(business_id);
|
|
CREATE INDEX IF NOT EXISTS idx_pipeline_business ON pipeline_entries(business_id,created_at);
|
|
CREATE INDEX IF NOT EXISTS idx_pipeline_stage ON pipeline_entries(organization_id,stage);
|
|
CREATE INDEX IF NOT EXISTS idx_notes_business ON notes(business_id,created_at);
|
|
CREATE INDEX IF NOT EXISTS idx_interactions_business ON interactions(business_id,created_at);
|
|
|
|
-- Phase 12 CRM workflow metadata and safe, auditable state.
|
|
CREATE TABLE IF NOT EXISTS pipeline_stages (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL, position INTEGER NOT NULL DEFAULT 0, active INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,name)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_pipeline_stages_org ON pipeline_stages(organization_id,position,id);
|
|
CREATE INDEX IF NOT EXISTS idx_interactions_org_created ON interactions(organization_id,created_at DESC,id DESC);
|
|
|
|
-- Phase 4 durable background jobs (additive-safe for existing databases).
|
|
CREATE TABLE IF NOT EXISTS jobs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id),
|
|
idempotency_key TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
payload TEXT NOT NULL DEFAULT '{}',
|
|
status TEXT NOT NULL DEFAULT 'queued' CHECK(status IN ('queued','running','succeeded','failed','cancelled')),
|
|
attempts INTEGER NOT NULL DEFAULT 0,
|
|
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
progress INTEGER NOT NULL DEFAULT 0 CHECK(progress BETWEEN 0 AND 100),
|
|
error_code TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
started_at TEXT,
|
|
completed_at TEXT,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,idempotency_key)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_jobs_org_created ON jobs(organization_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_jobs_queue ON jobs(status,created_at,id);
|
|
CREATE TABLE IF NOT EXISTS job_events (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
job_id INTEGER NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id),
|
|
sequence INTEGER NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
message TEXT NOT NULL DEFAULT '',
|
|
progress INTEGER NOT NULL DEFAULT 0 CHECK(progress BETWEEN 0 AND 100),
|
|
error_code TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(job_id,sequence)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_job_events_job_sequence ON job_events(job_id,sequence);
|
|
|
|
-- Phase 5 source framework (additive-safe; credentials contain metadata only).
|
|
CREATE TABLE IF NOT EXISTS sources (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id),
|
|
name TEXT NOT NULL, kind TEXT NOT NULL CHECK(kind IN ('csv','manual')), enabled INTEGER NOT NULL DEFAULT 0,
|
|
config_json TEXT NOT NULL DEFAULT '{}', health_status TEXT NOT NULL DEFAULT 'unknown',
|
|
consecutive_failures INTEGER NOT NULL DEFAULT 0, circuit_open INTEGER NOT NULL DEFAULT 0,
|
|
last_success_at TEXT, last_failure_at TEXT, last_error TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(organization_id,name)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_sources_org ON sources(organization_id,id);
|
|
CREATE TABLE IF NOT EXISTS source_credentials (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id), provider TEXT NOT NULL, key_name TEXT NOT NULL,
|
|
secret_ref TEXT NOT NULL DEFAULT '', metadata_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(source_id,provider,key_name)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS discovery_queries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id), source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL, query_json TEXT NOT NULL DEFAULT '{}', enabled INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(organization_id,name)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_discovery_queries_org ON discovery_queries(organization_id,id);
|
|
CREATE TABLE IF NOT EXISTS source_records (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id), source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
|
discovery_query_id INTEGER REFERENCES discovery_queries(id) ON DELETE SET NULL, content_hash TEXT NOT NULL, raw_json TEXT NOT NULL,
|
|
normalized_json TEXT NOT NULL, source_url TEXT NOT NULL DEFAULT '', query_context_json TEXT NOT NULL DEFAULT '{}', processing_status TEXT NOT NULL DEFAULT 'raw',
|
|
cursor_json TEXT NOT NULL DEFAULT '{}', rate_policy_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,source_id,content_hash)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_source_records_org ON source_records(organization_id,id DESC);
|
|
|
|
-- Phase 7 domain intelligence (additive-safe; DNS results are explicitly stateful).
|
|
CREATE TABLE IF NOT EXISTS domain_checks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id),
|
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE, domain TEXT NOT NULL,
|
|
status TEXT NOT NULL, result_json TEXT NOT NULL DEFAULT '{}', cache_key TEXT NOT NULL,
|
|
checked_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, cache_expires_at TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id, business_id, cache_key)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_domain_checks_org ON domain_checks(organization_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_domain_checks_business ON domain_checks(organization_id,business_id,created_at DESC);
|
|
CREATE TABLE IF NOT EXISTS domain_candidates (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id), business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
domain TEXT NOT NULL, source TEXT NOT NULL DEFAULT 'generated', rank INTEGER NOT NULL DEFAULT 0, metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(organization_id,business_id,domain)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_domain_candidates_business ON domain_candidates(organization_id,business_id,rank,id);
|
|
|
|
-- Phase 8 passive website analysis history; each scan is retained.
|
|
CREATE TABLE IF NOT EXISTS website_scans (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id),
|
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
website_id INTEGER REFERENCES websites(id) ON DELETE SET NULL,
|
|
input_url TEXT NOT NULL,
|
|
classification TEXT NOT NULL,
|
|
result_json TEXT NOT NULL DEFAULT '{}',
|
|
cache_key TEXT NOT NULL,
|
|
scanned_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
cache_expires_at TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_website_scans_org ON website_scans(organization_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_website_scans_business ON website_scans(organization_id,business_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_website_scans_cache ON website_scans(organization_id,cache_key,cache_expires_at);
|
|
|
|
-- Phase 9 contact extraction provenance. MX is intentionally a state value, not a probe.
|
|
CREATE TABLE IF NOT EXISTS contact_extractions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id),
|
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
website_scan_id INTEGER REFERENCES website_scans(id) ON DELETE SET NULL,
|
|
extraction_key TEXT NOT NULL,
|
|
kind TEXT NOT NULL,
|
|
value TEXT NOT NULL,
|
|
label TEXT NOT NULL DEFAULT '',
|
|
classification TEXT NOT NULL DEFAULT 'unknown',
|
|
confidence REAL NOT NULL DEFAULT 0,
|
|
source_url TEXT NOT NULL,
|
|
public_business INTEGER NOT NULL DEFAULT 1,
|
|
mx_status TEXT NOT NULL DEFAULT 'unknown',
|
|
suppressed INTEGER NOT NULL DEFAULT 0,
|
|
do_not_contact INTEGER NOT NULL DEFAULT 0,
|
|
provenance TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,business_id,extraction_key,kind,value)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_contact_extractions_org ON contact_extractions(organization_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_contact_extractions_business ON contact_extractions(organization_id,business_id,id DESC);
|
|
|
|
-- Phase 10 deterministic qualification rules and immutable score audit.
|
|
CREATE TABLE IF NOT EXISTS score_rules (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
code TEXT NOT NULL, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', condition_json TEXT NOT NULL DEFAULT '{}',
|
|
points INTEGER NOT NULL DEFAULT 0, max_applications INTEGER NOT NULL DEFAULT 1, enabled INTEGER NOT NULL DEFAULT 1,
|
|
version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,code)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_score_rules_org ON score_rules(organization_id,enabled,code);
|
|
CREATE TABLE IF NOT EXISTS score_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE, score INTEGER NOT NULL, eligible INTEGER NOT NULL,
|
|
priority_band TEXT NOT NULL, score_version TEXT NOT NULL, explanations_json TEXT NOT NULL DEFAULT '[]', signals_json TEXT NOT NULL DEFAULT '{}',
|
|
override_score INTEGER, override_eligible INTEGER, override_reason TEXT, actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_score_history_business ON score_history(organization_id,business_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_score_history_org ON score_history(organization_id,created_at DESC,id DESC);
|
|
|
|
-- Phase 11 operator workflow: saved views and review state.
|
|
CREATE TABLE IF NOT EXISTS saved_filters (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
filters_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,user_id,name)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_saved_filters_org_user ON saved_filters(organization_id,user_id,updated_at DESC,id DESC);
|
|
|
|
-- Phase 13 optional, evidence-bounded AI assistance. Outputs are drafts only.
|
|
CREATE TABLE IF NOT EXISTS ai_runs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
business_id INTEGER REFERENCES businesses(id) ON DELETE SET NULL,
|
|
input_evidence_hashes_json TEXT NOT NULL DEFAULT '[]',
|
|
model TEXT NOT NULL DEFAULT '', provider TEXT NOT NULL DEFAULT '', version TEXT NOT NULL DEFAULT '',
|
|
prompt_metadata_json TEXT NOT NULL DEFAULT '{}', data_minimization_json TEXT NOT NULL DEFAULT '{}',
|
|
status TEXT NOT NULL CHECK(status IN ('not_configured','succeeded','failed','rejected')),
|
|
approval_state TEXT NOT NULL DEFAULT 'pending' CHECK(approval_state IN ('pending','approved','rejected')),
|
|
output_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, approved_at TEXT, rejected_at TEXT,
|
|
actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_ai_runs_org_created ON ai_runs(organization_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_ai_runs_business ON ai_runs(organization_id,business_id,created_at DESC,id DESC);
|
|
CREATE TABLE IF NOT EXISTS ai_suggestions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ai_run_id INTEGER NOT NULL REFERENCES ai_runs(id) ON DELETE CASCADE,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
business_id INTEGER REFERENCES businesses(id) ON DELETE SET NULL,
|
|
suggestion_type TEXT NOT NULL, citations_json TEXT NOT NULL DEFAULT '[]', output_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_ai_suggestions_run ON ai_suggestions(organization_id,ai_run_id,id);
|
|
|
|
-- Phase 14 outreach preparation. Provider configuration is metadata plus a
|
|
-- one-way secret fingerprint; outbound transport is intentionally disabled.
|
|
CREATE TABLE IF NOT EXISTS outreach_provider_configs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
provider TEXT NOT NULL DEFAULT '', enabled INTEGER NOT NULL DEFAULT 0,
|
|
secret_fingerprint TEXT NOT NULL DEFAULT '',
|
|
policy_json TEXT NOT NULL DEFAULT '{"consent_required":true}',
|
|
daily_cap INTEGER NOT NULL DEFAULT 100, batch_cap INTEGER NOT NULL DEFAULT 25,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS outreach_drafts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
business_id INTEGER NOT NULL REFERENCES businesses(id) ON DELETE CASCADE,
|
|
target_kind TEXT NOT NULL CHECK(target_kind IN ('email','phone','whatsapp')),
|
|
target_value TEXT NOT NULL,
|
|
target_verified INTEGER NOT NULL DEFAULT 0,
|
|
subject TEXT NOT NULL DEFAULT '', body TEXT NOT NULL DEFAULT '',
|
|
template_json TEXT NOT NULL DEFAULT '{}', citations_json TEXT NOT NULL DEFAULT '[]',
|
|
provenance_json TEXT NOT NULL DEFAULT '{}', status TEXT NOT NULL DEFAULT 'pending_review'
|
|
CHECK(status IN ('pending_review','approved','blocked','sent')),
|
|
legal_basis TEXT NOT NULL DEFAULT '', consent_confirmed INTEGER NOT NULL DEFAULT 0,
|
|
actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
approved_by INTEGER REFERENCES users(id) ON DELETE SET NULL, approved_at TEXT,
|
|
sent_at TEXT, idempotency_key TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,idempotency_key)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_outreach_drafts_org ON outreach_drafts(organization_id,created_at DESC,id DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_outreach_drafts_business ON outreach_drafts(organization_id,business_id,id DESC);
|
|
|
|
-- Built-in scoped discovery runs. Results are reviewable business records only;
|
|
-- this feature never sends outreach.
|
|
CREATE TABLE IF NOT EXISTS discovery_runs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
job_id INTEGER NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
criteria_json TEXT NOT NULL DEFAULT '{}', seed_urls_json TEXT NOT NULL DEFAULT '[]',
|
|
result_json TEXT NOT NULL DEFAULT '{}', result_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(organization_id,job_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_discovery_runs_org ON discovery_runs(organization_id,created_at DESC,id DESC);
|