Fix meter_reading_dashboard for standalone Piston execution

Anpassung von meter_reading_dashboard für den Piston-Workflow. Das Script liefert das generierte Dashboard jetzt direkt als HTML-Ausgabe, enthält eindeutige Marker für den automatisierbaren Parameterbereich und behandelt dynamische Inhalte sowie Fehlerfälle robuster
main
mathias.sillhengst 2 weeks ago
parent 277c2126e2
commit 28cbf1843a

@ -1,15 +1,17 @@
import html
import json
import httpx
from datetime import datetime, timedelta
import math
# Parameter - diese können später als Eingabe konfiguriert werden
METER_NUMBER = "Any - Als Parameter" # Wird durch tatsächliche Zählernummer ersetzt
CHART_TYPE = "line" # line, bar, area
GROUP_BY = "hour" # hour, day, week
import httpx
# === PARAMETER START ===
METER_NUMBER = "Any - Als Parameter"
CHART_TYPE = "line"
GROUP_BY = "hour"
INCLUDE_STATISTICS = True
# === PARAMETER ENDE ===
# GraphQL Queries
FIND_SENSORS_QUERY = """
query FindSensors($meterNumber: String!) {
sensorsForMeterNumber(meterNumber: $meterNumber) {
@ -63,111 +65,138 @@ query FindObservations($measurementConceptId: ID!, $sensorName: String, $startTi
}
"""
def escape_text(value, default=""):
text = default if value is None else str(value)
return html.escape(text, quote=True)
def build_message(message, css_class):
return (
f"<div class='{css_class}' "
"style='font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; "
"padding: 16px; border-radius: 10px; margin: 20px;'>"
f"{escape_text(message)}"
"</div>"
)
def make_graphql_request(query, variables):
"""GraphQL Request ausführen"""
try:
with httpx.Client() as client:
response = client.post(
f"{EXTERNAL_BASE_URL}/graphql",
headers=AUTH_HEADERS,
json={"query": query, "variables": variables}
json={"query": query, "variables": variables},
timeout=30.0,
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"errors": [str(e)]}
except Exception as exc:
return {"errors": [str(exc)]}
def calculate_statistics(observations):
"""Berechnet Statistiken für die Messwerte"""
if not observations:
return {}
values = [obs.get('value', 0) for obs in observations]
meter_values = [obs.get('meterValue', 0) for obs in observations]
values = [obs.get("value") or 0 for obs in observations]
meter_values = [obs.get("meterValue") or 0 for obs in observations]
return {
'total_readings': len(observations),
'value_min': min(values) if values else 0,
'value_max': max(values) if values else 0,
'value_avg': sum(values) / len(values) if values else 0,
'meter_min': min(meter_values) if meter_values else 0,
'meter_max': max(meter_values) if meter_values else 0,
'meter_avg': sum(meter_values) / len(meter_values) if meter_values else 0,
'consumption': max(meter_values) - min(meter_values) if meter_values else 0
"total_readings": len(observations),
"value_min": min(values) if values else 0,
"value_max": max(values) if values else 0,
"value_avg": sum(values) / len(values) if values else 0,
"meter_min": min(meter_values) if meter_values else 0,
"meter_max": max(meter_values) if meter_values else 0,
"meter_avg": sum(meter_values) / len(meter_values) if meter_values else 0,
"consumption": max(meter_values) - min(meter_values) if meter_values else 0,
}
def format_datetime(dt_string):
"""Formatiert DateTime für Chart.js"""
try:
dt = datetime.fromisoformat(dt_string.replace('Z', '+00:00'))
return dt.strftime('%Y-%m-%d %H:%M:%S')
except:
dt = datetime.fromisoformat(dt_string.replace("Z", "+00:00"))
return dt.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
return dt_string
# Hauptlogik
result_html = ""
try:
# 1. Sensoren für Zählernummer finden
sensors_response = make_graphql_request(FIND_SENSORS_QUERY, {
"meterNumber": METER_NUMBER
})
def build_report():
sensors_response = make_graphql_request(
FIND_SENSORS_QUERY,
{"meterNumber": METER_NUMBER},
)
if "errors" in sensors_response:
result = f"<div class='error'>Fehler beim Abrufen der Sensoren: {sensors_response['errors']}</div>"
else:
sensors = sensors_response.get('data', {}).get('sensorsForMeterNumber', [])
return build_message(
f"Fehler beim Abrufen der Sensoren: {sensors_response['errors']}",
"error",
)
sensors = sensors_response.get("data", {}).get("sensorsForMeterNumber", [])
if not sensors:
result = f"<div class='warning'>Keine Sensoren für Zählernummer '{METER_NUMBER}' gefunden.</div>"
else:
sensor = sensors[0] # Ersten Sensor verwenden
sensor_id = sensor['sensorId']
sensor_name = sensor['sensorName']
measure_concept_id = sensor['measureConcept']['id']
return build_message(
f"Keine Sensoren fuer Zaehlernummer '{METER_NUMBER}' gefunden.",
"warning",
)
# 2. Verfügbare Variablen abrufen
variables_response = make_graphql_request(GET_VARIABLES_QUERY, {
"sensorId": sensor_id
})
sensor = sensors[0]
sensor_id = sensor["sensorId"]
sensor_name = sensor["sensorName"]
measure_concept = sensor.get("measureConcept") or {}
measure_concept_id = measure_concept.get("id")
measure_concept_name = measure_concept.get("name") or "Unbekannt"
available_vars = variables_response.get('data', {}).get('availableVariableUnits', [])
variables_response = make_graphql_request(
GET_VARIABLES_QUERY,
{"sensorId": sensor_id},
)
available_vars = variables_response.get("data", {}).get("availableVariableUnits", [])
# 3. Zeitraum: Letzten 3 Monate
end_time = datetime.now()
start_time = end_time - timedelta(days=90)
# 4. Messwerte abrufen
observations_response = make_graphql_request(FIND_OBSERVATIONS_QUERY, {
observations_response = make_graphql_request(
FIND_OBSERVATIONS_QUERY,
{
"measurementConceptId": measure_concept_id,
"sensorName": sensor_name,
"startTime": start_time.isoformat(),
"endTime": end_time.isoformat()
})
"endTime": end_time.isoformat(),
},
)
observations = observations_response.get('data', {}).get('findObservation', [])
if "errors" in observations_response:
return build_message(
f"Fehler beim Abrufen der Messwerte: {observations_response['errors']}",
"error",
)
# 5. Statistiken berechnen
observations = observations_response.get("data", {}).get("findObservation", [])
stats = calculate_statistics(observations)
# 6. Daten für Chart vorbereiten
chart_labels = []
chart_values = []
chart_meter_values = []
for obs in observations:
chart_labels.append(format_datetime(obs['moment']))
chart_values.append(obs.get('value', 0))
chart_meter_values.append(obs.get('meterValue', 0))
chart_labels.append(format_datetime(obs.get("moment", "")))
chart_values.append(obs.get("value") or 0)
chart_meter_values.append(obs.get("meterValue") or 0)
safe_sensor_name = escape_text(sensor_name, "Unbekannt")
safe_meter_number = escape_text(METER_NUMBER)
safe_measure_concept_name = escape_text(measure_concept_name, "Unbekannt")
# 7. HTML mit Chart.js generieren
result_html = f"""
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Messwerte Dashboard - {sensor_name}</title>
<title>Messwerte Dashboard - {safe_sensor_name}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {{
@ -252,22 +281,28 @@ try:
border-radius: 10px;
margin-top: 20px;
}}
.warning {{ background: #fef3cd; color: #856404; }}
.error {{ background: #f8d7da; color: #721c24; }}
.warning {{
background: #fef3cd;
color: #856404;
}}
.error {{
background: #f8d7da;
color: #721c24;
}}
</style>
</head>
<body>
<div class="dashboard">
<div class="header">
<h1>📊 Messwerte Dashboard</h1>
<div class="subtitle">Sensor: {sensor_name} | Zählernummer: {METER_NUMBER}</div>
<h1>Messwerte Dashboard</h1>
<div class="subtitle">Sensor: {safe_sensor_name} | Zaehlernummer: {safe_meter_number}</div>
<div class="subtitle">Zeitraum: {start_time.strftime('%d.%m.%Y')} - {end_time.strftime('%d.%m.%Y')}</div>
<div class="subtitle">Gruppierung: {escape_text(GROUP_BY)}</div>
</div>
<div class="content">
"""
# Statistiken hinzufügen
if INCLUDE_STATISTICS and stats:
result_html += f"""
<div class="stats-grid">
@ -281,53 +316,52 @@ try:
</div>
<div class="stat-card">
<div class="stat-value">{stats['value_avg']:,.2f}</div>
<div class="stat-label">Ø Messwert</div>
<div class="stat-label">Durchschnitt Messwert</div>
</div>
<div class="stat-card">
<div class="stat-value">{stats['meter_max']:,.2f}</div>
<div class="stat-label">Max Zählerstand</div>
<div class="stat-label">Max Zaehlerstand</div>
</div>
</div>
"""
# Chart hinzufügen
result_html += f"""
<div class="chart-container">
<h2 class="chart-title">Messwerte Verlauf ({CHART_TYPE.title()})</h2>
<h2 class="chart-title">Messwerte Verlauf ({escape_text(CHART_TYPE.title())})</h2>
<canvas id="mainChart" width="400" height="200"></canvas>
</div>
<div class="chart-container">
<h2 class="chart-title">Zählerstände</h2>
<h2 class="chart-title">Zaehlerstaende</h2>
<canvas id="meterChart" width="400" height="200"></canvas>
</div>
<div class="info-box">
<h3>Verfügbare Variablen:</h3>
<h3>Verfuegbare Variablen:</h3>
<ul>
"""
for var in available_vars:
result_html += f"<li>{var['variableName']} ({var['unitName']})</li>"
variable_name = escape_text(var.get("variableName"), "Unbekannt")
unit_name = escape_text(var.get("unitName"), "-")
result_html += f" <li>{variable_name} ({unit_name})</li>\n"
result_html += f"""
</ul>
<p><strong>Anzahl Sensoren gefunden:</strong> {len(sensors)}</p>
<p><strong>Measure Concept:</strong> {sensor['measureConcept']['name']}</p>
<p><strong>Measure Concept:</strong> {safe_measure_concept_name}</p>
</div>
</div>
</div>
<script>
// Chart.js Konfiguration
const chartLabels = {json.dumps(chart_labels[-100:])}; // Nur letzten 100 Werte
const chartLabels = {json.dumps(chart_labels[-100:])};
const chartValues = {json.dumps(chart_values[-100:])};
const chartMeterValues = {json.dumps(chart_meter_values[-100:])};
// Hauptchart (Messwerte)
const ctx1 = document.getElementById('mainChart').getContext('2d');
new Chart(ctx1, {{
type: '{CHART_TYPE}',
type: {json.dumps(CHART_TYPE)},
data: {{
labels: chartLabels,
datasets: [{{
@ -363,14 +397,13 @@ try:
}}
}});
// Zählerstand Chart
const ctx2 = document.getElementById('meterChart').getContext('2d');
new Chart(ctx2, {{
type: 'line',
data: {{
labels: chartLabels,
datasets: [{{
label: 'hlerstand',
label: 'Zaehlerstand',
data: chartMeterValues,
borderColor: 'rgb(34, 197, 94)',
backgroundColor: 'rgba(34, 197, 94, 0.1)',
@ -406,9 +439,12 @@ try:
</html>
"""
result = result_html
return result_html
except Exception as e:
result = f"<div class='error'>Allgemeiner Fehler: {str(e)}</div>"
try:
result = build_report()
except Exception as exc:
result = build_message(f"Allgemeiner Fehler: {exc}", "error")
print(f"Generated HTML dashboard with {len(chart_labels) if 'chart_labels' in locals() else 0} data points")
print(result)

Loading…
Cancel
Save