fix(ucepsa): separate eligible and observed stop minutes
This commit is contained in:
parent
48fb79d452
commit
d1f1b9a6c8
@ -0,0 +1,94 @@
|
||||
# UCEPSA — Semántica del resumen de paros Shop Floor v0.3.4.1
|
||||
|
||||
## Hallazgo
|
||||
|
||||
La validación de v0.3.4 mostró para el workorder 340:
|
||||
|
||||
```text
|
||||
physical_stop_count = 31
|
||||
eligible_stop_count = 28
|
||||
eligible_stop_minutes = 55,25
|
||||
```
|
||||
|
||||
El diagnóstico descomponía esos 55 minutos en:
|
||||
|
||||
```text
|
||||
CANDIDATE_GROUP 32,88 min
|
||||
CANDIDATE_INDIVIDUAL 17,51 min
|
||||
EXCLUDED_NOISE 0,11 min
|
||||
OPEN_MONITORING 4,81 min
|
||||
```
|
||||
|
||||
La vista de resumen sumaba todos los minutos observados en la columna
|
||||
`eligible_stop_minutes`, aunque el importe económico sí excluía correctamente
|
||||
el ruido y el paro abierto.
|
||||
|
||||
## Corrección
|
||||
|
||||
`eligible_stop_minutes` pasa a incluir únicamente filas con:
|
||||
|
||||
```text
|
||||
technically_eligible = true
|
||||
```
|
||||
|
||||
Se añaden columnas separadas:
|
||||
|
||||
- `observed_stop_minutes`
|
||||
- `telemetry_covered_stop_minutes`
|
||||
- `candidate_group_minutes`
|
||||
- `candidate_individual_minutes`
|
||||
- `excluded_noise_minutes`
|
||||
- `open_monitoring_minutes`
|
||||
- `blocked_minutes`
|
||||
|
||||
No se modifica la vista de candidatos, la cola SHADOW, la cola oficial ni los
|
||||
datos raw.
|
||||
|
||||
## Resultado esperado para la sesión 340
|
||||
|
||||
A la hora de la validación inicial, aproximadamente:
|
||||
|
||||
```text
|
||||
observed_stop_minutes 55,3
|
||||
eligible_stop_minutes 50,4
|
||||
candidate_group_minutes 32,9
|
||||
candidate_individual 17,5
|
||||
excluded_noise 0,1
|
||||
open_monitoring 4,8
|
||||
shadow_capacity_loss_eur 30,2
|
||||
```
|
||||
|
||||
El paro abierto puede seguir creciendo hasta que la máquina vuelva a arrancar,
|
||||
por lo que los valores exactos pueden variar.
|
||||
|
||||
## Despliegue
|
||||
|
||||
No es necesario detener servicios.
|
||||
|
||||
```bash
|
||||
FILE=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/sql/versions/107_fix_shopfloor_stop_summary_semantics_v0341.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_summary_semantics_v0341.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
|
||||
eligible_decomposition_errors = 0
|
||||
summary_candidate_mismatches = 0
|
||||
open_or_noise_inflation_errors = 0
|
||||
official_summary_rows = 0
|
||||
official_operator_queue_rows = 0
|
||||
```
|
||||
@ -0,0 +1,118 @@
|
||||
\pset pager off
|
||||
|
||||
\echo '=== 1. Columnas semánticas v0.3.4.1 ==='
|
||||
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
ordinal_position
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'mv_reports_ucepsa_prod'
|
||||
AND table_name =
|
||||
'li_shopfloor_session_stop_summary_v1'
|
||||
ORDER BY ordinal_position;
|
||||
|
||||
\echo '=== 2. Resumen corregido de la sesión 340 ==='
|
||||
|
||||
SELECT
|
||||
shopfloor_session_id,
|
||||
machine_id,
|
||||
production_order,
|
||||
odoo_workorder_id,
|
||||
session_status,
|
||||
operator_name,
|
||||
physical_stop_count,
|
||||
eligible_stop_count,
|
||||
observed_stop_minutes,
|
||||
telemetry_covered_stop_minutes,
|
||||
eligible_stop_minutes,
|
||||
candidate_group_minutes,
|
||||
candidate_individual_minutes,
|
||||
excluded_noise_minutes,
|
||||
open_monitoring_minutes,
|
||||
blocked_minutes,
|
||||
shadow_capacity_loss_eur,
|
||||
official_ledger_eligible
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shopfloor_session_stop_summary_v1
|
||||
WHERE odoo_workorder_id = 340;
|
||||
|
||||
\echo '=== 3. Eligible = grupo + individual ==='
|
||||
|
||||
SELECT count(*) AS eligible_decomposition_errors
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shopfloor_session_stop_summary_v1
|
||||
WHERE abs(
|
||||
eligible_stop_minutes
|
||||
- (
|
||||
candidate_group_minutes
|
||||
+ candidate_individual_minutes
|
||||
)
|
||||
) > 0.02;
|
||||
|
||||
\echo '=== 4. Resumen coincide con candidatos técnicamente elegibles ==='
|
||||
|
||||
WITH expected AS (
|
||||
SELECT
|
||||
shopfloor_session_id,
|
||||
round(
|
||||
sum(eligible_duration_s) FILTER (
|
||||
WHERE technically_eligible
|
||||
) / 60.0,
|
||||
2
|
||||
) AS expected_eligible_minutes,
|
||||
count(
|
||||
DISTINCT source_stop_id
|
||||
) FILTER (
|
||||
WHERE technically_eligible
|
||||
) AS expected_eligible_count
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_stop_shopfloor_candidates_v1
|
||||
WHERE shopfloor_session_id IS NOT NULL
|
||||
GROUP BY shopfloor_session_id
|
||||
)
|
||||
SELECT count(*) AS summary_candidate_mismatches
|
||||
FROM expected e
|
||||
JOIN
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shopfloor_session_stop_summary_v1 s
|
||||
ON s.shopfloor_session_id =
|
||||
e.shopfloor_session_id
|
||||
WHERE abs(
|
||||
s.eligible_stop_minutes
|
||||
- coalesce(e.expected_eligible_minutes, 0)
|
||||
) > 0.02
|
||||
OR s.eligible_stop_count
|
||||
<> e.expected_eligible_count;
|
||||
|
||||
\echo '=== 5. Abiertos y ruido no inflan eligible_stop_minutes ==='
|
||||
|
||||
SELECT count(*) AS open_or_noise_inflation_errors
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shopfloor_session_stop_summary_v1
|
||||
WHERE eligible_stop_minutes
|
||||
> observed_stop_minutes + 0.02
|
||||
OR eligible_stop_minutes
|
||||
+ excluded_noise_minutes
|
||||
+ open_monitoring_minutes
|
||||
+ blocked_minutes
|
||||
> observed_stop_minutes + 0.05;
|
||||
|
||||
\echo '=== 6. Nada oficial ==='
|
||||
|
||||
SELECT
|
||||
count(*) FILTER (
|
||||
WHERE official_ledger_eligible
|
||||
) AS official_summary_rows
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_shopfloor_session_stop_summary_v1;
|
||||
|
||||
\echo '=== 7. Cola oficial original sigue vacía ==='
|
||||
|
||||
SELECT count(*) AS official_operator_queue_rows
|
||||
FROM mv_reports_ucepsa_prod.li_operator_stop_queue_v2;
|
||||
@ -0,0 +1,158 @@
|
||||
BEGIN;
|
||||
|
||||
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,
|
||||
|
||||
COALESCE(
|
||||
ROUND(
|
||||
SUM(c.eligible_duration_s) FILTER (
|
||||
WHERE c.technically_eligible
|
||||
) / 60.0,
|
||||
2
|
||||
),
|
||||
0::numeric
|
||||
) 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,
|
||||
|
||||
ROUND(
|
||||
SUM(c.context_segment_duration_s) / 60.0,
|
||||
2
|
||||
) AS observed_stop_minutes,
|
||||
|
||||
ROUND(
|
||||
SUM(c.telemetry_covered_duration_s) / 60.0,
|
||||
2
|
||||
) AS telemetry_covered_stop_minutes,
|
||||
|
||||
COALESCE(
|
||||
ROUND(
|
||||
SUM(c.eligible_duration_s) FILTER (
|
||||
WHERE c.eligibility_status =
|
||||
'CANDIDATE_GROUP'
|
||||
) / 60.0,
|
||||
2
|
||||
),
|
||||
0::numeric
|
||||
) AS candidate_group_minutes,
|
||||
|
||||
COALESCE(
|
||||
ROUND(
|
||||
SUM(c.eligible_duration_s) FILTER (
|
||||
WHERE c.eligibility_status =
|
||||
'CANDIDATE_INDIVIDUAL'
|
||||
) / 60.0,
|
||||
2
|
||||
),
|
||||
0::numeric
|
||||
) AS candidate_individual_minutes,
|
||||
|
||||
COALESCE(
|
||||
ROUND(
|
||||
SUM(c.eligible_duration_s) FILTER (
|
||||
WHERE c.eligibility_status =
|
||||
'EXCLUDED_NOISE'
|
||||
) / 60.0,
|
||||
2
|
||||
),
|
||||
0::numeric
|
||||
) AS excluded_noise_minutes,
|
||||
|
||||
COALESCE(
|
||||
ROUND(
|
||||
SUM(c.eligible_duration_s) FILTER (
|
||||
WHERE c.eligibility_status =
|
||||
'OPEN_MONITORING'
|
||||
) / 60.0,
|
||||
2
|
||||
),
|
||||
0::numeric
|
||||
) AS open_monitoring_minutes,
|
||||
|
||||
COALESCE(
|
||||
ROUND(
|
||||
SUM(c.context_segment_duration_s) FILTER (
|
||||
WHERE c.eligibility_status LIKE
|
||||
'BLOCKED_%'
|
||||
) / 60.0,
|
||||
2
|
||||
),
|
||||
0::numeric
|
||||
) AS blocked_minutes
|
||||
|
||||
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 por sesión Shop Floor. eligible_stop_minutes incluye solo paros cerrados técnicamente elegibles; ruido, abiertos y bloqueados se muestran por separado.';
|
||||
|
||||
COMMIT;
|
||||
Loading…
x
Reference in New Issue
Block a user