diff --git a/scripts/ultimo_meter_readings_recorder.py b/scripts/ultimo_meter_readings_recorder.py
new file mode 100644
index 0000000..7fa0d49
--- /dev/null
+++ b/scripts/ultimo_meter_readings_recorder.py
@@ -0,0 +1,969 @@
+import json
+import httpx
+import base64
+from datetime import datetime, date
+from typing import List, Dict, Any, Optional
+
+def make_graphql_request(query: str, variables: dict = None) -> dict:
+ """Führt eine GraphQL-Anfrage aus mit Fehlerbehandlung"""
+ try:
+ response = httpx.post(
+ f"{EXTERNAL_BASE_URL}/graphql",
+ json={"query": query, "variables": variables or {}},
+ headers=AUTH_HEADERS,
+ timeout=30
+ )
+ response.raise_for_status()
+ data = response.json()
+
+ if "errors" in data:
+ error_msg = "; ".join([err.get("message", "Unbekannter Fehler") for err in data["errors"]])
+ return {"error": f"GraphQL Fehler: {error_msg}"}
+
+ return data.get("data", {})
+ except httpx.TimeoutException:
+ return {"error": "Timeout bei der Anfrage"}
+ except httpx.HTTPStatusError as e:
+ return {"error": f"HTTP Fehler {e.response.status_code}: {e.response.text}"}
+ except Exception as e:
+ return {"error": f"Unerwarteter Fehler: {str(e)}"}
+
+def search_sensors(meter_search: str) -> List[Dict]:
+ """Sucht Sensoren basierend auf Zählernummer"""
+ query = """
+ query SearchSensors($meterNumber: String!) {
+ sensorsForMeterNumber(meterNumber: $meterNumber) {
+ sensorId
+ sensorName
+ sensorNameExtern
+ descr
+ measureConcept {
+ id
+ name
+ descr
+ }
+ }
+ }
+ """
+
+ data = make_graphql_request(query, {"meterNumber": meter_search})
+ if "error" in data:
+ return []
+
+ return data.get("sensorsForMeterNumber", [])
+
+def get_variable_units(sensor_id: str) -> List[Dict]:
+ """Holt verfügbare Variablen und Units für einen Sensor"""
+ query = """
+ query GetVariableUnits($sensorId: ID!) {
+ availableVariableUnits(sensorId: $sensorId) {
+ variableUnitId
+ variableName
+ unitName
+ }
+ }
+ """
+
+ data = make_graphql_request(query, {"sensorId": sensor_id})
+ if "error" in data:
+ return []
+
+ return data.get("availableVariableUnits", [])
+
+def get_last_observation(sensor_id: str, variable_name: str) -> Optional[Dict]:
+ """Holt den letzten Zählerstand für einen Sensor"""
+ query = """
+ query GetLastObservation($sensorId: ID!, $variableName: String) {
+ lastObservation(sensorId: $sensorId, variableName: $variableName) {
+ id
+ moment
+ meterValue
+ }
+ }
+ """
+
+ data = make_graphql_request(query, {"sensorId": sensor_id, "variableName": variable_name})
+ if "error" in data:
+ return None
+
+ return data.get("lastObservation")
+
+def record_single_reading(sensor_id: str, moment: str, value: float, variable_name: str, variable_unit: str) -> Dict:
+ """Erfasst einen einzelnen Zählerstand"""
+ mutation = """
+ mutation RecordSingleReading($input: MeterReadingInput!) {
+ recordMeterReading(input: $input) {
+ success
+ observation {
+ id
+ moment
+ value
+ meterValue
+ }
+ errors {
+ code
+ message
+ details
+ }
+ }
+ }
+ """
+
+ input_data = {
+ "sensorId": sensor_id,
+ "moment": moment,
+ "value": value,
+ "variableName": variable_name,
+ "variableUnit": variable_unit
+ }
+
+ return make_graphql_request(mutation, {"input": input_data})
+
+def record_ultimo_readings(sensor_id: str, variable_name: str, variable_unit: str, readings: List[Dict]) -> Dict:
+ """Erfasst mehrere Ultimo-Zählerstände"""
+ mutation = """
+ mutation RecordUltimoReadings($input: UltimoReadingsInput!) {
+ recordUltimoReadings(input: $input) {
+ success
+ created {
+ id
+ moment
+ value
+ meterValue
+ }
+ errors {
+ code
+ message
+ details
+ }
+ }
+ }
+ """
+
+ input_data = {
+ "sensorId": sensor_id,
+ "variableName": variable_name,
+ "variableUnit": variable_unit,
+ "readings": readings
+ }
+
+ return make_graphql_request(mutation, {"input": input_data})
+
+def generate_month_list(start_year: int, start_month: int, end_year: int, end_month: int) -> List[str]:
+ """Generiert eine Liste von Monaten im Format YYYY-MM"""
+ months = []
+ current_year = start_year
+ current_month = start_month
+
+ while current_year < end_year or (current_year == end_year and current_month <= end_month):
+ months.append(f"{current_year:04d}-{current_month:02d}")
+ current_month += 1
+ if current_month > 12:
+ current_month = 1
+ current_year += 1
+
+ return months
+
+# Phase Detection
+if "sensor_id" not in PARAMS:
+ # PHASE 1: Sensor Suche
+ meter_search = PARAMS.get("meter_search", "")
+
+ if not meter_search:
+ result = {
+ "type": "error",
+ "message": "Kein Suchbegriff angegeben"
+ }
+ else:
+ sensors = search_sensors(meter_search)
+
+ if not sensors:
+ result = {
+ "type": "html",
+ "content": f"""
+
+
+
⚠️ Keine Sensoren gefunden
+
Für die Suche "{meter_search}" wurden keine Sensoren gefunden.
+
+
+
+
💡 Suchtipps:
+
+ - Verwenden Sie Teilstrings (z.B. "NG9" statt "NG9_40006_0_12")
+ - Probieren Sie verschiedene Suchbegriffe
+ - Achten Sie auf die korrekte Schreibweise
+
+
+
+
+ ← Zurück zur Suche
+
+
+ """
+ }
+ else:
+ # Erstelle Sensor-Dropdown-Optionen mit Zusatzinformationen
+ sensor_options = []
+ for sensor in sensors:
+ clean_name = sensor["sensorName"].strip()
+ measure_concept = sensor["measureConcept"]
+ concept_name = measure_concept["name"].strip() if measure_concept["name"] else "Unbekannt"
+ concept_desc = measure_concept.get("descr", "") or ""
+
+ label = f"{clean_name} ({concept_name}"
+ if concept_desc and concept_desc.strip():
+ label += f" - {concept_desc.strip()}"
+ label += ")"
+
+ sensor_options.append({
+ "value": sensor["sensorId"],
+ "label": label
+ })
+
+ # Sortiere Optionen alphabetisch
+ sensor_options.sort(key=lambda x: x["label"])
+
+ result = {
+ "type": "form",
+ "form_definition": {
+ "title": "Sensor Auswahl und Zählerstand Eingabe",
+ "description": f"Gefundene Sensoren für Suche: '{meter_search}'",
+ "layout": "sections",
+ "sections": [
+ {
+ "title": "Sensor Auswahl",
+ "icon": "sensors",
+ "field_names": ["sensor_id", "variable_selection"]
+ },
+ {
+ "title": "Erfassungsart",
+ "icon": "input",
+ "field_names": ["input_method"]
+ },
+ {
+ "title": "Einzelne Erfassung",
+ "icon": "schedule",
+ "field_names": ["single_datetime", "single_value"]
+ },
+ {
+ "title": "Ultimo Batch-Erfassung",
+ "icon": "batch_prediction",
+ "field_names": ["batch_start_year", "batch_start_month", "batch_end_year", "batch_end_month", "batch_values"]
+ }
+ ],
+ "fields": [
+ {
+ "name": "sensor_id",
+ "widget": "dropdown",
+ "label": "Sensor auswählen",
+ "options": sensor_options,
+ "validators": [
+ {"type": "required", "error_text": "Bitte wählen Sie einen Sensor aus"}
+ ]
+ },
+ {
+ "name": "variable_selection",
+ "widget": "text_field",
+ "label": "Variable wird automatisch geladen...",
+ "read_only": True,
+ "initial_value": "Wählen Sie zuerst einen Sensor aus",
+ "helper_text": "Die verfügbaren Variablen werden nach der Sensor-Auswahl angezeigt"
+ },
+ {
+ "name": "input_method",
+ "widget": "segmented_control",
+ "label": "Erfassungsart",
+ "initial_value": "single",
+ "options": [
+ {"value": "single", "label": "Einzeln"},
+ {"value": "batch", "label": "Batch (Ultimo)"}
+ ],
+ "validators": [
+ {"type": "required", "error_text": "Bitte wählen Sie eine Erfassungsart"}
+ ]
+ },
+ {
+ "name": "single_datetime",
+ "widget": "date_time_picker",
+ "label": "Datum und Uhrzeit",
+ "date_config": {
+ "input_type": "both",
+ "format": "dd.MM.yyyy HH:mm"
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "single",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Bitte wählen Sie Datum und Uhrzeit"}
+ ]
+ },
+ {
+ "name": "single_value",
+ "widget": "text_field",
+ "label": "Zählerstand",
+ "text_field_config": {
+ "keyboard_type": "number"
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "single",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Bitte geben Sie einen Zählerstand ein"},
+ {"type": "numeric", "error_text": "Zählerstand muss eine Zahl sein"},
+ {"type": "min", "value": 0, "error_text": "Zählerstand darf nicht negativ sein"}
+ ]
+ },
+ {
+ "name": "batch_start_year",
+ "widget": "text_field",
+ "label": "Start Jahr (YYYY)",
+ "text_field_config": {
+ "keyboard_type": "number"
+ },
+ "initial_value": "2024",
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Jahr ist erforderlich"},
+ {"type": "integer", "error_text": "Jahr muss eine ganze Zahl sein"},
+ {"type": "between", "value": 2020, "value2": 2030, "error_text": "Jahr muss zwischen 2020 und 2030 liegen"}
+ ]
+ },
+ {
+ "name": "batch_start_month",
+ "widget": "dropdown",
+ "label": "Start Monat",
+ "initial_value": "1",
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "options": [
+ {"value": "1", "label": "Januar"},
+ {"value": "2", "label": "Februar"},
+ {"value": "3", "label": "März"},
+ {"value": "4", "label": "April"},
+ {"value": "5", "label": "Mai"},
+ {"value": "6", "label": "Juni"},
+ {"value": "7", "label": "Juli"},
+ {"value": "8", "label": "August"},
+ {"value": "9", "label": "September"},
+ {"value": "10", "label": "Oktober"},
+ {"value": "11", "label": "November"},
+ {"value": "12", "label": "Dezember"}
+ ],
+ "validators": [
+ {"type": "required", "error_text": "Start Monat ist erforderlich"}
+ ]
+ },
+ {
+ "name": "batch_end_year",
+ "widget": "text_field",
+ "label": "Ende Jahr (YYYY)",
+ "text_field_config": {
+ "keyboard_type": "number"
+ },
+ "initial_value": "2024",
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Jahr ist erforderlich"},
+ {"type": "integer", "error_text": "Jahr muss eine ganze Zahl sein"},
+ {"type": "between", "value": 2020, "value2": 2030, "error_text": "Jahr muss zwischen 2020 und 2030 liegen"}
+ ]
+ },
+ {
+ "name": "batch_end_month",
+ "widget": "dropdown",
+ "label": "Ende Monat",
+ "initial_value": "12",
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "options": [
+ {"value": "1", "label": "Januar"},
+ {"value": "2", "label": "Februar"},
+ {"value": "3", "label": "März"},
+ {"value": "4", "label": "April"},
+ {"value": "5", "label": "Mai"},
+ {"value": "6", "label": "Juni"},
+ {"value": "7", "label": "Juli"},
+ {"value": "8", "label": "August"},
+ {"value": "9", "label": "September"},
+ {"value": "10", "label": "Oktober"},
+ {"value": "11", "label": "November"},
+ {"value": "12", "label": "Dezember"}
+ ],
+ "validators": [
+ {"type": "required", "error_text": "Ende Monat ist erforderlich"}
+ ]
+ },
+ {
+ "name": "batch_values",
+ "widget": "text_field",
+ "label": "Zählerstände (Komma-getrennt)",
+ "hint_text": "z.B: 1000.5, 2000.2, 3000.8",
+ "text_field_config": {
+ "max_lines": 3
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "helper_text": "Geben Sie die Zählerstände in chronologischer Reihenfolge an, getrennt durch Kommas",
+ "validators": [
+ {"type": "required", "error_text": "Bitte geben Sie die Zählerstände ein"}
+ ]
+ }
+ ],
+ "submit_label": "Nächster Schritt"
+ }
+ }
+
+else:
+ # PHASE 2: Variable Auswahl und Verarbeitung
+ sensor_id = PARAMS.get("sensor_id")
+
+ if not sensor_id:
+ result = {
+ "type": "error",
+ "message": "Sensor ID fehlt"
+ }
+ else:
+ # Prüfe ob bereits variable_name vorhanden ist (Phase 3)
+ if "variable_name" in PARAMS:
+ # PHASE 3: Ausführung
+ variable_name = PARAMS.get("variable_name")
+ variable_unit = PARAMS.get("variable_unit")
+ input_method = PARAMS.get("input_method", "single")
+
+ try:
+ if input_method == "single":
+ # Einzelne Erfassung
+ single_datetime = PARAMS.get("single_datetime")
+ single_value = float(PARAMS.get("single_value", 0))
+
+ # Konvertiere Datetime zu ISO Format
+ if isinstance(single_datetime, str):
+ # Parse verschiedene Datetime-Formate
+ try:
+ dt = datetime.fromisoformat(single_datetime.replace('Z', '+00:00'))
+ except:
+ dt = datetime.strptime(single_datetime, "%Y-%m-%dT%H:%M:%S")
+ else:
+ dt = single_datetime
+
+ iso_datetime = dt.isoformat()
+
+ # Führe Einzelerfassung aus
+ response = record_single_reading(
+ sensor_id, iso_datetime, single_value, variable_name, variable_unit
+ )
+
+ if "error" in response:
+ result = {
+ "type": "html",
+ "content": f"""
+
+
+
❌ Fehler bei der Erfassung
+
{response['error']}
+
+
+ """
+ }
+ else:
+ record_result = response.get("recordMeterReading", {})
+ if record_result.get("success"):
+ obs = record_result.get("observation", {})
+ result = {
+ "type": "html",
+ "content": f"""
+
+
+
✅ Zählerstand erfolgreich erfasst
+
Der Zählerstand wurde erfolgreich gespeichert.
+
+
+
+
📊 Erfasste Daten:
+
+ | Observation ID: | {obs.get('id', 'N/A')} |
+ | Zeitpunkt: | {obs.get('moment', 'N/A')} |
+ | Zählerstand: | {obs.get('meterValue', 'N/A')} |
+ | Berechneter Wert: | {obs.get('value', 'N/A')} |
+
+
+
+ """
+ }
+ else:
+ errors = record_result.get("errors", [])
+ error_html = ""
+ for err in errors:
+ error_html += f"- {err.get('code', 'ERROR')}: {err.get('message', 'Unbekannter Fehler')}
"
+ error_html += "
"
+
+ result = {
+ "type": "html",
+ "content": f"""
+
+
+
❌ Erfassung fehlgeschlagen
+
Die Erfassung konnte nicht durchgeführt werden:
+ {error_html}
+
+
+ """
+ }
+
+ else:
+ # Batch-Erfassung
+ start_year = int(PARAMS.get("batch_start_year", 2024))
+ start_month = int(PARAMS.get("batch_start_month", 1))
+ end_year = int(PARAMS.get("batch_end_year", 2024))
+ end_month = int(PARAMS.get("batch_end_month", 12))
+ batch_values_str = PARAMS.get("batch_values", "")
+
+ # Parse Batch-Werte
+ try:
+ values = [float(v.strip()) for v in batch_values_str.split(",") if v.strip()]
+ except ValueError:
+ result = {
+ "type": "error",
+ "message": "Ungültige Zahlenwerte in Batch-Eingabe. Verwenden Sie nur Zahlen getrennt durch Kommas."
+ }
+ else:
+ # Generiere Monatsliste
+ months = generate_month_list(start_year, start_month, end_year, end_month)
+
+ if len(values) != len(months):
+ result = {
+ "type": "html",
+ "content": f"""
+
+
+
⚠️ Anzahl Unstimmigkeit
+
Anzahl der Werte ({len(values)}) stimmt nicht mit der Anzahl der Monate ({len(months)}) überein.
+
Erwartete Monate: {', '.join(months)}
+
+
+ """
+ }
+ else:
+ # Erstelle Readings-Liste
+ readings = []
+ for i, month in enumerate(months):
+ readings.append({
+ "month": month,
+ "meterValue": values[i]
+ })
+
+ # Führe Batch-Erfassung aus
+ response = record_ultimo_readings(
+ sensor_id, variable_name, variable_unit, readings
+ )
+
+ if "error" in response:
+ result = {
+ "type": "html",
+ "content": f"""
+
+
+
❌ Fehler bei der Batch-Erfassung
+
{response['error']}
+
+
+ """
+ }
+ else:
+ batch_result = response.get("recordUltimoReadings", {})
+ created = batch_result.get("created", [])
+ errors = batch_result.get("errors", [])
+ success = batch_result.get("success", False)
+
+ html_content = f"""
+
+ """
+
+ if success and created:
+ html_content += f"""
+
+
✅ Batch-Erfassung erfolgreich
+
{len(created)} Zählerstände wurden erfolgreich erfasst.
+
+
+
+
📊 Erfasste Readings:
+
+
+
+ | ID |
+ Zeitpunkt |
+ Zählerstand |
+ Wert |
+
+
+
+ """
+
+ for i, obs in enumerate(created):
+ bg_color = "#f8f9fa" if i % 2 == 0 else "white"
+ html_content += f"""
+
+ | {obs.get('id', 'N/A')} |
+ {obs.get('moment', 'N/A')} |
+ {obs.get('meterValue', 'N/A')} |
+ {obs.get('value', 'N/A')} |
+
+ """
+
+ html_content += """
+
+
+
+ """
+
+ if errors:
+ html_content += f"""
+
+
⚠️ Warnungen/Fehler:
+
+ """
+
+ for err in errors:
+ html_content += f"- {err.get('code', 'ERROR')}: {err.get('message', 'Unbekannter Fehler')}"
+ if err.get('details'):
+ html_content += f" (Details: {err.get('details')})"
+ html_content += "
"
+
+ html_content += "
"
+
+ if not success and not created:
+ html_content += f"""
+
+
❌ Batch-Erfassung fehlgeschlagen
+
Die Erfassung konnte nicht durchgeführt werden.
+
+ """
+
+ html_content += "
"
+
+ result = {
+ "type": "html",
+ "content": html_content
+ }
+
+ except Exception as e:
+ result = {
+ "type": "error",
+ "message": f"Fehler bei der Verarbeitung: {str(e)}"
+ }
+
+ else:
+ # PHASE 2: Variable Auswahl
+ variable_units = get_variable_units(sensor_id)
+
+ if not variable_units:
+ result = {
+ "type": "error",
+ "message": "Keine Variablen für diesen Sensor gefunden"
+ }
+ else:
+ # Erstelle Variable-Dropdown-Optionen
+ variable_options = []
+ for vu in variable_units:
+ clean_var_name = vu["variableName"].strip()
+ clean_unit_name = vu["unitName"].strip()
+ label = f"{clean_var_name} ({clean_unit_name})"
+
+ variable_options.append({
+ "value": f"{clean_var_name}|{clean_unit_name}",
+ "label": label
+ })
+
+ # Sortiere Optionen alphabetisch
+ variable_options.sort(key=lambda x: x["label"])
+
+ # Hole letzten Zählerstand für Kontext
+ last_obs = None
+ if variable_options:
+ # Verwende erste Variable für Kontext
+ first_var = variable_options[0]["value"].split("|")[0]
+ last_obs = get_last_observation(sensor_id, first_var)
+
+ context_info = ""
+ if last_obs:
+ context_info = f"Letzter bekannter Zählerstand: {last_obs.get('meterValue', 'N/A')} am {last_obs.get('moment', 'N/A')}"
+ else:
+ context_info = "Kein vorheriger Zählerstand gefunden"
+
+ # Übertrage alle Parameter aus der ersten Phase
+ result = {
+ "type": "form",
+ "form_definition": {
+ "title": "Variable Auswahl und Zählerstand Eingabe",
+ "description": context_info,
+ "layout": "sections",
+ "sections": [
+ {
+ "title": "Variable/Einheit",
+ "icon": "analytics",
+ "field_names": ["variable_selection"]
+ },
+ {
+ "title": "Erfassungsart",
+ "icon": "input",
+ "field_names": ["input_method"]
+ },
+ {
+ "title": "Einzelne Erfassung",
+ "icon": "schedule",
+ "field_names": ["single_datetime", "single_value"]
+ },
+ {
+ "title": "Ultimo Batch-Erfassung",
+ "icon": "batch_prediction",
+ "field_names": ["batch_start_year", "batch_start_month", "batch_end_year", "batch_end_month", "batch_values"]
+ }
+ ],
+ "fields": [
+ # Versteckte Felder für übertragene Parameter
+ {
+ "name": "sensor_id",
+ "widget": "text_field",
+ "label": "Sensor ID",
+ "initial_value": sensor_id,
+ "read_only": True,
+ "enabled": False
+ },
+ {
+ "name": "variable_selection",
+ "widget": "dropdown",
+ "label": "Variable und Einheit auswählen",
+ "options": variable_options,
+ "validators": [
+ {"type": "required", "error_text": "Bitte wählen Sie eine Variable aus"}
+ ]
+ },
+ {
+ "name": "input_method",
+ "widget": "segmented_control",
+ "label": "Erfassungsart",
+ "initial_value": PARAMS.get("input_method", "single"),
+ "options": [
+ {"value": "single", "label": "Einzeln"},
+ {"value": "batch", "label": "Batch (Ultimo)"}
+ ],
+ "validators": [
+ {"type": "required", "error_text": "Bitte wählen Sie eine Erfassungsart"}
+ ]
+ },
+ {
+ "name": "single_datetime",
+ "widget": "date_time_picker",
+ "label": "Datum und Uhrzeit",
+ "initial_value": PARAMS.get("single_datetime", datetime.now().isoformat()),
+ "date_config": {
+ "input_type": "both",
+ "format": "dd.MM.yyyy HH:mm"
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "single",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Bitte wählen Sie Datum und Uhrzeit"}
+ ]
+ },
+ {
+ "name": "single_value",
+ "widget": "text_field",
+ "label": "Zählerstand",
+ "initial_value": PARAMS.get("single_value", ""),
+ "text_field_config": {
+ "keyboard_type": "number"
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "single",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Bitte geben Sie einen Zählerstand ein"},
+ {"type": "numeric", "error_text": "Zählerstand muss eine Zahl sein"},
+ {"type": "min", "value": 0, "error_text": "Zählerstand darf nicht negativ sein"}
+ ]
+ },
+ {
+ "name": "batch_start_year",
+ "widget": "text_field",
+ "label": "Start Jahr (YYYY)",
+ "initial_value": PARAMS.get("batch_start_year", "2024"),
+ "text_field_config": {
+ "keyboard_type": "number"
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Jahr ist erforderlich"},
+ {"type": "integer", "error_text": "Jahr muss eine ganze Zahl sein"},
+ {"type": "between", "value": 2020, "value2": 2030, "error_text": "Jahr muss zwischen 2020 und 2030 liegen"}
+ ]
+ },
+ {
+ "name": "batch_start_month",
+ "widget": "dropdown",
+ "label": "Start Monat",
+ "initial_value": PARAMS.get("batch_start_month", "1"),
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "options": [
+ {"value": "1", "label": "Januar"},
+ {"value": "2", "label": "Februar"},
+ {"value": "3", "label": "März"},
+ {"value": "4", "label": "April"},
+ {"value": "5", "label": "Mai"},
+ {"value": "6", "label": "Juni"},
+ {"value": "7", "label": "Juli"},
+ {"value": "8", "label": "August"},
+ {"value": "9", "label": "September"},
+ {"value": "10", "label": "Oktober"},
+ {"value": "11", "label": "November"},
+ {"value": "12", "label": "Dezember"}
+ ],
+ "validators": [
+ {"type": "required", "error_text": "Start Monat ist erforderlich"}
+ ]
+ },
+ {
+ "name": "batch_end_year",
+ "widget": "text_field",
+ "label": "Ende Jahr (YYYY)",
+ "initial_value": PARAMS.get("batch_end_year", "2024"),
+ "text_field_config": {
+ "keyboard_type": "number"
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "validators": [
+ {"type": "required", "error_text": "Jahr ist erforderlich"},
+ {"type": "integer", "error_text": "Jahr muss eine ganze Zahl sein"},
+ {"type": "between", "value": 2020, "value2": 2030, "error_text": "Jahr muss zwischen 2020 und 2030 liegen"}
+ ]
+ },
+ {
+ "name": "batch_end_month",
+ "widget": "dropdown",
+ "label": "Ende Monat",
+ "initial_value": PARAMS.get("batch_end_month", "12"),
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "options": [
+ {"value": "1", "label": "Januar"},
+ {"value": "2", "label": "Februar"},
+ {"value": "3", "label": "März"},
+ {"value": "4", "label": "April"},
+ {"value": "5", "label": "Mai"},
+ {"value": "6", "label": "Juni"},
+ {"value": "7", "label": "Juli"},
+ {"value": "8", "label": "August"},
+ {"value": "9", "label": "September"},
+ {"value": "10", "label": "Oktober"},
+ {"value": "11", "label": "November"},
+ {"value": "12", "label": "Dezember"}
+ ],
+ "validators": [
+ {"type": "required", "error_text": "Ende Monat ist erforderlich"}
+ ]
+ },
+ {
+ "name": "batch_values",
+ "widget": "text_field",
+ "label": "Zählerstände (Komma-getrennt)",
+ "initial_value": PARAMS.get("batch_values", ""),
+ "hint_text": "z.B: 1000.5, 2000.2, 3000.8",
+ "text_field_config": {
+ "max_lines": 3
+ },
+ "conditional": {
+ "field_name": "input_method",
+ "operator": "equals",
+ "value": "batch",
+ "action": "show"
+ },
+ "helper_text": "Geben Sie die Zählerstände in chronologischer Reihenfolge an, getrennt durch Kommas",
+ "validators": [
+ {"type": "required", "error_text": "Bitte geben Sie die Zählerstände ein"}
+ ]
+ }
+ ],
+ "submit_label": "Zählerstände erfassen"
+ }
+ }
+
+ # Erweitere das Form um die variable_name und variable_unit basierend auf variable_selection
+ if "variable_selection" in PARAMS:
+ var_selection = PARAMS["variable_selection"]
+ if "|" in var_selection:
+ var_name, var_unit = var_selection.split("|", 1)
+ # Füge versteckte Felder hinzu
+ result["form_definition"]["fields"].extend([
+ {
+ "name": "variable_name",
+ "widget": "text_field",
+ "label": "Variable Name",
+ "initial_value": var_name,
+ "read_only": True,
+ "enabled": False
+ },
+ {
+ "name": "variable_unit",
+ "widget": "text_field",
+ "label": "Variable Unit",
+ "initial_value": var_unit,
+ "read_only": True,
+ "enabled": False
+ }
+ ])