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,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 1,
|
||||
@ -1920,6 +1920,323 @@
|
||||
],
|
||||
"title": "Cobertura de contexto canónico · últimos 30 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": "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",
|
||||
@ -1935,7 +2252,10 @@
|
||||
"v037",
|
||||
"canonical-context",
|
||||
"arbitration",
|
||||
"v038"
|
||||
"v038",
|
||||
"readiness",
|
||||
"rollout-gate",
|
||||
"v039"
|
||||
],
|
||||
"templating": {
|
||||
"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