DROP VIEW IF EXISTS mv_reports_ucepsa_demo.loss_priority_board;
DROP VIEW IF EXISTS mv_reports_ucepsa_demo.loss_events_current;

CREATE VIEW mv_reports_ucepsa_demo.loss_events_current AS
WITH open_stop_losses AS (
    SELECT
        now()::timestamptz AS detected_at,
        s.machine_id::text AS machine_id,
        COALESCE(s.asset, '')::text AS asset,
        COALESCE(s.order_id, '')::text AS order_ref,
        'OPEN_STOP'::text AS loss_type,
        CASE
            WHEN COALESCE(s.current_duration_min, 0) >= 15 THEN 'ACTION_REQUIRED'
            WHEN COALESCE(s.current_duration_min, 0) >= 5 THEN 'ATTENTION'
            ELSE 'INFO'
        END AS severity,
        CASE
            WHEN COALESCE(s.current_duration_min, 0) >= 15 THEN 80
            WHEN COALESCE(s.current_duration_min, 0) >= 5 THEN 55
            ELSE 30
        END::int AS severity_score,
        COALESCE(s.current_duration_min, 0)::numeric AS duration_min,
        COALESCE(s.current_cost_eur, 0)::numeric AS cost_eur,
        COALESCE(s.status, 'OPEN')::text AS status,
        'Paro abierto pendiente de cierre o clasificación.'::text AS description,
        'Comprobar causa en máquina y clasificar el paro desde la consola.'::text AS recommended_action,
        jsonb_build_object(
            'stop_id', s.id,
            'classification_status', s.classification_status,
            'cause_name', s.cause_name,
            'started_at', s.started_at,
            'source', 'mv_reports_ucepsa_demo.open_stops'
        ) AS evidence_json
    FROM mv_reports_ucepsa_demo.open_stops s
),

unclassified_closed_stop_losses AS (
    SELECT
        now()::timestamptz AS detected_at,
        s.machine_id::text AS machine_id,
        COALESCE(s.asset, '')::text AS asset,
        COALESCE(s.order_id, '')::text AS order_ref,
        'UNCLASSIFIED_STOP'::text AS loss_type,
        CASE
            WHEN COALESCE(s.duration_min, 0) >= 15 THEN 'ACTION_REQUIRED'
            ELSE 'ATTENTION'
        END AS severity,
        CASE
            WHEN COALESCE(s.duration_min, 0) >= 15 THEN 75
            ELSE 55
        END::int AS severity_score,
        COALESCE(s.duration_min, 0)::numeric AS duration_min,
        COALESCE(s.cost_eur, 0)::numeric AS cost_eur,
        COALESCE(s.status, 'CLOSED')::text AS status,
        'Paro cerrado pendiente de clasificación.'::text AS description,
        'Asignar causa para evitar pérdida sin explicación.'::text AS recommended_action,
        jsonb_build_object(
            'stop_id', s.id,
            'classification_status', s.classification_status,
            'cause_name', s.cause_name,
            'started_at', s.started_at,
            'ended_at', s.ended_at,
            'source', 'mv_reports_ucepsa_demo.stops_recent'
        ) AS evidence_json
    FROM mv_reports_ucepsa_demo.stops_recent s
    WHERE s.status = 'CLOSED'
      AND s.classification_status = 'PENDING'
),

