40-clients/ucepsa/edge-oee-demo/grafana/scripts/patch_machine_dashboard_stops_pareto.py
2026-06-25 18:51:33 +02:00

152 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env python3
import base64
import json
import sys
import urllib.error
import urllib.request
from pathlib import Path
BASE_URL = "http://127.0.0.1:3000"
DASHBOARD_UID = "ucepsa-edge-oee-machine"
def read_env(path=".env"):
env = {}
p = Path(path)
if not p.exists():
return env
for raw in p.read_text().splitlines():
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
env[key.strip()] = value.strip().strip('"').strip("'")
return env
env = read_env()
user = env.get("GRAFANA_ADMIN_USER", "admin")
password = env.get("GRAFANA_ADMIN_PASSWORD")
if not password:
print("ERROR: no encuentro GRAFANA_ADMIN_PASSWORD en .env", file=sys.stderr)
sys.exit(1)
token = base64.b64encode(f"{user}:{password}".encode()).decode()
def api(method, path, payload=None):
data = None
headers = {
"Authorization": f"Basic {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
if payload is not None:
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
BASE_URL + path,
data=data,
headers=headers,
method=method,
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
body = resp.read().decode("utf-8")
return json.loads(body) if body else {}
except urllib.error.HTTPError as e:
print("HTTP ERROR:", e.code, e.read().decode("utf-8"), file=sys.stderr)
raise
def all_panels(panels):
out = []
for p in panels or []:
out.append(p)
out.extend(all_panels(p.get("panels", [])))
return out
doc = api("GET", f"/api/dashboards/uid/{DASHBOARD_UID}")
dashboard = doc["dashboard"]
meta = doc.get("meta", {})
changed = 0
for panel in all_panels(dashboard.get("panels", [])):
title = panel.get("title", "")
if title == "Nº paros":
panel["title"] = "Nº paros en rango"
changed += 1
elif title == "Minutos paro":
panel["title"] = "Minutos paro en rango"
changed += 1
elif title == "Coste paros":
panel["title"] = "Coste paros en rango"
changed += 1
elif title == "Duración media":
panel["title"] = "Duración media en rango"
changed += 1
elif title == "Pareto causas de paro":
panel["title"] = "Pareto causas clasificadas"
for target in panel.get("targets", []):
target["rawSql"] = """
SELECT
cause_name AS "Causa",
ROUND(SUM(duration_min)::numeric, 2) AS "Minutos",
COUNT(*) AS "Nº paros",
ROUND(SUM(cost_eur)::numeric, 2) AS "Coste €"
FROM mv_reports_ucepsa_demo.machine_stops_grafana
WHERE machine_id = '$machine_id'
AND classification_status = 'CLASSIFIED'
GROUP BY cause_name
ORDER BY SUM(duration_min) DESC
LIMIT 10
"""
target["format"] = "table"
changed += 1
elif title == "Paros pendientes de clasificación":
# Dejamos este panel sin filtro temporal: es backlog operativo.
for target in panel.get("targets", []):
target["rawSql"] = """
SELECT
id AS "ID",
started_at AS "Inicio",
ROUND(duration_min::numeric, 2) AS "Min",
cost_eur AS "Coste €",
status AS "Estado",
classification_status AS "Clasificación",
cause_name AS "Causa",
operator_note AS "Nota operario"
FROM mv_reports_ucepsa_demo.machine_stops_grafana
WHERE machine_id = '$machine_id'
AND classification_status = 'PENDING'
ORDER BY started_at DESC
"""
changed += 1
payload = {
"dashboard": dashboard,
"overwrite": True,
"message": "Fix stop report pareto and clarify time-scoped panels",
}
if meta.get("folderUid"):
payload["folderUid"] = meta["folderUid"]
result = api("POST", "/api/dashboards/db", payload)
print(f"Paneles modificados: {changed}")
print("Dashboard actualizado:")
print(BASE_URL + result.get("url", f"/d/{DASHBOARD_UID}"))