feat(ucepsa): add Shop Floor context health shadow

This commit is contained in:
Victor Fraile Garcia 2026-07-20 11:37:22 +02:00
parent d1f1b9a6c8
commit e5cb98e349
4 changed files with 1796 additions and 0 deletions

View File

@ -0,0 +1,76 @@
# Consultas Grafana — Shop Floor Context Health v0.3.5
## Estado por máquina
```sql
SELECT
machine_id AS "Máquina",
live_context_status AS "Estado",
live_severity AS "Severidad",
published_production_order AS "Orden publicada",
odoo_workorder_id AS "Workorder sesión",
COALESCE(
operator_name,
operator_candidate_name
) AS "Operario",
sync_health_status AS "Sync",
recommended_action AS "Acción"
FROM
mv_reports_ucepsa_prod
.li_shopfloor_machine_live_context_v1
ORDER BY machine_id;
```
## Incidencias
```sql
SELECT
observed_at AS "Hora",
severity AS "Severidad",
machine_id AS "Máquina",
incident_code AS "Código",
production_order AS "Orden",
odoo_workorder_id AS "Workorder",
operator_name AS "Operario",
detail AS "Situación",
recommended_action AS "Acción"
FROM
mv_reports_ucepsa_prod
.li_shopfloor_context_incidents_v1
ORDER BY
CASE severity
WHEN 'CRITICAL' THEN 1
WHEN 'WARNING' THEN 2
ELSE 3
END,
machine_id;
```
## Sesiones abiertas
```sql
SELECT
machine_id AS "Máquina",
production_order AS "Orden",
odoo_workorder_id AS "Workorder",
operator_name AS "Operario",
session_started_at AS "Inicio",
session_age_min AS "Minutos abierta",
machine_running AS "Máquina en marcha",
open_stop_duration_min AS "Paro abierto min",
watch_status AS "Estado",
watch_severity AS "Severidad"
FROM
mv_reports_ucepsa_prod
.li_shopfloor_open_session_watch_v1
ORDER BY machine_id;
```
## Resumen
```sql
SELECT *
FROM
mv_reports_ucepsa_prod
.li_shopfloor_context_health_summary_v1;
```

View File

