test(ucepsa): add context regression and hardening suite
This commit is contained in:
parent
b06f15534f
commit
3d0aef0410
@ -0,0 +1,174 @@
|
|||||||
|
# UCEPSA — Regresión y hardening SHADOW v0.3.13
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Proteger automáticamente los comportamientos ya validados:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cierre Shop Floor
|
||||||
|
arbitraje canónico
|
||||||
|
contexto legacy
|
||||||
|
conflictos sin prioridad silenciosa
|
||||||
|
casos raíz
|
||||||
|
decisiones consolidadas
|
||||||
|
agenda diaria
|
||||||
|
snapshots
|
||||||
|
readiness
|
||||||
|
guardas oficiales
|
||||||
|
```
|
||||||
|
|
||||||
|
La suite no modifica Odoo ni datos de producción.
|
||||||
|
|
||||||
|
## Contratos principales
|
||||||
|
|
||||||
|
```text
|
||||||
|
WO 340:
|
||||||
|
sesión cerrada
|
||||||
|
100 kg finales
|
||||||
|
Esteban confirmado
|
||||||
|
sin extensión posterior al cierre
|
||||||
|
|
||||||
|
Episodio 1:
|
||||||
|
LEGACY_ERP autorizado
|
||||||
|
intervalo exacto
|
||||||
|
cobertura canónica completa
|
||||||
|
|
||||||
|
WO 649/698:
|
||||||
|
conflicto histórico conservado
|
||||||
|
sin clave canónica
|
||||||
|
|
||||||
|
Gobernanza:
|
||||||
|
7/8/9 → un caso raíz
|
||||||
|
1-4, 5-6 y 7-8 → tres decisiones
|
||||||
|
agenda = backlog
|
||||||
|
snapshot 1 coherente
|
||||||
|
|
||||||
|
SHADOW:
|
||||||
|
cero filas oficiales
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objetos
|
||||||
|
|
||||||
|
```text
|
||||||
|
context_regression_policies
|
||||||
|
context_regression_runs
|
||||||
|
context_regression_results
|
||||||
|
|
||||||
|
li_context_regression_latest_run_v1
|
||||||
|
li_context_regression_latest_results_v1
|
||||||
|
li_context_regression_failures_v1
|
||||||
|
li_context_regression_run_history_v1
|
||||||
|
li_context_regression_health_v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Despliegue
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FILE=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/sql/versions/115_ucepsa_context_regression_hardening_shadow_v0313.sql
|
||||||
|
|
||||||
|
docker exec -i mv_ucepsa_postgres_hot sh -lc \
|
||||||
|
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
|
||||||
|
< "$FILE"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RUNTIME=/srv/mesavault/edge-oee-ucepsa
|
||||||
|
TARGET=/srv/mesavault/40-clients/ucepsa/edge-oee-demo
|
||||||
|
|
||||||
|
install -m 0644 \
|
||||||
|
"$TARGET/tools/context_regression_suite.py" \
|
||||||
|
"$RUNTIME/tools/"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ejecución previa sin persistencia
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MANAGER=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/ops/manage_context_regression_v0313.sh
|
||||||
|
|
||||||
|
"$MANAGER" dry-run \
|
||||||
|
--triggered-by "Víctor Fraile"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Primera ejecución persistente
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"$MANAGER" run \
|
||||||
|
--triggered-by "Víctor Fraile" \
|
||||||
|
--trigger-kind DEPLOYMENT \
|
||||||
|
--notes "Validación posterior al despliegue v0.3.13."
|
||||||
|
```
|
||||||
|
|
||||||
|
`PASS` es el resultado ideal. `WARN` es admisible si se limita a salud o
|
||||||
|
rendimiento. `FAIL` o `ERROR` requiere investigación antes de continuar.
|
||||||
|
|
||||||
|
## Consultas
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"$MANAGER" show
|
||||||
|
"$MANAGER" list --limit 20
|
||||||
|
"$MANAGER" show --run-id NUMERO
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exportar Markdown
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OUT=/srv/mesavault/exports/context-regression-$(date +%F).md
|
||||||
|
|
||||||
|
"$MANAGER" export-markdown \
|
||||||
|
> "$OUT"
|
||||||
|
|
||||||
|
sed -n '1,260p' "$OUT"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validación
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VALIDATION=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/ops/validate_context_regression_v0313.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
|
||||||
|
run_count_mismatches = 0
|
||||||
|
duplicate_test_codes = 0
|
||||||
|
suite_complete = true
|
||||||
|
latest_fail_or_error_count = 0
|
||||||
|
critical_contract_not_passed = 0
|
||||||
|
|
||||||
|
official_run_rows = 0
|
||||||
|
official_result_rows = 0
|
||||||
|
official_health_rows = 0
|
||||||
|
official_operator_queue_rows = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uso operativo recomendado
|
||||||
|
|
||||||
|
Ejecutar la suite:
|
||||||
|
|
||||||
|
```text
|
||||||
|
después de cada migración
|
||||||
|
después de modificar sincronizadores o arbitraje
|
||||||
|
antes de desplegar el dashboard
|
||||||
|
antes de activar cualquier nueva fase de readiness
|
||||||
|
```
|
||||||
|
|
||||||
|
La ejecución periódica puede programarse después de acordar la hora adecuada.
|
||||||
|
No se instala ningún cron automáticamente en esta versión.
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
|
||||||
|
Se mantiene el mismo UID y se añaden:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Última ejecución de regresión
|
||||||
|
Warnings y fallos
|
||||||
|
Histórico de regresión
|
||||||
|
```
|
||||||
|
|
||||||
|
Paneles totales esperados: 31.
|
||||||
@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"description": "Supervisión SHADOW de sesiones, arbitraje, casos raíz, decisiones humanas y agenda diaria de gobernanza. La revisión se organiza por preguntas operativas y puede guardarse como snapshot auditable.",
|
"description": "Supervisión SHADOW de sesiones, gobernanza diaria y regresión funcional. La suite protege contratos históricos, arbitraje, agrupación, agenda y guardas oficiales sin modificar datos productivos.",
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"fiscalYearStartMonth": 0,
|
"fiscalYearStartMonth": 0,
|
||||||
"graphTooltip": 1,
|
"graphTooltip": 1,
|
||||||
@ -3286,6 +3286,360 @@
|
|||||||
],
|
],
|
||||||
"title": "Indicadores de gobernanza diaria",
|
"title": "Indicadores de gobernanza diaria",
|
||||||
"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": "Estado"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"PASS": {
|
||||||
|
"text": "PASS",
|
||||||
|
"color": "green",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"WARN": {
|
||||||
|
"text": "WARN",
|
||||||
|
"color": "orange",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"FAIL": {
|
||||||
|
"text": "FAIL",
|
||||||
|
"color": "red",
|
||||||
|
"index": 2
|
||||||
|
},
|
||||||
|
"ERROR": {
|
||||||
|
"text": "ERROR",
|
||||||
|
"color": "red",
|
||||||
|
"index": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": {
|
||||||
|
"type": "color-background"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 218
|
||||||
|
},
|
||||||
|
"id": 29,
|
||||||
|
"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 run_id AS \"Ejecución\",\n suite_version AS \"Suite\",\n overall_status AS \"Estado\",\n triggered_by AS \"Disparada por\",\n trigger_kind AS \"Tipo\",\n started_at AS \"Inicio\",\n finished_at AS \"Fin\",\n duration_ms AS \"Duración ms\",\n result_count AS \"Tests\",\n pass_count AS \"PASS\",\n warn_count AS \"WARN\",\n fail_count AS \"FAIL\",\n error_count AS \"ERROR\",\n source_git_commit AS \"Commit\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_regression_latest_run_v1",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Última ejecución de regresión",
|
||||||
|
"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": "Estado"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"PASS": {
|
||||||
|
"text": "PASS",
|
||||||
|
"color": "green",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"WARN": {
|
||||||
|
"text": "WARN",
|
||||||
|
"color": "orange",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"FAIL": {
|
||||||
|
"text": "FAIL",
|
||||||
|
"color": "red",
|
||||||
|
"index": 2
|
||||||
|
},
|
||||||
|
"ERROR": {
|
||||||
|
"text": "ERROR",
|
||||||
|
"color": "red",
|
||||||
|
"index": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": {
|
||||||
|
"type": "color-background"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Contrato"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 420
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Detalle"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 520
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 226
|
||||||
|
},
|
||||||
|
"id": 30,
|
||||||
|
"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 test_code AS \"Test\",\n test_group AS \"Grupo\",\n test_title AS \"Contrato\",\n status AS \"Estado\",\n severity AS \"Severidad\",\n detail AS \"Detalle\",\n actual_json::text AS \"Resultado\",\n duration_ms AS \"Duración ms\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_regression_failures_v1\nORDER BY\n CASE status\n WHEN 'ERROR' THEN 1\n WHEN 'FAIL' THEN 2\n ELSE 3\n END,\n test_code",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Warnings y fallos de regresión",
|
||||||
|
"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": "Estado"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"PASS": {
|
||||||
|
"text": "PASS",
|
||||||
|
"color": "green",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"WARN": {
|
||||||
|
"text": "WARN",
|
||||||
|
"color": "orange",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"FAIL": {
|
||||||
|
"text": "FAIL",
|
||||||
|
"color": "red",
|
||||||
|
"index": 2
|
||||||
|
},
|
||||||
|
"ERROR": {
|
||||||
|
"text": "ERROR",
|
||||||
|
"color": "red",
|
||||||
|
"index": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": {
|
||||||
|
"type": "color-background"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 236
|
||||||
|
},
|
||||||
|
"id": 31,
|
||||||
|
"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 run_id AS \"Ejecución\",\n overall_status AS \"Estado\",\n triggered_by AS \"Disparada por\",\n trigger_kind AS \"Tipo\",\n started_at AS \"Inicio\",\n duration_ms AS \"Duración ms\",\n result_count AS \"Tests\",\n pass_count AS \"PASS\",\n warn_count AS \"WARN\",\n fail_count AS \"FAIL\",\n error_count AS \"ERROR\",\n source_git_commit AS \"Commit\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_regression_run_history_v1\nORDER BY\n started_at DESC,\n run_id DESC\nLIMIT 50",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Histórico de regresión · 30 días",
|
||||||
|
"type": "table"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refresh": "15s",
|
"refresh": "15s",
|
||||||
@ -3314,7 +3668,11 @@
|
|||||||
"daily-governance",
|
"daily-governance",
|
||||||
"agenda",
|
"agenda",
|
||||||
"evidence-pack",
|
"evidence-pack",
|
||||||
"v0312"
|
"v0312",
|
||||||
|
"regression",
|
||||||
|
"hardening",
|
||||||
|
"contracts",
|
||||||
|
"v0313"
|
||||||
],
|
],
|
||||||
"templating": {
|
"templating": {
|
||||||
"list": []
|
"list": []
|
||||||
|
|||||||
259
ucepsa/edge-oee-demo/ops/deploy_context_regression_dashboard_v0313.sh
Executable file
259
ucepsa/edge-oee-demo/ops/deploy_context_regression_dashboard_v0313.sh
Executable file
@ -0,0 +1,259 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ACTION="${1:-deploy}"
|
||||||
|
GRAFANA_CONTAINER="${GRAFANA_CONTAINER:-mv_ucepsa_grafana}"
|
||||||
|
REPO_ROOT="${REGRESSION_DASHBOARD_REPO_ROOT:-/srv/mesavault/40-clients/ucepsa/edge-oee-demo}"
|
||||||
|
DASHBOARD_FILE="${REGRESSION_DASHBOARD_FILE:-$REPO_ROOT/grafana/dashboards/ucepsa-shopfloor-context-health.dashboard.json}"
|
||||||
|
FOLDER_UID="${REGRESSION_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
|
||||||
|
echo "ERROR: falta GRAFANA_API_TOKEN." >&2
|
||||||
|
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 regression hardening "
|
||||||
|
"SHADOW v0.3.13"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
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", [])) != 31:
|
||||||
|
errors.append(
|
||||||
|
"El dashboard no tiene 31 paneles"
|
||||||
|
)
|
||||||
|
|
||||||
|
if dashboard.get("refresh") != "15s":
|
||||||
|
errors.append(
|
||||||
|
"El refresco no está en 15s"
|
||||||
|
)
|
||||||
|
|
||||||
|
if datasource_uids != [datasource_uid]:
|
||||||
|
errors.append(
|
||||||
|
f"Datasource inesperado: {datasource_uids!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
29: "li_context_regression_latest_run_v1",
|
||||||
|
30: "li_context_regression_failures_v1",
|
||||||
|
31: "li_context_regression_run_history_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} 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"),
|
||||||
|
"regression_panels": {
|
||||||
|
"latest_run": 29 in panels,
|
||||||
|
"issues": 30 in panels,
|
||||||
|
"history": 31 in panels,
|
||||||
|
},
|
||||||
|
"errors":
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(
|
||||||
|
output,
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise SystemExit(
|
||||||
|
"Uso: deploy_context_regression_dashboard_v0313.sh "
|
||||||
|
"{deploy|validate}"
|
||||||
|
)
|
||||||
|
PY
|
||||||
50
ucepsa/edge-oee-demo/ops/manage_context_regression_v0313.sh
Executable file
50
ucepsa/edge-oee-demo/ops/manage_context_regression_v0313.sh
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ACTION="${1:-show}"
|
||||||
|
ROOT="${CONTEXT_REGRESSION_RUNTIME_ROOT:-/srv/mesavault/edge-oee-ucepsa}"
|
||||||
|
REPO="${CONTEXT_REGRESSION_REPO_ROOT:-/srv/mesavault/40-clients}"
|
||||||
|
NETWORK="${CONTEXT_REGRESSION_NETWORK:-mv_ucepsa_net}"
|
||||||
|
POSTGRES_CONTAINER="${CONTEXT_REGRESSION_POSTGRES_CONTAINER:-mv_ucepsa_postgres_hot}"
|
||||||
|
PGHOST_OVERRIDE="${CONTEXT_REGRESSION_PGHOST_OVERRIDE:-mv_ucepsa_postgres_hot}"
|
||||||
|
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
IMAGE="$(
|
||||||
|
docker inspect \
|
||||||
|
mv_ucepsa_odoo_order_state_pg_sink \
|
||||||
|
--format '{{.Config.Image}}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
PGDATABASE_OVERRIDE="${CONTEXT_REGRESSION_PGDATABASE_OVERRIDE:-$(
|
||||||
|
docker inspect "$POSTGRES_CONTAINER" \
|
||||||
|
--format '{{range .Config.Env}}{{println .}}{{end}}' \
|
||||||
|
| sed -n 's/^POSTGRES_DB=//p' \
|
||||||
|
| head -n 1
|
||||||
|
)}"
|
||||||
|
|
||||||
|
if [[ -z "$PGDATABASE_OVERRIDE" ]]; then
|
||||||
|
echo "ERROR: no se pudo determinar POSTGRES_DB." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SOURCE_GIT_COMMIT="$(
|
||||||
|
git -C "$REPO" rev-parse HEAD 2>/dev/null \
|
||||||
|
|| printf 'unknown'
|
||||||
|
)"
|
||||||
|
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
docker run --rm -i \
|
||||||
|
--network "$NETWORK" \
|
||||||
|
--env-file .env \
|
||||||
|
-e "PGHOST=$PGHOST_OVERRIDE" \
|
||||||
|
-e "PGDATABASE=$PGDATABASE_OVERRIDE" \
|
||||||
|
-e "SOURCE_GIT_COMMIT=$SOURCE_GIT_COMMIT" \
|
||||||
|
-v "$ROOT:/app:ro" \
|
||||||
|
-w /app \
|
||||||
|
--entrypoint python \
|
||||||
|
"$IMAGE" \
|
||||||
|
tools/context_regression_suite.py \
|
||||||
|
"$ACTION" \
|
||||||
|
"$@"
|
||||||
217
ucepsa/edge-oee-demo/ops/validate_context_regression_v0313.sql
Normal file
217
ucepsa/edge-oee-demo/ops/validate_context_regression_v0313.sql
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
\pset pager off
|
||||||
|
|
||||||
|
\echo '=== 1. Objetos v0.3.13 ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
to_regclass(
|
||||||
|
'mv_loss_intelligence.context_regression_policies'
|
||||||
|
) AS policy_table,
|
||||||
|
to_regclass(
|
||||||
|
'mv_loss_intelligence.context_regression_runs'
|
||||||
|
) AS runs_table,
|
||||||
|
to_regclass(
|
||||||
|
'mv_loss_intelligence.context_regression_results'
|
||||||
|
) AS results_table,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_context_regression_latest_run_v1'
|
||||||
|
) AS latest_run_view,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_context_regression_latest_results_v1'
|
||||||
|
) AS latest_results_view,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_context_regression_failures_v1'
|
||||||
|
) AS failures_view,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_context_regression_run_history_v1'
|
||||||
|
) AS history_view,
|
||||||
|
to_regclass(
|
||||||
|
'mv_reports_ucepsa_prod.li_context_regression_health_v1'
|
||||||
|
) AS health_view;
|
||||||
|
|
||||||
|
\echo '=== 2. Última ejecución ==='
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_run_v1;
|
||||||
|
|
||||||
|
\echo '=== 3. Salud de regresión ==='
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_health_v1;
|
||||||
|
|
||||||
|
\echo '=== 4. Conteos de resultados coherentes ==='
|
||||||
|
|
||||||
|
SELECT COUNT(*) AS run_count_mismatches
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence.context_regression_runs r
|
||||||
|
WHERE r.result_count <>
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_regression_results x
|
||||||
|
WHERE x.run_id =
|
||||||
|
r.run_id
|
||||||
|
)
|
||||||
|
OR r.pass_count <>
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_regression_results x
|
||||||
|
WHERE x.run_id =
|
||||||
|
r.run_id
|
||||||
|
AND x.status = 'PASS'
|
||||||
|
)
|
||||||
|
OR r.warn_count <>
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_regression_results x
|
||||||
|
WHERE x.run_id =
|
||||||
|
r.run_id
|
||||||
|
AND x.status = 'WARN'
|
||||||
|
)
|
||||||
|
OR r.fail_count <>
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_regression_results x
|
||||||
|
WHERE x.run_id =
|
||||||
|
r.run_id
|
||||||
|
AND x.status = 'FAIL'
|
||||||
|
)
|
||||||
|
OR r.error_count <>
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence
|
||||||
|
.context_regression_results x
|
||||||
|
WHERE x.run_id =
|
||||||
|
r.run_id
|
||||||
|
AND x.status = 'ERROR'
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo '=== 5. Códigos de test únicos por ejecución ==='
|
||||||
|
|
||||||
|
SELECT COUNT(*) AS duplicate_test_codes
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
run_id,
|
||||||
|
test_code,
|
||||||
|
COUNT(*) AS n
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence.context_regression_results
|
||||||
|
GROUP BY
|
||||||
|
run_id,
|
||||||
|
test_code
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
) duplicated;
|
||||||
|
|
||||||
|
\echo '=== 6. La última suite está completa ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
l.run_id,
|
||||||
|
l.overall_status,
|
||||||
|
l.result_count,
|
||||||
|
p.minimum_expected_test_count,
|
||||||
|
(
|
||||||
|
l.result_count >=
|
||||||
|
p.minimum_expected_test_count
|
||||||
|
) AS suite_complete
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_run_v1 l
|
||||||
|
JOIN
|
||||||
|
mv_loss_intelligence.context_regression_policies p
|
||||||
|
ON p.tenant =
|
||||||
|
l.tenant
|
||||||
|
AND p.site =
|
||||||
|
l.site
|
||||||
|
AND p.policy_code =
|
||||||
|
l.policy_code
|
||||||
|
WHERE p.active;
|
||||||
|
|
||||||
|
\echo '=== 7. Fallos o errores críticos en la última suite ==='
|
||||||
|
|
||||||
|
SELECT COUNT(*) AS latest_fail_or_error_count
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_results_v1
|
||||||
|
WHERE status IN (
|
||||||
|
'FAIL',
|
||||||
|
'ERROR'
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo '=== 8. Contratos críticos conocidos ==='
|
||||||
|
|
||||||
|
SELECT COUNT(*) AS critical_contract_not_passed
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_results_v1
|
||||||
|
WHERE test_code IN (
|
||||||
|
'CANON-001',
|
||||||
|
'CANON-002',
|
||||||
|
'CANON-003',
|
||||||
|
'CANON-004',
|
||||||
|
'WO340-001',
|
||||||
|
'WO340-002',
|
||||||
|
'LEGACY-001',
|
||||||
|
'LEGACY-002',
|
||||||
|
'CONFLICT-001',
|
||||||
|
'CONFLICT-002',
|
||||||
|
'CASE-001',
|
||||||
|
'DECISION-001',
|
||||||
|
'AGENDA-001',
|
||||||
|
'SNAPSHOT-001',
|
||||||
|
'SHADOW-001'
|
||||||
|
)
|
||||||
|
AND status <> 'PASS';
|
||||||
|
|
||||||
|
\echo '=== 9. Warnings informativos ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
test_code,
|
||||||
|
test_group,
|
||||||
|
test_title,
|
||||||
|
status,
|
||||||
|
severity,
|
||||||
|
actual_json,
|
||||||
|
detail,
|
||||||
|
duration_ms
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_failures_v1;
|
||||||
|
|
||||||
|
\echo '=== 10. Nada oficial ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence.context_regression_runs
|
||||||
|
WHERE official_eligible
|
||||||
|
) AS official_run_rows,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence.context_regression_results
|
||||||
|
WHERE official_eligible
|
||||||
|
) AS official_result_rows,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_health_v1
|
||||||
|
WHERE official_eligible
|
||||||
|
) AS official_health_rows,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod.li_operator_stop_queue_v2
|
||||||
|
) AS official_operator_queue_rows;
|
||||||
@ -0,0 +1,501 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS
|
||||||
|
mv_loss_intelligence.context_regression_policies (
|
||||||
|
tenant text NOT NULL,
|
||||||
|
site text NOT NULL,
|
||||||
|
policy_code text NOT NULL,
|
||||||
|
suite_version text NOT NULL,
|
||||||
|
stale_after_hours numeric(10, 2) NOT NULL DEFAULT 26
|
||||||
|
CHECK (stale_after_hours > 0),
|
||||||
|
performance_warning_ms integer NOT NULL DEFAULT 2000
|
||||||
|
CHECK (performance_warning_ms > 0),
|
||||||
|
minimum_expected_test_count integer NOT NULL DEFAULT 20
|
||||||
|
CHECK (minimum_expected_test_count > 0),
|
||||||
|
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_regression_policies IS
|
||||||
|
'Política SHADOW para pruebas de regresión del contexto, arbitraje, gobernanza y guardas oficiales.';
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
mv_loss_intelligence.context_regression_policies (
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
policy_code,
|
||||||
|
suite_version,
|
||||||
|
stale_after_hours,
|
||||||
|
performance_warning_ms,
|
||||||
|
minimum_expected_test_count,
|
||||||
|
active,
|
||||||
|
notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'ucepsa',
|
||||||
|
'ucepsa_onpremise',
|
||||||
|
'CONTEXT_REGRESSION_V1',
|
||||||
|
'0.3.13',
|
||||||
|
26,
|
||||||
|
2000,
|
||||||
|
20,
|
||||||
|
true,
|
||||||
|
'La suite protege contratos históricos e invariantes estructurales. Las comprobaciones de salud o rendimiento pueden producir WARNING sin ocultar fallos funcionales.'
|
||||||
|
)
|
||||||
|
ON CONFLICT (
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
policy_code
|
||||||
|
)
|
||||||
|
DO UPDATE SET
|
||||||
|
suite_version =
|
||||||
|
EXCLUDED.suite_version,
|
||||||
|
stale_after_hours =
|
||||||
|
EXCLUDED.stale_after_hours,
|
||||||
|
performance_warning_ms =
|
||||||
|
EXCLUDED.performance_warning_ms,
|
||||||
|
minimum_expected_test_count =
|
||||||
|
EXCLUDED.minimum_expected_test_count,
|
||||||
|
active = true,
|
||||||
|
notes =
|
||||||
|
EXCLUDED.notes,
|
||||||
|
updated_at = now();
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS
|
||||||
|
mv_loss_intelligence.context_regression_runs (
|
||||||
|
run_id bigserial PRIMARY KEY,
|
||||||
|
tenant text NOT NULL,
|
||||||
|
site text NOT NULL,
|
||||||
|
policy_code text NOT NULL,
|
||||||
|
suite_version text NOT NULL,
|
||||||
|
overall_status text NOT NULL
|
||||||
|
CHECK (
|
||||||
|
overall_status IN (
|
||||||
|
'RUNNING',
|
||||||
|
'PASS',
|
||||||
|
'WARN',
|
||||||
|
'FAIL',
|
||||||
|
'ERROR'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
triggered_by text NOT NULL,
|
||||||
|
trigger_kind text NOT NULL DEFAULT 'MANUAL'
|
||||||
|
CHECK (
|
||||||
|
trigger_kind IN (
|
||||||
|
'MANUAL',
|
||||||
|
'DEPLOYMENT',
|
||||||
|
'SCHEDULED',
|
||||||
|
'CI'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
started_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
finished_at timestamptz,
|
||||||
|
duration_ms numeric(14, 2),
|
||||||
|
result_count integer NOT NULL DEFAULT 0
|
||||||
|
CHECK (result_count >= 0),
|
||||||
|
pass_count integer NOT NULL DEFAULT 0
|
||||||
|
CHECK (pass_count >= 0),
|
||||||
|
warn_count integer NOT NULL DEFAULT 0
|
||||||
|
CHECK (warn_count >= 0),
|
||||||
|
fail_count integer NOT NULL DEFAULT 0
|
||||||
|
CHECK (fail_count >= 0),
|
||||||
|
skip_count integer NOT NULL DEFAULT 0
|
||||||
|
CHECK (skip_count >= 0),
|
||||||
|
error_count integer NOT NULL DEFAULT 0
|
||||||
|
CHECK (error_count >= 0),
|
||||||
|
source_git_commit text,
|
||||||
|
source_state_hash text NOT NULL,
|
||||||
|
notes text,
|
||||||
|
official_eligible boolean NOT NULL DEFAULT false
|
||||||
|
CHECK (official_eligible = false),
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT ck_context_regression_run_time
|
||||||
|
CHECK (
|
||||||
|
finished_at IS NULL
|
||||||
|
OR finished_at >= started_at
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS
|
||||||
|
idx_context_regression_runs_latest
|
||||||
|
ON
|
||||||
|
mv_loss_intelligence.context_regression_runs (
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
started_at DESC,
|
||||||
|
run_id DESC
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS
|
||||||
|
mv_loss_intelligence.context_regression_results (
|
||||||
|
result_id bigserial PRIMARY KEY,
|
||||||
|
run_id bigint NOT NULL
|
||||||
|
REFERENCES
|
||||||
|
mv_loss_intelligence.context_regression_runs (
|
||||||
|
run_id
|
||||||
|
)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
test_code text NOT NULL,
|
||||||
|
test_group text NOT NULL,
|
||||||
|
test_title text NOT NULL,
|
||||||
|
status text NOT NULL
|
||||||
|
CHECK (
|
||||||
|
status IN (
|
||||||
|
'PASS',
|
||||||
|
'WARN',
|
||||||
|
'FAIL',
|
||||||
|
'SKIP',
|
||||||
|
'ERROR'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
severity text NOT NULL
|
||||||
|
CHECK (
|
||||||
|
severity IN (
|
||||||
|
'INFO',
|
||||||
|
'WARNING',
|
||||||
|
'CRITICAL'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
expected_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
actual_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
detail text,
|
||||||
|
duration_ms numeric(14, 2) NOT NULL
|
||||||
|
CHECK (duration_ms >= 0),
|
||||||
|
evidence_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
observed_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
official_eligible boolean NOT NULL DEFAULT false
|
||||||
|
CHECK (official_eligible = false),
|
||||||
|
CONSTRAINT ux_context_regression_run_test
|
||||||
|
UNIQUE (
|
||||||
|
run_id,
|
||||||
|
test_code
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS
|
||||||
|
idx_context_regression_results_status
|
||||||
|
ON
|
||||||
|
mv_loss_intelligence.context_regression_results (
|
||||||
|
run_id,
|
||||||
|
status,
|
||||||
|
severity,
|
||||||
|
test_code
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE
|
||||||
|
mv_loss_intelligence.context_regression_runs IS
|
||||||
|
'Ejecuciones auditadas de la suite de regresión v0.3.13.';
|
||||||
|
|
||||||
|
COMMENT ON TABLE
|
||||||
|
mv_loss_intelligence.context_regression_results IS
|
||||||
|
'Resultado individual de cada contrato de regresión. No altera datos de producción ni elegibilidad oficial.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_latest_run_v1
|
||||||
|
AS
|
||||||
|
SELECT DISTINCT ON (
|
||||||
|
tenant,
|
||||||
|
site
|
||||||
|
)
|
||||||
|
run_id,
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
policy_code,
|
||||||
|
suite_version,
|
||||||
|
overall_status,
|
||||||
|
triggered_by,
|
||||||
|
trigger_kind,
|
||||||
|
started_at,
|
||||||
|
finished_at,
|
||||||
|
duration_ms,
|
||||||
|
result_count,
|
||||||
|
pass_count,
|
||||||
|
warn_count,
|
||||||
|
fail_count,
|
||||||
|
skip_count,
|
||||||
|
error_count,
|
||||||
|
source_git_commit,
|
||||||
|
source_state_hash,
|
||||||
|
notes,
|
||||||
|
official_eligible
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence.context_regression_runs
|
||||||
|
ORDER BY
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
started_at DESC,
|
||||||
|
run_id DESC;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_latest_run_v1 IS
|
||||||
|
'Última ejecución de regresión por tenant y site.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_latest_results_v1
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
r.run_id,
|
||||||
|
r.overall_status,
|
||||||
|
r.started_at,
|
||||||
|
r.finished_at,
|
||||||
|
r.source_git_commit,
|
||||||
|
x.test_code,
|
||||||
|
x.test_group,
|
||||||
|
x.test_title,
|
||||||
|
x.status,
|
||||||
|
x.severity,
|
||||||
|
x.expected_json,
|
||||||
|
x.actual_json,
|
||||||
|
x.detail,
|
||||||
|
x.duration_ms,
|
||||||
|
x.evidence_json,
|
||||||
|
x.observed_at,
|
||||||
|
x.official_eligible
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_run_v1 r
|
||||||
|
JOIN
|
||||||
|
mv_loss_intelligence.context_regression_results x
|
||||||
|
ON x.run_id =
|
||||||
|
r.run_id;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_latest_results_v1 IS
|
||||||
|
'Resultados de la última suite de regresión.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_failures_v1
|
||||||
|
AS
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_results_v1
|
||||||
|
WHERE status IN (
|
||||||
|
'WARN',
|
||||||
|
'FAIL',
|
||||||
|
'ERROR'
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
CASE status
|
||||||
|
WHEN 'ERROR' THEN 1
|
||||||
|
WHEN 'FAIL' THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END,
|
||||||
|
CASE severity
|
||||||
|
WHEN 'CRITICAL' THEN 1
|
||||||
|
WHEN 'WARNING' THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END,
|
||||||
|
test_code;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_failures_v1 IS
|
||||||
|
'Warnings, fallos y errores de la última ejecución.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_run_history_v1
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
run_id,
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
suite_version,
|
||||||
|
overall_status,
|
||||||
|
triggered_by,
|
||||||
|
trigger_kind,
|
||||||
|
started_at,
|
||||||
|
finished_at,
|
||||||
|
duration_ms,
|
||||||
|
result_count,
|
||||||
|
pass_count,
|
||||||
|
warn_count,
|
||||||
|
fail_count,
|
||||||
|
skip_count,
|
||||||
|
error_count,
|
||||||
|
source_git_commit,
|
||||||
|
source_state_hash,
|
||||||
|
notes,
|
||||||
|
official_eligible
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence.context_regression_runs
|
||||||
|
WHERE started_at >=
|
||||||
|
now() - interval '30 days'
|
||||||
|
ORDER BY
|
||||||
|
started_at DESC,
|
||||||
|
run_id DESC;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_run_history_v1 IS
|
||||||
|
'Histórico de ejecuciones de regresión de los últimos 30 días.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_health_v1
|
||||||
|
AS
|
||||||
|
WITH policy AS (
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_loss_intelligence.context_regression_policies
|
||||||
|
WHERE tenant = 'ucepsa'
|
||||||
|
AND site = 'ucepsa_onpremise'
|
||||||
|
AND policy_code =
|
||||||
|
'CONTEXT_REGRESSION_V1'
|
||||||
|
AND active
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
),
|
||||||
|
latest AS (
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_run_v1
|
||||||
|
WHERE tenant = 'ucepsa'
|
||||||
|
AND site = 'ucepsa_onpremise'
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
p.tenant,
|
||||||
|
p.site,
|
||||||
|
p.policy_code,
|
||||||
|
p.suite_version
|
||||||
|
AS configured_suite_version,
|
||||||
|
p.stale_after_hours,
|
||||||
|
p.performance_warning_ms,
|
||||||
|
p.minimum_expected_test_count,
|
||||||
|
l.run_id,
|
||||||
|
l.suite_version
|
||||||
|
AS latest_suite_version,
|
||||||
|
l.overall_status,
|
||||||
|
l.started_at,
|
||||||
|
l.finished_at,
|
||||||
|
l.result_count,
|
||||||
|
l.pass_count,
|
||||||
|
l.warn_count,
|
||||||
|
l.fail_count,
|
||||||
|
l.skip_count,
|
||||||
|
l.error_count,
|
||||||
|
l.duration_ms,
|
||||||
|
l.source_git_commit,
|
||||||
|
CASE
|
||||||
|
WHEN l.started_at IS NULL
|
||||||
|
THEN NULL::numeric
|
||||||
|
ELSE ROUND(
|
||||||
|
GREATEST(
|
||||||
|
EXTRACT(
|
||||||
|
EPOCH FROM (
|
||||||
|
now()
|
||||||
|
- l.started_at
|
||||||
|
)
|
||||||
|
) / 3600.0,
|
||||||
|
0
|
||||||
|
)::numeric,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
END AS hours_since_run,
|
||||||
|
CASE
|
||||||
|
WHEN l.run_id IS NULL
|
||||||
|
THEN 'NOT_INITIALIZED'
|
||||||
|
WHEN l.overall_status IN (
|
||||||
|
'FAIL',
|
||||||
|
'ERROR'
|
||||||
|
)
|
||||||
|
THEN 'CRITICAL'
|
||||||
|
WHEN l.started_at <
|
||||||
|
now()
|
||||||
|
- (
|
||||||
|
p.stale_after_hours
|
||||||
|
* interval '1 hour'
|
||||||
|
)
|
||||||
|
THEN 'STALE'
|
||||||
|
WHEN l.result_count <
|
||||||
|
p.minimum_expected_test_count
|
||||||
|
THEN 'INCOMPLETE'
|
||||||
|
WHEN l.overall_status =
|
||||||
|
'WARN'
|
||||||
|
THEN 'WARNING'
|
||||||
|
WHEN l.overall_status =
|
||||||
|
'PASS'
|
||||||
|
THEN 'HEALTHY'
|
||||||
|
ELSE 'RUNNING'
|
||||||
|
END AS regression_health_status,
|
||||||
|
CASE
|
||||||
|
WHEN l.run_id IS NULL
|
||||||
|
OR l.overall_status IN (
|
||||||
|
'FAIL',
|
||||||
|
'ERROR'
|
||||||
|
)
|
||||||
|
OR l.started_at <
|
||||||
|
now()
|
||||||
|
- (
|
||||||
|
p.stale_after_hours
|
||||||
|
* interval '1 hour'
|
||||||
|
)
|
||||||
|
OR l.result_count <
|
||||||
|
p.minimum_expected_test_count
|
||||||
|
THEN 'CRITICAL'
|
||||||
|
WHEN l.overall_status =
|
||||||
|
'WARN'
|
||||||
|
THEN 'WARNING'
|
||||||
|
WHEN l.overall_status =
|
||||||
|
'PASS'
|
||||||
|
THEN 'OK'
|
||||||
|
ELSE 'INFO'
|
||||||
|
END AS severity,
|
||||||
|
false AS official_eligible,
|
||||||
|
now() AS observed_at
|
||||||
|
FROM policy p
|
||||||
|
LEFT JOIN latest l
|
||||||
|
ON true;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_context_regression_health_v1 IS
|
||||||
|
'Salud de la última suite: estado, antigüedad, completitud y versión.';
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_roles
|
||||||
|
WHERE rolname =
|
||||||
|
'grafana_ucepsa_ro'
|
||||||
|
) THEN
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_run_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_latest_results_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_failures_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_run_history_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
|
||||||
|
GRANT SELECT
|
||||||
|
ON
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_context_regression_health_v1
|
||||||
|
TO grafana_ucepsa_ro;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
1824
ucepsa/edge-oee-demo/tools/context_regression_suite.py
Executable file
1824
ucepsa/edge-oee-demo/tools/context_regression_suite.py
Executable file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user