„scripts/ultimo_meter_reading_entry.py“ löschen
parent
7e12060a2a
commit
37d2e88180
@ -1,387 +0,0 @@
|
|||||||
import httpx
|
|
||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import List, Dict, Any
|
|
||||||
|
|
||||||
# GraphQL Query Templates
|
|
||||||
SENSORS_QUERY = """
|
|
||||||
query SensorsForMeterNumber($meterNumber: String!) {
|
|
||||||
sensorsForMeterNumber(meterNumber: $meterNumber) {
|
|
||||||
sensorId
|
|
||||||
sensorName
|
|
||||||
sensorNameExtern
|
|
||||||
descr
|
|
||||||
measureConcept {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
descr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
AVAILABLE_VARIABLES_QUERY = """
|
|
||||||
query AvailableVariableUnits($sensorId: ID!) {
|
|
||||||
availableVariableUnits(sensorId: $sensorId) {
|
|
||||||
variableUnitId
|
|
||||||
variableName
|
|
||||||
unitName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
LAST_OBSERVATION_QUERY = """
|
|
||||||
query LastObservation($sensorId: ID!, $variableName: String) {
|
|
||||||
lastObservation(sensorId: $sensorId, variableName: $variableName) {
|
|
||||||
id
|
|
||||||
moment
|
|
||||||
value
|
|
||||||
meterValue
|
|
||||||
observationVariableUnit {
|
|
||||||
observationVariable {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
unit {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
FIND_OBSERVATIONS_QUERY = """
|
|
||||||
query FindObservations($measurementConceptId: ID!, $sensorName: String, $observationVariableNamePattern: String, $startTime: String, $endTime: String) {
|
|
||||||
findObservation(
|
|
||||||
measurementConceptId: $measurementConceptId
|
|
||||||
sensorName: $sensorName
|
|
||||||
observationVariableNamePattern: $observationVariableNamePattern
|
|
||||||
startTime: $startTime
|
|
||||||
endTime: $endTime
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
moment
|
|
||||||
value
|
|
||||||
meterValue
|
|
||||||
observationVariableUnit {
|
|
||||||
observationVariable {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
unit {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
RECORD_ULTIMO_MUTATION = """
|
|
||||||
mutation RecordUltimoReadings($input: UltimoReadingsInput!) {
|
|
||||||
recordUltimoReadings(input: $input) {
|
|
||||||
success
|
|
||||||
created {
|
|
||||||
id
|
|
||||||
moment
|
|
||||||
meterValue
|
|
||||||
observationVariableUnit {
|
|
||||||
observationVariable {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
unit {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errors {
|
|
||||||
code
|
|
||||||
message
|
|
||||||
details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def execute_graphql_query(query: str, variables: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
||||||
"""Execute GraphQL query"""
|
|
||||||
response = httpx.post(
|
|
||||||
f"{EXTERNAL_BASE_URL}/graphql",
|
|
||||||
headers=AUTH_HEADERS,
|
|
||||||
json={
|
|
||||||
"query": query,
|
|
||||||
"variables": variables or {}
|
|
||||||
},
|
|
||||||
timeout=30.0
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise Exception(f"HTTP {response.status_code}: {response.text}")
|
|
||||||
|
|
||||||
result = response.json()
|
|
||||||
if "errors" in result:
|
|
||||||
raise Exception(f"GraphQL Error: {result['errors']}")
|
|
||||||
|
|
||||||
return result["data"]
|
|
||||||
|
|
||||||
def parse_readings(readings_text: str) -> List[Dict[str, Any]]:
|
|
||||||
"""Parse readings from text format YYYY-MM:value"""
|
|
||||||
readings = []
|
|
||||||
for line in readings_text.strip().split('\n'):
|
|
||||||
line = line.strip()
|
|
||||||
if not line or ':' not in line:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
month_str, value_str = line.split(':', 1)
|
|
||||||
month_str = month_str.strip()
|
|
||||||
value = float(value_str.strip())
|
|
||||||
|
|
||||||
# Validate month format YYYY-MM
|
|
||||||
datetime.strptime(month_str, '%Y-%m')
|
|
||||||
|
|
||||||
readings.append({
|
|
||||||
"month": month_str,
|
|
||||||
"meterValue": value
|
|
||||||
})
|
|
||||||
except (ValueError, IndexError) as e:
|
|
||||||
raise Exception(f"Ungültiges Format in Zeile '{line}': {e}")
|
|
||||||
|
|
||||||
if not readings:
|
|
||||||
raise Exception("Keine gültigen Readings gefunden")
|
|
||||||
|
|
||||||
# Sort by month
|
|
||||||
readings.sort(key=lambda x: x["month"])
|
|
||||||
return readings
|
|
||||||
|
|
||||||
def format_observation_table(observations: List[Dict[str, Any]], title: str) -> str:
|
|
||||||
"""Format observations as HTML table"""
|
|
||||||
if not observations:
|
|
||||||
return f"<p><strong>{title}:</strong> Keine Daten gefunden</p>"
|
|
||||||
|
|
||||||
html = f"<h3>{title}</h3>"
|
|
||||||
html += "<table style='width: 100%; border-collapse: collapse; margin-bottom: 20px;'>"
|
|
||||||
html += "<thead style='background-color: var(--color-primary); color: white;'>"
|
|
||||||
html += "<tr><th style='padding: 10px; border: 1px solid #ddd;'>Datum/Zeit</th>"
|
|
||||||
html += "<th style='padding: 10px; border: 1px solid #ddd;'>Zählerstand</th>"
|
|
||||||
html += "<th style='padding: 10px; border: 1px solid #ddd;'>Variable</th>"
|
|
||||||
html += "<th style='padding: 10px; border: 1px solid #ddd;'>Einheit</th></tr>"
|
|
||||||
html += "</thead><tbody>"
|
|
||||||
|
|
||||||
for obs in observations:
|
|
||||||
moment = datetime.fromisoformat(obs["moment"].replace('Z', '+00:00')).strftime('%d.%m.%Y %H:%M')
|
|
||||||
meter_value = f"{obs['meterValue']:,.2f}"
|
|
||||||
var_name = obs["observationVariableUnit"]["observationVariable"]["name"].strip()
|
|
||||||
unit_name = obs["observationVariableUnit"]["unit"]["name"].strip()
|
|
||||||
|
|
||||||
html += f"<tr><td style='padding: 8px; border: 1px solid #ddd;'>{moment}</td>"
|
|
||||||
html += f"<td style='padding: 8px; border: 1px solid #ddd; text-align: right;'>{meter_value}</td>"
|
|
||||||
html += f"<td style='padding: 8px; border: 1px solid #ddd;'>{var_name}</td>"
|
|
||||||
html += f"<td style='padding: 8px; border: 1px solid #ddd;'>{unit_name}</td></tr>"
|
|
||||||
|
|
||||||
html += "</tbody></table>"
|
|
||||||
return html
|
|
||||||
|
|
||||||
def format_errors(errors: List[Dict[str, Any]]) -> str:
|
|
||||||
"""Format error messages as HTML"""
|
|
||||||
if not errors:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
html = "<div style='background-color: var(--color-danger); color: white; padding: 15px; margin: 10px 0; border-radius: 5px;'>"
|
|
||||||
html += "<h3>❌ Fehler aufgetreten:</h3><ul>"
|
|
||||||
|
|
||||||
for error in errors:
|
|
||||||
html += f"<li><strong>{error['code']}:</strong> {error['message']}"
|
|
||||||
if error.get('details'):
|
|
||||||
html += f"<br><small>{error['details']}</small>"
|
|
||||||
html += "</li>"
|
|
||||||
|
|
||||||
html += "</ul></div>"
|
|
||||||
return html
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Parse parameters
|
|
||||||
meter_number = PARAMS.get('meter_number', '').strip()
|
|
||||||
variable_name = PARAMS.get('variable_name', 'ENERGY_INST_VAL').strip()
|
|
||||||
readings_text = PARAMS.get('readings', '').strip()
|
|
||||||
|
|
||||||
if not meter_number:
|
|
||||||
raise Exception("Zählernummer ist erforderlich")
|
|
||||||
|
|
||||||
if not readings_text:
|
|
||||||
raise Exception("Ultimo-Stände sind erforderlich")
|
|
||||||
|
|
||||||
# Parse readings
|
|
||||||
readings = parse_readings(readings_text)
|
|
||||||
|
|
||||||
# Find sensors for meter number
|
|
||||||
sensors_data = execute_graphql_query(SENSORS_QUERY, {"meterNumber": meter_number})
|
|
||||||
sensors = sensors_data["sensorsForMeterNumber"]
|
|
||||||
|
|
||||||
if not sensors:
|
|
||||||
raise Exception(f"Keine Sensoren für Zählernummer '{meter_number}' gefunden")
|
|
||||||
|
|
||||||
# Use first sensor if multiple found
|
|
||||||
sensor = sensors[0]
|
|
||||||
sensor_id = sensor["sensorId"]
|
|
||||||
sensor_name = sensor["sensorName"].strip()
|
|
||||||
measure_concept_id = sensor["measureConcept"]["id"]
|
|
||||||
|
|
||||||
html_content = f"<h2>Ultimo-Zählerstände für {sensor_name}</h2>"
|
|
||||||
|
|
||||||
if len(sensors) > 1:
|
|
||||||
html_content += f"<p style='color: var(--color-warning);'>⚠️ Mehrere Sensoren gefunden, verwende ersten: {sensor_name}</p>"
|
|
||||||
|
|
||||||
# Get available variables
|
|
||||||
variables_data = execute_graphql_query(AVAILABLE_VARIABLES_QUERY, {"sensorId": sensor_id})
|
|
||||||
available_variables = variables_data["availableVariableUnits"]
|
|
||||||
|
|
||||||
# Check if requested variable exists
|
|
||||||
variable_found = any(var["variableName"].strip() == variable_name for var in available_variables)
|
|
||||||
if not variable_found:
|
|
||||||
html_content += f"<p style='color: var(--color-warning);'>⚠️ Variable '{variable_name}' nicht verfügbar für diesen Sensor. Verfügbare Variablen:</p><ul>"
|
|
||||||
for var in available_variables:
|
|
||||||
html_content += f"<li>{var['variableName'].strip()} ({var['unitName'].strip()})</li>"
|
|
||||||
html_content += "</ul>"
|
|
||||||
# Use first available variable
|
|
||||||
if available_variables:
|
|
||||||
variable_name = available_variables[0]["variableName"].strip()
|
|
||||||
html_content += f"<p>Verwende stattdessen: {variable_name}</p>"
|
|
||||||
|
|
||||||
# Record ultimo readings
|
|
||||||
ultimo_input = {
|
|
||||||
"sensorId": sensor_id,
|
|
||||||
"variableName": variable_name,
|
|
||||||
"readings": readings
|
|
||||||
}
|
|
||||||
|
|
||||||
ultimo_result = execute_graphql_query(RECORD_ULTIMO_MUTATION, {"input": ultimo_input})
|
|
||||||
result_data = ultimo_result["recordUltimoReadings"]
|
|
||||||
|
|
||||||
# Show results
|
|
||||||
if result_data["success"]:
|
|
||||||
html_content += "<div style='background-color: var(--color-success); color: white; padding: 15px; margin: 10px 0; border-radius: 5px;'>"
|
|
||||||
html_content += f"<h3>✅ Erfolgreich {len(result_data['created'])} Ultimo-Stände eingetragen</h3>"
|
|
||||||
html_content += "</div>"
|
|
||||||
|
|
||||||
# Show created observations
|
|
||||||
if result_data["created"]:
|
|
||||||
html_content += format_observation_table(result_data["created"], "Neu eingetragene Ultimo-Stände")
|
|
||||||
|
|
||||||
# Show errors if any
|
|
||||||
if result_data["errors"]:
|
|
||||||
html_content += format_errors(result_data["errors"])
|
|
||||||
|
|
||||||
# Get current reading (last observation)
|
|
||||||
try:
|
|
||||||
last_obs_data = execute_graphql_query(LAST_OBSERVATION_QUERY, {
|
|
||||||
"sensorId": sensor_id,
|
|
||||||
"variableName": variable_name
|
|
||||||
})
|
|
||||||
|
|
||||||
if last_obs_data["lastObservation"]:
|
|
||||||
html_content += format_observation_table([last_obs_data["lastObservation"]], "Aktueller Zählerstand")
|
|
||||||
except Exception as e:
|
|
||||||
html_content += f"<p style='color: var(--color-warning);'>⚠️ Aktueller Stand konnte nicht abgerufen werden: {e}</p>"
|
|
||||||
|
|
||||||
# Get last 10 observations
|
|
||||||
try:
|
|
||||||
# Calculate date range for recent observations (last 2 years)
|
|
||||||
end_time = datetime.now().strftime('%Y%m%d%H%M%S')
|
|
||||||
start_time = datetime.now().replace(year=datetime.now().year - 2).strftime('%Y%m%d%H%M%S')
|
|
||||||
|
|
||||||
recent_obs_data = execute_graphql_query(FIND_OBSERVATIONS_QUERY, {
|
|
||||||
"measurementConceptId": measure_concept_id,
|
|
||||||
"sensorName": sensor_name,
|
|
||||||
"observationVariableNamePattern": variable_name,
|
|
||||||
"startTime": start_time,
|
|
||||||
"endTime": end_time
|
|
||||||
})
|
|
||||||
|
|
||||||
observations = recent_obs_data["findObservation"] or []
|
|
||||||
|
|
||||||
# Sort by moment descending and take last 10
|
|
||||||
observations.sort(key=lambda x: x["moment"], reverse=True)
|
|
||||||
recent_observations = observations[:10]
|
|
||||||
|
|
||||||
if recent_observations:
|
|
||||||
html_content += format_observation_table(recent_observations, "Letzte 10 Messwerte")
|
|
||||||
else:
|
|
||||||
html_content += "<p><strong>Letzte 10 Messwerte:</strong> Keine historischen Daten gefunden</p>"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
html_content += f"<p style='color: var(--color-warning);'>⚠️ Historische Daten konnten nicht abgerufen werden: {e}</p>"
|
|
||||||
|
|
||||||
# Add summary
|
|
||||||
html_content += "<div style='margin-top: 30px; padding: 15px; background-color: #f5f5f5; border-radius: 5px;'>"
|
|
||||||
html_content += f"<h3>Zusammenfassung</h3>"
|
|
||||||
html_content += f"<p><strong>Sensor:</strong> {sensor_name} (ID: {sensor_id})</p>"
|
|
||||||
html_content += f"<p><strong>Variable:</strong> {variable_name}</p>"
|
|
||||||
html_content += f"<p><strong>Eingetragene Stände:</strong> {len(readings)}</p>"
|
|
||||||
if result_data["success"]:
|
|
||||||
html_content += f"<p><strong>Erfolgreich erstellt:</strong> {len(result_data['created'])}</p>"
|
|
||||||
if result_data["errors"]:
|
|
||||||
html_content += f"<p><strong>Fehler:</strong> {len(result_data['errors'])}</p>"
|
|
||||||
html_content += "</div>"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
html_content = f"<div style='background-color: var(--color-danger); color: white; padding: 15px; margin: 10px 0; border-radius: 5px;'>"
|
|
||||||
html_content += f"<h2>❌ Fehler beim Eintragen der Ultimo-Stände</h2>"
|
|
||||||
html_content += f"<p><strong>Fehlermeldung:</strong> {str(e)}</p>"
|
|
||||||
html_content += "</div>"
|
|
||||||
|
|
||||||
# Show debug info
|
|
||||||
html_content += "<div style='margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;'>"
|
|
||||||
html_content += "<h3>Debug-Informationen</h3>"
|
|
||||||
html_content += f"<p><strong>Zählernummer:</strong> {PARAMS.get('meter_number', 'nicht gesetzt')}</p>"
|
|
||||||
html_content += f"<p><strong>Variable:</strong> {PARAMS.get('variable_name', 'nicht gesetzt')}</p>"
|
|
||||||
html_content += f"<p><strong>Readings Text:</strong><br><pre>{PARAMS.get('readings', 'nicht gesetzt')}</pre></p>"
|
|
||||||
html_content += "</div>"
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"type": "html",
|
|
||||||
"content": f"""
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Ultimo-Zählerstände</title>
|
|
||||||
<style>
|
|
||||||
body {{
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}}
|
|
||||||
.container {{
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}}
|
|
||||||
h2 {{ color: var(--color-primary, #1976d2); }}
|
|
||||||
h3 {{ color: var(--color-primary, #1976d2); }}
|
|
||||||
table {{
|
|
||||||
font-size: 14px;
|
|
||||||
}}
|
|
||||||
th {{
|
|
||||||
background-color: var(--color-primary, #1976d2) !important;
|
|
||||||
}}
|
|
||||||
.success {{
|
|
||||||
background-color: var(--color-success, #4caf50);
|
|
||||||
}}
|
|
||||||
.warning {{
|
|
||||||
background-color: var(--color-warning, #ff9800);
|
|
||||||
}}
|
|
||||||
.danger {{
|
|
||||||
background-color: var(--color-danger, #f44336);
|
|
||||||
}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
{html_content}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue