-- =============================================================================
-- B2: URT Database Schema
-- Universal Review Taxonomy v5.1
-- =============================================================================
--
-- PostgreSQL schema for URT v5.1 classification storage and analytics.
-- Supports all implementation profiles (Lite, Core, Standard, Full).
--
-- Design Principles:
--   1. Normalized reference data (domains, categories, subcodes, causal codes)
--   2. Flexible span classification with metadata dimensions
--   3. Full issue lifecycle tracking per C1 framework
--   4. Comprehensive audit trail for compliance
--   5. Optimized for analytics with materialized views
--   6. Partitioning-ready for high-volume scenarios
--
-- Status: Production Ready
-- Version: 1.0
-- Date: 2026-01-23
-- Depends On: B1-urt-codes.yaml, C1-Issue-Lifecycle-Framework.md
-- =============================================================================

-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "btree_gist";  -- For exclusion constraints

-- =============================================================================
-- SECTION 1: REFERENCE TABLES
-- Core URT taxonomy structure from B1-urt-codes.yaml
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 1.1 Experience Domains (7 domains)
-- Top-level organizational structure
-- -----------------------------------------------------------------------------
CREATE TABLE urt_domains (
    domain_code     CHAR(1) PRIMARY KEY,
    name            VARCHAR(50) NOT NULL,
    description     TEXT NOT NULL,
    core_question   TEXT NOT NULL,
    default_owner   VARCHAR(100) NOT NULL,
    display_order   SMALLINT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT urt_domains_code_check
        CHECK (domain_code IN ('O', 'P', 'J', 'E', 'A', 'V', 'R'))
);

COMMENT ON TABLE urt_domains IS
    'URT v5.1 Experience Domains - 7 top-level classification areas';

-- -----------------------------------------------------------------------------
-- 1.2 Categories (28 categories)
-- Second-tier classification within domains
-- -----------------------------------------------------------------------------
CREATE TABLE urt_categories (
    category_code   VARCHAR(2) PRIMARY KEY,
    domain_code     CHAR(1) NOT NULL REFERENCES urt_domains(domain_code),
    name            VARCHAR(50) NOT NULL,
    definition      TEXT NOT NULL,
    display_order   SMALLINT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT urt_categories_code_format
        CHECK (category_code ~ '^[OPJEAVR][1-4]$')
);

CREATE INDEX idx_categories_domain ON urt_categories(domain_code);

COMMENT ON TABLE urt_categories IS
    'URT v5.1 Categories - 28 second-tier classifications within domains';

-- -----------------------------------------------------------------------------
-- 1.3 Subcodes (138 subcodes per B1 actual count)
-- Third-tier diagnostic codes for detailed classification
-- -----------------------------------------------------------------------------
CREATE TABLE urt_subcodes (
    subcode         VARCHAR(6) PRIMARY KEY,
    category_code   VARCHAR(2) NOT NULL REFERENCES urt_categories(category_code),
    domain_code     CHAR(1) NOT NULL REFERENCES urt_domains(domain_code),
    name            VARCHAR(100) NOT NULL,
    definition      TEXT NOT NULL,
    positive_example TEXT,
    negative_example TEXT,
    dont_confuse_with VARCHAR(6),
    dont_confuse_reason TEXT,
    display_order   SMALLINT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT urt_subcodes_code_format
        CHECK (subcode ~ '^[OPJEAVR][1-4]\.[0-9]{2}$')
);

CREATE INDEX idx_subcodes_category ON urt_subcodes(category_code);
CREATE INDEX idx_subcodes_domain ON urt_subcodes(domain_code);

COMMENT ON TABLE urt_subcodes IS
    'URT v5.1 Subcodes - 138 diagnostic-level classification codes';

-- -----------------------------------------------------------------------------
-- 1.4 Causal Codes (16 codes across 3 layers)
-- For root cause analysis in Full profile
-- -----------------------------------------------------------------------------
CREATE TABLE urt_causal_codes (
    causal_code     VARCHAR(4) PRIMARY KEY,
    layer           VARCHAR(20) NOT NULL,
    layer_prefix    VARCHAR(3) NOT NULL,
    name            VARCHAR(50) NOT NULL,
    definition      TEXT NOT NULL,
    display_order   SMALLINT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT urt_causal_layer_check
        CHECK (layer IN ('conditions', 'management', 'systemic')),
    CONSTRAINT urt_causal_prefix_check
        CHECK (layer_prefix IN ('CD-', 'MG-', 'SY-')),
    CONSTRAINT urt_causal_code_format
        CHECK (causal_code ~ '^(CD|MG|SY)-[A-Z]$')
);

CREATE INDEX idx_causal_layer ON urt_causal_codes(layer);

COMMENT ON TABLE urt_causal_codes IS
    'URT v5.1 Causal Codes - 16 root cause analysis codes (CD/MG/SY layers)';

-- -----------------------------------------------------------------------------
-- 1.5 Metadata Dimension Values (24 values across 7 dimensions)
-- All possible metadata values for span classification
-- -----------------------------------------------------------------------------
CREATE TABLE urt_metadata_values (
    dimension_code  VARCHAR(2) NOT NULL,
    value_code      VARCHAR(4) NOT NULL,
    dimension_name  VARCHAR(30) NOT NULL,
    label           VARCHAR(30) NOT NULL,
    definition      TEXT NOT NULL,
    is_default      BOOLEAN NOT NULL DEFAULT FALSE,
    display_order   SMALLINT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    PRIMARY KEY (dimension_code, value_code),

    CONSTRAINT urt_metadata_dimension_check
        CHECK (dimension_code IN ('V', 'I', 'S', 'A', 'T', 'E', 'CR'))
);

CREATE INDEX idx_metadata_dimension ON urt_metadata_values(dimension_code);

COMMENT ON TABLE urt_metadata_values IS
    'URT v5.1 Metadata Values - 24 values across 7 dimensions (V,I,S,A,T,E,CR)';

-- =============================================================================
-- SECTION 2: CORE CLASSIFICATION TABLES
-- Reviews, spans, and classifications
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 2.1 Reviews
-- Original review text and source metadata
-- -----------------------------------------------------------------------------
CREATE TABLE reviews (
    review_id       UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    external_id     VARCHAR(255),          -- ID from source system (e.g., Google place_id)
    source          VARCHAR(50) NOT NULL,  -- 'google', 'yelp', 'tripadvisor', etc.
    business_id     VARCHAR(255),          -- Business identifier
    author_name     VARCHAR(255),
    author_id       VARCHAR(255),
    review_text     TEXT NOT NULL,
    star_rating     NUMERIC(2,1),          -- 1.0-5.0 scale
    review_date     DATE,
    language_code   VARCHAR(5),            -- ISO 639-1
    raw_metadata    JSONB,                 -- Source-specific metadata
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at      TIMESTAMPTZ,           -- Soft delete support

    CONSTRAINT reviews_rating_check
        CHECK (star_rating IS NULL OR (star_rating >= 1.0 AND star_rating <= 5.0))
);

