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} - - - - -
-
-

📊 Messwerte Dashboard

-
Sensor: {sensor_name} | Zählernummer: {METER_NUMBER}
-
Zeitraum: {start_time.strftime('%d.%m.%Y')} - {end_time.strftime('%d.%m.%Y')}
-
- -
- """ - - # 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} + + + + +
+
+

Messwerte Dashboard

+
Sensor: {safe_sensor_name} | Zaehlernummer: {safe_meter_number}
+
Zeitraum: {start_time.strftime('%d.%m.%Y')} - {end_time.strftime('%d.%m.%Y')}
+
Gruppierung: {escape_text(GROUP_BY)}
+
+ +
+""" + + 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)