BEGIN;

CREATE SCHEMA IF NOT EXISTS mv_loss_intelligence;

COMMENT ON SCHEMA mv_loss_intelligence IS
'Capa común MESAVAULT LossIntelligence. Conserva configuración, catálogo de acciones, Loss Ledger, pruebas y decisiones. No sustituye las tablas raw de captura.';

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.deployment_config (
    id BIGSERIAL PRIMARY KEY,
    tenant TEXT NOT NULL,
    site TEXT NOT NULL,
    scope_code TEXT NOT NULL,
    scope_name TEXT NOT NULL,
    mode TEXT NOT NULL DEFAULT 'SHADOW'
        CHECK (mode IN ('SHADOW', 'ACTIVE', 'PAUSED', 'CLOSED')),
    planned_go_live_date DATE,
    actual_go_live_at TIMESTAMPTZ,
    timezone TEXT NOT NULL DEFAULT 'Europe/Madrid',
    review_owner_primary TEXT,
    review_owner_secondary TEXT,
    review_cadence TEXT NOT NULL DEFAULT 'DAILY',
    daily_review_limit INTEGER NOT NULL DEFAULT 5
        CHECK (daily_review_limit BETWEEN 1 AND 20),
    notes TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (tenant, site, scope_code),
    CHECK (actual_go_live_at IS NULL OR planned_go_live_date IS NULL OR actual_go_live_at::date >= planned_go_live_date - 31)
);

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.economic_parameters (
    id BIGSERIAL PRIMARY KEY,
    tenant TEXT NOT NULL,
    site TEXT NOT NULL,
    scope_code TEXT NOT NULL DEFAULT 'GLOBAL',
    machine_id TEXT,
    product_id BIGINT,
    parameter_code TEXT NOT NULL,
    parameter_value NUMERIC(18,6) NOT NULL,
    unit TEXT NOT NULL,
    confidence TEXT NOT NULL DEFAULT 'LOW'
        CHECK (confidence IN ('HIGH', 'MEDIUM', 'LOW', 'NOT_ACCEPTED')),
    includes_energy BOOLEAN,
    valid_from TIMESTAMPTZ NOT NULL DEFAULT now(),
    valid_to TIMESTAMPTZ,
    is_active BOOLEAN NOT NULL DEFAULT true,
    source_ref TEXT,
    approved_by TEXT,
    notes TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    CHECK (valid_to IS NULL OR valid_to > valid_from)
);

CREATE INDEX IF NOT EXISTS idx_li_economic_parameters_lookup
ON mv_loss_intelligence.economic_parameters
(tenant, site, parameter_code, is_active, valid_from DESC);

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.product_cycle_parameters (
    id BIGSERIAL PRIMARY KEY,
    tenant TEXT NOT NULL,
    site TEXT NOT NULL,
    machine_id TEXT,
    product_id BIGINT,
    product_default_code TEXT,
    product_name TEXT,
    units_per_cycle NUMERIC(10,4) NOT NULL
        CHECK (units_per_cycle > 0),
    target_cycle_rate_ppm NUMERIC(12,4)
        CHECK (target_cycle_rate_ppm IS NULL OR target_cycle_rate_ppm > 0),
    target_units_per_min NUMERIC(14,4)
        GENERATED ALWAYS AS (
            CASE
                WHEN target_cycle_rate_ppm IS NULL THEN NULL
                ELSE target_cycle_rate_ppm * units_per_cycle
            END
        ) STORED,
    confidence TEXT NOT NULL DEFAULT 'LOW'
        CHECK (confidence IN ('HIGH', 'MEDIUM', 'LOW')),
    source_ref TEXT,
    approved_by TEXT,
    valid_from TIMESTAMPTZ NOT NULL DEFAULT now(),
    valid_to TIMESTAMPTZ,
    is_active BOOLEAN NOT NULL DEFAULT true,
    notes TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    CHECK (product_id IS NOT NULL OR product_default_code IS NOT NULL),
    CHECK (valid_to IS NULL OR valid_to > valid_from)
);