CREATE INDEX idx_reviews_source ON reviews(source);
CREATE INDEX idx_reviews_business ON reviews(business_id);
CREATE INDEX idx_reviews_date ON reviews(review_date);
CREATE INDEX idx_reviews_external ON reviews(external_id) WHERE external_id IS NOT NULL;
CREATE INDEX idx_reviews_created ON reviews(created_at);
CREATE INDEX idx_reviews_deleted ON reviews(deleted_at) WHERE deleted_at IS NULL;

COMMENT ON TABLE reviews IS
    'Original customer reviews from various sources';

-- -----------------------------------------------------------------------------
-- 2.2 Spans
-- Individual classified text segments within reviews
-- A single review may contain multiple spans (composite review decomposition)
-- -----------------------------------------------------------------------------
CREATE TABLE spans (
    span_id             UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    review_id           UUID NOT NULL REFERENCES reviews(review_id) ON DELETE CASCADE,
    span_text           TEXT NOT NULL,
    char_start          INTEGER,           -- Start position in review_text
    char_end            INTEGER,           -- End position in review_text
    span_order          SMALLINT NOT NULL DEFAULT 1,  -- Order within review

    -- Implementation profile used
    profile             VARCHAR(10) NOT NULL DEFAULT 'standard',

    -- Primary classification (required for all profiles)
    primary_code        VARCHAR(6) NOT NULL,
    primary_tier        SMALLINT NOT NULL,  -- 1=domain, 2=category, 3=subcode

    -- Metadata dimensions (availability depends on profile)
    valence             VARCHAR(2) NOT NULL,
    intensity           VARCHAR(2) NOT NULL,
    specificity         VARCHAR(2),         -- Standard+ only
    actionability       VARCHAR(2),         -- Standard+ only
    temporal            VARCHAR(2) DEFAULT 'TC',
    evidence            VARCHAR(2) DEFAULT 'ES',
    comparative         VARCHAR(4) DEFAULT 'CR-N',

    -- Extended attributes for issue aggregation
    entity_reference    VARCHAR(255),       -- Product, person, feature
    location_reference  VARCHAR(255),       -- Physical or logical location

    -- Confidence and annotation metadata
    confidence_score    NUMERIC(3,2),       -- 0.00-1.00
    annotator_notes     TEXT,
    annotation_source   VARCHAR(20),        -- 'human', 'llm', 'hybrid'

    -- Timestamps
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at          TIMESTAMPTZ,

    CONSTRAINT spans_profile_check
        CHECK (profile IN ('lite', 'core', 'standard', 'full')),
    CONSTRAINT spans_tier_check
        CHECK (primary_tier IN (1, 2, 3)),
    CONSTRAINT spans_valence_check
        CHECK (valence IN ('V+', 'V-', 'V0', 'V±')),
    CONSTRAINT spans_intensity_check
        CHECK (intensity IN ('I1', 'I2', 'I3')),
    CONSTRAINT spans_specificity_check
        CHECK (specificity IS NULL OR specificity IN ('S1', 'S2', 'S3')),
    CONSTRAINT spans_actionability_check
        CHECK (actionability IS NULL OR actionability IN ('A1', 'A2', 'A3')),
    CONSTRAINT spans_temporal_check
        CHECK (temporal IS NULL OR temporal IN ('TC', 'TR', 'TH', 'TF')),
    CONSTRAINT spans_evidence_check
        CHECK (evidence IS NULL OR evidence IN ('ES', 'EI', 'EC')),
    CONSTRAINT spans_comparative_check
        CHECK (comparative IS NULL OR comparative IN ('CR-N', 'CR-B', 'CR-W', 'CR-S')),
    CONSTRAINT spans_confidence_check
        CHECK (confidence_score IS NULL OR (confidence_score >= 0 AND confidence_score <= 1)),
    CONSTRAINT spans_char_range_check
        CHECK ((char_start IS NULL AND char_end IS NULL) OR (char_start < char_end))
);

CREATE INDEX idx_spans_review ON spans(review_id);
CREATE INDEX idx_spans_primary_code ON spans(primary_code);
CREATE INDEX idx_spans_valence ON spans(valence);
CREATE INDEX idx_spans_intensity ON spans(intensity);
CREATE INDEX idx_spans_comparative ON spans(comparative) WHERE comparative != 'CR-N';
CREATE INDEX idx_spans_created ON spans(created_at);
CREATE INDEX idx_spans_deleted ON spans(deleted_at) WHERE deleted_at IS NULL;
CREATE INDEX idx_spans_entity ON spans(entity_reference) WHERE entity_reference IS NOT NULL;
CREATE INDEX idx_spans_location ON spans(location_reference) WHERE location_reference IS NOT NULL;

-- Composite indexes for common query patterns
CREATE INDEX idx_spans_classification ON spans(primary_code, valence, intensity);
CREATE INDEX idx_spans_negative ON spans(primary_code, created_at) WHERE valence = 'V-';

COMMENT ON TABLE spans IS
    'Individual classified text segments from reviews with URT metadata';

-- -----------------------------------------------------------------------------
-- 2.3 Span Classifications (Secondary Codes)
-- Supports up to 2 secondary codes per span (Standard+ profiles)
-- -----------------------------------------------------------------------------
CREATE TABLE span_classifications (
    classification_id   UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    span_id             UUID NOT NULL REFERENCES spans(span_id) ON DELETE CASCADE,
    code                VARCHAR(6) NOT NULL,
    code_tier           SMALLINT NOT NULL,
    is_primary          BOOLEAN NOT NULL DEFAULT FALSE,
    classification_order SMALLINT NOT NULL DEFAULT 1,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT span_class_tier_check
        CHECK (code_tier IN (1, 2, 3)),
    CONSTRAINT span_class_order_check
        CHECK (classification_order BETWEEN 1 AND 3),

    -- Ensure max 3 classifications per span (1 primary + 2 secondary)
    UNIQUE (span_id, classification_order)
);

CREATE INDEX idx_span_class_span ON span_classifications(span_id);
CREATE INDEX idx_span_class_code ON span_classifications(code);
CREATE INDEX idx_span_class_primary ON span_classifications(span_id) WHERE is_primary = TRUE;

COMMENT ON TABLE span_classifications IS
    'Primary and secondary code assignments for spans (max 3 per span)';