@ -0,0 +1,211 @@
# UCEPSA — Shop Floor Context Health v0.3.5
## Objetivo
Supervisar en vivo cinco capas:
```text
máquina física
orden Odoo publicada
sesión Odoo Shop Floor
sincronizador
calidad temporal
```
Esta versión no cambia la atribución de paros v0.3.4, no modifica Odoo y no
alimenta el Ledger oficial.
## Nuevos objetos
### Política
```text
mv_loss_intelligence.odoo_shopfloor_health_policies
```
Umbrales iniciales:
```text
intervalo esperado 15 s
DELAYED a partir de 45 s
STALE a partir de 120 s
sesión OPEN obsoleta 12 h
tolerancia fecha futura 5 min
```
### Vistas
```text
li_shopfloor_sync_health_v1
li_shopfloor_session_data_quality_v1
li_context_identity_registry_v1
li_shopfloor_machine_live_context_v1
li_shopfloor_open_session_watch_v1
li_shopfloor_context_incidents_v1
li_shopfloor_context_health_summary_v1
```
## Estados vivos principales
### Producción coherente
```text
máquina en marcha
+ orden publicada
+ una sesión OPEN del mismo workorder
→ HEALTHY_OPEN_SESSION
```
### Parada dentro de una orden
```text
máquina parada
+ orden publicada
+ sesión OPEN
→ TEMPORARY_STOP_INSIDE_SESSION
```
La parada física no cierra la sesión.
### Producción sin Shop Floor
```text
máquina en marcha
+ sin orden
+ sin sesión
→ RUNNING_WITHOUT_SHOPFLOOR_SESSION
```
Acción: confirmar sistema anterior, prueba, material residual o inicio omitido.
### Orden sin sesión
```text
orden publicada
+ ninguna sesión OPEN
→ RUNNING_WITH_ORDER_NO_SHOPFLOOR_SESSION
u ORDER_WITHOUT_SHOPFLOOR_SESSION
```
### Conflicto
```text
más de una sesión OPEN
o workorder publicado distinto del workorder de la sesión
→ CRITICAL
```
## Identidades con namespace
Nunca se cruza un identificador numérico desnudo.
Ejemplos:
```text
ODOO_SHOPFLOOR:WORKORDER:340
PRODUCTION_CONTEXT:LEGACY_ERP:1
```
## Incidencias
La vista de incidencias expone:
```text
severidad
código
máquina
context_key
orden
workorder
operario
detalle
acción recomendada
```
No crea tickets ni escribe en Odoo.
## Despliegue
No es necesario detener servicios.
```bash
FILE=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/sql/versions/108_ucepsa_shopfloor_context_health_v035.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_context_health_v035.sql
docker exec -i mv_ucepsa_postgres_hot sh -lc \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
< "$VALIDATION"
```
Valores estructurales obligatorios:
```text
live_machine_rows = 3
distinct_machine_rows = 3
pending_has_time_count = 0
open_missing_start_count = 0
open_has_end_count = 0
closed_missing_start_count = 0
closed_missing_end_count = 0
invalid_interval_count = 0
context_key_collision_count = 0
temporary_stop_wrong_severity = 0
healthy_session_wrong_severity = 0
idle_wrong_severity = 0
official_live_rows = 0
official_incident_rows = 0
official_operator_queue_rows = 0
```
`multiple_open_session_rows` debe ser cero en operación normal. Si no lo es, la
vista lo elevará como incidencia crítica.
## Lectura esperada
Mientras el workorder 340 siga abierto:
```text
CORT-02
context_key = ODOO_SHOPFLOOR:WORKORDER:340
operario = Esteban
estado = HEALTHY_OPEN_SESSION
```
Durante una parada física:
```text
estado = TEMPORARY_STOP_INSIDE_SESSION
severidad = INFO
```
CORT-00 solo aparecerá como advertencia cuando realmente esté en marcha sin
orden ni sesión.
## Grafana
La vista recomendada para una tabla operativa es:
```text
li_shopfloor_machine_live_context_v1
```
Para una lista de excepciones:
```text
li_shopfloor_context_incidents_v1
```
Para un indicador único:
```text
li_shopfloor_context_health_summary_v1
```

View File

