## PostgreSQL DDL Specification (Version 2) ```sql -- Enable pgcrypto for UUID generation if on PostgreSQL < 13 CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- 1. PERSON TABLE CREATE TABLE person ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), full_name TEXT NOT NULL, display_name TEXT, maiden_name TEXT, birth_date DATE, birth_date_raw TEXT, birth_place TEXT, death_date DATE, death_date_raw TEXT, death_place TEXT, biography TEXT, portrait_path TEXT, metadata JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- 2. DOCUMENT TABLE CREATE TABLE document ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, document_type TEXT, document_date DATE, document_date_raw TEXT, location_created TEXT, notes TEXT, archive_identifier TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- 3. DOCUMENT_PERSON (Junction Table for Multi-Author / Multi-Recipient) CREATE TABLE document_person ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID NOT NULL REFERENCES document(id) ON DELETE CASCADE, person_id UUID NOT NULL REFERENCES person(id) ON DELETE CASCADE, role VARCHAR(20) NOT NULL, -- 'author' or 'recipient' created_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT unique_document_person_role UNIQUE (document_id, person_id, role) ); -- 4. JOB TABLE (Batch-level orchestrator) CREATE TABLE job ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID NOT NULL REFERENCES document(id) ON DELETE CASCADE, status VARCHAR(50) NOT NULL DEFAULT 'queued', -- 'queued', 'processing', 'completed', 'partial_success', 'failed' retry_count INTEGER NOT NULL DEFAULT 0, provider TEXT NOT NULL, -- e.g., 'openai', 'anthropic' model TEXT NOT NULL, -- e.g., 'gpt-4o', 'claude-3-5-sonnet' prompt_name TEXT, date_created TIMESTAMPTZ NOT NULL DEFAULT now(), date_updated TIMESTAMPTZ NOT NULL DEFAULT now() ); -- 5. SOURCE TABLE (Physical image files & active state) CREATE TABLE source ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID NOT NULL REFERENCES document(id) ON DELETE CASCADE, page_number INTEGER NOT NULL DEFAULT 1, upload_name TEXT NOT NULL, filename TEXT NOT NULL, file_path TEXT NOT NULL, raw_transcription TEXT, -- Cached active AI text output revised_text TEXT, -- Active human edited text date_uploaded TIMESTAMPTZ NOT NULL DEFAULT now(), date_revised TIMESTAMPTZ ); -- 6. JOB_SOURCE (Junction Table: Per-Image Execution Record) CREATE TABLE job_source ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), job_id UUID NOT NULL REFERENCES job(id) ON DELETE CASCADE, source_id UUID NOT NULL REFERENCES source(id) ON DELETE CASCADE, status VARCHAR(50) NOT NULL DEFAULT 'pending', -- 'pending', 'transcribed', 'failed' raw_transcription TEXT, -- Point-in-time raw AI text output ai_metadata JSONB, -- Page-level bounding boxes, tokens, confidence raw_api_response JSONB, -- Complete REST response envelope error_detail TEXT, executed_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT unique_job_source UNIQUE (job_id, source_id) ); -- INDEXES FOR FAST LOOKUPS & QUERY PERFORMANCE CREATE INDEX idx_person_full_name ON person(full_name); CREATE INDEX idx_document_date ON document(document_date); CREATE INDEX idx_document_person_doc ON document_person(document_id); CREATE INDEX idx_document_person_per ON document_person(person_id); CREATE INDEX idx_source_document ON source(document_id); CREATE INDEX idx_source_page_order ON source(document_id, page_number); CREATE INDEX idx_job_document ON job(document_id); CREATE INDEX idx_job_source_job ON job_source(job_id); CREATE INDEX idx_job_source_source ON job_source(source_id); CREATE INDEX idx_job_source_ai_metadata ON job_source USING GIN (ai_metadata); ```