-- -----------------------------------------------------------------------------
-- 2.4 Causal Chains
-- Links spans to causal codes (CD -> MG -> SY) for Full profile
-- -----------------------------------------------------------------------------
CREATE TABLE causal_chains (
    chain_id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    span_id             UUID NOT NULL REFERENCES spans(span_id) ON DELETE CASCADE,

    -- Causal chain: Conditions -> Management -> Systemic
    condition_code      VARCHAR(4) REFERENCES urt_causal_codes(causal_code),
    management_code     VARCHAR(4) REFERENCES urt_causal_codes(causal_code),
    systemic_code       VARCHAR(4) REFERENCES urt_causal_codes(causal_code),

    -- Confidence in causal attribution
    chain_confidence    NUMERIC(3,2),
    analyst_notes       TEXT,

    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT causal_chain_layer_check CHECK (
        (condition_code IS NULL OR condition_code ~ '^CD-') AND
        (management_code IS NULL OR management_code ~ '^MG-') AND
        (systemic_code IS NULL OR systemic_code ~ '^SY-')
    ),
    CONSTRAINT causal_chain_minimum CHECK (
        condition_code IS NOT NULL OR management_code IS NOT NULL OR systemic_code IS NOT NULL
    )
);

CREATE INDEX idx_causal_span ON causal_chains(span_id);
CREATE INDEX idx_causal_condition ON causal_chains(condition_code) WHERE condition_code IS NOT NULL;
CREATE INDEX idx_causal_management ON causal_chains(management_code) WHERE management_code IS NOT NULL;
CREATE INDEX idx_causal_systemic ON causal_chains(systemic_code) WHERE systemic_code IS NOT NULL;

COMMENT ON TABLE causal_chains IS
    'Root cause analysis chains linking spans to CD/MG/SY causal codes';

-- =============================================================================
-- SECTION 3: ISSUE TRACKING TABLES
-- Based on C1 Issue Lifecycle Framework
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 3.1 Issues
-- Aggregated issue records from negative spans
-- -----------------------------------------------------------------------------
CREATE TABLE issues (
    issue_id            VARCHAR(20) PRIMARY KEY,  -- Format: ISSUE-YYYY-NNNN

    -- Classification context
    primary_subcode     VARCHAR(6) NOT NULL,
    domain_code         CHAR(1) NOT NULL REFERENCES urt_domains(domain_code),

    -- Aggregation context
    entity_reference    VARCHAR(255),
    location_reference  VARCHAR(255),

    -- Current state (per C1 state machine)
    state               VARCHAR(15) NOT NULL DEFAULT 'DETECTED',
    state_changed_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    -- Metrics from aggregated spans
    span_count          INTEGER NOT NULL DEFAULT 1,
    max_intensity       VARCHAR(2) NOT NULL,
    priority_score      NUMERIC(6,2) NOT NULL,
    confidence_score    NUMERIC(3,2) NOT NULL,

    -- Ownership
    owner_team          VARCHAR(100),
    owner_individual    VARCHAR(100),

    -- Resolution details
    resolution_code     VARCHAR(20),
    resolution_notes    TEXT,
    decline_reason      VARCHAR(10),

    -- Causal analysis (for Full profile)
    causal_codes        VARCHAR(4)[],

    -- Recurrence tracking
    reopen_count        INTEGER NOT NULL DEFAULT 0,
    verification_window_days INTEGER DEFAULT 60,

    -- Key timestamps
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    acknowledged_at     TIMESTAMPTZ,
    work_started_at     TIMESTAMPTZ,
    resolved_at         TIMESTAMPTZ,
    verified_at         TIMESTAMPTZ,
    declined_at         TIMESTAMPTZ,

    -- Soft delete
    deleted_at          TIMESTAMPTZ,

    CONSTRAINT issues_state_check CHECK (
        state IN ('DETECTED', 'ACKNOWLEDGED', 'IN_PROGRESS', 'RESOLVED',
                  'VERIFIED', 'DECLINED', 'REOPENED', 'STALE')
    ),
    CONSTRAINT issues_intensity_check
        CHECK (max_intensity IN ('I1', 'I2', 'I3')),
    CONSTRAINT issues_decline_reason_check CHECK (
        decline_reason IS NULL OR
        decline_reason IN ('DEC-DUP', 'DEC-OOS', 'DEC-INS', 'DEC-NAR',
                           'DEC-EXT', 'DEC-POL', 'DEC-OLD')
    ),
    CONSTRAINT issues_verification_window_check
        CHECK (verification_window_days IN (30, 60, 90))
);

CREATE INDEX idx_issues_state ON issues(state);
CREATE INDEX idx_issues_domain ON issues(domain_code);
CREATE INDEX idx_issues_subcode ON issues(primary_subcode);
CREATE INDEX idx_issues_priority ON issues(priority_score DESC);
CREATE INDEX idx_issues_created ON issues(created_at);
CREATE INDEX idx_issues_owner_team ON issues(owner_team);
CREATE INDEX idx_issues_deleted ON issues(deleted_at) WHERE deleted_at IS NULL;

-- Partial indexes for active issues
CREATE INDEX idx_issues_open ON issues(priority_score DESC)
    WHERE state IN ('DETECTED', 'ACKNOWLEDGED', 'IN_PROGRESS', 'REOPENED');
CREATE INDEX idx_issues_pending_verification ON issues(resolved_at, verification_window_days)
    WHERE state = 'RESOLVED';

COMMENT ON TABLE issues IS
    'Aggregated issues from negative feedback spans per C1 lifecycle framework';

-- -----------------------------------------------------------------------------
-- 3.2 Issue-Span Links
-- Many-to-many relationship between issues and contributing spans
-- -----------------------------------------------------------------------------
CREATE TABLE issue_spans (
    issue_id            VARCHAR(20) NOT NULL REFERENCES issues(issue_id) ON DELETE CASCADE,
    span_id             UUID NOT NULL REFERENCES spans(span_id) ON DELETE CASCADE,
    linked_at           TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    link_type           VARCHAR(20) NOT NULL DEFAULT 'contributor',
    correlation_score   NUMERIC(3,2),

    PRIMARY KEY (issue_id, span_id),

    CONSTRAINT issue_spans_link_type_check
        CHECK (link_type IN ('contributor', 'verification', 'reopen_trigger'))
);

CREATE INDEX idx_issue_spans_span ON issue_spans(span_id);
CREATE INDEX idx_issue_spans_linked ON issue_spans(linked_at);

COMMENT ON TABLE issue_spans IS
    'Links between issues and their contributing spans';

-- -----------------------------------------------------------------------------
-- 3.3 Issue State History
-- Complete audit trail of state transitions
-- -----------------------------------------------------------------------------
CREATE TABLE issue_state_history (
    history_id          UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    issue_id            VARCHAR(20) NOT NULL REFERENCES issues(issue_id) ON DELETE CASCADE,

    from_state          VARCHAR(15),
    to_state            VARCHAR(15) NOT NULL,
    trigger_type        VARCHAR(20) NOT NULL,  -- 'manual', 'auto', 'span', 'sla'
    trigger_span_id     UUID REFERENCES spans(span_id),

    actor_id            VARCHAR(100),
    actor_type          VARCHAR(20),  -- 'user', 'system', 'rule'
    notes               TEXT,

    transitioned_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT history_state_check CHECK (
        to_state IN ('DETECTED', 'ACKNOWLEDGED', 'IN_PROGRESS', 'RESOLVED',
                     'VERIFIED', 'DECLINED', 'REOPENED', 'STALE')
    )
);

