Fix: Komplett auf Europe/Berlin + DB-Migration + Timer-Fix
- ALLE Timestamps einheitlich Europe/Berlin (kein UTC mehr) - DB-Migration: 1704 bestehende Timestamps von UTC nach Berlin konvertiert - Auto-Refresh Timer Fix: ORDER BY id DESC statt completed_at DESC (verhindert falsche Sortierung bei gemischten Timestamp-Formaten) - started_at statt completed_at fuer Timer-Vergleich (konsistenter) - Manuelle Refreshes werden bei Intervall-Pruefung beruecksichtigt - Debug-Logging fuer Auto-Refresh Entscheidungen - astimezone() fuer Timestamps mit Offset-Info Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
38
src/main.py
38
src/main.py
@@ -5,7 +5,7 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, Request, Response
|
||||
@@ -93,15 +93,15 @@ async def check_auto_refresh():
|
||||
)
|
||||
incidents = await cursor.fetchall()
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(TIMEZONE)
|
||||
|
||||
for incident in incidents:
|
||||
incident_id = incident["id"]
|
||||
interval = incident["refresh_interval"]
|
||||
|
||||
# Nur letzten AUTO-Refresh prüfen (manuelle Refreshs ignorieren)
|
||||
# Letzten abgeschlossenen Refresh prüfen (egal ob auto oder manual)
|
||||
cursor = await db.execute(
|
||||
"SELECT completed_at FROM refresh_log WHERE incident_id = ? AND trigger_type = 'auto' AND status = 'completed' ORDER BY completed_at DESC LIMIT 1",
|
||||
"SELECT started_at FROM refresh_log WHERE incident_id = ? AND status = 'completed' ORDER BY id DESC LIMIT 1",
|
||||
(incident_id,),
|
||||
)
|
||||
last_refresh = await cursor.fetchone()
|
||||
@@ -110,21 +110,27 @@ async def check_auto_refresh():
|
||||
if not last_refresh:
|
||||
should_refresh = True
|
||||
else:
|
||||
last_time = datetime.fromisoformat(last_refresh["completed_at"])
|
||||
last_time = datetime.fromisoformat(last_refresh["started_at"])
|
||||
if last_time.tzinfo is None:
|
||||
last_time = last_time.replace(tzinfo=timezone.utc)
|
||||
last_time = last_time.replace(tzinfo=TIMEZONE)
|
||||
else:
|
||||
last_time = last_time.astimezone(TIMEZONE)
|
||||
elapsed = (now - last_time).total_seconds() / 60
|
||||
if elapsed >= interval:
|
||||
should_refresh = True
|
||||
logger.info(f"Auto-Refresh Lage {incident_id}: {elapsed:.1f} Min seit letztem Refresh (Intervall: {interval} Min)")
|
||||
else:
|
||||
logger.debug(f"Auto-Refresh Lage {incident_id}: {elapsed:.1f}/{interval} Min — noch nicht faellig")
|
||||
|
||||
if should_refresh:
|
||||
# Prüfen ob bereits ein laufender Refresh existiert
|
||||
cursor = await db.execute(
|
||||
running_cursor = await db.execute(
|
||||
"SELECT id FROM refresh_log WHERE incident_id = ? AND status = 'running' LIMIT 1",
|
||||
(incident_id,),
|
||||
)
|
||||
if await cursor.fetchone():
|
||||
continue # Laufender Refresh — überspringen
|
||||
if await running_cursor.fetchone():
|
||||
logger.debug(f"Auto-Refresh Lage {incident_id}: uebersprungen (laeuft bereits)")
|
||||
continue
|
||||
|
||||
await orchestrator.enqueue_refresh(incident_id, trigger_type="auto")
|
||||
|
||||
@@ -143,11 +149,13 @@ async def cleanup_expired():
|
||||
)
|
||||
incidents = await cursor.fetchall()
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(TIMEZONE)
|
||||
for incident in incidents:
|
||||
created = datetime.fromisoformat(incident["created_at"])
|
||||
if created.tzinfo is None:
|
||||
created = created.replace(tzinfo=timezone.utc)
|
||||
created = created.replace(tzinfo=TIMEZONE)
|
||||
else:
|
||||
created = created.astimezone(TIMEZONE)
|
||||
age_days = (now - created).days
|
||||
if age_days >= incident["retention_days"]:
|
||||
await db.execute(
|
||||
@@ -164,12 +172,14 @@ async def cleanup_expired():
|
||||
for orphan in orphans:
|
||||
started = datetime.fromisoformat(orphan["started_at"])
|
||||
if started.tzinfo is None:
|
||||
started = started.replace(tzinfo=timezone.utc)
|
||||
started = started.replace(tzinfo=TIMEZONE)
|
||||
else:
|
||||
started = started.astimezone(TIMEZONE)
|
||||
age_minutes = (now - started).total_seconds() / 60
|
||||
if age_minutes >= 15:
|
||||
await db.execute(
|
||||
"UPDATE refresh_log SET status = 'error', completed_at = ?, error_message = ? WHERE id = ?",
|
||||
(now.isoformat(), f"Verwaist (>{int(age_minutes)} Min ohne Abschluss, automatisch bereinigt)", orphan["id"]),
|
||||
(now.strftime('%Y-%m-%d %H:%M:%S'), f"Verwaist (>{int(age_minutes)} Min ohne Abschluss, automatisch bereinigt)", orphan["id"]),
|
||||
)
|
||||
logger.warning(f"Verwaisten Refresh #{orphan['id']} für Lage {orphan['incident_id']} bereinigt ({int(age_minutes)} Min)")
|
||||
|
||||
@@ -195,7 +205,7 @@ async def lifespan(app: FastAPI):
|
||||
try:
|
||||
result = await db.execute(
|
||||
"UPDATE refresh_log SET status = 'error', completed_at = ?, error_message = 'Verwaist (Neustart, automatisch bereinigt)' WHERE status = 'running'",
|
||||
(datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S'),),
|
||||
(datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S'),),
|
||||
)
|
||||
if result.rowcount > 0:
|
||||
await db.commit()
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren