CREATE OR REPLACE VIEW mv_hot.edge_oee_open_stops AS SELECT s.id, s.tenant, s.site, s.vertical, s.asset, s.machine_id, s.order_id, s.started_at, GREATEST( EXTRACT(EPOCH FROM (now() - s.started_at))::numeric, 0::numeric )::numeric(14,2) AS current_duration_s, ROUND(( GREATEST( EXTRACT(EPOCH FROM (now() - s.started_at))::numeric, 0::numeric ) / 60.0 )::numeric, 2) AS current_duration_min, ROUND(( ( GREATEST( EXTRACT(EPOCH FROM (now() - s.started_at))::numeric, 0::numeric ) / 3600.0 ) * s.value_hour_eur )::numeric, 2) AS current_cost_eur, s.status, s.classification_status, s.cause_code, c.cause_name, s.operator_note, s.value_hour_eur FROM mv_hot.edge_oee_machine_stops s LEFT JOIN mv_hot.edge_oee_stop_causes c ON c.cause_code = s.cause_code WHERE s.status = 'OPEN' ORDER BY s.started_at DESC; CREATE OR REPLACE VIEW mv_hot.edge_oee_stops_recent AS WITH base AS ( SELECT s.*, c.cause_name, c.is_planned, CASE WHEN s.ended_at IS NOT NULL THEN GREATEST( EXTRACT(EPOCH FROM (s.ended_at - s.started_at))::numeric, 0::numeric ) ELSE GREATEST( EXTRACT(EPOCH FROM (now() - s.started_at))::numeric, 0::numeric ) END AS safe_duration_s FROM mv_hot.edge_oee_machine_stops s LEFT JOIN mv_hot.edge_oee_stop_causes c ON c.cause_code = s.cause_code ) SELECT id, tenant, site, vertical, asset, machine_id, order_id, started_at, ended_at, safe_duration_s::numeric(14,2) AS duration_s, ROUND((safe_duration_s / 60.0)::numeric, 2) AS duration_min, ROUND(((safe_duration_s / 3600.0) * value_hour_eur)::numeric, 2) AS cost_eur, status, classification_status, cause_code, cause_name, is_planned, operator_note, value_hour_eur, start_event_name, end_event_name, created_at, updated_at FROM base ORDER BY started_at DESC; CREATE OR REPLACE VIEW mv_hot.edge_oee_stop_summary_demo AS WITH base AS ( SELECT s.*, c.cause_name, CASE WHEN s.ended_at IS NOT NULL THEN GREATEST( EXTRACT(EPOCH FROM (s.ended_at - s.started_at))::numeric, 0::numeric ) ELSE GREATEST( EXTRACT(EPOCH FROM (now() - s.started_at))::numeric, 0::numeric ) END AS safe_duration_s FROM mv_hot.edge_oee_machine_stops s LEFT JOIN mv_hot.edge_oee_stop_causes c ON c.cause_code = s.cause_code ) SELECT COALESCE(cause_name, 'Pendiente de clasificar') AS cause_name, COALESCE(cause_code, 'pending') AS cause_code, COUNT(*) AS stop_count, ROUND(SUM(safe_duration_s) / 60.0, 2) AS total_min, ROUND(SUM((safe_duration_s / 3600.0) * value_hour_eur)::numeric, 2) AS total_cost_eur FROM base GROUP BY COALESCE(cause_name, 'Pendiente de clasificar'), COALESCE(cause_code, 'pending') ORDER BY total_cost_eur DESC; CREATE OR REPLACE VIEW mv_reports_ucepsa_demo.open_stops AS SELECT * FROM mv_hot.edge_oee_open_stops WHERE asset = 'revpi_oee_node_01'; CREATE OR REPLACE VIEW mv_reports_ucepsa_demo.stops_recent AS SELECT * FROM mv_hot.edge_oee_stops_recent WHERE asset = 'revpi_oee_node_01'; CREATE OR REPLACE VIEW mv_reports_ucepsa_demo.stop_summary AS SELECT * FROM mv_hot.edge_oee_stop_summary_demo; GRANT SELECT ON mv_hot.edge_oee_open_stops, mv_hot.edge_oee_stops_recent, mv_hot.edge_oee_stop_summary_demo TO grafana_ucepsa_ro, edge_oee_operator_writer, edge_oee_mqtt_sink_writer; GRANT SELECT ON mv_reports_ucepsa_demo.open_stops, mv_reports_ucepsa_demo.stops_recent, mv_reports_ucepsa_demo.stop_summary TO grafana_ucepsa_ro;