odoo_machine_losses AS (
    SELECT
        now()::timestamptz AS detected_at,
        o.machine_id::text AS machine_id,
        COALESCE(o.asset, '')::text AS asset,
        COALESCE(o.odoo_order_ref, '')::text AS order_ref,
        o.status_check::text AS loss_type,
        CASE
            WHEN o.status_check IN ('ORDEN_EN_CURSO_MAQUINA_PARADA', 'MAQUINA_EN_MARCHA_SIN_ORDEN_INICIADA') THEN 'ACTION_REQUIRED'
            WHEN o.status_check = 'SIN_TELEMETRIA' THEN 'ATTENTION'
            ELSE 'INFO'
        END AS severity,
        CASE
            WHEN o.status_check = 'ORDEN_EN_CURSO_MAQUINA_PARADA' THEN 85
            WHEN o.status_check = 'MAQUINA_EN_MARCHA_SIN_ORDEN_INICIADA' THEN 75
            WHEN o.status_check = 'SIN_TELEMETRIA' THEN 60
            ELSE 20
        END::int AS severity_score,
        COALESCE(o.stop_min, 0)::numeric AS duration_min,
        COALESCE(o.stop_cost_eur, 0)::numeric AS cost_eur,
        o.status_check::text AS status,
        CASE
            WHEN o.status_check = 'ORDEN_EN_CURSO_MAQUINA_PARADA'
                THEN 'Odoo indica orden en curso, pero la máquina está parada.'
            WHEN o.status_check = 'MAQUINA_EN_MARCHA_SIN_ORDEN_INICIADA'
                THEN 'La máquina está en marcha, pero la orden no aparece iniciada en Odoo.'
            WHEN o.status_check = 'SIN_TELEMETRIA'
                THEN 'No hay telemetría reciente para contrastar Odoo contra máquina.'
            ELSE 'Sin discrepancia relevante.'
        END AS description,
        CASE
            WHEN o.status_check = 'ORDEN_EN_CURSO_MAQUINA_PARADA'
                THEN 'Revisar paro real, operario, causa pendiente y estado de máquina.'
            WHEN o.status_check = 'MAQUINA_EN_MARCHA_SIN_ORDEN_INICIADA'
                THEN 'Revisar disciplina de inicio de orden en Odoo o desfase de sincronización.'
            WHEN o.status_check = 'SIN_TELEMETRIA'
                THEN 'Revisar RevPi, MQTT, VPN/red, contador y sink.'
            ELSE 'Sin acción.'
        END AS recommended_action,
        jsonb_build_object(
            'odoo_order_ref', o.odoo_order_ref,
            'odoo_state', o.odoo_state,
            'machine_running', o.machine_running,
            'telemetry_age_s', o.telemetry_age_s,
            'real_cycles', o.real_cycles,
            'planned_cycles', o.planned_cycles,
            'cycle_completion_pct', o.cycle_completion_pct,
            'source', 'mv_reports_ucepsa_demo.odoo_vs_machine_demo'
        ) AS evidence_json
    FROM mv_reports_ucepsa_demo.odoo_vs_machine_demo o
    WHERE o.status_check IS DISTINCT FROM 'OK'
),

telemetry_losses AS (
    SELECT
        now()::timestamptz AS detected_at,
        l.machine_id::text AS machine_id,
        COALESCE(l.asset, '')::text AS asset,
        COALESCE(l.order_id, '')::text AS order_ref,
        'STALE_OR_BAD_TELEMETRY'::text AS loss_type,
        CASE
            WHEN COALESCE(l.comm_ok, false) = false THEN 'ACTION_REQUIRED'
            WHEN COALESCE(l.age_s, 999999) > 120 THEN 'ACTION_REQUIRED'
            WHEN COALESCE(l.age_s, 999999) > 30 THEN 'ATTENTION'
            ELSE 'INFO'
        END AS severity,
        CASE
            WHEN COALESCE(l.comm_ok, false) = false THEN 80
            WHEN COALESCE(l.age_s, 999999) > 120 THEN 75
            WHEN COALESCE(l.age_s, 999999) > 30 THEN 45
            ELSE 10
        END::int AS severity_score,
        0::numeric AS duration_min,
        0::numeric AS cost_eur,
        CASE
            WHEN COALESCE(l.comm_ok, false) = false THEN 'BAD_COMMUNICATION'
            WHEN COALESCE(l.age_s, 999999) > 120 THEN 'STALE_TELEMETRY'
            WHEN COALESCE(l.age_s, 999999) > 30 THEN 'DELAYED_TELEMETRY'
            ELSE 'OK'
        END::text AS status,
        CASE
            WHEN COALESCE(l.comm_ok, false) = false THEN 'La RevPi publica, pero la lectura Modbus no es válida.'
            WHEN COALESCE(l.age_s, 999999) > 120 THEN 'No hay telemetría reciente de máquina.'
            WHEN COALESCE(l.age_s, 999999) > 30 THEN 'La telemetría llega con retraso.'
            ELSE 'Telemetría correcta.'
        END AS description,
        CASE
            WHEN COALESCE(l.comm_ok, false) = false THEN 'Revisar RS-485, SDM120-M, alimentación del contador y logs de la RevPi.'
            WHEN COALESCE(l.age_s, 999999) > 120 THEN 'Revisar RevPi, red/VPN, Mosquitto y sink MQTT.'
            WHEN COALESCE(l.age_s, 999999) > 30 THEN 'Revisar estabilidad de red y frecuencia de publicación.'
            ELSE 'Sin acción.'
        END AS recommended_action,
        jsonb_build_object(
            'age_s', l.age_s,
            'comm_ok', l.comm_ok,
            'error_text', l.error_text,
            'ingest_ts', l.ingest_ts,
            'source', 'mv_reports_ucepsa_demo.latest_machine_state_fast'
        ) AS evidence_json
    FROM mv_reports_ucepsa_demo.latest_machine_state_fast l
    WHERE COALESCE(l.comm_ok, false) = false
       OR COALESCE(l.age_s, 999999) > 30
),

