feat(ucepsa): add canonical context rollout readiness shadow
This commit is contained in:
parent
2efcb750a3
commit
ce086b697c
@ -0,0 +1,161 @@
|
|||||||
|
# UCEPSA — Readiness de rollout del contexto canónico v0.3.9
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Responder con números a esta pregunta:
|
||||||
|
|
||||||
|
> ¿Qué le falta a cada cortadora antes de poder usar el contexto canónico en un
|
||||||
|
> piloto controlado?
|
||||||
|
|
||||||
|
La versión no activa el Ledger oficial. Incluso una máquina en
|
||||||
|
`READY_FOR_CONTROLLED_SHADOW` conserva:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ready_for_official_ledger = false
|
||||||
|
official_ledger_eligible = false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Criterios iniciales
|
||||||
|
|
||||||
|
Ventana:
|
||||||
|
|
||||||
|
```text
|
||||||
|
7 días
|
||||||
|
```
|
||||||
|
|
||||||
|
Umbrales configurables:
|
||||||
|
|
||||||
|
```text
|
||||||
|
mínimo observado 8 h
|
||||||
|
contexto confirmado mínimo 90 %
|
||||||
|
contexto sin confirmar máximo 10 %
|
||||||
|
conflicto máximo 0 min
|
||||||
|
revisiones pendientes máximas 0
|
||||||
|
antigüedad máxima revisión 24 h
|
||||||
|
DQ crítica máxima 0
|
||||||
|
sync requerido HEALTHY
|
||||||
|
colector requerido HEALTHY
|
||||||
|
```
|
||||||
|
|
||||||
|
Estos valores son iniciales y pueden revisarse antes de octubre.
|
||||||
|
|
||||||
|
## Estados
|
||||||
|
|
||||||
|
```text
|
||||||
|
TECHNICAL_BLOCKED
|
||||||
|
INSUFFICIENT_OBSERVATION
|
||||||
|
REVIEW_BACKLOG
|
||||||
|
COVERAGE_NOT_READY
|
||||||
|
READY_FOR_CONTROLLED_SHADOW
|
||||||
|
```
|
||||||
|
|
||||||
|
La evaluación se detiene en el primer bloqueo operativo relevante.
|
||||||
|
|
||||||
|
## Beneficios
|
||||||
|
|
||||||
|
### Evita activar por intuición
|
||||||
|
|
||||||
|
No basta con que un dashboard se vea bien. La vista exige salud técnica,
|
||||||
|
observación suficiente, ausencia de conflictos, backlog resuelto y cobertura
|
||||||
|
mínima.
|
||||||
|
|
||||||
|
### Convierte el rollout en una lista de acciones
|
||||||
|
|
||||||
|
Ejemplos:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CORT-00
|
||||||
|
REVIEW_BACKLOG
|
||||||
|
→ preguntar a Producción por el episodio pendiente
|
||||||
|
|
||||||
|
CORT-01
|
||||||
|
INSUFFICIENT_OBSERVATION
|
||||||
|
→ acumular horas reales
|
||||||
|
|
||||||
|
CORT-02
|
||||||
|
COVERAGE_NOT_READY
|
||||||
|
→ aumentar uso correcto de Shop Floor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Separa readiness SHADOW de activación oficial
|
||||||
|
|
||||||
|
El sistema puede indicar que una máquina está preparada para una prueba
|
||||||
|
controlada sin permitir todavía que genere pérdidas oficiales.
|
||||||
|
|
||||||
|
### Da una evolución medible
|
||||||
|
|
||||||
|
Cada semana puede observarse:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sube % confirmado
|
||||||
|
baja % sin confirmar
|
||||||
|
desaparece backlog
|
||||||
|
conflictos siguen en cero
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objetos
|
||||||
|
|
||||||
|
```text
|
||||||
|
context_rollout_readiness_policies
|
||||||
|
|
||||||
|
li_canonical_context_readiness_metrics_v1
|
||||||
|
li_canonical_context_readiness_gate_v1
|
||||||
|
li_context_rollout_blockers_v1
|
||||||
|
li_context_rollout_readiness_summary_v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Despliegue
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FILE=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/sql/versions/111_ucepsa_context_rollout_readiness_shadow_v039.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_context_rollout_readiness_v039.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
|
||||||
|
machine_rows = 3
|
||||||
|
distinct_machines = 3
|
||||||
|
official_metric_rows = 0
|
||||||
|
|
||||||
|
negative_observed_hours = 0
|
||||||
|
negative_confirmed_hours = 0
|
||||||
|
negative_unconfirmed_hours = 0
|
||||||
|
negative_conflict_minutes = 0
|
||||||
|
|
||||||
|
invalid_confirmed_pct = 0
|
||||||
|
invalid_unconfirmed_pct = 0
|
||||||
|
invalid_operator_pct = 0
|
||||||
|
|
||||||
|
activity_decomposition_errors = 0
|
||||||
|
ready_gate_mismatches = 0
|
||||||
|
|
||||||
|
official_gate_rows = 0
|
||||||
|
official_blocker_rows = 0
|
||||||
|
official_operator_queue_rows = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
|
||||||
|
Se añaden al dashboard existente:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Readiness de contexto por máquina
|
||||||
|
Bloqueos de rollout
|
||||||
|
Resumen de readiness
|
||||||
|
```
|
||||||
|
|
||||||
|
El UID no cambia y el refresco permanece en 15 segundos.
|
||||||
@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"description": "Supervisión SHADOW de máquina, Odoo Shop Floor, contextos explícitos, histórico de incidencias y arbitraje canónico. Cero fuentes autoritativas queda sin confirmar y más de una queda bloqueada por conflicto. No alimenta el Ledger oficial.",
|
"description": "Supervisión SHADOW de sesiones, incidencias, arbitraje canónico y readiness de rollout. Los criterios muestran qué falta antes de un piloto controlado; nunca habilitan el Ledger oficial.",
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"fiscalYearStartMonth": 0,
|
"fiscalYearStartMonth": 0,
|
||||||
"graphTooltip": 1,
|
"graphTooltip": 1,
|
||||||
@ -1920,6 +1920,323 @@
|
|||||||
],
|
],
|
||||||
"title": "Cobertura de contexto canónico · últimos 30 días",
|
"title": "Cobertura de contexto canónico · últimos 30 días",
|
||||||
"type": "table"
|
"type": "table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "postgres",
|
||||||
|
"uid": "bfnbcasbm6hhca"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": {
|
||||||
|
"type": "auto"
|
||||||
|
},
|
||||||
|
"filterable": true,
|
||||||
|
"inspect": false
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Readiness"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"READY_FOR_CONTROLLED_SHADOW": {
|
||||||
|
"text": "READY SHADOW",
|
||||||
|
"color": "green",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"TECHNICAL_BLOCKED": {
|
||||||
|
"text": "BLOQUEO TÉCNICO",
|
||||||
|
"color": "red",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"INSUFFICIENT_OBSERVATION": {
|
||||||
|
"text": "FALTA OBSERVACIÓN",
|
||||||
|
"color": "blue",
|
||||||
|
"index": 2
|
||||||
|
},
|
||||||
|
"REVIEW_BACKLOG": {
|
||||||
|
"text": "REVISIÓN PENDIENTE",
|
||||||
|
"color": "orange",
|
||||||
|
"index": 3
|
||||||
|
},
|
||||||
|
"COVERAGE_NOT_READY": {
|
||||||
|
"text": "COBERTURA INSUFICIENTE",
|
||||||
|
"color": "orange",
|
||||||
|
"index": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": {
|
||||||
|
"type": "color-background"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 185
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Siguiente acción"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 540
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 99
|
||||||
|
},
|
||||||
|
"id": 17,
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"countRows": false,
|
||||||
|
"fields": "",
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": []
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "postgres",
|
||||||
|
"uid": "bfnbcasbm6hhca"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"format": "table",
|
||||||
|
"rawQuery": true,
|
||||||
|
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n observed_activity_hours AS \"Horas observadas\",\n confirmed_context_pct AS \"% confirmado\",\n unconfirmed_context_pct AS \"% sin confirmar\",\n pending_review_count AS \"Revisiones pendientes\",\n oldest_pending_review_hours AS \"Antigüedad revisión h\",\n recent_conflict_minutes AS \"Conflicto min\",\n critical_session_dq_count AS \"DQ crítica\",\n readiness_status AS \"Readiness\",\n next_action AS \"Siguiente acción\"\nFROM\n mv_reports_ucepsa_prod\n .li_canonical_context_readiness_gate_v1\nORDER BY machine_id",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Readiness de contexto por máquina · ventana de 7 días",
|
||||||
|
"type": "table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "postgres",
|
||||||
|
"uid": "bfnbcasbm6hhca"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": {
|
||||||
|
"type": "auto"
|
||||||
|
},
|
||||||
|
"filterable": true,
|
||||||
|
"inspect": false
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Severidad"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"INFO": {
|
||||||
|
"text": "INFO",
|
||||||
|
"color": "blue",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"WARNING": {
|
||||||
|
"text": "WARNING",
|
||||||
|
"color": "orange",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"CRITICAL": {
|
||||||
|
"text": "CRITICAL",
|
||||||
|
"color": "red",
|
||||||
|
"index": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": {
|
||||||
|
"type": "color-text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 110
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Acción"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 540
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 109
|
||||||
|
},
|
||||||
|
"id": 18,
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"countRows": false,
|
||||||
|
"fields": "",
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": []
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "postgres",
|
||||||
|
"uid": "bfnbcasbm6hhca"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"format": "table",
|
||||||
|
"rawQuery": true,
|
||||||
|
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n blocker_severity AS \"Severidad\",\n blocker_code AS \"Criterio\",\n current_value AS \"Valor actual\",\n target_value AS \"Objetivo\",\n recommended_action AS \"Acción\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_rollout_blockers_v1\nORDER BY\n CASE blocker_severity\n WHEN 'CRITICAL' THEN 1\n WHEN 'WARNING' THEN 2\n ELSE 3\n END,\n machine_id,\n blocker_code",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Bloqueos de rollout",
|
||||||
|
"type": "table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "postgres",
|
||||||
|
"uid": "bfnbcasbm6hhca"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": {
|
||||||
|
"type": "auto"
|
||||||
|
},
|
||||||
|
"filterable": true,
|
||||||
|
"inspect": false
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 119
|
||||||
|
},
|
||||||
|
"id": 19,
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"countRows": false,
|
||||||
|
"fields": "",
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": []
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "postgres",
|
||||||
|
"uid": "bfnbcasbm6hhca"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"format": "table",
|
||||||
|
"rawQuery": true,
|
||||||
|
"rawSql": "SELECT\n overall_readiness_status AS \"Estado global\",\n machine_count AS \"Máquinas\",\n ready_machine_count AS \"Ready SHADOW\",\n technical_blocked_machine_count AS \"Bloqueo técnico\",\n insufficient_observation_machine_count AS \"Falta observación\",\n review_backlog_machine_count AS \"Revisión pendiente\",\n coverage_not_ready_machine_count AS \"Cobertura insuficiente\",\n blocker_count AS \"Bloqueos\",\n critical_blocker_count AS \"Bloqueos críticos\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_rollout_readiness_summary_v1",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Resumen de readiness del contexto canónico",
|
||||||
|
"type": "table"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refresh": "15s",
|
"refresh": "15s",
|
||||||
@ -1935,7 +2252,10 @@
|
|||||||
"v037",
|
"v037",
|
||||||
"canonical-context",
|
"canonical-context",
|
||||||
"arbitration",
|
"arbitration",
|
||||||
"v038"
|
"v038",
|
||||||
|
"readiness",
|
||||||
|
"rollout-gate",
|
||||||
|
"v039"
|
||||||
],
|
],
|
||||||
"templating": {
|
"templating": {
|
||||||
"list": []
|
"list": []
|
||||||
|
|||||||
315
ucepsa/edge-oee-demo/ops/deploy_context_rollout_readiness_dashboard_v039.sh
Executable file
315
ucepsa/edge-oee-demo/ops/deploy_context_rollout_readiness_dashboard_v039.sh
Executable file
@ -0,0 +1,315 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ACTION="${1:-deploy}"
|
||||||
|
GRAFANA_CONTAINER="${GRAFANA_CONTAINER:-mv_ucepsa_grafana}"
|
||||||
|
REPO_ROOT="${READINESS_DASHBOARD_REPO_ROOT:-/srv/mesavault/40-clients/ucepsa/edge-oee-demo}"
|
||||||
|
DASHBOARD_FILE="${READINESS_DASHBOARD_FILE:-$REPO_ROOT/grafana/dashboards/ucepsa-shopfloor-context-health.dashboard.json}"
|
||||||
|
FOLDER_UID="${READINESS_DASHBOARD_FOLDER_UID:-ucepsa-mesavault}"
|
||||||
|
DASHBOARD_UID="ucepsa-shopfloor-context-health"
|
||||||
|
DATASOURCE_UID="bfnbcasbm6hhca"
|
||||||
|
|
||||||
|
resolve_url() {
|
||||||
|
if [[ -n "${GRAFANA_URL:-}" ]]; then
|
||||||
|
printf '%s' "$GRAFANA_URL"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local port_line host_port container_ip
|
||||||
|
|
||||||
|
port_line="$(
|
||||||
|
docker port "$GRAFANA_CONTAINER" 3000/tcp 2>/dev/null \
|
||||||
|
| head -n 1 || true
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -n "$port_line" ]]; then
|
||||||
|
host_port="${port_line##*:}"
|
||||||
|
printf 'http://127.0.0.1:%s' "$host_port"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
container_ip="$(
|
||||||
|
docker inspect "$GRAFANA_CONTAINER" \
|
||||||
|
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "$container_ip" ]]; then
|
||||||
|
echo "ERROR: no se pudo resolver Grafana." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'http://%s:3000' "$container_ip"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "${GRAFANA_API_TOKEN:-}" ]]; then
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
ERROR: falta GRAFANA_API_TOKEN.
|
||||||
|
|
||||||
|
read -rsp "Token temporal de Grafana: " GRAFANA_API_TOKEN
|
||||||
|
echo
|
||||||
|
export GRAFANA_API_TOKEN
|
||||||
|
MSG
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export GRAFANA_URL_RESOLVED
|
||||||
|
GRAFANA_URL_RESOLVED="$(resolve_url)"
|
||||||
|
|
||||||
|
python3 - \
|
||||||
|
"$ACTION" \
|
||||||
|
"$DASHBOARD_FILE" \
|
||||||
|
"$FOLDER_UID" \
|
||||||
|
"$DASHBOARD_UID" \
|
||||||
|
"$DATASOURCE_UID" <<'PY'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
(
|
||||||
|
action,
|
||||||
|
dashboard_file,
|
||||||
|
folder_uid,
|
||||||
|
dashboard_uid,
|
||||||
|
datasource_uid,
|
||||||
|
) = sys.argv[1:]
|
||||||
|
|
||||||
|
base_url = os.environ[
|
||||||
|
"GRAFANA_URL_RESOLVED"
|
||||||
|
].rstrip("/")
|
||||||
|
token = os.environ["GRAFANA_API_TOKEN"]
|
||||||
|
|
||||||
|
|
||||||
|
def request(method, path, payload=None):
|
||||||
|
body = None
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload is not None:
|
||||||
|
body = json.dumps(
|
||||||
|
payload,
|
||||||
|
ensure_ascii=False,
|
||||||
|
).encode("utf-8")
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
base_url + path,
|
||||||
|
data=body,
|
||||||
|
headers=headers,
|
||||||
|
method=method,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(
|
||||||
|
req,
|
||||||
|
timeout=20,
|
||||||
|
) as response:
|
||||||
|
raw = response.read()
|
||||||
|
return (
|
||||||
|
json.loads(
|
||||||
|
raw.decode("utf-8")
|
||||||
|
)
|
||||||
|
if raw
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
detail = exc.read().decode(
|
||||||
|
"utf-8",
|
||||||
|
errors="replace",
|
||||||
|
)
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Grafana API {method} {path}: "
|
||||||
|
f"HTTP {exc.code}: {detail}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
|
health = request("GET", "/api/health")
|
||||||
|
if not health or health.get("database") != "ok":
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Grafana no está saludable: {health!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if action == "deploy":
|
||||||
|
with open(
|
||||||
|
dashboard_file,
|
||||||
|
encoding="utf-8",
|
||||||
|
) as handle:
|
||||||
|
dashboard = json.load(handle)
|
||||||
|
|
||||||
|
result = request(
|
||||||
|
"POST",
|
||||||
|
"/api/dashboards/db",
|
||||||
|
{
|
||||||
|
"dashboard": dashboard,
|
||||||
|
"folderUid": folder_uid,
|
||||||
|
"overwrite": True,
|
||||||
|
"message": (
|
||||||
|
"MESAVAULT context rollout "
|
||||||
|
"readiness SHADOW v0.3.9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
print(json.dumps(
|
||||||
|
{
|
||||||
|
"status": "deployed",
|
||||||
|
"dashboard_uid": dashboard_uid,
|
||||||
|
"folder_uid": folder_uid,
|
||||||
|
"response": result,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
))
|
||||||
|
|
||||||
|
elif action == "validate":
|
||||||
|
result = request(
|
||||||
|
"GET",
|
||||||
|
(
|
||||||
|
"/api/dashboards/uid/"
|
||||||
|
+ urllib.parse.quote(
|
||||||
|
dashboard_uid
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
dashboard = result["dashboard"]
|
||||||
|
panels = {
|
||||||
|
panel.get("id"): panel
|
||||||
|
for panel in dashboard.get(
|
||||||
|
"panels",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource_uids = sorted({
|
||||||
|
panel.get(
|
||||||
|
"datasource",
|
||||||
|
{},
|
||||||
|
).get("uid")
|
||||||
|
for panel in dashboard.get(
|
||||||
|
"panels",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
if isinstance(
|
||||||
|
panel.get("datasource"),
|
||||||
|
dict,
|
||||||
|
)
|
||||||
|
and panel.get(
|
||||||
|
"datasource",
|
||||||
|
{},
|
||||||
|
).get("uid")
|
||||||
|
})
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if len(
|
||||||
|
dashboard.get(
|
||||||
|
"panels",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
) != 19:
|
||||||
|
errors.append(
|
||||||
|
"El dashboard no tiene 19 paneles"
|
||||||
|
)
|
||||||
|
|
||||||
|
if dashboard.get("refresh") != "15s":
|
||||||
|
errors.append(
|
||||||
|
"El refresco no está en 15s"
|
||||||
|
)
|
||||||
|
|
||||||
|
if datasource_uids != [datasource_uid]:
|
||||||
|
errors.append(
|
||||||
|
f"Datasource inesperado: "
|
||||||
|
f"{datasource_uids!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
17:
|
||||||
|
"li_canonical_context_readiness_gate_v1",
|
||||||
|
18:
|
||||||
|
"li_context_rollout_blockers_v1",
|
||||||
|
19:
|
||||||
|
"li_context_rollout_readiness_summary_v1",
|
||||||
|
}
|
||||||
|
|
||||||
|
for panel_id, fragment in expected.items():
|
||||||
|
sql = (
|
||||||
|
panels.get(
|
||||||
|
panel_id,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"targets",
|
||||||
|
[{}],
|
||||||
|
)[0]
|
||||||
|
.get(
|
||||||
|
"rawSql",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if fragment not in sql:
|
||||||
|
errors.append(
|
||||||
|
f"Panel {panel_id} "
|
||||||
|
f"no consulta {fragment}"
|
||||||
|
)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
"status":
|
||||||
|
"ok" if not errors else "error",
|
||||||
|
"dashboard_uid":
|
||||||
|
dashboard.get("uid"),
|
||||||
|
"folder_uid":
|
||||||
|
result.get(
|
||||||
|
"meta",
|
||||||
|
{},
|
||||||
|
).get("folderUid"),
|
||||||
|
"panel_count":
|
||||||
|
len(
|
||||||
|
dashboard.get(
|
||||||
|
"panels",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"refresh":
|
||||||
|
dashboard.get("refresh"),
|
||||||
|
"datasource_uids":
|
||||||
|
datasource_uids,
|
||||||
|
"version":
|
||||||
|
dashboard.get("version"),
|
||||||
|
"url":
|
||||||
|
result.get(
|
||||||
|
"meta",
|
||||||
|
{},
|
||||||
|
).get("url"),
|
||||||
|
"readiness_panels": {
|
||||||
|
"machine_gate":
|
||||||
|
17 in panels,
|
||||||
|
"blockers":
|
||||||
|
18 in panels,
|
||||||
|
"summary":
|
||||||
|
19 in panels,
|
||||||
|
},
|
||||||
|
"errors":
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(
|
||||||
|
output,
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise SystemExit(
|
||||||
|
"Uso: "
|
||||||
|
"deploy_context_rollout_readiness_dashboard_v039.sh "
|
||||||
|
"{deploy|validate}"
|
||||||
|
)
|
||||||
|
PY
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
\pset pager off
|
||||||
|
|
||||||
|
\echo '=== 1. Objetos v0.3.9 ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
to_regclass(
|
||||||
|
'mv_loss_intelligence.context_rollout_readiness_policies'
|
||||||
|
) AS policy_table,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_canonical_context_readiness_metrics_v1'
|
||||||
|
) AS metrics_view,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_canonical_context_readiness_gate_v1'
|
||||||
|
) AS gate_view,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_context_rollout_blockers_v1'
|
||||||
|
) AS blockers_view,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_context_rollout_readiness_summary_v1'
|
||||||
|
) AS summary_view;
|
||||||
|
|
||||||
|
\echo '=== 2. Política activa ==='
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_rollout_readiness_policies
|
||||||
|
WHERE tenant = 'ucepsa'
|
||||||
|
AND site = 'ucepsa_onpremise'
|
||||||
|
AND policy_code =
|
||||||
|
'CUTTER_CONTEXT_ROLLOUT_READINESS_V1';
|
||||||
|
|
||||||
|
\echo '=== 3. Una fila por máquina ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
COUNT(*) AS machine_rows,
|
||||||
|
COUNT(
|
||||||
|
DISTINCT machine_id
|
||||||
|
) AS distinct_machines,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE official_ledger_eligible
|
||||||
|
) AS official_metric_rows
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_metrics_v1;
|
||||||
|
|
||||||
|
\echo '=== 4. Rangos y duraciones ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE observed_activity_hours < 0
|
||||||
|
) AS negative_observed_hours,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE confirmed_context_hours < 0
|
||||||
|
) AS negative_confirmed_hours,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE unconfirmed_context_hours < 0
|
||||||
|
) AS negative_unconfirmed_hours,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE conflict_context_minutes < 0
|
||||||
|
) AS negative_conflict_minutes,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE confirmed_context_pct < 0
|
||||||
|
OR confirmed_context_pct > 100
|
||||||
|
) AS invalid_confirmed_pct,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE unconfirmed_context_pct < 0
|
||||||
|
OR unconfirmed_context_pct > 100
|
||||||
|
) AS invalid_unconfirmed_pct,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE productive_operator_confirmed_pct < 0
|
||||||
|
OR productive_operator_confirmed_pct > 100
|
||||||
|
) AS invalid_operator_pct
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_metrics_v1;
|
||||||
|
|
||||||
|
\echo '=== 5. Descomposición de actividad ==='
|
||||||
|
|
||||||
|
SELECT COUNT(*) AS activity_decomposition_errors
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_metrics_v1
|
||||||
|
WHERE confirmed_context_hours
|
||||||
|
+ unconfirmed_context_hours
|
||||||
|
+ (
|
||||||
|
conflict_context_minutes
|
||||||
|
/ 60.0
|
||||||
|
)
|
||||||
|
> observed_activity_hours + 0.05;
|
||||||
|
|
||||||
|
\echo '=== 6. Gate por máquina ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
machine_id,
|
||||||
|
observed_activity_hours,
|
||||||
|
confirmed_context_pct,
|
||||||
|
unconfirmed_context_pct,
|
||||||
|
recent_conflict_minutes,
|
||||||
|
pending_review_count,
|
||||||
|
oldest_pending_review_hours,
|
||||||
|
critical_session_dq_count,
|
||||||
|
sync_health_status,
|
||||||
|
collector_health_status,
|
||||||
|
technical_gate_pass,
|
||||||
|
observation_gate_pass,
|
||||||
|
review_gate_pass,
|
||||||
|
coverage_gate_pass,
|
||||||
|
readiness_status,
|
||||||
|
readiness_stage,
|
||||||
|
next_action,
|
||||||
|
ready_for_official_ledger,
|
||||||
|
official_ledger_eligible
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1
|
||||||
|
ORDER BY machine_id;
|
||||||
|
|
||||||
|
\echo '=== 7. Estados READY coherentes ==='
|
||||||
|
|
||||||
|
SELECT COUNT(*) AS ready_gate_mismatches
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1
|
||||||
|
WHERE readiness_status =
|
||||||
|
'READY_FOR_CONTROLLED_SHADOW'
|
||||||
|
AND NOT (
|
||||||
|
technical_gate_pass
|
||||||
|
AND observation_gate_pass
|
||||||
|
AND review_gate_pass
|
||||||
|
AND coverage_gate_pass
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo '=== 8. Bloqueos explicables ==='
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_rollout_blockers_v1
|
||||||
|
ORDER BY
|
||||||
|
CASE blocker_severity
|
||||||
|
WHEN 'CRITICAL' THEN 1
|
||||||
|
WHEN 'WARNING' THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END,
|
||||||
|
machine_id,
|
||||||
|
blocker_code;
|
||||||
|
|
||||||
|
\echo '=== 9. Resumen ==='
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_rollout_readiness_summary_v1;
|
||||||
|
|
||||||
|
\echo '=== 10. Nada oficial ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1
|
||||||
|
WHERE ready_for_official_ledger
|
||||||
|
OR official_ledger_eligible
|
||||||
|
) AS official_gate_rows,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_rollout_blockers_v1
|
||||||
|
WHERE official_ledger_eligible
|
||||||
|
) AS official_blocker_rows,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_operator_stop_queue_v2
|
||||||
|
) AS official_operator_queue_rows;
|
||||||
@ -0,0 +1,962 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS
|
||||||
|
mv_loss_intelligence.context_rollout_readiness_policies (
|
||||||
|
tenant text NOT NULL,
|
||||||
|
site text NOT NULL,
|
||||||
|
policy_code text NOT NULL,
|
||||||
|
lookback_days integer NOT NULL
|
||||||
|
CHECK (lookback_days > 0),
|
||||||
|
minimum_observed_activity_hours numeric NOT NULL
|
||||||
|
CHECK (minimum_observed_activity_hours >= 0),
|
||||||
|
minimum_confirmed_context_pct numeric NOT NULL
|
||||||
|
CHECK (
|
||||||
|
minimum_confirmed_context_pct >= 0
|
||||||
|
AND minimum_confirmed_context_pct <= 100
|
||||||
|
),
|
||||||
|
maximum_unconfirmed_context_pct numeric NOT NULL
|
||||||
|
CHECK (
|
||||||
|
maximum_unconfirmed_context_pct >= 0
|
||||||
|
AND maximum_unconfirmed_context_pct <= 100
|
||||||
|
),
|
||||||
|
maximum_conflict_minutes numeric NOT NULL
|
||||||
|
CHECK (maximum_conflict_minutes >= 0),
|
||||||
|
maximum_pending_review_count integer NOT NULL
|
||||||
|
CHECK (maximum_pending_review_count >= 0),
|
||||||
|
maximum_oldest_pending_review_hours numeric NOT NULL
|
||||||
|
CHECK (maximum_oldest_pending_review_hours >= 0),
|
||||||
|
maximum_critical_session_dq_count integer NOT NULL
|
||||||
|
CHECK (maximum_critical_session_dq_count >= 0),
|
||||||
|
require_sync_healthy boolean NOT NULL DEFAULT true,
|
||||||
|
require_incident_collector_healthy boolean NOT NULL DEFAULT true,
|
||||||
|
active boolean NOT NULL DEFAULT true,
|
||||||
|
notes text,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
policy_code
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE
|
||||||
|
mv_loss_intelligence.context_rollout_readiness_policies IS
|
||||||
|
'Umbrales SHADOW para medir preparación del contexto canónico antes de cualquier activación controlada. No habilita el Ledger oficial.';
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
mv_loss_intelligence.context_rollout_readiness_policies (
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
policy_code,
|
||||||
|
lookback_days,
|
||||||
|
minimum_observed_activity_hours,
|
||||||
|
minimum_confirmed_context_pct,
|
||||||
|
maximum_unconfirmed_context_pct,
|
||||||
|
maximum_conflict_minutes,
|
||||||
|
maximum_pending_review_count,
|
||||||
|
maximum_oldest_pending_review_hours,
|
||||||
|
maximum_critical_session_dq_count,
|
||||||
|
require_sync_healthy,
|
||||||
|
require_incident_collector_healthy,
|
||||||
|
active,
|
||||||
|
notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'ucepsa',
|
||||||
|
'ucepsa_onpremise',
|
||||||
|
'CUTTER_CONTEXT_ROLLOUT_READINESS_V1',
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
90,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
24,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
'Criterios iniciales para readiness SHADOW. Son configurables y no constituyen autorización de puesta en producción oficial.'
|
||||||
|
)
|
||||||
|
ON CONFLICT (
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
policy_code
|
||||||
|
)
|
||||||
|
DO UPDATE SET
|
||||||
|
lookback_days =
|
||||||
|
EXCLUDED.lookback_days,
|
||||||
|
minimum_observed_activity_hours =
|
||||||
|
EXCLUDED.minimum_observed_activity_hours,
|
||||||
|
minimum_confirmed_context_pct =
|
||||||
|
EXCLUDED.minimum_confirmed_context_pct,
|
||||||
|
maximum_unconfirmed_context_pct =
|
||||||
|
EXCLUDED.maximum_unconfirmed_context_pct,
|
||||||
|
maximum_conflict_minutes =
|
||||||
|
EXCLUDED.maximum_conflict_minutes,
|
||||||
|
maximum_pending_review_count =
|
||||||
|
EXCLUDED.maximum_pending_review_count,
|
||||||
|
maximum_oldest_pending_review_hours =
|
||||||
|
EXCLUDED.maximum_oldest_pending_review_hours,
|
||||||
|
maximum_critical_session_dq_count =
|
||||||
|
EXCLUDED.maximum_critical_session_dq_count,
|
||||||
|
require_sync_healthy =
|
||||||
|
EXCLUDED.require_sync_healthy,
|
||||||
|
require_incident_collector_healthy =
|
||||||
|
EXCLUDED.require_incident_collector_healthy,
|
||||||
|
active = true,
|
||||||
|
notes =
|
||||||
|
EXCLUDED.notes,
|
||||||
|
updated_at = now();
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_canonical_context_readiness_metrics_v1
|
||||||
|
AS
|
||||||
|
WITH policy AS (
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_rollout_readiness_policies
|
||||||
|
WHERE tenant = 'ucepsa'
|
||||||
|
AND site = 'ucepsa_onpremise'
|
||||||
|
AND policy_code =
|
||||||
|
'CUTTER_CONTEXT_ROLLOUT_READINESS_V1'
|
||||||
|
AND active
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
),
|
||||||
|
machines AS (
|
||||||
|
SELECT
|
||||||
|
l.tenant,
|
||||||
|
l.site,
|
||||||
|
l.machine_id,
|
||||||
|
l.telemetry_fresh,
|
||||||
|
l.machine_running,
|
||||||
|
l.canonical_context_status
|
||||||
|
AS live_canonical_context_status,
|
||||||
|
l.canonical_context_key
|
||||||
|
AS live_canonical_context_key,
|
||||||
|
l.canonical_health_status
|
||||||
|
AS live_canonical_health_status,
|
||||||
|
l.order_alignment_status
|
||||||
|
AS live_order_alignment_status,
|
||||||
|
l.recommended_action
|
||||||
|
AS live_recommended_action
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_machine_canonical_context_live_v1 l
|
||||||
|
),
|
||||||
|
window_config AS (
|
||||||
|
SELECT
|
||||||
|
p.*,
|
||||||
|
now() - (
|
||||||
|
p.lookback_days
|
||||||
|
* interval '1 day'
|
||||||
|
) AS window_from,
|
||||||
|
now() AS window_to
|
||||||
|
FROM policy p
|
||||||
|
),
|
||||||
|
clipped_segments AS (
|
||||||
|
SELECT
|
||||||
|
s.machine_id,
|
||||||
|
s.canonical_context_status,
|
||||||
|
s.canonical_context_source,
|
||||||
|
s.canonical_productive_context,
|
||||||
|
s.individual_attribution_eligible,
|
||||||
|
s.review_required,
|
||||||
|
GREATEST(
|
||||||
|
s.segment_from,
|
||||||
|
w.window_from
|
||||||
|
) AS clipped_from,
|
||||||
|
LEAST(
|
||||||
|
s.segment_to,
|
||||||
|
w.window_to
|
||||||
|
) AS clipped_to,
|
||||||
|
EXTRACT(
|
||||||
|
EPOCH FROM (
|
||||||
|
LEAST(
|
||||||
|
s.segment_to,
|
||||||
|
w.window_to
|
||||||
|
)
|
||||||
|
- GREATEST(
|
||||||
|
s.segment_from,
|
||||||
|
w.window_from
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)::numeric AS clipped_duration_s
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_segments_v1 s
|
||||||
|
CROSS JOIN window_config w
|
||||||
|
WHERE s.segment_to >
|
||||||
|
w.window_from
|
||||||
|
AND s.segment_from <
|
||||||
|
w.window_to
|
||||||
|
AND LEAST(
|
||||||
|
s.segment_to,
|
||||||
|
w.window_to
|
||||||
|
) >
|
||||||
|
GREATEST(
|
||||||
|
s.segment_from,
|
||||||
|
w.window_from
|
||||||
|
)
|
||||||
|
),
|
||||||
|
activity_rollup AS (
|
||||||
|
SELECT
|
||||||
|
machine_id,
|
||||||
|
COALESCE(
|
||||||
|
SUM(clipped_duration_s),
|
||||||
|
0::numeric
|
||||||
|
) AS observed_activity_s,
|
||||||
|
COALESCE(
|
||||||
|
SUM(clipped_duration_s) FILTER (
|
||||||
|
WHERE canonical_context_status NOT IN (
|
||||||
|
'UNCONFIRMED_CONTEXT',
|
||||||
|
'CONTEXT_CONFLICT',
|
||||||
|
'UNSUPPORTED_CONTEXT'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
0::numeric
|
||||||
|
) AS confirmed_context_s,
|
||||||
|
COALESCE(
|
||||||
|
SUM(clipped_duration_s) FILTER (
|
||||||
|
WHERE canonical_productive_context
|
||||||
|
),
|
||||||
|
0::numeric
|
||||||
|
) AS productive_context_s,
|
||||||
|
COALESCE(
|
||||||
|
SUM(clipped_duration_s) FILTER (
|
||||||
|
WHERE canonical_context_status =
|
||||||
|
'UNCONFIRMED_CONTEXT'
|
||||||
|
),
|
||||||
|
0::numeric
|
||||||
|
) AS unconfirmed_context_s,
|
||||||
|
COALESCE(
|
||||||
|
SUM(clipped_duration_s) FILTER (
|
||||||
|
WHERE canonical_context_status =
|
||||||
|
'CONTEXT_CONFLICT'
|
||||||
|
),
|
||||||
|
0::numeric
|
||||||
|
) AS conflict_context_s,
|
||||||
|
COALESCE(
|
||||||
|
SUM(clipped_duration_s) FILTER (
|
||||||
|
WHERE canonical_productive_context
|
||||||
|
AND individual_attribution_eligible
|
||||||
|
),
|
||||||
|
0::numeric
|
||||||
|
) AS operator_confirmed_productive_s,
|
||||||
|
COUNT(*) AS segment_count,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE review_required
|
||||||
|
) AS review_segment_count
|
||||||
|
FROM clipped_segments
|
||||||
|
GROUP BY machine_id
|
||||||
|
),
|
||||||
|
pending_reviews AS (
|
||||||
|
SELECT
|
||||||
|
b.machine_id,
|
||||||
|
COUNT(*) AS pending_review_count,
|
||||||
|
MIN(b.first_seen_at)
|
||||||
|
AS oldest_pending_review_at,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE b.max_severity =
|
||||||
|
'CRITICAL'
|
||||||
|
) AS pending_critical_review_count
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_shopfloor_context_incident_backlog_v1 b
|
||||||
|
WHERE b.machine_id IS NOT NULL
|
||||||
|
GROUP BY b.machine_id
|
||||||
|
),
|
||||||
|
session_dq AS (
|
||||||
|
SELECT
|
||||||
|
d.machine_id,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE d.dq_status =
|
||||||
|
'CRITICAL'
|
||||||
|
) AS critical_session_dq_count,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE d.dq_status =
|
||||||
|
'WARNING'
|
||||||
|
) AS warning_session_dq_count
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_shopfloor_session_data_quality_v1 d
|
||||||
|
GROUP BY d.machine_id
|
||||||
|
),
|
||||||
|
recent_conflicts AS (
|
||||||
|
SELECT
|
||||||
|
c.machine_id,
|
||||||
|
COUNT(*) AS conflict_segment_count,
|
||||||
|
COALESCE(
|
||||||
|
SUM(c.segment_duration_s)
|
||||||
|
/ 60.0,
|
||||||
|
0::numeric
|
||||||
|
) AS conflict_minutes
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_arbitration_conflicts_v1 c
|
||||||
|
CROSS JOIN window_config w
|
||||||
|
WHERE c.segment_to >
|
||||||
|
w.window_from
|
||||||
|
AND c.segment_from <
|
||||||
|
w.window_to
|
||||||
|
GROUP BY c.machine_id
|
||||||
|
),
|
||||||
|
sync_health AS (
|
||||||
|
SELECT
|
||||||
|
sync_health_status,
|
||||||
|
severity AS sync_severity,
|
||||||
|
seconds_since_success
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_shopfloor_sync_health_v1
|
||||||
|
),
|
||||||
|
collector_health AS (
|
||||||
|
SELECT
|
||||||
|
collector_health_status,
|
||||||
|
severity AS collector_severity,
|
||||||
|
seconds_since_success
|
||||||
|
AS collector_seconds_since_success
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_shopfloor_context_incident_collector_health_v1
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
m.tenant,
|
||||||
|
m.site,
|
||||||
|
m.machine_id,
|
||||||
|
w.policy_code,
|
||||||
|
w.lookback_days,
|
||||||
|
w.window_from,
|
||||||
|
w.window_to,
|
||||||
|
w.minimum_observed_activity_hours,
|
||||||
|
w.minimum_confirmed_context_pct,
|
||||||
|
w.maximum_unconfirmed_context_pct,
|
||||||
|
w.maximum_conflict_minutes,
|
||||||
|
w.maximum_pending_review_count,
|
||||||
|
w.maximum_oldest_pending_review_hours,
|
||||||
|
w.maximum_critical_session_dq_count,
|
||||||
|
w.require_sync_healthy,
|
||||||
|
w.require_incident_collector_healthy,
|
||||||
|
m.telemetry_fresh,
|
||||||
|
m.machine_running,
|
||||||
|
m.live_canonical_context_status,
|
||||||
|
m.live_canonical_context_key,
|
||||||
|
m.live_canonical_health_status,
|
||||||
|
m.live_order_alignment_status,
|
||||||
|
m.live_recommended_action,
|
||||||
|
COALESCE(
|
||||||
|
a.segment_count,
|
||||||
|
0
|
||||||
|
) AS segment_count,
|
||||||
|
COALESCE(
|
||||||
|
a.review_segment_count,
|
||||||
|
0
|
||||||
|
) AS review_segment_count,
|
||||||
|
ROUND(
|
||||||
|
COALESCE(
|
||||||
|
a.observed_activity_s,
|
||||||
|
0
|
||||||
|
) / 3600.0,
|
||||||
|
2
|
||||||
|
) AS observed_activity_hours,
|
||||||
|
ROUND(
|
||||||
|
COALESCE(
|
||||||
|
a.confirmed_context_s,
|
||||||
|
0
|
||||||
|
) / 3600.0,
|
||||||
|
2
|
||||||
|
) AS confirmed_context_hours,
|
||||||
|
ROUND(
|
||||||
|
COALESCE(
|
||||||
|
a.productive_context_s,
|
||||||
|
0
|
||||||
|
) / 3600.0,
|
||||||
|
2
|
||||||
|
) AS productive_context_hours,
|
||||||
|
ROUND(
|
||||||
|
COALESCE(
|
||||||
|
a.unconfirmed_context_s,
|
||||||
|
0
|
||||||
|
) / 3600.0,
|
||||||
|
2
|
||||||
|
) AS unconfirmed_context_hours,
|
||||||
|
ROUND(
|
||||||
|
COALESCE(
|
||||||
|
a.conflict_context_s,
|
||||||
|
0
|
||||||
|
) / 60.0,
|
||||||
|
2
|
||||||
|
) AS conflict_context_minutes,
|
||||||
|
ROUND(
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(
|
||||||
|
a.observed_activity_s,
|
||||||
|
0
|
||||||
|
) > 0
|
||||||
|
THEN (
|
||||||
|
100.0
|
||||||
|
* COALESCE(
|
||||||
|
a.confirmed_context_s,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
/ a.observed_activity_s
|
||||||
|
)
|
||||||
|
ELSE 0
|
||||||
|
END::numeric,
|
||||||
|
2
|
||||||
|
) AS confirmed_context_pct,
|
||||||
|
ROUND(
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(
|
||||||
|
a.observed_activity_s,
|
||||||
|
0
|
||||||
|
) > 0
|
||||||
|
THEN (
|
||||||
|
100.0
|
||||||
|
* COALESCE(
|
||||||
|
a.unconfirmed_context_s,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
/ a.observed_activity_s
|
||||||
|
)
|
||||||
|
ELSE 0
|
||||||
|
END::numeric,
|
||||||
|
2
|
||||||
|
) AS unconfirmed_context_pct,
|
||||||
|
ROUND(
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(
|
||||||
|
a.productive_context_s,
|
||||||
|
0
|
||||||
|
) > 0
|
||||||
|
THEN (
|
||||||
|
100.0
|
||||||
|
* COALESCE(
|
||||||
|
a.operator_confirmed_productive_s,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
/ a.productive_context_s
|
||||||
|
)
|
||||||
|
ELSE 0
|
||||||
|
END::numeric,
|
||||||
|
2
|
||||||
|
) AS productive_operator_confirmed_pct,
|
||||||
|
COALESCE(
|
||||||
|
p.pending_review_count,
|
||||||
|
0
|
||||||
|
) AS pending_review_count,
|
||||||
|
COALESCE(
|
||||||
|
p.pending_critical_review_count,
|
||||||
|
0
|
||||||
|
) AS pending_critical_review_count,
|
||||||
|
p.oldest_pending_review_at,
|
||||||
|
ROUND(
|
||||||
|
CASE
|
||||||
|
WHEN p.oldest_pending_review_at
|
||||||
|
IS NULL
|
||||||
|
THEN 0
|
||||||
|
ELSE EXTRACT(
|
||||||
|
EPOCH FROM (
|
||||||
|
now()
|
||||||
|
- p.oldest_pending_review_at
|
||||||
|
)
|
||||||
|
)::numeric / 3600.0
|
||||||
|
END,
|
||||||
|
2
|
||||||
|
) AS oldest_pending_review_hours,
|
||||||
|
COALESCE(
|
||||||
|
d.critical_session_dq_count,
|
||||||
|
0
|
||||||
|
) AS critical_session_dq_count,
|
||||||
|
COALESCE(
|
||||||
|
d.warning_session_dq_count,
|
||||||
|
0
|
||||||
|
) AS warning_session_dq_count,
|
||||||
|
COALESCE(
|
||||||
|
c.conflict_segment_count,
|
||||||
|
0
|
||||||
|
) AS conflict_segment_count,
|
||||||
|
ROUND(
|
||||||
|
COALESCE(
|
||||||
|
c.conflict_minutes,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
2
|
||||||
|
) AS recent_conflict_minutes,
|
||||||
|
s.sync_health_status,
|
||||||
|
s.sync_severity,
|
||||||
|
s.seconds_since_success
|
||||||
|
AS sync_seconds_since_success,
|
||||||
|
ch.collector_health_status,
|
||||||
|
ch.collector_severity,
|
||||||
|
ch.collector_seconds_since_success,
|
||||||
|
false AS official_ledger_eligible,
|
||||||
|
now() AS observed_at
|
||||||
|
FROM machines m
|
||||||
|
CROSS JOIN window_config w
|
||||||
|
CROSS JOIN sync_health s
|
||||||
|
CROSS JOIN collector_health ch
|
||||||
|
LEFT JOIN activity_rollup a
|
||||||
|
ON a.machine_id =
|
||||||
|
m.machine_id
|
||||||
|
LEFT JOIN pending_reviews p
|
||||||
|
ON p.machine_id =
|
||||||
|
m.machine_id
|
||||||
|
LEFT JOIN session_dq d
|
||||||
|
ON d.machine_id =
|
||||||
|
m.machine_id
|
||||||
|
LEFT JOIN recent_conflicts c
|
||||||
|
ON c.machine_id =
|
||||||
|
m.machine_id;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_canonical_context_readiness_metrics_v1 IS
|
||||||
|
'Métricas SHADOW por máquina para decidir qué falta antes de un piloto controlado de contexto canónico. No concede elegibilidad oficial.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_canonical_context_readiness_gate_v1
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
m.*,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
NOT m.require_sync_healthy
|
||||||
|
OR m.sync_health_status =
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
NOT m.require_incident_collector_healthy
|
||||||
|
OR m.collector_health_status =
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
AND m.critical_session_dq_count <=
|
||||||
|
m.maximum_critical_session_dq_count
|
||||||
|
AND m.recent_conflict_minutes <=
|
||||||
|
m.maximum_conflict_minutes
|
||||||
|
AND m.live_canonical_health_status <>
|
||||||
|
'CRITICAL'
|
||||||
|
) AS technical_gate_pass,
|
||||||
|
(
|
||||||
|
m.observed_activity_hours >=
|
||||||
|
m.minimum_observed_activity_hours
|
||||||
|
) AS observation_gate_pass,
|
||||||
|
(
|
||||||
|
m.pending_review_count <=
|
||||||
|
m.maximum_pending_review_count
|
||||||
|
AND m.oldest_pending_review_hours <=
|
||||||
|
m.maximum_oldest_pending_review_hours
|
||||||
|
) AS review_gate_pass,
|
||||||
|
(
|
||||||
|
m.confirmed_context_pct >=
|
||||||
|
m.minimum_confirmed_context_pct
|
||||||
|
AND m.unconfirmed_context_pct <=
|
||||||
|
m.maximum_unconfirmed_context_pct
|
||||||
|
) AS coverage_gate_pass,
|
||||||
|
CASE
|
||||||
|
WHEN (
|
||||||
|
(
|
||||||
|
m.require_sync_healthy
|
||||||
|
AND m.sync_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
m.require_incident_collector_healthy
|
||||||
|
AND m.collector_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
OR m.critical_session_dq_count >
|
||||||
|
m.maximum_critical_session_dq_count
|
||||||
|
OR m.recent_conflict_minutes >
|
||||||
|
m.maximum_conflict_minutes
|
||||||
|
OR m.live_canonical_health_status =
|
||||||
|
'CRITICAL'
|
||||||
|
)
|
||||||
|
THEN 'TECHNICAL_BLOCKED'
|
||||||
|
WHEN m.observed_activity_hours <
|
||||||
|
m.minimum_observed_activity_hours
|
||||||
|
THEN 'INSUFFICIENT_OBSERVATION'
|
||||||
|
WHEN (
|
||||||
|
m.pending_review_count >
|
||||||
|
m.maximum_pending_review_count
|
||||||
|
OR m.oldest_pending_review_hours >
|
||||||
|
m.maximum_oldest_pending_review_hours
|
||||||
|
)
|
||||||
|
THEN 'REVIEW_BACKLOG'
|
||||||
|
WHEN (
|
||||||
|
m.confirmed_context_pct <
|
||||||
|
m.minimum_confirmed_context_pct
|
||||||
|
OR m.unconfirmed_context_pct >
|
||||||
|
m.maximum_unconfirmed_context_pct
|
||||||
|
)
|
||||||
|
THEN 'COVERAGE_NOT_READY'
|
||||||
|
ELSE 'READY_FOR_CONTROLLED_SHADOW'
|
||||||
|
END AS readiness_status,
|
||||||
|
CASE
|
||||||
|
WHEN (
|
||||||
|
(
|
||||||
|
m.require_sync_healthy
|
||||||
|
AND m.sync_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
m.require_incident_collector_healthy
|
||||||
|
AND m.collector_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
THEN
|
||||||
|
'Restablecer la salud de sincronización y del colector.'
|
||||||
|
WHEN m.critical_session_dq_count >
|
||||||
|
m.maximum_critical_session_dq_count
|
||||||
|
THEN
|
||||||
|
'Resolver errores críticos de calidad en sesiones Shop Floor.'
|
||||||
|
WHEN m.recent_conflict_minutes >
|
||||||
|
m.maximum_conflict_minutes
|
||||||
|
OR m.live_canonical_health_status =
|
||||||
|
'CRITICAL'
|
||||||
|
THEN
|
||||||
|
'Resolver los conflictos de contexto antes de continuar.'
|
||||||
|
WHEN m.observed_activity_hours <
|
||||||
|
m.minimum_observed_activity_hours
|
||||||
|
THEN
|
||||||
|
'Acumular más horas reales de observación.'
|
||||||
|
WHEN m.pending_review_count >
|
||||||
|
m.maximum_pending_review_count
|
||||||
|
THEN
|
||||||
|
'Revisar los episodios pendientes con Producción o el responsable técnico.'
|
||||||
|
WHEN m.oldest_pending_review_hours >
|
||||||
|
m.maximum_oldest_pending_review_hours
|
||||||
|
THEN
|
||||||
|
'Evitar que la revisión más antigua supere el plazo configurado.'
|
||||||
|
WHEN m.confirmed_context_pct <
|
||||||
|
m.minimum_confirmed_context_pct
|
||||||
|
THEN
|
||||||
|
'Aumentar el uso correcto de Shop Floor o autorizar explícitamente los contextos legacy/manuales reales.'
|
||||||
|
WHEN m.unconfirmed_context_pct >
|
||||||
|
m.maximum_unconfirmed_context_pct
|
||||||
|
THEN
|
||||||
|
'Reducir actividad física sin contexto confirmado.'
|
||||||
|
ELSE
|
||||||
|
'La máquina cumple los criterios SHADOW configurados.'
|
||||||
|
END AS next_action,
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN (
|
||||||
|
(
|
||||||
|
m.require_sync_healthy
|
||||||
|
AND m.sync_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
m.require_incident_collector_healthy
|
||||||
|
AND m.collector_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
)
|
||||||
|
OR m.critical_session_dq_count >
|
||||||
|
m.maximum_critical_session_dq_count
|
||||||
|
OR m.recent_conflict_minutes >
|
||||||
|
m.maximum_conflict_minutes
|
||||||
|
OR m.live_canonical_health_status =
|
||||||
|
'CRITICAL'
|
||||||
|
)
|
||||||
|
THEN 1
|
||||||
|
WHEN m.observed_activity_hours <
|
||||||
|
m.minimum_observed_activity_hours
|
||||||
|
THEN 2
|
||||||
|
WHEN (
|
||||||
|
m.pending_review_count >
|
||||||
|
m.maximum_pending_review_count
|
||||||
|
OR m.oldest_pending_review_hours >
|
||||||
|
m.maximum_oldest_pending_review_hours
|
||||||
|
)
|
||||||
|
THEN 3
|
||||||
|
WHEN (
|
||||||
|
m.confirmed_context_pct <
|
||||||
|
m.minimum_confirmed_context_pct
|
||||||
|
OR m.unconfirmed_context_pct >
|
||||||
|
m.maximum_unconfirmed_context_pct
|
||||||
|
)
|
||||||
|
THEN 4
|
||||||
|
ELSE 5
|
||||||
|
END
|
||||||
|
) AS readiness_stage,
|
||||||
|
false AS ready_for_official_ledger,
|
||||||
|
false AS official_ledger_eligible
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_metrics_v1 m;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_canonical_context_readiness_gate_v1 IS
|
||||||
|
'Puerta de readiness SHADOW por máquina. READY_FOR_CONTROLLED_SHADOW no significa activación oficial y ready_for_official_ledger permanece false.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_rollout_blockers_v1
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'SYNC_HEALTH'::text
|
||||||
|
AS blocker_code,
|
||||||
|
'CRITICAL'::text
|
||||||
|
AS blocker_severity,
|
||||||
|
g.sync_health_status
|
||||||
|
AS current_value,
|
||||||
|
'HEALTHY'::text
|
||||||
|
AS target_value,
|
||||||
|
'Restablecer la sincronización Shop Floor.'::text
|
||||||
|
AS recommended_action,
|
||||||
|
false AS official_ledger_eligible
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.require_sync_healthy
|
||||||
|
AND g.sync_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'INCIDENT_COLLECTOR_HEALTH',
|
||||||
|
'CRITICAL',
|
||||||
|
g.collector_health_status,
|
||||||
|
'HEALTHY',
|
||||||
|
'Restablecer el colector de episodios de incidencia.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.require_incident_collector_healthy
|
||||||
|
AND g.collector_health_status <>
|
||||||
|
'HEALTHY'
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'CRITICAL_SESSION_DQ',
|
||||||
|
'CRITICAL',
|
||||||
|
g.critical_session_dq_count::text,
|
||||||
|
g.maximum_critical_session_dq_count::text,
|
||||||
|
'Resolver las sesiones Shop Floor con errores críticos.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.critical_session_dq_count >
|
||||||
|
g.maximum_critical_session_dq_count
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'CONTEXT_CONFLICT_MINUTES',
|
||||||
|
'CRITICAL',
|
||||||
|
g.recent_conflict_minutes::text,
|
||||||
|
g.maximum_conflict_minutes::text,
|
||||||
|
'Resolver los solapamientos entre fuentes autoritativas.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.recent_conflict_minutes >
|
||||||
|
g.maximum_conflict_minutes
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'OBSERVED_ACTIVITY_HOURS',
|
||||||
|
'INFO',
|
||||||
|
g.observed_activity_hours::text,
|
||||||
|
g.minimum_observed_activity_hours::text,
|
||||||
|
'Acumular más horas reales de observación.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.observed_activity_hours <
|
||||||
|
g.minimum_observed_activity_hours
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'PENDING_REVIEW_COUNT',
|
||||||
|
'WARNING',
|
||||||
|
g.pending_review_count::text,
|
||||||
|
g.maximum_pending_review_count::text,
|
||||||
|
'Resolver el backlog de revisión de incidencias.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.pending_review_count >
|
||||||
|
g.maximum_pending_review_count
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'OLDEST_PENDING_REVIEW_HOURS',
|
||||||
|
'WARNING',
|
||||||
|
g.oldest_pending_review_hours::text,
|
||||||
|
g.maximum_oldest_pending_review_hours::text,
|
||||||
|
'Revisar el episodio pendiente más antiguo.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.oldest_pending_review_hours >
|
||||||
|
g.maximum_oldest_pending_review_hours
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'CONFIRMED_CONTEXT_PCT',
|
||||||
|
'WARNING',
|
||||||
|
g.confirmed_context_pct::text,
|
||||||
|
g.minimum_confirmed_context_pct::text,
|
||||||
|
'Aumentar la actividad con contexto canónico confirmado.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.confirmed_context_pct <
|
||||||
|
g.minimum_confirmed_context_pct
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.machine_id,
|
||||||
|
'UNCONFIRMED_CONTEXT_PCT',
|
||||||
|
'WARNING',
|
||||||
|
g.unconfirmed_context_pct::text,
|
||||||
|
g.maximum_unconfirmed_context_pct::text,
|
||||||
|
'Reducir actividad física sin contexto confirmado.',
|
||||||
|
false
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1 g
|
||||||
|
WHERE g.unconfirmed_context_pct >
|
||||||
|
g.maximum_unconfirmed_context_pct;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_rollout_blockers_v1 IS
|
||||||
|
'Lista explicable de criterios que impiden que una máquina alcance READY_FOR_CONTROLLED_SHADOW.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_rollout_readiness_summary_v1
|
||||||
|
AS
|
||||||
|
WITH gate AS (
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1
|
||||||
|
),
|
||||||
|
blockers AS (
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_rollout_blockers_v1
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
now() AS observed_at,
|
||||||
|
COUNT(*) AS machine_count,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE readiness_status =
|
||||||
|
'READY_FOR_CONTROLLED_SHADOW'
|
||||||
|
) AS ready_machine_count,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE readiness_status =
|
||||||
|
'TECHNICAL_BLOCKED'
|
||||||
|
) AS technical_blocked_machine_count,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE readiness_status =
|
||||||
|
'INSUFFICIENT_OBSERVATION'
|
||||||
|
) AS insufficient_observation_machine_count,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE readiness_status =
|
||||||
|
'REVIEW_BACKLOG'
|
||||||
|
) AS review_backlog_machine_count,
|
||||||
|
COUNT(*) FILTER (
|
||||||
|
WHERE readiness_status =
|
||||||
|
'COVERAGE_NOT_READY'
|
||||||
|
) AS coverage_not_ready_machine_count,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM blockers
|
||||||
|
) AS blocker_count,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM blockers
|
||||||
|
WHERE blocker_severity =
|
||||||
|
'CRITICAL'
|
||||||
|
) AS critical_blocker_count,
|
||||||
|
CASE
|
||||||
|
WHEN COUNT(*) FILTER (
|
||||||
|
WHERE readiness_status =
|
||||||
|
'TECHNICAL_BLOCKED'
|
||||||
|
) > 0
|
||||||
|
THEN 'TECHNICAL_BLOCKED'
|
||||||
|
WHEN COUNT(*) FILTER (
|
||||||
|
WHERE readiness_status =
|
||||||
|
'READY_FOR_CONTROLLED_SHADOW'
|
||||||
|
) = COUNT(*)
|
||||||
|
THEN 'READY_FOR_CONTROLLED_SHADOW'
|
||||||
|
ELSE 'NOT_READY'
|
||||||
|
END AS overall_readiness_status,
|
||||||
|
false AS ready_for_official_ledger,
|
||||||
|
false AS official_ledger_eligible
|
||||||
|
FROM gate;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_rollout_readiness_summary_v1 IS
|
||||||
|
'Resumen global de readiness del contexto canónico. No autoriza activación oficial.';
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_roles
|
||||||
|
WHERE rolname =
|
||||||
|
'grafana_ucepsa_ro'
|
||||||
|
) THEN
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_rollout_readiness_policies
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_metrics_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_canonical_context_readiness_gate_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_rollout_blockers_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_rollout_readiness_summary_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Loading…
x
Reference in New Issue
Block a user