CREATE INDEX idx_history_issue ON issue_state_history(issue_id);
CREATE INDEX idx_history_transition ON issue_state_history(transitioned_at);
CREATE INDEX idx_history_state ON issue_state_history(to_state);

COMMENT ON TABLE issue_state_history IS
    'Audit trail of all issue state transitions';

-- -----------------------------------------------------------------------------
-- 3.4 Issue Resolutions
-- Detailed resolution records for resolved/verified issues
-- -----------------------------------------------------------------------------
CREATE TABLE issue_resolutions (
    resolution_id       UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    issue_id            VARCHAR(20) NOT NULL REFERENCES issues(issue_id) ON DELETE CASCADE,

    resolution_code     VARCHAR(20) NOT NULL,
    resolution_type     VARCHAR(30) NOT NULL,  -- 'fix', 'workaround', 'policy_change', etc.
    resolution_summary  TEXT NOT NULL,
    resolution_details  TEXT,

    -- Preventive measures
    prevention_notes    TEXT,
    process_changes     TEXT,

    -- Verification
    verified_by_span_id UUID REFERENCES spans(span_id),
    verification_method VARCHAR(20),  -- 'cr_b', 'positive_span', 'time_based'

    resolved_by         VARCHAR(100),
    resolved_at         TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    verified_at         TIMESTAMPTZ,

    CONSTRAINT resolution_type_check CHECK (
        resolution_type IN ('fix', 'workaround', 'policy_change', 'training',
                           'process_improvement', 'equipment', 'other')
    )
);

CREATE INDEX idx_resolutions_issue ON issue_resolutions(issue_id);
CREATE INDEX idx_resolutions_code ON issue_resolutions(resolution_code);
CREATE INDEX idx_resolutions_resolved ON issue_resolutions(resolved_at);

COMMENT ON TABLE issue_resolutions IS
    'Detailed resolution records with verification tracking';

-- =============================================================================
-- SECTION 4: AUDIT AND HISTORY TABLES
-- Comprehensive tracking for compliance and analytics
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 4.1 Annotation Audit
-- Who annotated what, when, and how
-- -----------------------------------------------------------------------------
CREATE TABLE annotation_audit (
    audit_id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    span_id             UUID NOT NULL REFERENCES spans(span_id) ON DELETE CASCADE,

    annotator_id        VARCHAR(100) NOT NULL,
    annotator_type      VARCHAR(20) NOT NULL,  -- 'human', 'llm', 'rule'
    model_version       VARCHAR(50),           -- For LLM annotations

    -- What was set
    field_name          VARCHAR(30) NOT NULL,
    old_value           TEXT,
    new_value           TEXT NOT NULL,

    -- Confidence and reasoning
    confidence          NUMERIC(3,2),
    reasoning           TEXT,

    annotated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT audit_annotator_type_check
        CHECK (annotator_type IN ('human', 'llm', 'rule', 'hybrid'))
);

CREATE INDEX idx_audit_span ON annotation_audit(span_id);
CREATE INDEX idx_audit_annotator ON annotation_audit(annotator_id);
CREATE INDEX idx_audit_field ON annotation_audit(field_name);
CREATE INDEX idx_audit_time ON annotation_audit(annotated_at);

COMMENT ON TABLE annotation_audit IS
    'Complete audit trail of all span annotation changes';

-- -----------------------------------------------------------------------------
-- 4.2 Classification History
-- Track changes to span classifications over time
-- -----------------------------------------------------------------------------
CREATE TABLE classification_history (
    history_id          UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    span_id             UUID NOT NULL REFERENCES spans(span_id) ON DELETE CASCADE,

    change_type         VARCHAR(20) NOT NULL,  -- 'create', 'update', 'delete'

    -- Previous state (null for create)
    prev_primary_code   VARCHAR(6),
    prev_valence        VARCHAR(2),
    prev_intensity      VARCHAR(2),
    prev_secondary_codes VARCHAR(6)[],

    -- New state (null for delete)
    new_primary_code    VARCHAR(6),
    new_valence         VARCHAR(2),
    new_intensity       VARCHAR(2),
    new_secondary_codes VARCHAR(6)[],

    change_reason       TEXT,
    changed_by          VARCHAR(100) NOT NULL,
    changed_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT history_change_type_check
        CHECK (change_type IN ('create', 'update', 'delete'))
);

CREATE INDEX idx_class_history_span ON classification_history(span_id);
CREATE INDEX idx_class_history_time ON classification_history(changed_at);
CREATE INDEX idx_class_history_type ON classification_history(change_type);

COMMENT ON TABLE classification_history IS
    'Historical record of classification changes for audit and rollback';

-- =============================================================================
-- SECTION 5: ANALYTICS VIEWS AND MATERIALIZED VIEWS
-- Pre-computed aggregations for dashboard and reporting
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 5.1 Domain Summary View
-- Current counts and averages by domain
-- -----------------------------------------------------------------------------
CREATE MATERIALIZED VIEW mv_domain_summary AS
SELECT
    d.domain_code,
    d.name AS domain_name,
    d.default_owner,
    COUNT(DISTINCT s.span_id) AS total_spans,
    COUNT(DISTINCT s.span_id) FILTER (WHERE s.valence = 'V+') AS positive_spans,
    COUNT(DISTINCT s.span_id) FILTER (WHERE s.valence = 'V-') AS negative_spans,
    COUNT(DISTINCT s.span_id) FILTER (WHERE s.valence = 'V0') AS neutral_spans,
    COUNT(DISTINCT s.span_id) FILTER (WHERE s.valence = 'V±') AS mixed_spans,
    COUNT(DISTINCT s.span_id) FILTER (WHERE s.intensity = 'I3') AS critical_spans,
    ROUND(AVG(s.confidence_score), 3) AS avg_confidence,
    COUNT(DISTINCT i.issue_id) FILTER (WHERE i.state NOT IN ('VERIFIED', 'DECLINED')) AS open_issues,
    MAX(s.created_at) AS last_span_at
FROM urt_domains d
LEFT JOIN spans s ON LEFT(s.primary_code, 1) = d.domain_code
    AND s.deleted_at IS NULL
LEFT JOIN issues i ON i.domain_code = d.domain_code
    AND i.deleted_at IS NULL
GROUP BY d.domain_code, d.name, d.default_owner
ORDER BY d.display_order;

