You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Fastlane/scripts/ultimo_meter_readings_recor...

970 lines
49 KiB
Python

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"""
<div style="padding: 20px; max-width: 800px;">
<div style="background: var(--color-warning); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin: 0 0 10px 0;">⚠️ Keine Sensoren gefunden</h3>
<p style="margin: 0;">Für die Suche "{meter_search}" wurden keine Sensoren gefunden.</p>
</div>
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid var(--color-primary);">
<h4 style="margin: 0 0 10px 0;">💡 Suchtipps:</h4>
<ul style="margin: 0; padding-left: 20px;">
<li>Verwenden Sie Teilstrings (z.B. "NG9" statt "NG9_40006_0_12")</li>
<li>Probieren Sie verschiedene Suchbegriffe</li>
<li>Achten Sie auf die korrekte Schreibweise</li>
</ul>
</div>
<p style="margin-top: 20px;">
<a href="javascript:history.back()" style="color: var(--color-primary); text-decoration: none;">← Zurück zur Suche</a>
</p>
</div>
"""
}
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"""
<div style="padding: 20px; max-width: 800px;">
<div style="background: var(--color-danger); color: white; padding: 15px; border-radius: 8px;">
<h3 style="margin: 0 0 10px 0;">❌ Fehler bei der Erfassung</h3>
<p style="margin: 0;">{response['error']}</p>
</div>
</div>
"""
}
else:
record_result = response.get("recordMeterReading", {})
if record_result.get("success"):
obs = record_result.get("observation", {})
result = {
"type": "html",
"content": f"""
<div style="padding: 20px; max-width: 800px;">
<div style="background: var(--color-success); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin: 0 0 10px 0;">✅ Zählerstand erfolgreich erfasst</h3>
<p style="margin: 0;">Der Zählerstand wurde erfolgreich gespeichert.</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid var(--color-success);">
<h4 style="margin: 0 0 15px 0;">📊 Erfasste Daten:</h4>
<table style="width: 100%; border-collapse: collapse;">
<tr><td style="padding: 8px 0; font-weight: bold;">Observation ID:</td><td style="padding: 8px 0;">{obs.get('id', 'N/A')}</td></tr>
<tr><td style="padding: 8px 0; font-weight: bold;">Zeitpunkt:</td><td style="padding: 8px 0;">{obs.get('moment', 'N/A')}</td></tr>
<tr><td style="padding: 8px 0; font-weight: bold;">Zählerstand:</td><td style="padding: 8px 0;">{obs.get('meterValue', 'N/A')}</td></tr>
<tr><td style="padding: 8px 0; font-weight: bold;">Berechneter Wert:</td><td style="padding: 8px 0;">{obs.get('value', 'N/A')}</td></tr>
</table>
</div>
</div>
"""
}
else:
errors = record_result.get("errors", [])
error_html = "<ul style='margin: 10px 0; padding-left: 20px;'>"
for err in errors:
error_html += f"<li><strong>{err.get('code', 'ERROR')}:</strong> {err.get('message', 'Unbekannter Fehler')}</li>"
error_html += "</ul>"
result = {
"type": "html",
"content": f"""
<div style="padding: 20px; max-width: 800px;">
<div style="background: var(--color-danger); color: white; padding: 15px; border-radius: 8px;">
<h3 style="margin: 0 0 10px 0;">❌ Erfassung fehlgeschlagen</h3>
<p style="margin: 0;">Die Erfassung konnte nicht durchgeführt werden:</p>
{error_html}
</div>
</div>
"""
}
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"""
<div style="padding: 20px; max-width: 800px;">
<div style="background: var(--color-warning); color: white; padding: 15px; border-radius: 8px;">
<h3 style="margin: 0 0 10px 0;">⚠️ Anzahl Unstimmigkeit</h3>
<p style="margin: 0;">Anzahl der Werte ({len(values)}) stimmt nicht mit der Anzahl der Monate ({len(months)}) überein.</p>
<p style="margin: 10px 0 0 0;"><strong>Erwartete Monate:</strong> {', '.join(months)}</p>
</div>
</div>
"""
}
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"""
<div style="padding: 20px; max-width: 800px;">
<div style="background: var(--color-danger); color: white; padding: 15px; border-radius: 8px;">
<h3 style="margin: 0 0 10px 0;">❌ Fehler bei der Batch-Erfassung</h3>
<p style="margin: 0;">{response['error']}</p>
</div>
</div>
"""
}
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"""
<div style="padding: 20px; max-width: 800px;">
"""
if success and created:
html_content += f"""
<div style="background: var(--color-success); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin: 0 0 10px 0;">✅ Batch-Erfassung erfolgreich</h3>
<p style="margin: 0;">{len(created)} Zählerstände wurden erfolgreich erfasst.</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid var(--color-success); margin-bottom: 20px;">
<h4 style="margin: 0 0 15px 0;">📊 Erfasste Readings:</h4>
<table style="width: 100%; border-collapse: collapse; background: white; border-radius: 4px; overflow: hidden;">
<thead>
<tr style="background: var(--color-primary); color: white;">
<th style="padding: 12px; text-align: left;">ID</th>
<th style="padding: 12px; text-align: left;">Zeitpunkt</th>
<th style="padding: 12px; text-align: right;">Zählerstand</th>
<th style="padding: 12px; text-align: right;">Wert</th>
</tr>
</thead>
<tbody>
"""
for i, obs in enumerate(created):
bg_color = "#f8f9fa" if i % 2 == 0 else "white"
html_content += f"""
<tr style="background: {bg_color};">
<td style="padding: 10px; border-bottom: 1px solid #dee2e6;">{obs.get('id', 'N/A')}</td>
<td style="padding: 10px; border-bottom: 1px solid #dee2e6;">{obs.get('moment', 'N/A')}</td>
<td style="padding: 10px; text-align: right; border-bottom: 1px solid #dee2e6;">{obs.get('meterValue', 'N/A')}</td>
<td style="padding: 10px; text-align: right; border-bottom: 1px solid #dee2e6;">{obs.get('value', 'N/A')}</td>
</tr>
"""
html_content += """
</tbody>
</table>
</div>
"""
if errors:
html_content += f"""
<div style="background: var(--color-warning); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h4 style="margin: 0 0 10px 0;">⚠️ Warnungen/Fehler:</h4>
<ul style="margin: 0; padding-left: 20px;">
"""
for err in errors:
html_content += f"<li><strong>{err.get('code', 'ERROR')}:</strong> {err.get('message', 'Unbekannter Fehler')}"
if err.get('details'):
html_content += f" (Details: {err.get('details')})"
html_content += "</li>"
html_content += "</ul></div>"
if not success and not created:
html_content += f"""
<div style="background: var(--color-danger); color: white; padding: 15px; border-radius: 8px;">
<h3 style="margin: 0 0 10px 0;">❌ Batch-Erfassung fehlgeschlagen</h3>
<p style="margin: 0;">Die Erfassung konnte nicht durchgeführt werden.</p>
</div>
"""
html_content += "</div>"
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
}
])