## 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);  
```