feat(ucepsa): attribute stops to Shop Floor sessions shadow
This commit is contained in:
parent
86fc845a5e
commit
48fb79d452
@ -0,0 +1,126 @@
|
||||
# UCEPSA — Shop Floor Stop Attribution SHADOW v0.3.4
|
||||
|
||||
## Objetivo
|
||||
|
||||
Cruzar en paralelo:
|
||||
|
||||
```text
|
||||
paro físico
|
||||
∩ telemetría válida
|
||||
∩ sesión Odoo Shop Floor OPEN/CLOSED
|
||||
```
|
||||
|
||||
La versión no reemplaza las vistas context-aware v0.3.1 y no alimenta la cola
|
||||
oficial.
|
||||
|
||||
## Evidencia que permite avanzar
|
||||
|
||||
La sesión real `workorder 340 / WH/MO/00352` fue observada:
|
||||
|
||||
```text
|
||||
Odoo date_start: 07:36:19
|
||||
Primera observación MESAVAULT: 07:36:27
|
||||
Operario confirmado después: Esteban Vaquerizo García
|
||||
```
|
||||
|
||||
El inicio original se conservó aunque la identidad apareció más tarde.
|
||||
|
||||
CORT-00, por el contrario, registró actividad física sin workorder Odoo en
|
||||
`progress`; debe continuar bloqueada como ausencia de sesión Shop Floor.
|
||||
|
||||
## Nuevas vistas
|
||||
|
||||
- `li_stop_shopfloor_slices_v1`
|
||||
- `li_stop_shopfloor_candidates_v1`
|
||||
- `li_shadow_review_queue_shopfloor_v1`
|
||||
- `li_shopfloor_session_stop_summary_v1`
|
||||
- `li_stop_shopfloor_diagnostics_v1`
|
||||
|
||||
## Reglas
|
||||
|
||||
Un tramo solo puede ser candidato cuando:
|
||||
|
||||
1. el paro está cerrado;
|
||||
2. existe exactamente una sesión Shop Floor;
|
||||
3. existe telemetría válida;
|
||||
4. supera el umbral de ruido.
|
||||
|
||||
La atribución temporal y la atribución individual son independientes:
|
||||
|
||||
```text
|
||||
sesión válida + operario confirmado
|
||||
→ CONFIRMED_OPERATOR
|
||||
|
||||
sesión válida + persona candidata
|
||||
→ PENDING_OPERATOR_CANDIDATE
|
||||
|
||||
sesión válida sin persona
|
||||
→ UNRESOLVED_OPERATOR
|
||||
```
|
||||
|
||||
Los paros pueden analizarse por sesión aunque todavía no exista atribución
|
||||
individual.
|
||||
|
||||
## Alcance económico
|
||||
|
||||
`shadow_capacity_loss_eur` es exposición provisional de capacidad usando el
|
||||
parámetro de 36 €/h cuando proceda. No es una pérdida aprobada.
|
||||
|
||||
## Garantías
|
||||
|
||||
- `official_ledger_eligible=false`
|
||||
- no modifica Odoo;
|
||||
- no modifica sesiones;
|
||||
- no modifica paros raw;
|
||||
- no modifica balizas;
|
||||
- no modifica RevPi ni WISE;
|
||||
- no sustituye v0.3.1;
|
||||
- no alimenta `li_operator_stop_queue_v2`.
|
||||
|
||||
## Despliegue
|
||||
|
||||
No es necesario detener servicios.
|
||||
|
||||
```bash
|
||||
FILE=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/sql/versions/106_ucepsa_shopfloor_stop_attribution_shadow_v034.sql
|
||||
|
||||
docker exec -i mv_ucepsa_postgres_hot sh -lc \
|
||||
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
|
||||
< "$FILE"
|
||||
```
|
||||
|
||||
## Validación
|
||||
|
||||
```bash
|
||||
VALIDATION=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/ops/validate_shopfloor_stop_attribution_shadow_v034.sql
|
||||
|
||||
docker exec -i mv_ucepsa_postgres_hot sh -lc \
|
||||
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
|
||||
< "$VALIDATION"
|
||||
```
|
||||
|
||||
Valores obligatorios:
|
||||
|
||||
```text
|
||||
invalid_slice_intervals = 0
|
||||
negative_slice_durations = 0
|
||||
duration_overflow_count = 0
|
||||
open_stops_marked_eligible = 0
|
||||
cort00_attributed_rows = 0
|
||||
official_candidate_rows = 0
|
||||
official_operator_queue_rows = 0
|
||||
```
|
||||
|
||||
La consulta de la sesión 340 debe devolver paros de CORT-02 atribuidos al
|
||||
workorder y al operario Esteban, siempre que existan paros cerrados posteriores
|
||||
a las 07:36:19.
|
||||
|
||||
## Siguiente observación
|
||||
|
||||
Cuando el workorder 340 se cierre:
|
||||
|
||||
- la sesión debe pasar `OPEN → CLOSED`;
|
||||
- debe conservar el inicio 07:36:19;
|
||||
- debe obtener un `ended_at` real;
|
||||
- deben resolverse cantidad y posible continuación;
|
||||
- los paros posteriores al cierre deben quedar fuera de la sesión.
|
||||
@ -0,0 +1,157 @@
|
||||
\pset pager off
|
||||
|
||||
\echo '=== 1. Objetos v0.3.4 ==='
|
||||
|
||||
SELECT
|
||||
to_regclass(
|
||||
'mv_reports_ucepsa_prod.li_stop_shopfloor_slices_v1'
|
||||
) AS slices_view,
|
||||
to_regclass(
|
||||
'mv_reports_ucepsa_prod.li_stop_shopfloor_candidates_v1'
|
||||
) AS candidates_view,
|
||||
to_regclass(
|
||||
'mv_reports_ucepsa_prod.li_shadow_review_queue_shopfloor_v1'
|
||||
) AS queue_view,
|
||||
to_regclass(
|
||||
'mv_reports_ucepsa_prod.li_shopfloor_session_stop_summary_v1'
|
||||
) AS summary_view,
|
||||
to_regclass(
|
||||
'mv_reports_ucepsa_prod.li_stop_shopfloor_diagnostics_v1'
|
||||
) AS diagnostics_view;
|
||||
|
||||
\echo '=== 2. Invariantes temporales ==='
|
||||
|
||||
SELECT
|
||||
count(*) FILTER (
|
||||
WHERE segment_to <= segment_from
|
||||
) AS invalid_slice_intervals,
|
||||
count(*) FILTER (
|
||||
WHERE segment_duration_s < 0
|
||||
) AS negative_slice_durations
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_slices_v1;
|
||||
|
||||
\echo '=== 3. Duración atribuida no supera el paro raw ==='
|
||||
|
||||
WITH totals AS (
|
||||
SELECT
|
||||
source_stop_id,
|
||||
max(raw_duration_s) AS raw_duration_s,
|
||||
sum(segment_duration_s) AS sliced_duration_s
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_slices_v1
|
||||
GROUP BY source_stop_id
|
||||
)
|
||||
SELECT count(*) AS duration_overflow_count
|
||||
FROM totals
|
||||
WHERE sliced_duration_s
|
||||
> raw_duration_s + 1::numeric;
|
||||
|
||||
\echo '=== 4. Paros abiertos no elegibles ==='
|
||||
|
||||
SELECT count(*) AS open_stops_marked_eligible
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1
|
||||
WHERE source_status = 'OPEN'
|
||||
AND technically_eligible;
|
||||
|
||||
\echo '=== 5. Atribución de la sesión real 340 ==='
|
||||
|
||||
SELECT
|
||||
machine_id,
|
||||
shopfloor_session_id,
|
||||
production_order,
|
||||
odoo_workorder_id,
|
||||
session_status,
|
||||
operator_employee_id,
|
||||
operator_name,
|
||||
operator_attribution_status,
|
||||
count(
|
||||
DISTINCT source_stop_id
|
||||
) AS stop_count,
|
||||
round(
|
||||
sum(eligible_duration_s) / 60.0,
|
||||
2
|
||||
) AS eligible_stop_minutes,
|
||||
round(
|
||||
sum(
|
||||
coalesce(
|
||||
shadow_capacity_loss_eur,
|
||||
0
|
||||
)
|
||||
),
|
||||
2
|
||||
) AS shadow_capacity_loss_eur
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1
|
||||
WHERE odoo_workorder_id = 340
|
||||
GROUP BY
|
||||
machine_id,
|
||||
shopfloor_session_id,
|
||||
production_order,
|
||||
odoo_workorder_id,
|
||||
session_status,
|
||||
operator_employee_id,
|
||||
operator_name,
|
||||
operator_attribution_status;
|
||||
|
||||
\echo '=== 6. CORT-00 sigue sin sesión Shop Floor hoy ==='
|
||||
|
||||
SELECT
|
||||
count(*) FILTER (
|
||||
WHERE shopfloor_session_id IS NOT NULL
|
||||
) AS cort00_attributed_rows,
|
||||
count(*) FILTER (
|
||||
WHERE eligibility_status =
|
||||
'BLOCKED_NO_SHOPFLOOR_SESSION'
|
||||
) AS cort00_blocked_rows
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1
|
||||
WHERE machine_id = 'CORT-00'
|
||||
AND raw_started_at >=
|
||||
'2026-07-20 00:00:00+02'::timestamptz;
|
||||
|
||||
\echo '=== 7. Resumen por sesión ==='
|
||||
|
||||
SELECT *
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shopfloor_session_stop_summary_v1
|
||||
WHERE odoo_workorder_id IN (87, 810, 340)
|
||||
ORDER BY odoo_workorder_id;
|
||||
|
||||
\echo '=== 8. Cola SHADOW Shop Floor ==='
|
||||
|
||||
SELECT *
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shadow_review_queue_shopfloor_v1
|
||||
ORDER BY shadow_rank
|
||||
LIMIT 30;
|
||||
|
||||
\echo '=== 9. Nada oficial ==='
|
||||
|
||||
SELECT
|
||||
count(*) FILTER (
|
||||
WHERE official_ledger_eligible
|
||||
) AS official_candidate_rows
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1;
|
||||
|
||||
\echo '=== 10. Cola oficial original sigue vacía ==='
|
||||
|
||||
SELECT count(*) AS official_operator_queue_rows
|
||||
FROM mv_reports_ucepsa_prod.li_operator_stop_queue_v2;
|
||||
|
||||
\echo '=== 11. Diagnóstico 24 horas ==='
|
||||
|
||||
SELECT *
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_diagnostics_v1;
|
||||
@ -0,0 +1,992 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE OR REPLACE VIEW
|
||||
mv_reports_ucepsa_prod.li_stop_shopfloor_slices_v1
|
||||
AS
|
||||
WITH raw_stops AS (
|
||||
SELECT
|
||||
s.id AS source_stop_id,
|
||||
s.tenant,
|
||||
s.site,
|
||||
s.vertical,
|
||||
s.asset,
|
||||
s.machine_id,
|
||||
s.order_id AS source_order_id,
|
||||
s.started_at AS raw_started_at,
|
||||
s.ended_at AS raw_ended_at,
|
||||
COALESCE(s.ended_at, now()) AS raw_effective_end,
|
||||
EXTRACT(
|
||||
EPOCH FROM (
|
||||
COALESCE(s.ended_at, now())
|
||||
- s.started_at
|
||||
)
|
||||
)::numeric AS raw_duration_s,
|
||||
s.status AS source_status,
|
||||
s.classification_status,
|
||||
s.cause_code,
|
||||
s.operator_note,
|
||||
s.value_hour_eur,
|
||||
s.start_event_name,
|
||||
s.end_event_name,
|
||||
s.start_payload_json,
|
||||
s.end_payload_json
|
||||
FROM mv_hot.edge_oee_machine_stops s
|
||||
WHERE s.tenant = 'ucepsa'
|
||||
AND s.site = 'ucepsa_onpremise'
|
||||
AND s.machine_id IS NOT NULL
|
||||
AND COALESCE(s.ended_at, now()) > s.started_at
|
||||
),
|
||||
sessions AS (
|
||||
SELECT
|
||||
c.context_session_id AS shopfloor_session_id,
|
||||
c.tenant,
|
||||
c.site,
|
||||
c.machine_id,
|
||||
c.source_type,
|
||||
c.source_ref,
|
||||
c.external_order_ref,
|
||||
c.odoo_production_id,
|
||||
c.odoo_workorder_id,
|
||||
c.product_id,
|
||||
c.product_default_code,
|
||||
c.product_name,
|
||||
c.operator_employee_id,
|
||||
c.operator_name,
|
||||
c.operator_candidate_employee_id,
|
||||
c.operator_candidate_name,
|
||||
c.effective_from,
|
||||
COALESCE(c.effective_to, now()) AS effective_to,
|
||||
c.status,
|
||||
c.authorization_status,
|
||||
c.confidence,
|
||||
c.official_eligible,
|
||||
c.context_priority,
|
||||
c.context_time_usable,
|
||||
c.individual_attribution_eligible,
|
||||
c.operator_resolution,
|
||||
c.operator_confirmation_status,
|
||||
c.quantity_status,
|
||||
c.quantity_confirmation_status,
|
||||
c.production_chain_key,
|
||||
c.evidence_json
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_odoo_shopfloor_context_intervals_v1 c
|
||||
WHERE c.context_time_usable
|
||||
AND COALESCE(c.effective_to, now()) > c.effective_from
|
||||
),
|
||||
telemetry AS (
|
||||
SELECT
|
||||
t.tenant,
|
||||
t.site,
|
||||
t.machine_id,
|
||||
t.telemetry_island_id,
|
||||
t.coverage_from,
|
||||
t.coverage_to
|
||||
FROM mv_reports_ucepsa_prod.li_telemetry_coverage_v1 t
|
||||
),
|
||||
session_edges AS (
|
||||
SELECT
|
||||
s.source_stop_id,
|
||||
GREATEST(
|
||||
s.raw_started_at,
|
||||
c.effective_from
|
||||
) AS boundary_at
|
||||
FROM raw_stops s
|
||||
JOIN sessions c
|
||||
ON c.tenant = s.tenant
|
||||
AND c.site = s.site
|
||||
AND c.machine_id = s.machine_id
|
||||
AND s.raw_effective_end > c.effective_from
|
||||
AND s.raw_started_at < c.effective_to
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
s.source_stop_id,
|
||||
LEAST(
|
||||
s.raw_effective_end,
|
||||
c.effective_to
|
||||
) AS boundary_at
|
||||
FROM raw_stops s
|
||||
JOIN sessions c
|
||||
ON c.tenant = s.tenant
|
||||
AND c.site = s.site
|
||||
AND c.machine_id = s.machine_id
|
||||
AND s.raw_effective_end > c.effective_from
|
||||
AND s.raw_started_at < c.effective_to
|
||||
),
|
||||
telemetry_edges AS (
|
||||
SELECT
|
||||
s.source_stop_id,
|
||||
GREATEST(
|
||||
s.raw_started_at,
|
||||
t.coverage_from
|
||||
) AS boundary_at
|
||||
FROM raw_stops s
|
||||
JOIN telemetry t
|
||||
ON t.tenant = s.tenant
|
||||
AND t.site = s.site
|
||||
AND t.machine_id = s.machine_id
|
||||
AND s.raw_effective_end > t.coverage_from
|
||||
AND s.raw_started_at < t.coverage_to
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
s.source_stop_id,
|
||||
LEAST(
|
||||
s.raw_effective_end,
|
||||
t.coverage_to
|
||||
) AS boundary_at
|
||||
FROM raw_stops s
|
||||
JOIN telemetry t
|
||||
ON t.tenant = s.tenant
|
||||
AND t.site = s.site
|
||||
AND t.machine_id = s.machine_id
|
||||
AND s.raw_effective_end > t.coverage_from
|
||||
AND s.raw_started_at < t.coverage_to
|
||||
),
|
||||
boundaries AS (
|
||||
SELECT
|
||||
source_stop_id,
|
||||
raw_started_at AS boundary_at
|
||||
FROM raw_stops
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
source_stop_id,
|
||||
raw_effective_end AS boundary_at
|
||||
FROM raw_stops
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT source_stop_id, boundary_at
|
||||
FROM session_edges
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT source_stop_id, boundary_at
|
||||
FROM telemetry_edges
|
||||
),
|
||||
distinct_boundaries AS (
|
||||
SELECT DISTINCT
|
||||
source_stop_id,
|
||||
boundary_at
|
||||
FROM boundaries
|
||||
),
|
||||
segmented AS (
|
||||
SELECT
|
||||
source_stop_id,
|
||||
boundary_at AS segment_from,
|
||||
LEAD(boundary_at) OVER (
|
||||
PARTITION BY source_stop_id
|
||||
ORDER BY boundary_at
|
||||
) AS segment_to
|
||||
FROM distinct_boundaries
|
||||
),
|
||||
segments AS (
|
||||
SELECT
|
||||
source_stop_id,
|
||||
segment_from,
|
||||
segment_to
|
||||
FROM segmented
|
||||
WHERE segment_to IS NOT NULL
|
||||
AND segment_to > segment_from
|
||||
),
|
||||
session_matches AS (
|
||||
SELECT
|
||||
s.source_stop_id,
|
||||
s.segment_from,
|
||||
s.segment_to,
|
||||
c.shopfloor_session_id,
|
||||
c.source_ref,
|
||||
c.external_order_ref,
|
||||
c.odoo_production_id,
|
||||
c.odoo_workorder_id,
|
||||
c.product_id,
|
||||
c.product_default_code,
|
||||
c.product_name,
|
||||
c.operator_employee_id,
|
||||
c.operator_name,
|
||||
c.operator_candidate_employee_id,
|
||||
c.operator_candidate_name,
|
||||
c.effective_from,
|
||||
c.effective_to,
|
||||
c.status,
|
||||
c.authorization_status,
|
||||
c.confidence,
|
||||
c.official_eligible,
|
||||
c.context_priority,
|
||||
c.individual_attribution_eligible,
|
||||
c.operator_resolution,
|
||||
c.operator_confirmation_status,
|
||||
c.quantity_status,
|
||||
c.quantity_confirmation_status,
|
||||
c.production_chain_key,
|
||||
c.evidence_json
|
||||
FROM segments s
|
||||
JOIN raw_stops r
|
||||
ON r.source_stop_id = s.source_stop_id
|
||||
JOIN sessions c
|
||||
ON c.tenant = r.tenant
|
||||
AND c.site = r.site
|
||||
AND c.machine_id = r.machine_id
|
||||
AND s.segment_to > c.effective_from
|
||||
AND s.segment_from < c.effective_to
|
||||
),
|
||||
session_ranked AS (
|
||||
SELECT
|
||||
m.*,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY
|
||||
m.source_stop_id,
|
||||
m.segment_from,
|
||||
m.segment_to
|
||||
ORDER BY
|
||||
m.context_priority,
|
||||
m.shopfloor_session_id
|
||||
) AS session_rank
|
||||
FROM session_matches m
|
||||
),
|
||||
session_summary AS (
|
||||
SELECT
|
||||
source_stop_id,
|
||||
segment_from,
|
||||
segment_to,
|
||||
COUNT(
|
||||
DISTINCT shopfloor_session_id
|
||||
) AS session_count
|
||||
FROM session_matches
|
||||
GROUP BY
|
||||
source_stop_id,
|
||||
segment_from,
|
||||
segment_to
|
||||
),
|
||||
telemetry_matches AS (
|
||||
SELECT
|
||||
s.source_stop_id,
|
||||
s.segment_from,
|
||||
s.segment_to,
|
||||
t.telemetry_island_id,
|
||||
t.coverage_from,
|
||||
t.coverage_to
|
||||
FROM segments s
|
||||
JOIN raw_stops r
|
||||
ON r.source_stop_id = s.source_stop_id
|
||||
JOIN telemetry t
|
||||
ON t.tenant = r.tenant
|
||||
AND t.site = r.site
|
||||
AND t.machine_id = r.machine_id
|
||||
AND s.segment_to > t.coverage_from
|
||||
AND s.segment_from < t.coverage_to
|
||||
),
|
||||
telemetry_summary AS (
|
||||
SELECT
|
||||
source_stop_id,
|
||||
segment_from,
|
||||
segment_to,
|
||||
COUNT(
|
||||
DISTINCT telemetry_island_id
|
||||
) AS telemetry_island_count,
|
||||
MIN(coverage_from)
|
||||
AS first_telemetry_coverage_from,
|
||||
MAX(coverage_to)
|
||||
AS last_telemetry_coverage_to
|
||||
FROM telemetry_matches
|
||||
GROUP BY
|
||||
source_stop_id,
|
||||
segment_from,
|
||||
segment_to
|
||||
)
|
||||
SELECT
|
||||
r.source_stop_id,
|
||||
r.tenant,
|
||||
r.site,
|
||||
r.vertical,
|
||||
r.asset,
|
||||
r.machine_id,
|
||||
r.source_order_id,
|
||||
r.raw_started_at,
|
||||
r.raw_ended_at,
|
||||
r.raw_effective_end,
|
||||
r.raw_duration_s,
|
||||
r.source_status,
|
||||
r.classification_status,
|
||||
r.cause_code,
|
||||
r.operator_note,
|
||||
r.value_hour_eur,
|
||||
r.start_event_name,
|
||||
r.end_event_name,
|
||||
r.start_payload_json,
|
||||
r.end_payload_json,
|
||||
s.segment_from,
|
||||
s.segment_to,
|
||||
EXTRACT(
|
||||
EPOCH FROM (
|
||||
s.segment_to - s.segment_from
|
||||
)
|
||||
)::numeric AS segment_duration_s,
|
||||
COALESCE(ss.session_count, 0)
|
||||
AS shopfloor_session_count,
|
||||
sr.shopfloor_session_id,
|
||||
sr.source_ref AS shopfloor_source_ref,
|
||||
sr.external_order_ref AS production_order,
|
||||
sr.odoo_production_id,
|
||||
sr.odoo_workorder_id,
|
||||
sr.product_id,
|
||||
sr.product_default_code,
|
||||
sr.product_name,
|
||||
sr.operator_employee_id,
|
||||
sr.operator_name,
|
||||
sr.operator_candidate_employee_id,
|
||||
sr.operator_candidate_name,
|
||||
sr.effective_from AS session_from,
|
||||
sr.effective_to AS session_to,
|
||||
sr.status AS session_status,
|
||||
sr.authorization_status,
|
||||
sr.confidence AS session_confidence,
|
||||
sr.official_eligible AS session_official_eligible,
|
||||
sr.individual_attribution_eligible,
|
||||
sr.operator_resolution,
|
||||
sr.operator_confirmation_status,
|
||||
sr.quantity_status,
|
||||
sr.quantity_confirmation_status,
|
||||
sr.production_chain_key,
|
||||
sr.evidence_json AS session_evidence_json,
|
||||
COALESCE(
|
||||
ts.telemetry_island_count,
|
||||
0
|
||||
) AS telemetry_island_count,
|
||||
COALESCE(
|
||||
ts.telemetry_island_count,
|
||||
0
|
||||
) > 0 AS telemetry_covered,
|
||||
ts.first_telemetry_coverage_from,
|
||||
ts.last_telemetry_coverage_to,
|
||||
CASE
|
||||
WHEN COALESCE(ss.session_count, 0) = 0
|
||||
THEN 'NO_SHOPFLOOR_SESSION'
|
||||
WHEN COALESCE(ss.session_count, 0) = 1
|
||||
THEN 'SINGLE_SHOPFLOOR_SESSION'
|
||||
ELSE 'SHOPFLOOR_SESSION_CONFLICT'
|
||||
END AS shopfloor_context_status
|
||||
FROM segments s
|
||||
JOIN raw_stops r
|
||||
ON r.source_stop_id = s.source_stop_id
|
||||
LEFT JOIN session_summary ss
|
||||
ON ss.source_stop_id = s.source_stop_id
|
||||
AND ss.segment_from = s.segment_from
|
||||
AND ss.segment_to = s.segment_to
|
||||
LEFT JOIN session_ranked sr
|
||||
ON sr.source_stop_id = s.source_stop_id
|
||||
AND sr.segment_from = s.segment_from
|
||||
AND sr.segment_to = s.segment_to
|
||||
AND sr.session_rank = 1
|
||||
LEFT JOIN telemetry_summary ts
|
||||
ON ts.source_stop_id = s.source_stop_id
|
||||
AND ts.segment_from = s.segment_from
|
||||
AND ts.segment_to = s.segment_to;
|
||||
|
||||
COMMENT ON VIEW
|
||||
mv_reports_ucepsa_prod.li_stop_shopfloor_slices_v1 IS
|
||||
'Vista SHADOW que divide cada paro físico por límites de sesión Odoo Shop Floor y cobertura de telemetría. No modifica la tubería context-aware v0.3.1.';
|
||||
|
||||
CREATE OR REPLACE VIEW
|
||||
mv_reports_ucepsa_prod.li_stop_shopfloor_candidates_v1
|
||||
AS
|
||||
WITH slice_rollup AS (
|
||||
SELECT
|
||||
s.source_stop_id,
|
||||
s.tenant,
|
||||
s.site,
|
||||
MAX(s.vertical) AS vertical,
|
||||
MAX(s.asset) AS asset,
|
||||
s.machine_id,
|
||||
MAX(s.source_order_id) AS source_order_id,
|
||||
MAX(s.raw_started_at) AS raw_started_at,
|
||||
MAX(s.raw_ended_at) AS raw_ended_at,
|
||||
MAX(s.raw_effective_end) AS raw_effective_end,
|
||||
MAX(s.raw_duration_s) AS raw_duration_s,
|
||||
MAX(s.source_status) AS source_status,
|
||||
MAX(s.classification_status)
|
||||
AS classification_status,
|
||||
MAX(s.cause_code) AS cause_code,
|
||||
MAX(s.operator_note) AS operator_note,
|
||||
MAX(s.value_hour_eur) AS value_hour_eur,
|
||||
MAX(s.start_event_name) AS start_event_name,
|
||||
MAX(s.end_event_name) AS end_event_name,
|
||||
s.shopfloor_context_status,
|
||||
s.shopfloor_session_count,
|
||||
s.shopfloor_session_id,
|
||||
s.shopfloor_source_ref,
|
||||
s.production_order,
|
||||
s.odoo_production_id,
|
||||
s.odoo_workorder_id,
|
||||
s.product_id,
|
||||
s.product_default_code,
|
||||
s.product_name,
|
||||
s.operator_employee_id,
|
||||
s.operator_name,
|
||||
s.operator_candidate_employee_id,
|
||||
s.operator_candidate_name,
|
||||
MIN(s.session_from) AS session_from,
|
||||
MAX(s.session_to) AS session_to,
|
||||
s.session_status,
|
||||
s.authorization_status,
|
||||
s.session_confidence,
|
||||
BOOL_AND(
|
||||
COALESCE(
|
||||
s.session_official_eligible,
|
||||
false
|
||||
)
|
||||
) AS session_official_eligible,
|
||||
BOOL_AND(
|
||||
COALESCE(
|
||||
s.individual_attribution_eligible,
|
||||
false
|
||||
)
|
||||
) AS individual_attribution_eligible,
|
||||
s.operator_resolution,
|
||||
s.operator_confirmation_status,
|
||||
s.quantity_status,
|
||||
s.quantity_confirmation_status,
|
||||
s.production_chain_key,
|
||||
(
|
||||
jsonb_agg(
|
||||
s.session_evidence_json
|
||||
ORDER BY s.segment_from
|
||||
) FILTER (
|
||||
WHERE s.session_evidence_json
|
||||
IS NOT NULL
|
||||
)
|
||||
)->0 AS session_evidence_json,
|
||||
MIN(s.segment_from) AS started_at,
|
||||
MAX(s.segment_to) AS ended_at,
|
||||
SUM(s.segment_duration_s)
|
||||
AS context_segment_duration_s,
|
||||
SUM(
|
||||
CASE
|
||||
WHEN s.telemetry_covered
|
||||
THEN s.segment_duration_s
|
||||
ELSE 0::numeric
|
||||
END
|
||||
) AS telemetry_covered_duration_s,
|
||||
COUNT(*) AS segment_count,
|
||||
COUNT(*) FILTER (
|
||||
WHERE s.telemetry_covered
|
||||
) AS telemetry_slice_count,
|
||||
MIN(s.first_telemetry_coverage_from)
|
||||
AS first_telemetry_coverage_from,
|
||||
MAX(s.last_telemetry_coverage_to)
|
||||
AS last_telemetry_coverage_to
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_slices_v1 s
|
||||
GROUP BY
|
||||
s.source_stop_id,
|
||||
s.tenant,
|
||||
s.site,
|
||||
s.machine_id,
|
||||
s.shopfloor_context_status,
|
||||
s.shopfloor_session_count,
|
||||
s.shopfloor_session_id,
|
||||
s.shopfloor_source_ref,
|
||||
s.production_order,
|
||||
s.odoo_production_id,
|
||||
s.odoo_workorder_id,
|
||||
s.product_id,
|
||||
s.product_default_code,
|
||||
s.product_name,
|
||||
s.operator_employee_id,
|
||||
s.operator_name,
|
||||
s.operator_candidate_employee_id,
|
||||
s.operator_candidate_name,
|
||||
s.session_status,
|
||||
s.authorization_status,
|
||||
s.session_confidence,
|
||||
s.operator_resolution,
|
||||
s.operator_confirmation_status,
|
||||
s.quantity_status,
|
||||
s.quantity_confirmation_status,
|
||||
s.production_chain_key
|
||||
),
|
||||
with_boundary_count AS (
|
||||
SELECT
|
||||
r.*,
|
||||
COUNT(*) OVER (
|
||||
PARTITION BY r.source_stop_id
|
||||
) > 1 AS crosses_shopfloor_boundary
|
||||
FROM slice_rollup r
|
||||
),
|
||||
enriched AS (
|
||||
SELECT
|
||||
r.*,
|
||||
p.noise_below_s,
|
||||
p.aggregate_below_s,
|
||||
p.operator_individual_from_s,
|
||||
p.aggregation_window_min,
|
||||
c.cause_name,
|
||||
c.is_planned,
|
||||
COALESCE(
|
||||
ep.parameter_value,
|
||||
r.value_hour_eur
|
||||
) AS applied_capacity_eur_h,
|
||||
COALESCE(
|
||||
ep.confidence,
|
||||
'LOW'
|
||||
) AS capacity_cost_confidence,
|
||||
CASE
|
||||
WHEN r.shopfloor_session_count = 1
|
||||
THEN r.telemetry_covered_duration_s
|
||||
ELSE 0::numeric
|
||||
END AS eligible_duration_s
|
||||
FROM with_boundary_count r
|
||||
LEFT JOIN mv_hot.edge_oee_stop_causes c
|
||||
ON c.cause_code = r.cause_code
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT x.*
|
||||
FROM mv_loss_intelligence.loss_policies x
|
||||
WHERE x.tenant = r.tenant
|
||||
AND x.site = r.site
|
||||
AND x.policy_code = 'CUTTER_STOPS_V1'
|
||||
AND x.is_active = true
|
||||
ORDER BY x.id DESC
|
||||
LIMIT 1
|
||||
) p ON true
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT x.*
|
||||
FROM
|
||||
mv_loss_intelligence
|
||||
.economic_parameters x
|
||||
WHERE x.tenant = r.tenant
|
||||
AND x.site = r.site
|
||||
AND x.parameter_code =
|
||||
'CAPACITY_HOUR_EUR'
|
||||
AND x.is_active = true
|
||||
AND (
|
||||
x.machine_id IS NULL
|
||||
OR x.machine_id = r.machine_id
|
||||
)
|
||||
AND (
|
||||
x.product_id IS NULL
|
||||
OR x.product_id = r.product_id
|
||||
)
|
||||
AND r.started_at >= x.valid_from
|
||||
AND (
|
||||
x.valid_to IS NULL
|
||||
OR r.started_at < x.valid_to
|
||||
)
|
||||
ORDER BY
|
||||
(x.machine_id IS NOT NULL) DESC,
|
||||
(x.product_id IS NOT NULL) DESC,
|
||||
x.valid_from DESC,
|
||||
x.id DESC
|
||||
LIMIT 1
|
||||
) ep ON true
|
||||
),
|
||||
classified AS (
|
||||
SELECT
|
||||
e.*,
|
||||
CASE
|
||||
WHEN e.source_status = 'OPEN'
|
||||
THEN 'OPEN_MONITORING'
|
||||
WHEN e.shopfloor_session_count = 0
|
||||
THEN 'BLOCKED_NO_SHOPFLOOR_SESSION'
|
||||
WHEN e.shopfloor_session_count > 1
|
||||
THEN 'BLOCKED_SHOPFLOOR_CONFLICT'
|
||||
WHEN e.eligible_duration_s <= 0
|
||||
THEN 'BLOCKED_NO_TELEMETRY'
|
||||
WHEN e.eligible_duration_s
|
||||
< e.noise_below_s
|
||||
THEN 'EXCLUDED_NOISE'
|
||||
WHEN e.eligible_duration_s
|
||||
< e.operator_individual_from_s
|
||||
THEN 'CANDIDATE_GROUP'
|
||||
ELSE 'CANDIDATE_INDIVIDUAL'
|
||||
END AS eligibility_status,
|
||||
(
|
||||
e.source_status = 'CLOSED'
|
||||
AND e.shopfloor_session_count = 1
|
||||
AND e.eligible_duration_s
|
||||
>= e.noise_below_s
|
||||
) AS technically_eligible,
|
||||
CASE
|
||||
WHEN e.shopfloor_session_count <> 1
|
||||
THEN 'NOT_APPLICABLE'
|
||||
WHEN e.individual_attribution_eligible
|
||||
THEN 'CONFIRMED_OPERATOR'
|
||||
WHEN e.operator_candidate_employee_id
|
||||
IS NOT NULL
|
||||
THEN 'PENDING_OPERATOR_CANDIDATE'
|
||||
ELSE 'UNRESOLVED_OPERATOR'
|
||||
END AS operator_attribution_status
|
||||
FROM enriched e
|
||||
)
|
||||
SELECT
|
||||
c.source_stop_id,
|
||||
c.tenant,
|
||||
c.site,
|
||||
c.vertical,
|
||||
c.asset,
|
||||
c.machine_id,
|
||||
c.source_order_id,
|
||||
c.raw_started_at,
|
||||
c.raw_ended_at,
|
||||
c.raw_effective_end,
|
||||
c.raw_duration_s,
|
||||
c.started_at,
|
||||
c.ended_at,
|
||||
c.context_segment_duration_s,
|
||||
c.telemetry_covered_duration_s,
|
||||
c.eligible_duration_s,
|
||||
ROUND(
|
||||
c.eligible_duration_s / 60.0,
|
||||
2
|
||||
) AS eligible_duration_min,
|
||||
GREATEST(
|
||||
c.context_segment_duration_s
|
||||
- c.eligible_duration_s,
|
||||
0::numeric
|
||||
) AS excluded_duration_s,
|
||||
ROUND(
|
||||
GREATEST(
|
||||
c.context_segment_duration_s
|
||||
- c.eligible_duration_s,
|
||||
0::numeric
|
||||
) / 60.0,
|
||||
2
|
||||
) AS excluded_duration_min,
|
||||
c.context_segment_duration_s
|
||||
< (c.raw_duration_s - 1::numeric)
|
||||
AS session_clipped,
|
||||
c.eligible_duration_s
|
||||
< (c.context_segment_duration_s - 1::numeric)
|
||||
AS telemetry_clipped,
|
||||
c.crosses_shopfloor_boundary,
|
||||
c.segment_count,
|
||||
c.telemetry_slice_count,
|
||||
c.first_telemetry_coverage_from,
|
||||
c.last_telemetry_coverage_to,
|
||||
c.source_status,
|
||||
c.classification_status,
|
||||
c.cause_code,
|
||||
c.cause_name,
|
||||
c.is_planned,
|
||||
c.operator_note,
|
||||
c.start_event_name,
|
||||
c.end_event_name,
|
||||
c.shopfloor_context_status,
|
||||
c.shopfloor_session_count,
|
||||
c.shopfloor_session_id,
|
||||
c.shopfloor_source_ref,
|
||||
c.production_chain_key,
|
||||
c.production_order,
|
||||
c.odoo_production_id,
|
||||
c.odoo_workorder_id,
|
||||
c.product_id,
|
||||
c.product_default_code,
|
||||
c.product_name,
|
||||
c.session_from,
|
||||
c.session_to,
|
||||
c.session_status,
|
||||
c.authorization_status,
|
||||
c.session_confidence,
|
||||
c.operator_employee_id,
|
||||
c.operator_name,
|
||||
c.operator_candidate_employee_id,
|
||||
c.operator_candidate_name,
|
||||
c.operator_resolution,
|
||||
c.operator_confirmation_status,
|
||||
c.individual_attribution_eligible,
|
||||
c.operator_attribution_status,
|
||||
c.quantity_status,
|
||||
c.quantity_confirmation_status,
|
||||
c.applied_capacity_eur_h,
|
||||
c.capacity_cost_confidence,
|
||||
CASE
|
||||
WHEN c.technically_eligible
|
||||
THEN ROUND(
|
||||
c.eligible_duration_s
|
||||
/ 3600.0
|
||||
* c.applied_capacity_eur_h,
|
||||
2
|
||||
)
|
||||
ELSE NULL::numeric
|
||||
END AS shadow_capacity_loss_eur,
|
||||
c.eligibility_status,
|
||||
c.technically_eligible,
|
||||
false AS official_ledger_eligible,
|
||||
CASE
|
||||
WHEN c.eligibility_status =
|
||||
'OPEN_MONITORING'
|
||||
THEN
|
||||
'El paro continúa abierto.'
|
||||
WHEN c.eligibility_status =
|
||||
'BLOCKED_NO_SHOPFLOOR_SESSION'
|
||||
THEN
|
||||
'No existe una sesión Odoo Shop Floor OPEN/CLOSED que cubra el tramo.'
|
||||
WHEN c.eligibility_status =
|
||||
'BLOCKED_SHOPFLOOR_CONFLICT'
|
||||
THEN
|
||||
'Más de una sesión Shop Floor cubre el mismo tramo.'
|
||||
WHEN c.eligibility_status =
|
||||
'BLOCKED_NO_TELEMETRY'
|
||||
THEN
|
||||
'No existe cobertura de telemetría válida.'
|
||||
WHEN c.eligibility_status =
|
||||
'EXCLUDED_NOISE'
|
||||
THEN
|
||||
'Duración inferior al umbral de ruido.'
|
||||
WHEN c.eligibility_status IN (
|
||||
'CANDIDATE_GROUP',
|
||||
'CANDIDATE_INDIVIDUAL'
|
||||
)
|
||||
THEN
|
||||
'Candidato técnico SHADOW atribuido a una sesión Shop Floor.'
|
||||
ELSE
|
||||
'Tramo bloqueado por política Shop Floor.'
|
||||
END AS blocking_reason,
|
||||
jsonb_build_object(
|
||||
'source_stop_id',
|
||||
c.source_stop_id,
|
||||
'raw_started_at',
|
||||
c.raw_started_at,
|
||||
'raw_ended_at',
|
||||
c.raw_ended_at,
|
||||
'shopfloor_session_id',
|
||||
c.shopfloor_session_id,
|
||||
'odoo_workorder_id',
|
||||
c.odoo_workorder_id,
|
||||
'production_order',
|
||||
c.production_order,
|
||||
'session_from',
|
||||
c.session_from,
|
||||
'session_to',
|
||||
c.session_to,
|
||||
'operator_attribution_status',
|
||||
c.operator_attribution_status,
|
||||
'operator_employee_id',
|
||||
c.operator_employee_id,
|
||||
'operator_candidate_employee_id',
|
||||
c.operator_candidate_employee_id,
|
||||
'eligible_duration_s',
|
||||
c.eligible_duration_s,
|
||||
'session_clipped',
|
||||
c.context_segment_duration_s
|
||||
< (c.raw_duration_s - 1::numeric),
|
||||
'telemetry_clipped',
|
||||
c.eligible_duration_s
|
||||
< (c.context_segment_duration_s - 1::numeric),
|
||||
'crosses_shopfloor_boundary',
|
||||
c.crosses_shopfloor_boundary,
|
||||
'session_evidence',
|
||||
c.session_evidence_json
|
||||
) AS evidence_json
|
||||
FROM classified c;
|
||||
|
||||
COMMENT ON VIEW
|
||||
mv_reports_ucepsa_prod.li_stop_shopfloor_candidates_v1 IS
|
||||
'Candidatos de paro atribuidos exclusivamente a sesiones Odoo Shop Floor. Capa paralela SHADOW; no sustituye v0.3.1 ni alimenta la cola oficial.';
|
||||
|
||||
CREATE OR REPLACE VIEW
|
||||
mv_reports_ucepsa_prod.li_shadow_review_queue_shopfloor_v1
|
||||
AS
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (
|
||||
ORDER BY
|
||||
shadow_capacity_loss_eur DESC NULLS LAST,
|
||||
eligible_duration_s DESC,
|
||||
started_at DESC
|
||||
) AS shadow_rank,
|
||||
source_stop_id,
|
||||
machine_id,
|
||||
shopfloor_session_id,
|
||||
production_order,
|
||||
odoo_workorder_id,
|
||||
product_name,
|
||||
operator_employee_id,
|
||||
operator_name,
|
||||
operator_candidate_employee_id,
|
||||
operator_candidate_name,
|
||||
operator_attribution_status,
|
||||
cause_code,
|
||||
cause_name,
|
||||
started_at,
|
||||
ended_at,
|
||||
eligible_duration_min,
|
||||
shadow_capacity_loss_eur,
|
||||
eligibility_status,
|
||||
session_clipped,
|
||||
telemetry_clipped,
|
||||
crosses_shopfloor_boundary,
|
||||
'SHADOW_SHOPFLOOR_NOT_OFFICIAL'::text
|
||||
AS queue_status,
|
||||
official_ledger_eligible
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1
|
||||
WHERE eligibility_status IN (
|
||||
'CANDIDATE_GROUP',
|
||||
'CANDIDATE_INDIVIDUAL'
|
||||
)
|
||||
AND ended_at >= now() - interval '24 hours';
|
||||
|
||||
COMMENT ON VIEW
|
||||
mv_reports_ucepsa_prod.li_shadow_review_queue_shopfloor_v1 IS
|
||||
'Cola de revisión SHADOW basada en sesiones Shop Floor durante las últimas 24 horas. No alimenta la cola oficial.';
|
||||
|
||||
CREATE OR REPLACE VIEW
|
||||
mv_reports_ucepsa_prod.li_shopfloor_session_stop_summary_v1
|
||||
AS
|
||||
SELECT
|
||||
c.shopfloor_session_id,
|
||||
c.machine_id,
|
||||
c.production_chain_key,
|
||||
c.production_order,
|
||||
c.odoo_production_id,
|
||||
c.odoo_workorder_id,
|
||||
c.product_id,
|
||||
c.product_name,
|
||||
c.session_from,
|
||||
c.session_to,
|
||||
c.session_status,
|
||||
c.operator_employee_id,
|
||||
c.operator_name,
|
||||
c.operator_candidate_employee_id,
|
||||
c.operator_candidate_name,
|
||||
c.operator_attribution_status,
|
||||
COUNT(
|
||||
DISTINCT c.source_stop_id
|
||||
) AS physical_stop_count,
|
||||
COUNT(
|
||||
DISTINCT c.source_stop_id
|
||||
) FILTER (
|
||||
WHERE c.technically_eligible
|
||||
) AS eligible_stop_count,
|
||||
ROUND(
|
||||
SUM(c.eligible_duration_s) / 60.0,
|
||||
2
|
||||
) AS eligible_stop_minutes,
|
||||
ROUND(
|
||||
SUM(
|
||||
COALESCE(
|
||||
c.shadow_capacity_loss_eur,
|
||||
0
|
||||
)
|
||||
),
|
||||
2
|
||||
) AS shadow_capacity_loss_eur,
|
||||
BOOL_OR(c.session_clipped)
|
||||
AS any_session_clipped,
|
||||
BOOL_OR(c.telemetry_clipped)
|
||||
AS any_telemetry_clipped,
|
||||
BOOL_OR(c.crosses_shopfloor_boundary)
|
||||
AS any_crosses_shopfloor_boundary,
|
||||
false AS official_ledger_eligible
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1 c
|
||||
WHERE c.shopfloor_session_id IS NOT NULL
|
||||
GROUP BY
|
||||
c.shopfloor_session_id,
|
||||
c.machine_id,
|
||||
c.production_chain_key,
|
||||
c.production_order,
|
||||
c.odoo_production_id,
|
||||
c.odoo_workorder_id,
|
||||
c.product_id,
|
||||
c.product_name,
|
||||
c.session_from,
|
||||
c.session_to,
|
||||
c.session_status,
|
||||
c.operator_employee_id,
|
||||
c.operator_name,
|
||||
c.operator_candidate_employee_id,
|
||||
c.operator_candidate_name,
|
||||
c.operator_attribution_status;
|
||||
|
||||
COMMENT ON VIEW
|
||||
mv_reports_ucepsa_prod.li_shopfloor_session_stop_summary_v1 IS
|
||||
'Resumen SHADOW de paros físicos por sesión Odoo Shop Floor. Los importes son exposición provisional de capacidad, no pérdida aprobada.';
|
||||
|
||||
CREATE OR REPLACE VIEW
|
||||
mv_reports_ucepsa_prod.li_stop_shopfloor_diagnostics_v1
|
||||
AS
|
||||
SELECT
|
||||
machine_id,
|
||||
shopfloor_context_status,
|
||||
eligibility_status,
|
||||
operator_attribution_status,
|
||||
COUNT(*) AS candidate_row_count,
|
||||
COUNT(
|
||||
DISTINCT source_stop_id
|
||||
) AS source_stop_count,
|
||||
ROUND(
|
||||
SUM(context_segment_duration_s) / 60.0,
|
||||
2
|
||||
) AS context_minutes,
|
||||
ROUND(
|
||||
SUM(eligible_duration_s) / 60.0,
|
||||
2
|
||||
) AS eligible_minutes
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1
|
||||
WHERE raw_started_at >= now() - interval '24 hours'
|
||||
GROUP BY
|
||||
machine_id,
|
||||
shopfloor_context_status,
|
||||
eligibility_status,
|
||||
operator_attribution_status
|
||||
ORDER BY
|
||||
machine_id,
|
||||
eligibility_status,
|
||||
operator_attribution_status;
|
||||
|
||||
COMMENT ON VIEW
|
||||
mv_reports_ucepsa_prod.li_stop_shopfloor_diagnostics_v1 IS
|
||||
'Diagnóstico de 24 horas del cruce paro físico × sesión Shop Floor × telemetría.';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_roles
|
||||
WHERE rolname = 'grafana_ucepsa_ro'
|
||||
) THEN
|
||||
GRANT SELECT
|
||||
ON
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_slices_v1
|
||||
TO grafana_ucepsa_ro;
|
||||
|
||||
GRANT SELECT
|
||||
ON
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1
|
||||
TO grafana_ucepsa_ro;
|
||||
|
||||
GRANT SELECT
|
||||
ON
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shadow_review_queue_shopfloor_v1
|
||||
TO grafana_ucepsa_ro;
|
||||
|
||||
GRANT SELECT
|
||||
ON
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shopfloor_session_stop_summary_v1
|
||||
TO grafana_ucepsa_ro;
|
||||
|
||||
GRANT SELECT
|
||||
ON
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_diagnostics_v1
|
||||
TO grafana_ucepsa_ro;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
Loading…
x
Reference in New Issue
Block a user