CREATE UNIQUE INDEX idx_mv_domain_summary ON mv_domain_summary(domain_code);

COMMENT ON MATERIALIZED VIEW mv_domain_summary IS
    'Aggregated metrics by URT domain for executive dashboards';

-- -----------------------------------------------------------------------------
-- 5.2 Category Breakdown View
-- Detailed metrics at category level
-- -----------------------------------------------------------------------------
CREATE MATERIALIZED VIEW mv_category_breakdown AS
SELECT
    c.category_code,
    c.name AS category_name,
    c.domain_code,
    d.name AS domain_name,
    COUNT(DISTINCT s.span_id) AS total_spans,
    COUNT(DISTINCT s.span_id) FILTER (WHERE s.valence = 'V-') AS negative_count,
    COUNT(DISTINCT s.span_id) FILTER (WHERE s.valence = 'V+') AS positive_count,
    ROUND(
        COUNT(DISTINCT s.span_id) FILTER (WHERE s.valence = 'V-')::NUMERIC /
        NULLIF(COUNT(DISTINCT s.span_id), 0) * 100, 1
    ) AS negative_pct,
    COUNT(DISTINCT i.issue_id) AS total_issues,
    COUNT(DISTINCT i.issue_id) FILTER (WHERE i.state = 'VERIFIED') AS resolved_verified,
    ROUND(AVG(CASE
        WHEN s.intensity = 'I1' THEN 1
        WHEN s.intensity = 'I2' THEN 2
        WHEN s.intensity = 'I3' THEN 3
    END), 2) AS avg_intensity
FROM urt_categories c
JOIN urt_domains d ON c.domain_code = d.domain_code
LEFT JOIN spans s ON s.primary_code LIKE c.category_code || '%'
    AND s.deleted_at IS NULL
LEFT JOIN issues i ON i.primary_subcode LIKE c.category_code || '%'
    AND i.deleted_at IS NULL
GROUP BY c.category_code, c.name, c.domain_code, d.name
ORDER BY c.domain_code, c.display_order;

CREATE UNIQUE INDEX idx_mv_category_breakdown ON mv_category_breakdown(category_code);

COMMENT ON MATERIALIZED VIEW mv_category_breakdown IS
    'Detailed category-level metrics for operational analysis';

-- -----------------------------------------------------------------------------
-- 5.3 Issue Pipeline View
-- Current state of all open issues
-- -----------------------------------------------------------------------------
CREATE MATERIALIZED VIEW mv_issue_pipeline AS
SELECT
    i.state,
    i.domain_code,
    d.name AS domain_name,
    COUNT(*) AS issue_count,
    AVG(i.priority_score) AS avg_priority,
    AVG(i.span_count) AS avg_span_count,
    AVG(EXTRACT(EPOCH FROM (NOW() - i.created_at)) / 3600) AS avg_age_hours,
    COUNT(*) FILTER (WHERE i.max_intensity = 'I3') AS critical_count,
    COUNT(*) FILTER (WHERE i.reopen_count > 0) AS reopened_count
FROM issues i
JOIN urt_domains d ON i.domain_code = d.domain_code
WHERE i.deleted_at IS NULL
  AND i.state NOT IN ('VERIFIED', 'DECLINED')
GROUP BY i.state, i.domain_code, d.name
ORDER BY
    CASE i.state
        WHEN 'DETECTED' THEN 1
        WHEN 'ACKNOWLEDGED' THEN 2
        WHEN 'IN_PROGRESS' THEN 3
        WHEN 'REOPENED' THEN 4
        WHEN 'RESOLVED' THEN 5
        ELSE 6
    END,
    d.display_order;

CREATE INDEX idx_mv_issue_pipeline ON mv_issue_pipeline(state, domain_code);

COMMENT ON MATERIALIZED VIEW mv_issue_pipeline IS
    'Current issue pipeline by state and domain for SLA tracking';

-- -----------------------------------------------------------------------------
-- 5.4 Comparative Reference Trends View
-- Track improvement/decline signals over time
-- -----------------------------------------------------------------------------
CREATE MATERIALIZED VIEW mv_cr_trends AS
SELECT
    DATE_TRUNC('week', s.created_at) AS week_start,
    s.comparative,
    LEFT(s.primary_code, 1) AS domain_code,
    COUNT(*) AS span_count,
    COUNT(*) FILTER (WHERE s.valence = 'V+') AS positive_count,
    COUNT(*) FILTER (WHERE s.valence = 'V-') AS negative_count
FROM spans s
WHERE s.deleted_at IS NULL
  AND s.comparative IS NOT NULL
  AND s.created_at >= NOW() - INTERVAL '90 days'
GROUP BY DATE_TRUNC('week', s.created_at), s.comparative, LEFT(s.primary_code, 1)
ORDER BY week_start DESC, domain_code;

CREATE INDEX idx_mv_cr_trends ON mv_cr_trends(week_start, comparative);

COMMENT ON MATERIALIZED VIEW mv_cr_trends IS
    'Weekly trends of comparative reference signals (CR-B/W/S) for improvement tracking';

-- -----------------------------------------------------------------------------
-- 5.5 Daily Span Volume (for time-series analysis)
-- Partitioning-ready daily aggregation
-- -----------------------------------------------------------------------------
CREATE MATERIALIZED VIEW mv_daily_volume AS
SELECT
    DATE(s.created_at) AS span_date,
    LEFT(s.primary_code, 1) AS domain_code,
    s.valence,
    s.intensity,
    COUNT(*) AS span_count,
    COUNT(DISTINCT s.review_id) AS review_count,
    AVG(s.confidence_score) AS avg_confidence
FROM spans s
WHERE s.deleted_at IS NULL
GROUP BY DATE(s.created_at), LEFT(s.primary_code, 1), s.valence, s.intensity
ORDER BY span_date DESC;

CREATE INDEX idx_mv_daily_volume_date ON mv_daily_volume(span_date);
CREATE INDEX idx_mv_daily_volume_domain ON mv_daily_volume(domain_code);

COMMENT ON MATERIALIZED VIEW mv_daily_volume IS
    'Daily span volume by domain/valence/intensity for trend analysis';

-- =============================================================================
-- SECTION 6: HELPER FUNCTIONS
-- Utility functions for common operations
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 6.1 Generate Issue ID
-- Format: ISSUE-YYYY-NNNN (sequential within year)
-- -----------------------------------------------------------------------------
CREATE SEQUENCE issue_id_seq START 1;

CREATE OR REPLACE FUNCTION generate_issue_id()
RETURNS VARCHAR(20) AS $$
DECLARE
    year_part VARCHAR(4);
    seq_part INTEGER;
BEGIN
    year_part := TO_CHAR(NOW(), 'YYYY');
    seq_part := NEXTVAL('issue_id_seq');
    RETURN 'ISSUE-' || year_part || '-' || LPAD(seq_part::TEXT, 4, '0');
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION generate_issue_id IS
    'Generate sequential issue IDs in format ISSUE-YYYY-NNNN';