@ -0,0 +1,222 @@
\pset pager off
\echo '=== 1. Objetos v0.3.5 ==='
SELECT
to_regclass(
'mv_loss_intelligence.odoo_shopfloor_health_policies'
) AS policy_table,
to_regclass(
'mv_reports_ucepsa_prod.li_shopfloor_sync_health_v1'
) AS sync_health_view,
to_regclass(
'mv_reports_ucepsa_prod.li_shopfloor_session_data_quality_v1'
) AS session_dq_view,
to_regclass(
'mv_reports_ucepsa_prod.li_context_identity_registry_v1'
) AS identity_registry_view,
to_regclass(
'mv_reports_ucepsa_prod.li_shopfloor_machine_live_context_v1'
) AS live_context_view,
to_regclass(
'mv_reports_ucepsa_prod.li_shopfloor_open_session_watch_v1'
) AS open_watch_view,
to_regclass(
'mv_reports_ucepsa_prod.li_shopfloor_context_incidents_v1'
) AS incidents_view,
to_regclass(
'mv_reports_ucepsa_prod.li_shopfloor_context_health_summary_v1'
) AS summary_view;
\echo '=== 2. Política activa ==='
SELECT *
FROM
mv_loss_intelligence
.odoo_shopfloor_health_policies
WHERE tenant = 'ucepsa'
AND site = 'ucepsa_onpremise'
AND policy_code =
'CUTTER_SHOPFLOOR_HEALTH_V1';
\echo '=== 3. Salud del sincronizador ==='
SELECT *
FROM
mv_reports_ucepsa_prod
.li_shopfloor_sync_health_v1;
\echo '=== 4. Tres máquinas y una fila por máquina ==='
SELECT
count(*) AS live_machine_rows,
count(
DISTINCT machine_id
) AS distinct_machine_rows
FROM
mv_reports_ucepsa_prod
.li_shopfloor_machine_live_context_v1;
\echo '=== 5. Estado vivo por máquina ==='
SELECT
machine_id,
telemetry_fresh,
machine_running,
published_order_present,
published_order_coherent,
published_production_order,
published_workorder_id,
open_session_count,
context_key,
session_production_order,
odoo_workorder_id,
operator_name,
operator_candidate_name,
sync_health_status,
live_context_status,
live_severity,
recommended_action,
official_eligible
FROM
mv_reports_ucepsa_prod
.li_shopfloor_machine_live_context_v1
ORDER BY machine_id;
\echo '=== 6. Invariantes de sesiones ==='
SELECT
count(*) FILTER (
WHERE pending_has_time
) AS pending_has_time_count,
count(*) FILTER (
WHERE open_missing_start
) AS open_missing_start_count,
count(*) FILTER (
WHERE open_has_end
) AS open_has_end_count,
count(*) FILTER (
WHERE closed_missing_start
) AS closed_missing_start_count,
count(*) FILTER (
WHERE closed_missing_end
) AS closed_missing_end_count,
count(*) FILTER (
WHERE invalid_interval
) AS invalid_interval_count,
count(*) FILTER (
WHERE multiple_open_sessions
) AS multiple_open_session_rows
FROM
mv_reports_ucepsa_prod
.li_shopfloor_session_data_quality_v1;
\echo '=== 7. Identidades con namespace sin colisiones ==='
WITH duplicated AS (
SELECT
tenant,
site,
context_key,
count(*) AS n
FROM
mv_reports_ucepsa_prod
.li_context_identity_registry_v1
GROUP BY
tenant,
site,
context_key
HAVING count(*) > 1
)
SELECT count(*) AS context_key_collision_count
FROM duplicated;
\echo '=== 8. Sesiones OPEN bajo vigilancia ==='
SELECT *
FROM
mv_reports_ucepsa_prod
.li_shopfloor_open_session_watch_v1
ORDER BY machine_id, session_started_at;
\echo '=== 9. Incidencias vivas ==='
SELECT
severity,
incident_scope,
incident_code,
machine_id,
context_key,
production_order,
odoo_workorder_id,
operator_name,
detail,
recommended_action,
official_eligible
FROM
mv_reports_ucepsa_prod
.li_shopfloor_context_incidents_v1
ORDER BY
CASE severity
WHEN 'CRITICAL' THEN 1
WHEN 'WARNING' THEN 2
ELSE 3
END,
machine_id NULLS FIRST,
incident_code;
\echo '=== 10. Coherencia de severidades ==='
SELECT
count(*) FILTER (
WHERE live_context_status =
'TEMPORARY_STOP_INSIDE_SESSION'
AND live_severity <> 'INFO'
) AS temporary_stop_wrong_severity,
count(*) FILTER (
WHERE live_context_status =
'HEALTHY_OPEN_SESSION'
AND live_severity <> 'OK'
) AS healthy_session_wrong_severity,
count(*) FILTER (
WHERE live_context_status =
'IDLE_NO_ACTIVE_CONTEXT'
AND live_severity <> 'OK'
) AS idle_wrong_severity,
count(*) FILTER (
WHERE official_eligible
) AS official_live_rows
FROM
mv_reports_ucepsa_prod
.li_shopfloor_machine_live_context_v1;
\echo '=== 11. Resumen ejecutivo ==='
SELECT *
FROM
mv_reports_ucepsa_prod
.li_shopfloor_context_health_summary_v1;
\echo '=== 12. Nada oficial ==='
SELECT
(
SELECT count(*)
FROM
mv_reports_ucepsa_prod
.li_shopfloor_context_incidents_v1
WHERE official_eligible
) AS official_incident_rows,
(
SELECT count(*)
FROM
mv_reports_ucepsa_prod
.li_context_identity_registry_v1
WHERE official_eligible
) AS official_identity_rows,
(
SELECT count(*)
FROM
mv_reports_ucepsa_prod
.li_operator_stop_queue_v2
) AS official_operator_queue_rows;

File diff suppressed because it is too large Load Diff