low_cycle_rate_losses AS (
    SELECT
        now()::timestamptz AS detected_at,
        l.machine_id::text AS machine_id,
        COALESCE(l.asset, '')::text AS asset,
        COALESCE(o.odoo_order_ref, '')::text AS order_ref,
        'LOW_CYCLE_RATE'::text AS loss_type,
        CASE
            WHEN COALESCE(l.cycle_rate_ppm, 0) <= 0 THEN 'ATTENTION'
            WHEN COALESCE(l.cycle_rate_ppm, 0) < 7 THEN 'ATTENTION'
            ELSE 'INFO'
        END AS severity,
        CASE
            WHEN COALESCE(l.cycle_rate_ppm, 0) <= 0 THEN 50
            WHEN COALESCE(l.cycle_rate_ppm, 0) < 7 THEN 40
            ELSE 10
        END::int AS severity_score,
        0::numeric AS duration_min,
        0::numeric AS cost_eur,
        'LOW_RATE'::text AS status,
        'La máquina aparece en marcha, pero la velocidad de ciclos/golpes es baja respecto al objetivo inicial.'::text AS description,
        'Revisar golpes reales, sensor I_2, velocidad programada, material, ajuste de máquina y criterio de bolsas por golpe.'::text AS recommended_action,
        jsonb_build_object(
            'cycle_rate_ppm', l.cycle_rate_ppm,
            'cycle_pulse_count', l.cycle_pulse_count,
            'machine_running', l.machine_running,
            'target_cycle_rate_ppm_assumed', 10,
            'odoo_order_ref', o.odoo_order_ref,
            'source', 'mv_reports_ucepsa_demo.latest_machine_state_fast'
        ) AS evidence_json
    FROM mv_reports_ucepsa_demo.latest_machine_state_fast l
    LEFT JOIN mv_reports_ucepsa_demo.odoo_current_order_demo o
        ON o.machine_id = l.machine_id
    WHERE COALESCE(l.age_s, 999999) <= 120
      AND COALESCE(l.machine_running, false) = true
      AND COALESCE(l.cycle_rate_ppm, 0) < 7
)

SELECT *
FROM open_stop_losses

UNION ALL

SELECT *
FROM unclassified_closed_stop_losses

UNION ALL

SELECT *
FROM odoo_machine_losses

UNION ALL

SELECT *
FROM telemetry_losses

UNION ALL

SELECT *
FROM low_cycle_rate_losses;

CREATE VIEW mv_reports_ucepsa_demo.loss_priority_board AS
SELECT
    row_number() OVER (
        ORDER BY
            (
                severity_score
                + LEAST((COALESCE(cost_eur, 0) * 10), 50)::int
                + LEAST((COALESCE(duration_min, 0) * 2), 40)::int
            ) DESC,
            detected_at DESC
    ) AS priority_rank,
    (
        severity_score
        + LEAST((COALESCE(cost_eur, 0) * 10), 50)::int
        + LEAST((COALESCE(duration_min, 0) * 2), 40)::int
    ) AS priority_score,
    CASE
        WHEN (
            severity_score
            + LEAST((COALESCE(cost_eur, 0) * 10), 50)::int
            + LEAST((COALESCE(duration_min, 0) * 2), 40)::int
        ) >= 90 THEN 'CRÍTICA'
        WHEN (
            severity_score
            + LEAST((COALESCE(cost_eur, 0) * 10), 50)::int
            + LEAST((COALESCE(duration_min, 0) * 2), 40)::int
        ) >= 60 THEN 'ALTA'
        WHEN (
            severity_score
            + LEAST((COALESCE(cost_eur, 0) * 10), 50)::int
            + LEAST((COALESCE(duration_min, 0) * 2), 40)::int
        ) >= 35 THEN 'MEDIA'
        ELSE 'BAJA'
    END AS priority_level,
    detected_at,
    machine_id,
    asset,
    order_ref,
    loss_type,
    severity,
    duration_min,
    cost_eur,
    status,
    description,
    recommended_action,
    evidence_json
FROM mv_reports_ucepsa_demo.loss_events_current
ORDER BY priority_score DESC, detected_at DESC;

GRANT SELECT ON mv_reports_ucepsa_demo.loss_events_current TO grafana_ucepsa_ro;
GRANT SELECT ON mv_reports_ucepsa_demo.loss_priority_board TO grafana_ucepsa_ro;