-- -----------------------------------------------------------------------------
-- 6.2 Calculate Priority Score
-- Based on C1 framework: I_weight * (1 + log(span_count)) * decay * recurrence_boost
-- -----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION calculate_priority_score(
    p_max_intensity VARCHAR(2),
    p_span_count INTEGER,
    p_days_old NUMERIC,
    p_recurrence_count INTEGER DEFAULT 0,
    p_has_cr_w BOOLEAN DEFAULT FALSE
) RETURNS NUMERIC AS $$
DECLARE
    intensity_weight NUMERIC;
    decay_factor NUMERIC;
    recurrence_boost NUMERIC;
    trend_modifier NUMERIC;
BEGIN
    -- Intensity weights
    intensity_weight := CASE p_max_intensity
        WHEN 'I1' THEN 1.0
        WHEN 'I2' THEN 2.0
        WHEN 'I3' THEN 4.0
        ELSE 1.0
    END;

    -- Time decay: exp(-lambda * days), lambda = 0.023 (half-life ~30 days)
    decay_factor := EXP(-0.023 * p_days_old);

    -- Recurrence boost: 1.0 + 0.5 * log2(recurrence_count + 1)
    IF p_recurrence_count > 0 THEN
        recurrence_boost := 1.0 + 0.5 * LOG(2, p_recurrence_count + 1);
    ELSE
        recurrence_boost := 1.0;
    END IF;

    -- Trend modifier (CR-W = worsening = 1.3)
    trend_modifier := CASE WHEN p_has_cr_w THEN 1.3 ELSE 1.0 END;

    RETURN ROUND(
        intensity_weight * (1 + LOG(GREATEST(p_span_count, 1))) *
        decay_factor * recurrence_boost * trend_modifier,
        2
    );
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION calculate_priority_score IS
    'Calculate issue priority score per C1 time decay model';

-- -----------------------------------------------------------------------------
-- 6.3 Calculate Confidence Score
-- Based on C1: base_confidence + specificity_bonus, weighted by evidence type
-- -----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION calculate_confidence_score(
    p_span_count INTEGER,
    p_max_specificity VARCHAR(2),
    p_avg_evidence_weight NUMERIC
) RETURNS NUMERIC AS $$
DECLARE
    base_confidence NUMERIC;
    specificity_bonus NUMERIC;
BEGIN
    -- Base confidence by span count
    base_confidence := CASE
        WHEN p_span_count = 1 THEN 0.50
        WHEN p_span_count = 2 THEN 0.70
        WHEN p_span_count = 3 THEN 0.80
        WHEN p_span_count BETWEEN 4 AND 5 THEN 0.85
        WHEN p_span_count BETWEEN 6 AND 10 THEN 0.90
        ELSE 0.95
    END;

    -- Specificity bonus
    specificity_bonus := CASE p_max_specificity
        WHEN 'S1' THEN 0.00
        WHEN 'S2' THEN 0.05
        WHEN 'S3' THEN 0.10
        ELSE 0.00
    END;

    RETURN ROUND(
        LEAST(0.95, base_confidence + specificity_bonus) * COALESCE(p_avg_evidence_weight, 1.0),
        3
    );
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION calculate_confidence_score IS
    'Calculate issue confidence score per C1 framework';

-- =============================================================================
-- SECTION 7: TRIGGERS
-- Automated data maintenance
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 7.1 Auto-update timestamps
-- -----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tr_reviews_updated
    BEFORE UPDATE ON reviews
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER tr_spans_updated
    BEFORE UPDATE ON spans
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER tr_causal_chains_updated
    BEFORE UPDATE ON causal_chains
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- -----------------------------------------------------------------------------
-- 7.2 Refresh materialized views (scheduled separately)
-- -----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION refresh_urt_materialized_views()
RETURNS void AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY mv_domain_summary;
    REFRESH MATERIALIZED VIEW CONCURRENTLY mv_category_breakdown;
    REFRESH MATERIALIZED VIEW mv_issue_pipeline;
    REFRESH MATERIALIZED VIEW mv_cr_trends;
    REFRESH MATERIALIZED VIEW mv_daily_volume;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION refresh_urt_materialized_views IS
    'Refresh all URT materialized views - call periodically via pg_cron or similar';

-- =============================================================================
-- SECTION 8: REFERENCE DATA INSERTS
-- Populate reference tables from B1-urt-codes.yaml
-- =============================================================================

-- -----------------------------------------------------------------------------
-- 8.1 Insert Domains
-- -----------------------------------------------------------------------------
INSERT INTO urt_domains (domain_code, name, description, core_question, default_owner, display_order) VALUES
('O', 'Offering', 'The core product, service, or outcome delivered', 'Does what we provide actually work and meet expectations?', 'Product / Operations', 1),
('P', 'People', 'Human interactions and personnel behavior', 'How do the people we interact with treat us and perform their roles?', 'HR / Training', 2),
('J', 'Journey', 'The process, timing, and operational flow', 'Is the experience smooth, timely, and friction-free?', 'Operations / Process', 3),
('E', 'Environment', 'Physical, digital, and ambient context', 'Is the space where the experience occurs functional, safe, and pleasant?', 'Facilities / IT', 4),
('A', 'Access', 'Availability, accessibility, and inclusivity', 'Can everyone who wants to participate do so fully and fairly?', 'Compliance / Design', 5),
('V', 'Value', 'Cost, pricing, and worth of the exchange', 'Is what I am giving up fair for what I am getting?', 'Finance / Pricing', 6),
('R', 'Relationship', 'Trust, reliability, and ongoing connection', 'Can I trust this business and do they value our relationship?', 'Leadership / CX', 7);

