Add artifact_2/src/artifact_2/core.py
This commit is contained in:
parent
58c0037401
commit
f1bef8136a
1 changed files with 85 additions and 0 deletions
85
artifact_2/src/artifact_2/core.py
Normal file
85
artifact_2/src/artifact_2/core.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LogEntry:
|
||||||
|
"""Repräsentiert einen einzelnen Log-Eintrag mit Zeitinformationen."""
|
||||||
|
|
||||||
|
epoch_ms: int
|
||||||
|
monotonic_ns: int
|
||||||
|
tz_offset_minutes: int
|
||||||
|
run_id: str
|
||||||
|
step_id: str
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
# Eingabevalidierung (CI-Ready & Input Validation Required)
|
||||||
|
assert isinstance(self.epoch_ms, int) and self.epoch_ms >= 0, 'epoch_ms muss eine positive Ganzzahl sein.'
|
||||||
|
assert isinstance(self.monotonic_ns, int) and self.monotonic_ns >= 0, 'monotonic_ns muss eine positive Ganzzahl sein.'
|
||||||
|
assert isinstance(self.tz_offset_minutes, int), 'tz_offset_minutes muss eine Ganzzahl sein.'
|
||||||
|
assert isinstance(self.run_id, str) and self.run_id.strip(), 'run_id muss ein nicht-leerer String sein.'
|
||||||
|
assert isinstance(self.step_id, str) and self.step_id.strip(), 'step_id muss ein nicht-leerer String sein.'
|
||||||
|
|
||||||
|
def to_datetime(self) -> datetime:
|
||||||
|
tz = timezone(timedelta(minutes=self.tz_offset_minutes))
|
||||||
|
return datetime.fromtimestamp(self.epoch_ms / 1000.0, tz=tz)
|
||||||
|
|
||||||
|
|
||||||
|
def check_timestamp_consistency(log_entries: List[LogEntry]) -> bool:
|
||||||
|
"""Prüft die Konsistenz von Zeitstempeln.
|
||||||
|
|
||||||
|
Stellt sicher:
|
||||||
|
- epochale Zeit und monotone Zeit steigen monoton.
|
||||||
|
- höchstens ein Zeitzonenwechsel tritt auf.
|
||||||
|
- keine negativen Deltas.
|
||||||
|
|
||||||
|
Rückgabe:
|
||||||
|
True, wenn konsistent; sonst False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(log_entries, list) or not all(isinstance(e, LogEntry) for e in log_entries):
|
||||||
|
raise TypeError('log_entries muss eine Liste von LogEntry-Instanzen sein.')
|
||||||
|
|
||||||
|
if not log_entries:
|
||||||
|
logger.info('Leere Eingabeliste – gilt als konsistent.')
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Nach Zeit sortieren zur Sicherheit
|
||||||
|
log_entries_sorted = sorted(log_entries, key=lambda e: e.epoch_ms)
|
||||||
|
tz_switches = set()
|
||||||
|
|
||||||
|
prev_entry = log_entries_sorted[0]
|
||||||
|
previous_dt = prev_entry.to_datetime()
|
||||||
|
previous_mono = prev_entry.monotonic_ns
|
||||||
|
tz_switches.add(prev_entry.tz_offset_minutes)
|
||||||
|
|
||||||
|
for entry in log_entries_sorted[1:]:
|
||||||
|
current_dt = entry.to_datetime()
|
||||||
|
current_mono = entry.monotonic_ns
|
||||||
|
|
||||||
|
# Zeitzonenwechsel speichern
|
||||||
|
tz_switches.add(entry.tz_offset_minutes)
|
||||||
|
|
||||||
|
# Zeitdifferenzen prüfen
|
||||||
|
delta_epoch = (current_dt - previous_dt).total_seconds()
|
||||||
|
delta_mono = current_mono - previous_mono
|
||||||
|
|
||||||
|
if delta_epoch < 0 or delta_mono < 0:
|
||||||
|
logger.warning('Negative Zeitdifferenz erkannt zwischen %s und %s', previous_dt, current_dt)
|
||||||
|
return False
|
||||||
|
|
||||||
|
previous_dt = current_dt
|
||||||
|
previous_mono = current_mono
|
||||||
|
|
||||||
|
# Gültig: höchstens ein Zeitzonenwechsel
|
||||||
|
if len(tz_switches) > 2:
|
||||||
|
logger.warning('Mehrere Zeitzonenwechsel erkannt: %s', tz_switches)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
Loading…
Reference in a new issue