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