-- -----------------------------------------------------------------------------
-- 8.2 Insert Categories (28 total)
-- -----------------------------------------------------------------------------
INSERT INTO urt_categories (category_code, domain_code, name, definition, display_order) VALUES
-- Offering
('O1', 'O', 'Function', 'Does it do what it is supposed to do?', 1),
('O2', 'O', 'Quality', 'How well is it made or executed?', 2),
('O3', 'O', 'Completeness', 'Is everything included that should be?', 3),
('O4', 'O', 'Fit', 'Does it match the customer specific needs?', 4),
-- People
('P1', 'P', 'Attitude', 'Disposition, manner, and emotional tone', 1),
('P2', 'P', 'Competence', 'Knowledge, skill, and professional capability', 2),
('P3', 'P', 'Responsiveness', 'Attentiveness, initiative, and follow-through', 3),
('P4', 'P', 'Communication', 'Quality of information exchange', 4),
-- Journey
('J1', 'J', 'Timing', 'Speed, punctuality, and time management', 1),
('J2', 'J', 'Ease', 'Effort required and friction encountered', 2),
('J3', 'J', 'Reliability', 'Consistency and predictability of process', 3),
('J4', 'J', 'Resolution', 'How problems are handled when they arise', 4),
-- Environment
('E1', 'E', 'Physical Space', 'Tangible environment attributes', 1),
('E2', 'E', 'Digital Space', 'Online and application interface', 2),
('E3', 'E', 'Ambiance', 'Intangible environmental qualities', 3),
('E4', 'E', 'Safety', 'Security and wellbeing factors', 4),
-- Access
('A1', 'A', 'Availability', 'Can you get it when you need it?', 1),
('A2', 'A', 'Accessibility', 'Can everyone use it regardless of ability?', 2),
('A3', 'A', 'Inclusivity', 'Does it work for diverse backgrounds?', 3),
('A4', 'A', 'Convenience', 'Is it easy to reach and engage with?', 4),
-- Value
('V1', 'V', 'Price', 'The monetary cost', 1),
('V2', 'V', 'Transparency', 'Clarity and honesty about costs', 2),
('V3', 'V', 'Effort', 'Non-monetary costs (time, hassle)', 3),
('V4', 'V', 'Worth', 'Overall value assessment', 4),
-- Relationship
('R1', 'R', 'Integrity', 'Honesty and ethical behavior', 1),
('R2', 'R', 'Dependability', 'Consistency over time', 2),
('R3', 'R', 'Recovery', 'Response to failures', 3),
('R4', 'R', 'Loyalty', 'Investment in ongoing relationship', 4);

-- -----------------------------------------------------------------------------
-- 8.3 Insert Causal Codes (16 total)
-- -----------------------------------------------------------------------------
INSERT INTO urt_causal_codes (causal_code, layer, layer_prefix, name, definition, display_order) VALUES
-- Conditions layer (5 codes)
('CD-S', 'conditions', 'CD-', 'Staff State', 'Fatigue, training, motivation, experience', 1),
('CD-T', 'conditions', 'CD-', 'Team Dynamics', 'Handoffs, coordination, communication', 2),
('CD-E', 'conditions', 'CD-', 'Equipment', 'Malfunction, unavailable, outdated', 3),
('CD-F', 'conditions', 'CD-', 'Facility', 'Maintenance, capacity, hazards', 4),
('CD-O', 'conditions', 'CD-', 'Operational', 'Understaffing, demand surge, time pressure', 5),
-- Management layer (5 codes)
('MG-P', 'management', 'MG-', 'Planning', 'Staffing plans, scheduling, forecasting', 1),
('MG-T', 'management', 'MG-', 'Training', 'Preparation, development, competency', 2),
('MG-O', 'management', 'MG-', 'Oversight', 'Supervision, monitoring, correction', 3),
('MG-R', 'management', 'MG-', 'Resources', 'Maintenance, supplies, equipment', 4),
('MG-C', 'management', 'MG-', 'Communication', 'Policy relay, expectations, culture', 5),
-- Systemic layer (6 codes)
('SY-R', 'systemic', 'SY-', 'Resource Decisions', 'Budget, investment, staffing levels', 1),
('SY-P', 'systemic', 'SY-', 'Policy/Procedure', 'Rules, requirements, bureaucracy', 2),
('SY-C', 'systemic', 'SY-', 'Culture', 'Values, priorities, norms', 3),
('SY-S', 'systemic', 'SY-', 'Standards', 'Quality thresholds, metrics, expectations', 4),
('SY-H', 'systemic', 'SY-', 'Human Capital', 'Compensation, hiring, retention', 5),
('SY-X', 'systemic', 'SY-', 'External', 'Market, regulatory, competitive pressure', 6);

-- -----------------------------------------------------------------------------
-- 8.4 Insert Metadata Values (24 total across 7 dimensions)
-- -----------------------------------------------------------------------------
INSERT INTO urt_metadata_values (dimension_code, value_code, dimension_name, label, definition, is_default, display_order) VALUES
-- Valence (4 values)
('V', 'V+', 'Valence', 'Positive', 'Praise, satisfaction', FALSE, 1),
('V', 'V-', 'Valence', 'Negative', 'Complaint, dissatisfaction', FALSE, 2),
('V', 'V0', 'Valence', 'Neutral', 'Observation without judgment', FALSE, 3),
('V', 'V±', 'Valence', 'Mixed', 'Both positive and negative in same span', FALSE, 4),
-- Intensity (3 values)
('I', 'I1', 'Intensity', 'Mild', 'Slight preference/concern', FALSE, 1),
('I', 'I2', 'Intensity', 'Moderate', 'Clear but not extreme', FALSE, 2),
('I', 'I3', 'Intensity', 'Strong', 'Emphatic or intense', FALSE, 3),
-- Specificity (3 values)
('S', 'S1', 'Specificity', 'Vague', 'General impression only', FALSE, 1),
('S', 'S2', 'Specificity', 'Moderate', 'Some details or context', FALSE, 2),
('S', 'S3', 'Specificity', 'Specific', 'Concrete details, names, times, amounts', FALSE, 3),
-- Actionability (3 values)
('A', 'A1', 'Actionability', 'Low', 'Feeling with no clear action path', FALSE, 1),
('A', 'A2', 'Actionability', 'Medium', 'Suggests improvement area', FALSE, 2),
('A', 'A3', 'Actionability', 'High', 'Specific implementable feedback', FALSE, 3),
-- Temporal (4 values)
('T', 'TC', 'Temporal Reference', 'Current', 'This specific visit/experience', TRUE, 1),
('T', 'TR', 'Temporal Reference', 'Recent', 'Recent pattern of experiences', FALSE, 2),
('T', 'TH', 'Temporal Reference', 'Historical', 'Long-standing pattern', FALSE, 3),
('T', 'TF', 'Temporal Reference', 'Future', 'Expectations or predictions', FALSE, 4),
-- Evidence (3 values)
('E', 'ES', 'Evidence Type', 'Stated', 'Explicitly said by customer', TRUE, 1),
('E', 'EI', 'Evidence Type', 'Inferred', 'Logically entailed by text', FALSE, 2),
('E', 'EC', 'Evidence Type', 'Contextual', 'Requires surrounding text', FALSE, 3),
-- Comparative Reference (4 values)
('CR', 'CR-N', 'Comparative Reference', 'None', 'No comparison to previous state', TRUE, 1),
('CR', 'CR-B', 'Comparative Reference', 'Better', 'Explicit improvement vs. before', FALSE, 2),
('CR', 'CR-W', 'Comparative Reference', 'Worse', 'Explicit decline vs. before', FALSE, 3),
('CR', 'CR-S', 'Comparative Reference', 'Same', 'Explicitly unchanged', FALSE, 4);

-- -----------------------------------------------------------------------------
-- 8.5 Insert Subcodes (sample - O domain)
-- Full 138 subcodes would be inserted similarly
-- -----------------------------------------------------------------------------
INSERT INTO urt_subcodes (subcode, category_code, domain_code, name, definition, positive_example, negative_example, dont_confuse_with, dont_confuse_reason, display_order) VALUES
-- O1: Function
('O1.01', 'O1', 'O', 'Works/Doesn''t Work', 'Basic functionality success or failure', 'Software runs perfectly', 'Car won''t start', 'J3.03', 'J3.03 is system uptime, O1.01 is product function', 1),
('O1.02', 'O1', 'O', 'Performance Level', 'How well it operates', 'Incredibly fast processor', 'Sluggish and laggy', 'E2.03', 'E2.03 is interface speed, O1.02 is product performance', 2),
('O1.03', 'O1', 'O', 'Durability', 'Longevity and resistance to wear', 'Still perfect after 5 years', 'Fell apart in a month', 'O2.01', 'O2.01 is material quality, O1.03 is longevity', 3),
('O1.04', 'O1', 'O', 'Reliability', 'Consistency of function over time', 'Never fails me', 'Works sometimes, not others', 'J3.01', 'J3.01 is process consistency, O1.04 is product reliability', 4),
('O1.05', 'O1', 'O', 'Outcome Achievement', 'Did customer accomplish their goal?', 'Passed my exam!', 'Treatment didn''t work', 'V4.03', 'V4.03 is satisfaction with exchange, O1.05 is goal achievement', 5),
-- O2: Quality
('O2.01', 'O2', 'O', 'Materials/Inputs', 'Quality of components or ingredients', 'Real leather, premium feel', 'Cheap plastic parts', 'O1.03', 'O1.03 is durability, O2.01 is material quality', 1),
('O2.02', 'O2', 'O', 'Craftsmanship', 'Skill of construction or execution', 'Beautifully sewn seams', 'Sloppy assembly', 'P2.02', 'P2.02 is staff skill, O2.02 is product craftsmanship', 2),
('O2.03', 'O2', 'O', 'Presentation', 'Visual and aesthetic quality', 'Gorgeous plating', 'Looked thrown together', 'E3.05', 'E3.05 is space aesthetics, O2.03 is product presentation', 3),
('O2.04', 'O2', 'O', 'Attention to Detail', 'Finishing touches and refinement', 'Every corner perfect', 'Full of typos', 'O3.01', 'O3.01 is completeness, O2.04 is refinement', 4),
('O2.05', 'O2', 'O', 'Condition at Delivery', 'State when received', 'Still warm from oven', 'Arrived damaged', 'J3.02', 'J3.02 is process accuracy, O2.05 is delivery condition', 5),
-- O3: Completeness
('O3.01', 'O3', 'O', 'All Components Present', 'Nothing missing from what was promised', 'Everything in the box', 'Missing the charger', 'O4.01', 'O4.01 is spec match, O3.01 is completeness', 1),
('O3.02', 'O3', 'O', 'Feature Availability', 'Promised features actually work', 'All menu items available', 'Half the features disabled', 'A1.03', 'A1.03 is inventory, O3.02 is feature availability', 2),
('O3.03', 'O3', 'O', 'Scope Delivery', 'Full scope of work completed', 'Cleaned entire house', 'Left the bathrooms', 'J4.04', 'J4.04 is resolution quality, O3.03 is scope delivery', 3),
('O3.04', 'O3', 'O', 'Documentation', 'Supporting materials provided', 'Great user manual', 'No instructions at all', 'J2.01', 'J2.01 is process simplicity, O3.04 is documentation as product artifact', 4),
-- O4: Fit
('O4.01', 'O4', 'O', 'Specification Match', 'Matches what was ordered', 'Exactly what I ordered', 'Wrong size delivered', 'J3.02', 'J3.02 is execution accuracy, O4.01 is spec match', 1),
('O4.02', 'O4', 'O', 'Personalization', 'Adapted to individual preferences', 'Remembered my usual', 'No way to save prefs', 'P3.01', 'P3.01 is attentiveness, O4.02 is personalization', 2),
('O4.03', 'O4', 'O', 'Flexibility', 'Can be modified or adjusted', 'Happy to substitute', 'No modifications allowed', 'V2.04', 'V2.04 is policy fairness, O4.03 is flexibility', 3),
('O4.04', 'O4', 'O', 'Appropriateness', 'Right solution for the need', 'Perfect recommendation', 'Sold me wrong thing', 'P2.01', 'P2.01 is knowledge, O4.04 is appropriateness', 4);

-- Note: Additional subcodes for P, J, E, A, V, R domains would be inserted similarly.
-- The full B1-urt-codes.yaml contains 138 subcodes total.
-- For production, generate INSERT statements from the YAML source.

-- =============================================================================
-- SECTION 9: PARTITIONING STRATEGY (for high-volume scenarios)
-- =============================================================================
--
-- For deployments expecting >1M spans/month, consider partitioning:
--
-- 1. Spans table: Partition by created_at (monthly)
--    CREATE TABLE spans (...) PARTITION BY RANGE (created_at);
--    CREATE TABLE spans_2026_01 PARTITION OF spans
--        FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
--
-- 2. Reviews table: Same monthly partitioning
--
-- 3. Annotation audit: Partition by annotated_at
--
-- 4. Issue state history: Partition by transitioned_at
--
-- Benefits:
--   - Faster queries on recent data (partition pruning)
--   - Easier data retention management (drop old partitions)
--   - Parallel query execution across partitions
--
-- =============================================================================

-- =============================================================================
-- SECTION 10: GRANTS AND SECURITY
-- =============================================================================

-- Create application roles
-- CREATE ROLE urt_reader;
-- CREATE ROLE urt_writer;
-- CREATE ROLE urt_admin;

-- Reader permissions (dashboards, analytics)
-- GRANT SELECT ON ALL TABLES IN SCHEMA public TO urt_reader;
-- GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO urt_reader;

-- Writer permissions (classification pipeline)
-- GRANT SELECT, INSERT, UPDATE ON reviews, spans, span_classifications,
--       causal_chains, issues, issue_spans, issue_state_history,
--       issue_resolutions, annotation_audit, classification_history TO urt_writer;
-- GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO urt_writer;

-- Admin permissions (schema management)
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO urt_admin;
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO urt_admin;

-- =============================================================================
-- END OF SCHEMA
-- =============================================================================
--
-- Post-deployment checklist:
-- 1. Run: SELECT refresh_urt_materialized_views();
-- 2. Set up pg_cron job for periodic MV refresh
-- 3. Configure connection pooling (PgBouncer recommended)
-- 4. Set up monitoring for table sizes and index usage
-- 5. Import full subcode data from B1-urt-codes.yaml
--
-- =============================================================================
