diff --git a/scripts/meter_reading_dashboard.py b/scripts/meter_reading_dashboard.py
index 0a46b0c..b543017 100644
--- a/scripts/meter_reading_dashboard.py
+++ b/scripts/meter_reading_dashboard.py
@@ -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,352 +65,386 @@ 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"
"
+ f"{escape_text(message)}"
+ "
"
+ )
+
+
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"Fehler beim Abrufen der Sensoren: {sensors_response['errors']}
"
- else:
- sensors = sensors_response.get('data', {}).get('sensorsForMeterNumber', [])
-
- if not sensors:
- result = f"Keine Sensoren für Zählernummer '{METER_NUMBER}' gefunden.
"
- else:
- sensor = sensors[0] # Ersten Sensor verwenden
- sensor_id = sensor['sensorId']
- sensor_name = sensor['sensorName']
- measure_concept_id = sensor['measureConcept']['id']
-
- # 2. Verfügbare Variablen abrufen
- 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, {
- "measurementConceptId": measure_concept_id,
- "sensorName": sensor_name,
- "startTime": start_time.isoformat(),
- "endTime": end_time.isoformat()
- })
-
- observations = observations_response.get('data', {}).get('findObservation', [])
-
- # 5. Statistiken berechnen
- 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))
-
- # 7. HTML mit Chart.js generieren
- result_html = f"""
-
-
-
-
-
- Messwerte Dashboard - {sensor_name}
-
-
-
-
-
-
-
-
- """
-
- # Statistiken hinzufügen
- if INCLUDE_STATISTICS and stats:
- result_html += f"""
-
-
-
{stats['total_readings']:,}
-
Gesamte Messwerte
-
-
-
{stats['consumption']:,.2f}
-
Verbrauch (Differenz)
-
-
-
{stats['value_avg']:,.2f}
-
Ø Messwert
-
-
-
{stats['meter_max']:,.2f}
-
Max Zählerstand
-
-
- """
-
- # Chart hinzufügen
- result_html += f"""
-
-
Messwerte Verlauf ({CHART_TYPE.title()})
-
-
-
-
-
Zählerstände
-
-
-
-
-
Verfügbare Variablen:
-
- """
-
- for var in available_vars:
- result_html += f"- {var['variableName']} ({var['unitName']})
"
-
- result_html += f"""
-
-
Anzahl Sensoren gefunden: {len(sensors)}
-
Measure Concept: {sensor['measureConcept']['name']}
-
-
+ return build_message(
+ f"Fehler beim Abrufen der Sensoren: {sensors_response['errors']}",
+ "error",
+ )
+
+ sensors = sensors_response.get("data", {}).get("sensorsForMeterNumber", [])
+ if not sensors:
+ return build_message(
+ f"Keine Sensoren fuer Zaehlernummer '{METER_NUMBER}' gefunden.",
+ "warning",
+ )
+
+ 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"
+
+ variables_response = make_graphql_request(
+ GET_VARIABLES_QUERY,
+ {"sensorId": sensor_id},
+ )
+ available_vars = variables_response.get("data", {}).get("availableVariableUnits", [])
+
+ end_time = datetime.now()
+ start_time = end_time - timedelta(days=90)
+
+ observations_response = make_graphql_request(
+ FIND_OBSERVATIONS_QUERY,
+ {
+ "measurementConceptId": measure_concept_id,
+ "sensorName": sensor_name,
+ "startTime": start_time.isoformat(),
+ "endTime": end_time.isoformat(),
+ },
+ )
+
+ if "errors" in observations_response:
+ return build_message(
+ f"Fehler beim Abrufen der Messwerte: {observations_response['errors']}",
+ "error",
+ )
+
+ observations = observations_response.get("data", {}).get("findObservation", [])
+ stats = calculate_statistics(observations)
+
+ chart_labels = []
+ chart_values = []
+ chart_meter_values = []
+
+ for obs in observations:
+ 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")
+
+ result_html = f"""
+
+
+
+
+
+
Messwerte Dashboard - {safe_sensor_name}
+
+
+
+
+
+
+
+
+"""
+
+ if INCLUDE_STATISTICS and stats:
+ result_html += f"""
+
+
+
{stats['total_readings']:,}
+
Gesamte Messwerte
-
-
-
-
- """
-
- result = result_html
-
-except Exception as e:
- result = f"
Allgemeiner Fehler: {str(e)}
"
-
-print(f"Generated HTML dashboard with {len(chart_labels) if 'chart_labels' in locals() else 0} data points")
\ No newline at end of file
+ }},
+ y: {{
+ display: true,
+ beginAtZero: false
+ }}
+ }}
+ }}
+ }});
+
+
+
+"""
+
+ return result_html
+
+
+try:
+ result = build_report()
+except Exception as exc:
+ result = build_message(f"Allgemeiner Fehler: {exc}", "error")
+
+print(result)