CREATE INDEX IF NOT EXISTS idx_li_product_cycle_parameters_lookup
ON mv_loss_intelligence.product_cycle_parameters
(tenant, site, product_id, product_default_code, machine_id, is_active, valid_from DESC);

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.loss_policies (
    id BIGSERIAL PRIMARY KEY,
    tenant TEXT NOT NULL,
    site TEXT NOT NULL,
    policy_code TEXT NOT NULL,
    source_type TEXT NOT NULL DEFAULT 'MACHINE_STOP',
    noise_below_s NUMERIC(12,2) NOT NULL DEFAULT 5
        CHECK (noise_below_s >= 0),
    aggregate_below_s NUMERIC(12,2) NOT NULL DEFAULT 300
        CHECK (aggregate_below_s >= noise_below_s),
    operator_individual_from_s NUMERIC(12,2) NOT NULL DEFAULT 300
        CHECK (operator_individual_from_s >= noise_below_s),
    aggregation_window_min INTEGER NOT NULL DEFAULT 15
        CHECK (aggregation_window_min BETWEEN 1 AND 1440),
    require_valid_order BOOLEAN NOT NULL DEFAULT true,
    require_closed_event BOOLEAN NOT NULL DEFAULT true,
    exclude_planned_by_default BOOLEAN NOT NULL DEFAULT true,
    is_active BOOLEAN NOT NULL DEFAULT true,
    notes TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (tenant, site, policy_code)
);

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.action_catalog (
    action_id TEXT PRIMARY KEY,
    catalog_code TEXT NOT NULL,
    catalog_version TEXT NOT NULL,
    tenant TEXT,
    site TEXT,
    loss_type TEXT NOT NULL,
    source_cause_code TEXT,
    action_type TEXT NOT NULL
        CHECK (action_type IN ('TECHNICAL', 'METHOD', 'ORGANIZATIONAL', 'PROCESS_DESIGN', 'DATA_QUALITY', 'INVESTMENT_DECISION')),
    action_candidate TEXT NOT NULL,
    applies_when JSONB NOT NULL DEFAULT '[]'::jsonb,
    does_not_apply_when JSONB NOT NULL DEFAULT '[]'::jsonb,
    suggested_owner TEXT,
    test_window TEXT NOT NULL,
    metric_before TEXT NOT NULL,
    metric_after TEXT NOT NULL,
    success_criteria TEXT NOT NULL,
    risk_level TEXT NOT NULL DEFAULT 'LOW'
        CHECK (risk_level IN ('LOW', 'MEDIUM', 'HIGH')),
    cost_to_test TEXT NOT NULL DEFAULT 'LOW'
        CHECK (cost_to_test IN ('LOW', 'MEDIUM', 'HIGH')),
    is_active BOOLEAN NOT NULL DEFAULT true,
    source_ref TEXT,
    notes TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_li_action_catalog_lookup
ON mv_loss_intelligence.action_catalog
(loss_type, source_cause_code, is_active);

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.loss_ledger (
    loss_id BIGSERIAL PRIMARY KEY,
    tenant TEXT NOT NULL,
    site TEXT NOT NULL,
    deployment_id BIGINT REFERENCES mv_loss_intelligence.deployment_config(id),
    ledger_phase TEXT NOT NULL DEFAULT 'SHADOW'
        CHECK (ledger_phase IN ('SHADOW', 'ACTIVE')),
    source_type TEXT NOT NULL,
    source_key TEXT NOT NULL,
    machine_id TEXT,
    asset TEXT,
    order_ref TEXT,
    odoo_production_id BIGINT,
    odoo_workorder_id BIGINT,
    product_id BIGINT,
    product_default_code TEXT,
    product_name TEXT,
    started_at TIMESTAMPTZ,
    ended_at TIMESTAMPTZ,
    event_count INTEGER NOT NULL DEFAULT 1 CHECK (event_count > 0),
    duration_s NUMERIC(18,2) NOT NULL DEFAULT 0 CHECK (duration_s >= 0),
    cycle_count NUMERIC(18,4),
    units_per_cycle NUMERIC(10,4),
    estimated_units NUMERIC(18,4),
    loss_type TEXT NOT NULL,
    cause_code TEXT,
    classification_status TEXT NOT NULL DEFAULT 'PENDING'
        CHECK (classification_status IN ('PENDING', 'CLASSIFIED', 'EXCLUDED', 'NOT_REQUIRED')),
    capacity_loss_eur NUMERIC(18,2),
    energy_waste_kwh NUMERIC(18,6),
    energy_waste_eur NUMERIC(18,2),
    technical_confidence TEXT NOT NULL DEFAULT 'MEDIUM'
        CHECK (technical_confidence IN ('HIGH', 'MEDIUM', 'LOW')),
    economic_confidence TEXT NOT NULL DEFAULT 'LOW'
        CHECK (economic_confidence IN ('HIGH', 'MEDIUM', 'LOW', 'NOT_ACCEPTED')),
    priority_score NUMERIC(12,2),
    priority_level TEXT
        CHECK (priority_level IS NULL OR priority_level IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')),
    action_catalog_id TEXT REFERENCES mv_loss_intelligence.action_catalog(action_id),
    action_candidate TEXT,
    action_final TEXT,
    review_owner TEXT,
    review_due_at TIMESTAMPTZ,
    status TEXT NOT NULL DEFAULT 'DETECTED'
        CHECK (status IN ('DETECTED', 'CANDIDATE', 'IN_REVIEW', 'IN_TRIAL', 'VALIDATED', 'ADAPTED', 'DISCARDED', 'ESCALATED', 'CLOSED')),
    learning_reusable TEXT,
    evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
    detected_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    promoted_at TIMESTAMPTZ,
    closed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (tenant, site, source_type, source_key)
);

CREATE INDEX IF NOT EXISTS idx_li_loss_ledger_review
ON mv_loss_intelligence.loss_ledger
(tenant, site, status, review_due_at, priority_score DESC);

CREATE INDEX IF NOT EXISTS idx_li_loss_ledger_order_machine
ON mv_loss_intelligence.loss_ledger
(tenant, site, machine_id, order_ref, started_at DESC);

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.action_trials (
    trial_id BIGSERIAL PRIMARY KEY,
    loss_id BIGINT NOT NULL REFERENCES mv_loss_intelligence.loss_ledger(loss_id) ON DELETE CASCADE,
    trial_code TEXT NOT NULL,
    action_final TEXT NOT NULL,
    owner TEXT NOT NULL,
    baseline_from TIMESTAMPTZ,
    baseline_to TIMESTAMPTZ,
    test_from TIMESTAMPTZ,
    test_to TIMESTAMPTZ,
    metric_code TEXT NOT NULL,
    metric_unit TEXT,
    metric_before NUMERIC(18,6),
    metric_after NUMERIC(18,6),
    change_pct NUMERIC(12,4),
    success_criteria TEXT NOT NULL,
    result TEXT
        CHECK (result IS NULL OR result IN ('PENDING', 'SUCCESS', 'PARTIAL', 'NO_CHANGE', 'WORSE', 'INVALID_TEST')),
    decision TEXT
        CHECK (decision IS NULL OR decision IN ('KEEP', 'ADAPT', 'REPEAT', 'DISCARD', 'SCALE')),
    learning TEXT,
    evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (loss_id, trial_code)
);

CREATE TABLE IF NOT EXISTS mv_loss_intelligence.review_decisions (
    decision_id BIGSERIAL PRIMARY KEY,
    loss_id BIGINT REFERENCES mv_loss_intelligence.loss_ledger(loss_id) ON DELETE CASCADE,
    trial_id BIGINT REFERENCES mv_loss_intelligence.action_trials(trial_id) ON DELETE CASCADE,
    decision_type TEXT NOT NULL
        CHECK (decision_type IN ('PROMOTE', 'CLASSIFY', 'ASSIGN_ACTION', 'ADAPT_ACTION', 'DEFER', 'EXCLUDE', 'CLOSE', 'ESCALATE')),
    decision_text TEXT NOT NULL,
    decided_by TEXT NOT NULL,
    decided_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    next_review_at TIMESTAMPTZ,
    evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
    CHECK (loss_id IS NOT NULL OR trial_id IS NOT NULL)
);

DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ucepsa_ro') THEN
        GRANT USAGE ON SCHEMA mv_loss_intelligence TO grafana_ucepsa_ro;
        GRANT SELECT ON ALL TABLES IN SCHEMA mv_loss_intelligence TO grafana_ucepsa_ro;
    END IF;
END $$;

COMMIT;
