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

525 lines
13 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()
req = urllib.request.Request(
BASE_URL + path,
data=data,
headers=headers,
method=method,
)
try:
with urllib.request.urlopen(req, timeout=20) as resp:
body = resp.read().decode()
return json.loads(body) if body else {}
except urllib.error.HTTPError as e:
print("HTTP ERROR:", e.code, e.read().decode(), file=sys.stderr)
raise
datasources = api("GET", "/api/datasources")
print("Datasources encontrados:")
for d in datasources:
print(f"- name={d.get('name')} type={d.get('type')} uid={d.get('uid')} url={d.get('url')} database={d.get('database')}")
pg = next(
(
d for d in datasources
if d.get("type") in ("postgres", "grafana-postgresql-datasource")
or "postgres" in (d.get("type") or "").lower()
or "postgres" in (d.get("name") or "").lower()
or "mesavault" in (d.get("name") or "").lower()
),
None,
)
if not pg:
print("ERROR: no encuentro datasource PostgreSQL/MESAVAULT en Grafana", file=sys.stderr)
sys.exit(1)
DS = {
"type": "postgres",
"uid": pg["uid"],
}
def target(sql, ref="A", fmt="table"):
return {
"refId": ref,
"datasource": DS,
"format": fmt,
"rawQuery": True,
"rawSql": sql,
"editorMode": "code",
}
def stat_panel(panel_id, title, x, y, w, h, sql, unit="none", mappings=None):
field_defaults = {
"unit": unit,
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": None},
],
},
}
if mappings:
field_defaults["mappings"] = mappings
return {
"id": panel_id,
"type": "stat",
"title": title,
"datasource": DS,
"gridPos": {"x": x, "y": y, "w": w, "h": h},
"targets": [target(sql)],
"options": {
"reduceOptions": {
"values": False,
"calcs": ["lastNotNull"],
"fields": "",
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
},
"fieldConfig": {
"defaults": field_defaults,
"overrides": [],
},
}
def timeseries_panel(panel_id, title, x, y, w, h, sql, unit="none"):
return {
"id": panel_id,
"type": "timeseries",
"title": title,
"datasource": DS,
"gridPos": {"x": x, "y": y, "w": w, "h": h},
"targets": [target(sql, fmt="time_series")],
"fieldConfig": {
"defaults": {
"unit": unit,
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"lineWidth": 1,
"fillOpacity": 5,
"showPoints": "never",
},
},
"overrides": [],
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": True,
},
"tooltip": {
"mode": "multi",
"sort": "none",
},
},
}
def table_panel(panel_id, title, x, y, w, h, sql):
return {
"id": panel_id,
"type": "table",
"title": title,
"datasource": DS,
"gridPos": {"x": x, "y": y, "w": w, "h": h},
"targets": [target(sql)],
"options": {
"showHeader": True,
"cellHeight": "sm",
},
"fieldConfig": {
"defaults": {},
"overrides": [],
},
}
comm_mapping = [
{
"type": "value",
"options": {
"1": {"text": "OK", "color": "green"},
"0": {"text": "NO OK", "color": "red"},
},
}
]
run_mapping = [
{
"type": "value",
"options": {
"1": {"text": "EN MARCHA", "color": "green"},
"0": {"text": "PARADA", "color": "yellow"},
},
}
]
dashboard = {
"id": None,
"uid": DASHBOARD_UID,
"title": "MESAVAULT Edge-OEE — Máquina",
"tags": ["mesavault", "ucepsa", "edge-oee"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "5s",
"time": {
"from": "now-30m",
"to": "now",
},
"templating": {
"list": [
{
"name": "machine_id",
"type": "query",
"label": "Máquina",
"datasource": DS,
"refresh": 1,
"sort": 1,
"multi": False,
"includeAll": False,
"query": "SELECT machine_id FROM mv_reports_ucepsa_demo.machine_state_latest_multi ORDER BY machine_id",
"current": {
"text": "CORT-00",
"value": "CORT-00",
"selected": True,
},
}
]
},
"panels": [
stat_panel(
1,
"Comunicación",
0,
0,
6,
4,
"""
SELECT
CASE
WHEN age_s < 30 AND comm_ok = true AND COALESCE(sample_valid, true) = true THEN 1
ELSE 0
END AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
mappings=comm_mapping,
),
stat_panel(
2,
"Edad último dato",
6,
0,
6,
4,
"""
SELECT age_s AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
unit="s",
),
stat_panel(
3,
"Estado máquina",
12,
0,
6,
4,
"""
SELECT
CASE WHEN machine_running = true THEN 1 ELSE 0 END AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
mappings=run_mapping,
),
stat_panel(
4,
"Ciclos/min",
18,
0,
6,
4,
"""
SELECT cycle_rate_ppm AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
unit="cpm",
),
stat_panel(
5,
"Potencia total",
0,
4,
6,
4,
"""
SELECT power_total_kw AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
unit="kwatt",
),
stat_panel(
6,
"Energía importada",
6,
4,
6,
4,
"""
SELECT import_kwh AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
unit="kwatth",
),
stat_panel(
7,
"Frecuencia",
12,
4,
6,
4,
"""
SELECT frequency_hz AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
unit="hertz",
),
stat_panel(
8,
"Pulsos acumulados",
18,
4,
6,
4,
"""
SELECT cycle_pulse_count AS value
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
unit="none",
),
timeseries_panel(
9,
"Potencia por fase",
0,
8,
12,
8,
"""
SELECT
ts AS "time",
active_power_l1_w AS "L1 W",
active_power_l2_w AS "L2 W",
active_power_l3_w AS "L3 W",
active_power_w AS "Total W"
FROM mv_reports_ucepsa_demo.energy_3phase_timeseries
WHERE $__timeFilter(ts)
AND site = 'ucepsa_onpremise'
AND machine_id = '$machine_id'
ORDER BY ts
""",
unit="watt",
),
timeseries_panel(
10,
"Tensiones L1/L2/L3",
12,
8,
12,
8,
"""
SELECT
ts AS "time",
voltage_l1_v AS "L1 V",
voltage_l2_v AS "L2 V",
voltage_l3_v AS "L3 V"
FROM mv_reports_ucepsa_demo.energy_3phase_timeseries
WHERE $__timeFilter(ts)
AND site = 'ucepsa_onpremise'
AND machine_id = '$machine_id'
ORDER BY ts
""",
unit="volt",
),
timeseries_panel(
11,
"Corrientes L1/L2/L3",
0,
16,
12,
8,
"""
SELECT
ts AS "time",
current_l1_a AS "L1 A",
current_l2_a AS "L2 A",
current_l3_a AS "L3 A"
FROM mv_reports_ucepsa_demo.energy_3phase_timeseries
WHERE $__timeFilter(ts)
AND site = 'ucepsa_onpremise'
AND machine_id = '$machine_id'
ORDER BY ts
""",
unit="amp",
),
table_panel(
12,
"Última lectura trifásica",
12,
16,
12,
8,
"""
SELECT
machine_id,
round(age_s::numeric, 1) AS age_s,
comm_ok,
sample_valid,
energy_map,
machine_running,
cycle_input_state,
cycle_pulse_count,
wise_cycle_counter_raw,
round(voltage_l1_v::numeric, 2) AS v_l1,
round(voltage_l2_v::numeric, 2) AS v_l2,
round(voltage_l3_v::numeric, 2) AS v_l3,
round(current_l1_a::numeric, 3) AS i_l1,
round(current_l2_a::numeric, 3) AS i_l2,
round(current_l3_a::numeric, 3) AS i_l3,
round(power_total_kw::numeric, 4) AS kw_total,
digital_inputs
FROM mv_reports_ucepsa_demo.machine_state_latest_multi
WHERE machine_id = '$machine_id'
""",
),
table_panel(
13,
"Odoo vs máquina",
0,
24,
12,
7,
"""
SELECT
machine_id,
order_ref,
product_name,
odoo_state,
machine_running,
telemetry_age_s,
cycle_rate_ppm,
status_check,
diagnostic_message
FROM mv_reports_ucepsa_demo.odoo_vs_machine_demo
WHERE machine_id = '$machine_id'
""",
),
table_panel(
14,
"Loss Intelligence",
12,
24,
12,
7,
"""
SELECT
priority_rank,
priority_score,
priority_level,
machine_id,
order_ref,
loss_type,
severity,
duration_min,
cost_eur,
status,
description,
recommended_action
FROM mv_reports_ucepsa_demo.loss_priority_board
WHERE machine_id = '$machine_id'
ORDER BY priority_rank
""",
),
],
}
payload = {
"dashboard": dashboard,
"overwrite": True,
"message": "Create MESAVAULT Edge-OEE machine dashboard with machine selector",
}
result = api("POST", "/api/dashboards/db", payload)
print("Dashboard creado/actualizado:")
print(BASE_URL + result.get("url", f"/d/{DASHBOARD_UID}"))