DROP VIEW IF EXISTS mv_reports_ucepsa_demo.odoo_discrepancies_demo; DROP VIEW IF EXISTS mv_reports_ucepsa_demo.odoo_vs_machine_demo; CREATE VIEW mv_reports_ucepsa_demo.odoo_vs_machine_demo AS WITH odoo AS ( SELECT * FROM mv_reports_ucepsa_demo.odoo_current_order_demo ), machine AS ( SELECT * FROM mv_reports_ucepsa_demo.latest_machine_state_fast WHERE machine_id = 'CORT-01' ORDER BY ingest_ts DESC LIMIT 1 ), stops AS ( SELECT o.odoo_workorder_id, COUNT(s.*)::int AS stop_count, COALESCE(ROUND(SUM(s.duration_min)::numeric, 2), 0) AS stop_min, COALESCE(ROUND(SUM(s.cost_eur)::numeric, 2), 0) AS stop_cost_eur FROM odoo o LEFT JOIN mv_reports_ucepsa_demo.stops_recent s ON s.machine_id = o.machine_id AND s.started_at >= COALESCE(o.odoo_reference_start, o.odoo_start, o.snapshot_at) AND s.started_at <= COALESCE(o.odoo_end, now()) GROUP BY o.odoo_workorder_id ) SELECT o.odoo_workorder_id, o.odoo_production_id, o.odoo_order_ref, o.odoo_state, o.odoo_is_started, o.product, o.machine_id, o.asset, o.odoo_start, o.odoo_reference_start, o.odoo_end, COALESCE(m.machine_running, false) AS machine_running, m.age_s AS telemetry_age_s, COALESCE(m.cycle_pulse_count, 0) AS cycle_pulse_count, COALESCE(m.cycle_rate_ppm, 0) AS cycle_rate_ppm, ma.first_machine_activity, CASE WHEN o.odoo_is_started = true AND ma.first_machine_activity IS NOT NULL AND COALESCE(o.odoo_reference_start, o.odoo_start) IS NOT NULL THEN ROUND( GREATEST( EXTRACT(EPOCH FROM (ma.first_machine_activity - COALESCE(o.odoo_reference_start, o.odoo_start)))::numeric, 0 ) / 60.0, 2 ) ELSE NULL::numeric END AS start_delay_min, COALESCE(s.stop_count, 0) AS stop_count, COALESCE(s.stop_min, 0) AS stop_min, COALESCE(s.stop_cost_eur, 0) AS stop_cost_eur, COALESCE(m.cycle_pulse_count, 0)::numeric AS real_cycles, COALESCE(o.planned_cycles, o.qty_planned, 0)::numeric AS planned_cycles, CASE WHEN COALESCE(o.planned_cycles, o.qty_planned, 0) > 0 THEN ROUND( ( COALESCE(m.cycle_pulse_count, 0)::numeric / COALESCE(o.planned_cycles, o.qty_planned, 0)::numeric ) * 100, 2 ) ELSE 0::numeric END AS cycle_completion_pct, CASE WHEN m.age_s IS NULL OR m.age_s > 120 THEN 'SIN_TELEMETRIA' WHEN o.odoo_state = 'ready' AND COALESCE(m.machine_running, false) = true THEN 'MAQUINA_EN_MARCHA_SIN_ORDEN_INICIADA' WHEN o.odoo_state = 'ready' AND COALESCE(m.machine_running, false) = false THEN 'ORDEN_PREPARADA_MAQUINA_PARADA' WHEN o.odoo_state IN ('progress', 'in_progress') AND COALESCE(m.machine_running, false) = false THEN 'ORDEN_EN_CURSO_MAQUINA_PARADA' ELSE 'OK' END AS status_check FROM odoo o LEFT JOIN machine m ON true LEFT JOIN LATERAL ( SELECT MIN(r.ingest_ts) AS first_machine_activity FROM mv_hot.edge_oee_eastron_readings r WHERE r.machine_id = o.machine_id AND (r.payload_json ->> 'machine_running')::boolean = true AND r.ingest_ts >= COALESCE(o.odoo_reference_start, o.odoo_start, o.snapshot_at) AND r.ingest_ts <= COALESCE(o.odoo_end, now()) ) ma ON true LEFT JOIN stops s ON s.odoo_workorder_id = o.odoo_workorder_id; CREATE VIEW mv_reports_ucepsa_demo.odoo_discrepancies_demo AS SELECT now() AS checked_at, odoo_order_ref, product, machine_id, status_check, CASE WHEN status_check = 'MAQUINA_EN_MARCHA_SIN_ORDEN_INICIADA' THEN 'La máquina está en marcha, pero la orden de Odoo está preparada o en espera, no iniciada.' WHEN status_check = 'ORDEN_PREPARADA_MAQUINA_PARADA' THEN 'La orden está preparada/en espera y la máquina está parada. No hay desviación productiva activa.' WHEN status_check = 'ORDEN_EN_CURSO_MAQUINA_PARADA' THEN 'La orden está en curso en Odoo, pero la máquina está parada.' WHEN status_check = 'SIN_TELEMETRIA' THEN 'No hay telemetría reciente de máquina.' ELSE 'Sin discrepancia relevante.' END AS descripcion, start_delay_min, stop_count, stop_min, stop_cost_eur, cycle_completion_pct FROM mv_reports_ucepsa_demo.odoo_vs_machine_demo; GRANT SELECT ON mv_reports_ucepsa_demo.odoo_vs_machine_demo TO grafana_ucepsa_ro; GRANT SELECT ON mv_reports_ucepsa_demo.odoo_discrepancies_demo TO grafana_ucepsa_ro;