„scripts/ultimo_meter_readings.py“ löschen
parent
2b8ea8301b
commit
2c28b48e5e
@ -1,462 +0,0 @@
|
|||||||
import httpx
|
|
||||||
import json
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import re
|
|
||||||
from typing import List, Dict, Optional
|
|
||||||
|
|
||||||
# Parse readings from text input
|
|
||||||
def parse_readings(readings_text: str) -> List[Dict[str, any]]:
|
|
||||||
"""Parse readings from text input format: YYYY-MM: 12345.67"""
|
|
||||||
readings = []
|
|
||||||
lines = readings_text.strip().split('\n')
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Match format: YYYY-MM: value
|
|
||||||
match = re.match(r'^(\d{4}-\d{2})\s*:\s*(\d+(?:\.\d+)?)$', line)
|
|
||||||
if match:
|
|
||||||
month_str = match.group(1)
|
|
||||||
value = float(match.group(2))
|
|
||||||
readings.append({
|
|
||||||
'month': month_str,
|
|
||||||
'meterValue': value
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Ungültiges Format in Zeile: {line}")
|
|
||||||
|
|
||||||
# Sort by month
|
|
||||||
readings.sort(key=lambda x: x['month'])
|
|
||||||
return readings
|
|
||||||
|
|
||||||
# GraphQL query functions
|
|
||||||
def execute_graphql_query(query: str, variables: Dict = None) -> Dict:
|
|
||||||
"""Execute a GraphQL query against the API"""
|
|
||||||
headers = dict(AUTH_HEADERS)
|
|
||||||
headers['Content-Type'] = 'application/json'
|
|
||||||
|
|
||||||
payload = {'query': query}
|
|
||||||
if variables:
|
|
||||||
payload['variables'] = variables
|
|
||||||
|
|
||||||
response = httpx.post(
|
|
||||||
f"{EXTERNAL_BASE_URL}/graphql",
|
|
||||||
headers=headers,
|
|
||||||
json=payload,
|
|
||||||
timeout=30
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise Exception(f"HTTP Error {response.status_code}: {response.text}")
|
|
||||||
|
|
||||||
result = response.json()
|
|
||||||
if 'errors' in result:
|
|
||||||
raise Exception(f"GraphQL Error: {result['errors']}")
|
|
||||||
|
|
||||||
return result['data']
|
|
||||||
|
|
||||||
# Main execution
|
|
||||||
try:
|
|
||||||
# Get parameters
|
|
||||||
sensor_id = PARAMS.get('sensor_id')
|
|
||||||
variable_name = PARAMS.get('variable_name', 'ENERGY_INST_VAL')
|
|
||||||
unit_name = PARAMS.get('unit_name', 'WH')
|
|
||||||
readings_text = PARAMS.get('readings_text', '')
|
|
||||||
|
|
||||||
if not sensor_id or not readings_text:
|
|
||||||
raise ValueError("Sensor ID und Zählerstände sind erforderlich")
|
|
||||||
|
|
||||||
# Parse readings
|
|
||||||
readings = parse_readings(readings_text)
|
|
||||||
|
|
||||||
if not readings:
|
|
||||||
raise ValueError("Keine gültigen Zählerstände gefunden")
|
|
||||||
|
|
||||||
# Get sensor info
|
|
||||||
sensor_query = """
|
|
||||||
query GetSensor($sensorId: ID!) {
|
|
||||||
sensor(id: $sensorId) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
nameExtern
|
|
||||||
description
|
|
||||||
measureConcept {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
sensor_data = execute_graphql_query(sensor_query, {"sensorId": sensor_id})
|
|
||||||
sensor = sensor_data['sensor']
|
|
||||||
|
|
||||||
if not sensor:
|
|
||||||
raise ValueError(f"Sensor mit ID {sensor_id} nicht gefunden")
|
|
||||||
|
|
||||||
# Get current reading before insert
|
|
||||||
last_reading_query = """
|
|
||||||
query GetLastReading($sensorId: ID!, $variableName: String) {
|
|
||||||
lastObservation(sensorId: $sensorId, variableName: $variableName) {
|
|
||||||
id
|
|
||||||
moment
|
|
||||||
value
|
|
||||||
meterValue
|
|
||||||
observationVariableUnit {
|
|
||||||
observationVariable {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
unit {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
current_reading_data = execute_graphql_query(last_reading_query, {
|
|
||||||
"sensorId": sensor_id,
|
|
||||||
"variableName": variable_name
|
|
||||||
})
|
|
||||||
current_reading = current_reading_data.get('lastObservation')
|
|
||||||
|
|
||||||
# Execute the ultimo readings mutation
|
|
||||||
ultimo_mutation = """
|
|
||||||
mutation RecordUltimoReadings($input: UltimoReadingsInput!) {
|
|
||||||
recordUltimoReadings(input: $input) {
|
|
||||||
success
|
|
||||||
created {
|
|
||||||
id
|
|
||||||
moment
|
|
||||||
value
|
|
||||||
meterValue
|
|
||||||
observationVariableUnit {
|
|
||||||
observationVariable {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
unit {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errors {
|
|
||||||
code
|
|
||||||
message
|
|
||||||
details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
ultimo_input = {
|
|
||||||
"sensorId": sensor_id,
|
|
||||||
"variableName": variable_name,
|
|
||||||
"variableUnit": unit_name,
|
|
||||||
"readings": readings
|
|
||||||
}
|
|
||||||
|
|
||||||
ultimo_result = execute_graphql_query(ultimo_mutation, {"input": ultimo_input})
|
|
||||||
ultimo_data = ultimo_result['recordUltimoReadings']
|
|
||||||
|
|
||||||
# Get updated current reading and last 10
|
|
||||||
recent_readings_query = """
|
|
||||||
query GetRecentReadings($measurementConceptId: ID!, $sensorName: String, $variableName: String) {
|
|
||||||
findObservation(
|
|
||||||
measurementConceptId: $measurementConceptId
|
|
||||||
sensorName: $sensorName
|
|
||||||
observationVariableNamePattern: $variableName
|
|
||||||
startTime: "2020-01-01 00:00:00"
|
|
||||||
endTime: "2030-12-31 23:59:59"
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
moment
|
|
||||||
value
|
|
||||||
meterValue
|
|
||||||
observationVariableUnit {
|
|
||||||
observationVariable {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
unit {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
recent_data = execute_graphql_query(recent_readings_query, {
|
|
||||||
"measurementConceptId": sensor['measureConcept']['id'],
|
|
||||||
"sensorName": sensor['name'].strip(),
|
|
||||||
"variableName": variable_name
|
|
||||||
})
|
|
||||||
all_observations = recent_data.get('findObservation', [])
|
|
||||||
# Sort by moment descending and take last 11 (current + 10)
|
|
||||||
all_observations.sort(key=lambda x: x['moment'], reverse=True)
|
|
||||||
recent_observations = all_observations[:11]
|
|
||||||
except:
|
|
||||||
recent_observations = []
|
|
||||||
|
|
||||||
# Build HTML result
|
|
||||||
html_content = f"""
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Ultimo-Zählerstände Ergebnis</title>
|
|
||||||
<style>
|
|
||||||
body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }}
|
|
||||||
.container {{ max-width: 1200px; margin: 0 auto; }}
|
|
||||||
.header {{ background: var(--color-primary, #1976d2); color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
|
|
||||||
.sensor-info {{ background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
|
|
||||||
.result-section {{ background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
|
|
||||||
.success {{ background-color: var(--color-success, #4caf50); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px; }}
|
|
||||||
.error {{ background-color: var(--color-danger, #f44336); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px; }}
|
|
||||||
.warning {{ background-color: var(--color-warning, #ff9800); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px; }}
|
|
||||||
table {{ width: 100%; border-collapse: collapse; margin: 15px 0; }}
|
|
||||||
th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
|
|
||||||
th {{ background-color: #f8f9fa; font-weight: bold; }}
|
|
||||||
.meter-value {{ font-weight: bold; color: var(--color-primary, #1976d2); }}
|
|
||||||
.timestamp {{ font-size: 0.9em; color: #666; }}
|
|
||||||
.badge {{ display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.8em; font-weight: bold; }}
|
|
||||||
.badge-success {{ background-color: var(--color-success, #4caf50); color: white; }}
|
|
||||||
.badge-error {{ background-color: var(--color-danger, #f44336); color: white; }}
|
|
||||||
.details {{ background-color: #f8f9fa; padding: 10px; border-radius: 4px; margin-top: 10px; font-family: monospace; font-size: 0.9em; }}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>🎯 Ultimo-Zählerstände Ergebnis</h1>
|
|
||||||
<p>Verarbeitung der Zählerstände für Sensor {sensor['name'].strip()}</p>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Sensor information
|
|
||||||
html_content += f"""
|
|
||||||
<div class="sensor-info">
|
|
||||||
<h2>📊 Sensor-Informationen</h2>
|
|
||||||
<table>
|
|
||||||
<tr><th>Sensor ID</th><td>{sensor['id']}</td></tr>
|
|
||||||
<tr><th>Name</th><td>{sensor['name'].strip()}</td></tr>
|
|
||||||
<tr><th>Extern</th><td>{sensor.get('nameExtern', '-')}</td></tr>
|
|
||||||
<tr><th>Beschreibung</th><td>{sensor.get('description', '-')}</td></tr>
|
|
||||||
<tr><th>Messkonzept</th><td>{sensor['measureConcept']['name'].strip()}</td></tr>
|
|
||||||
<tr><th>Variable</th><td>{variable_name}</td></tr>
|
|
||||||
<tr><th>Einheit</th><td>{unit_name}</td></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Results
|
|
||||||
if ultimo_data['success']:
|
|
||||||
created_count = len(ultimo_data['created'])
|
|
||||||
html_content += f"""
|
|
||||||
<div class="success">
|
|
||||||
✅ <strong>Erfolgreich!</strong> {created_count} Ultimo-Zählerstände wurden erfolgreich eingetragen.
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Show created observations
|
|
||||||
if ultimo_data['created']:
|
|
||||||
html_content += """
|
|
||||||
<div class="result-section">
|
|
||||||
<h3>📝 Neu erstellte Zählerstände</h3>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Zeitpunkt</th>
|
|
||||||
<th>Zählerstand</th>
|
|
||||||
<th>Wert</th>
|
|
||||||
<th>Variable</th>
|
|
||||||
<th>Einheit</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
"""
|
|
||||||
|
|
||||||
for obs in ultimo_data['created']:
|
|
||||||
moment_str = datetime.fromisoformat(obs['moment'].replace('Z', '+00:00')).strftime('%d.%m.%Y %H:%M:%S')
|
|
||||||
var_name = obs['observationVariableUnit']['observationVariable']['name'].strip()
|
|
||||||
unit_name = obs['observationVariableUnit']['unit']['name'].strip()
|
|
||||||
|
|
||||||
html_content += f"""
|
|
||||||
<tr>
|
|
||||||
<td class="timestamp">{moment_str}</td>
|
|
||||||
<td class="meter-value">{obs['meterValue']:,.2f}</td>
|
|
||||||
<td>{obs['value']:,.2f}</td>
|
|
||||||
<td>{var_name}</td>
|
|
||||||
<td>{unit_name}</td>
|
|
||||||
</tr>
|
|
||||||
"""
|
|
||||||
|
|
||||||
html_content += """</tbody></table></div>"""
|
|
||||||
|
|
||||||
# Show errors if any
|
|
||||||
if ultimo_data['errors']:
|
|
||||||
html_content += """
|
|
||||||
<div class="error">
|
|
||||||
❌ <strong>Fehler aufgetreten:</strong>
|
|
||||||
</div>
|
|
||||||
<div class="result-section">
|
|
||||||
<h3>🚨 Fehlermeldungen</h3>
|
|
||||||
"""
|
|
||||||
|
|
||||||
for error in ultimo_data['errors']:
|
|
||||||
html_content += f"""
|
|
||||||
<div class="error" style="margin: 10px 0;">
|
|
||||||
<strong>{error['code']}:</strong> {error['message']}
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error.get('details'):
|
|
||||||
html_content += f"""
|
|
||||||
<div class="details">{error['details']}</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
html_content += "</div>"
|
|
||||||
|
|
||||||
html_content += "</div>"
|
|
||||||
|
|
||||||
# Show current reading and history
|
|
||||||
if recent_observations:
|
|
||||||
current = recent_observations[0] if recent_observations else None
|
|
||||||
history = recent_observations[1:11] if len(recent_observations) > 1 else []
|
|
||||||
|
|
||||||
html_content += """
|
|
||||||
<div class="result-section">
|
|
||||||
<h3>📈 Aktueller Zählerstand und Verlauf</h3>
|
|
||||||
"""
|
|
||||||
|
|
||||||
if current:
|
|
||||||
current_moment = datetime.fromisoformat(current['moment'].replace('Z', '+00:00')).strftime('%d.%m.%Y %H:%M:%S')
|
|
||||||
html_content += f"""
|
|
||||||
<div class="success" style="margin-bottom: 15px;">
|
|
||||||
<strong>Aktueller Stand:</strong> {current['meterValue']:,.2f} {current['observationVariableUnit']['unit']['name'].strip()}
|
|
||||||
(vom {current_moment})
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
if history:
|
|
||||||
html_content += """
|
|
||||||
<h4>📊 Letzte 10 Messungen (historisch)</h4>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Rang</th>
|
|
||||||
<th>Zeitpunkt</th>
|
|
||||||
<th>Zählerstand</th>
|
|
||||||
<th>Wert</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
"""
|
|
||||||
|
|
||||||
for i, obs in enumerate(history, 1):
|
|
||||||
moment_str = datetime.fromisoformat(obs['moment'].replace('Z', '+00:00')).strftime('%d.%m.%Y %H:%M:%S')
|
|
||||||
is_recent = i <= len(ultimo_data.get('created', []))
|
|
||||||
status_badge = '<span class="badge badge-success">Neu</span>' if is_recent else '<span class="badge">Historisch</span>'
|
|
||||||
|
|
||||||
html_content += f"""
|
|
||||||
<tr>
|
|
||||||
<td>{i}</td>
|
|
||||||
<td class="timestamp">{moment_str}</td>
|
|
||||||
<td class="meter-value">{obs['meterValue']:,.2f}</td>
|
|
||||||
<td>{obs['value']:,.2f}</td>
|
|
||||||
<td>{status_badge}</td>
|
|
||||||
</tr>
|
|
||||||
"""
|
|
||||||
|
|
||||||
html_content += """</tbody></table>"""
|
|
||||||
|
|
||||||
html_content += "</div>"
|
|
||||||
|
|
||||||
elif current_reading:
|
|
||||||
# Show only the previous current reading
|
|
||||||
current_moment = datetime.fromisoformat(current_reading['moment'].replace('Z', '+00:00')).strftime('%d.%m.%Y %H:%M:%S')
|
|
||||||
html_content += f"""
|
|
||||||
<div class="result-section">
|
|
||||||
<h3>📈 Vorheriger Zählerstand</h3>
|
|
||||||
<div class="warning">
|
|
||||||
<strong>Vorheriger Stand:</strong> {current_reading['meterValue']:,.2f} {current_reading['observationVariableUnit']['unit']['name'].strip()}
|
|
||||||
(vom {current_moment})
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
total_readings = len(readings)
|
|
||||||
successful_readings = len(ultimo_data.get('created', []))
|
|
||||||
failed_readings = total_readings - successful_readings
|
|
||||||
|
|
||||||
html_content += f"""
|
|
||||||
<div class="result-section">
|
|
||||||
<h3>📋 Zusammenfassung</h3>
|
|
||||||
<table>
|
|
||||||
<tr><th>Eingegebene Zählerstände</th><td>{total_readings}</td></tr>
|
|
||||||
<tr><th>Erfolgreich erstellt</th><td style="color: var(--color-success, #4caf50);">{successful_readings}</td></tr>
|
|
||||||
<tr><th>Fehlgeschlagen</th><td style="color: var(--color-danger, #f44336);">{failed_readings}</td></tr>
|
|
||||||
<tr><th>Verarbeitungszeit</th><td>{datetime.now().strftime('%d.%m.%Y %H:%M:%S')}</td></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"type": "html",
|
|
||||||
"content": html_content
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Error handling
|
|
||||||
error_html = f"""
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Fehler bei Ultimo-Zählerstände</title>
|
|
||||||
<style>
|
|
||||||
body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }}
|
|
||||||
.container {{ max-width: 800px; margin: 0 auto; }}
|
|
||||||
.error {{ background-color: var(--color-danger, #f44336); color: white; padding: 20px; border-radius: 8px; }}
|
|
||||||
.details {{ background-color: white; padding: 20px; border-radius: 8px; margin-top: 20px; }}
|
|
||||||
.code {{ background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-family: monospace; }}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="error">
|
|
||||||
<h1>❌ Fehler bei der Verarbeitung</h1>
|
|
||||||
<p><strong>Es ist ein Fehler aufgetreten:</strong> {str(e)}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="details">
|
|
||||||
<h3>🔧 Lösungsvorschläge:</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Überprüfen Sie, ob der Sensor existiert und Messwerte hat</li>
|
|
||||||
<li>Stellen Sie sicher, dass das Format der Zählerstände korrekt ist (YYYY-MM: 12345.67)</li>
|
|
||||||
<li>Überprüfen Sie, ob die Variable und Einheit für den Sensor verfügbar sind</li>
|
|
||||||
<li>Vermeiden Sie doppelte Einträge für denselben Monat</li>
|
|
||||||
<li>Stellen Sie sicher, dass Sie die erforderlichen Berechtigungen haben</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>📋 Parameter:</h3>
|
|
||||||
<div class="code">
|
|
||||||
Sensor ID: {PARAMS.get('sensor_id', 'nicht gesetzt')}<br>
|
|
||||||
Variable: {PARAMS.get('variable_name', 'nicht gesetzt')}<br>
|
|
||||||
Einheit: {PARAMS.get('unit_name', 'nicht gesetzt')}<br>
|
|
||||||
Zählerstände: {len(PARAMS.get('readings_text', '').split('\n')) if PARAMS.get('readings_text') else 0} Zeilen
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"type": "html",
|
|
||||||
"content": error_html
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue