-- V5__Add_Normalized_Learning_History.sql
-- Normalized PostgreSQL event store for factor decisions and learning data.

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE SCHEMA IF NOT EXISTS engine_history;

CREATE TABLE IF NOT EXISTS engine_history.source_observation (
    observation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    observed_at TIMESTAMPTZ NOT NULL,
    instrument_id TEXT NOT NULL,
    source_name TEXT NOT NULL,
    source_version TEXT NOT NULL,
    payload JSONB NOT NULL,
    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_source_observation_instrument_time
    ON engine_history.source_observation (instrument_id, observed_at DESC);

CREATE TABLE IF NOT EXISTS engine_history.factor_definition (
    factor_id TEXT NOT NULL,
    factor_version TEXT NOT NULL,
    formula_id TEXT NOT NULL,
    effective_from TIMESTAMPTZ NOT NULL,
    effective_to TIMESTAMPTZ,
    definition JSONB NOT NULL DEFAULT '{}'::jsonb,
    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
    PRIMARY KEY (factor_id, factor_version)
);

CREATE TABLE IF NOT EXISTS engine_history.factor_observation (
    factor_observation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    observation_id UUID NOT NULL REFERENCES engine_history.source_observation(observation_id),
    factor_id TEXT NOT NULL,
    factor_version TEXT NOT NULL,
    observed_at TIMESTAMPTZ NOT NULL,
    numeric_value NUMERIC,
    text_value TEXT,
    gate TEXT NOT NULL,
    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
    CONSTRAINT fk_factor_definition
        FOREIGN KEY (factor_id, factor_version)
        REFERENCES engine_history.factor_definition(factor_id, factor_version),
    CONSTRAINT factor_value_present CHECK (numeric_value IS NOT NULL OR text_value IS NOT NULL)
);
CREATE INDEX IF NOT EXISTS ix_factor_observation_factor_time
    ON engine_history.factor_observation (factor_id, observed_at DESC);

CREATE TABLE IF NOT EXISTS engine_history.decision_event (
    decision_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    decision_key TEXT NOT NULL UNIQUE,
    decided_at TIMESTAMPTZ NOT NULL,
    instrument_id TEXT NOT NULL,
    action TEXT NOT NULL,
    gate TEXT NOT NULL,
    score NUMERIC,
    source_version TEXT NOT NULL,
    trace JSONB NOT NULL DEFAULT '{}'::jsonb,
    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_decision_event_instrument_time
    ON engine_history.decision_event (instrument_id, decided_at DESC);

CREATE TABLE IF NOT EXISTS engine_history.decision_factor_evidence (
    decision_id UUID NOT NULL REFERENCES engine_history.decision_event(decision_id),
    factor_observation_id UUID NOT NULL REFERENCES engine_history.factor_observation(factor_observation_id),
    role TEXT NOT NULL,
    PRIMARY KEY (decision_id, factor_observation_id)
);

CREATE TABLE IF NOT EXISTS engine_history.outcome_evaluation (
    evaluation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    decision_id UUID NOT NULL REFERENCES engine_history.decision_event(decision_id),
    horizon_days INTEGER NOT NULL CHECK (horizon_days > 0),
    evaluated_at TIMESTAMPTZ NOT NULL,
    realized_return NUMERIC,
    benchmark_return NUMERIC,
    excess_return NUMERIC,
    outcome_class TEXT NOT NULL,
    evaluation_gate TEXT NOT NULL,
    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
    UNIQUE (decision_id, horizon_days)
);

-- Read-optimized projection for model training and calibration jobs.
CREATE OR REPLACE VIEW engine_history.training_example_v1 AS
SELECT
    d.decision_id,
    d.decision_key,
    d.decided_at,
    d.instrument_id,
    d.action,
    d.gate AS decision_gate,
    d.score,
    d.source_version,
    e.horizon_days,
    e.realized_return,
    e.benchmark_return,
    e.excess_return,
    e.outcome_class,
    e.evaluation_gate,
    jsonb_agg(jsonb_build_object(
        'factor_id', f.factor_id,
        'factor_version', f.factor_version,
        'numeric_value', f.numeric_value,
        'text_value', f.text_value,
        'gate', f.gate,
        'role', evidence.role
    ) ORDER BY f.factor_id) AS factor_features
FROM engine_history.decision_event d
JOIN engine_history.outcome_evaluation e ON e.decision_id = d.decision_id
JOIN engine_history.decision_factor_evidence evidence ON evidence.decision_id = d.decision_id
JOIN engine_history.factor_observation f ON f.factor_observation_id = evidence.factor_observation_id
GROUP BY d.decision_id, d.decision_key, d.decided_at, d.instrument_id, d.action,
         d.gate, d.score, d.source_version, e.horizon_days, e.realized_return,
         e.benchmark_return, e.excess_return, e.outcome_class, e.evaluation_gate;
