Files
AccountForger-neuerUpload/domain/value_objects/report.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

204 Zeilen
6.8 KiB
Python

"""
Report Value Object - Strukturierte Berichte für Analytics
"""
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
from enum import Enum
class ReportType(Enum):
"""Typen von verfügbaren Berichten"""
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
CUSTOM = "custom"
REAL_TIME = "real_time"
class MetricType(Enum):
"""Typen von Metriken in Berichten"""
SUCCESS_RATE = "success_rate"
ERROR_RATE = "error_rate"
AVG_DURATION = "avg_duration"
TOTAL_ACCOUNTS = "total_accounts"
ACCOUNTS_PER_HOUR = "accounts_per_hour"
RETRY_RATE = "retry_rate"
RECOVERY_RATE = "recovery_rate"
@dataclass(frozen=True)
class Metric:
"""Einzelne Metrik im Bericht"""
name: str
value: float
unit: str
trend: float = 0.0 # Prozentuale Veränderung zum Vorperiode
@property
def is_improving(self) -> bool:
"""Prüft ob sich die Metrik verbessert"""
positive_metrics = ["success_rate", "recovery_rate", "accounts_per_hour"]
if self.name in positive_metrics:
return self.trend > 0
return self.trend < 0
@dataclass(frozen=True)
class PlatformStats:
"""Statistiken für eine spezifische Plattform"""
platform: str
total_attempts: int
successful_accounts: int
failed_attempts: int
avg_duration_seconds: float
error_distribution: Dict[str, int]
@property
def success_rate(self) -> float:
"""Berechnet die Erfolgsrate"""
if self.total_attempts > 0:
return self.successful_accounts / self.total_attempts
return 0.0
@dataclass(frozen=True)
class TimeSeriesData:
"""Zeitreihen-Daten für Graphen"""
timestamps: List[datetime]
values: List[float]
label: str
def get_average(self) -> float:
"""Berechnet den Durchschnitt der Werte"""
if self.values:
return sum(self.values) / len(self.values)
return 0.0
def get_trend(self) -> float:
"""Berechnet den Trend (vereinfacht: Vergleich erste/letzte Hälfte)"""
if len(self.values) < 2:
return 0.0
mid = len(self.values) // 2
first_half_avg = sum(self.values[:mid]) / mid
second_half_avg = sum(self.values[mid:]) / (len(self.values) - mid)
if first_half_avg > 0:
return ((second_half_avg - first_half_avg) / first_half_avg) * 100
return 0.0
@dataclass(frozen=True)
class Report:
"""
Strukturierter Bericht für Analytics.
Frozen dataclass macht es unveränderlich (Value Object).
"""
report_id: str
report_type: ReportType
start_date: datetime
end_date: datetime
generated_at: datetime
# Zusammenfassende Metriken
total_accounts_created: int
total_attempts: int
overall_success_rate: float
avg_creation_time: float # in Sekunden
# Detaillierte Metriken
metrics: List[Metric]
platform_stats: List[PlatformStats]
error_summaries: List[Dict[str, Any]] # ErrorSummary als Dict
# Zeitreihen-Daten
success_rate_timeline: Optional[TimeSeriesData] = None
creation_rate_timeline: Optional[TimeSeriesData] = None
error_rate_timeline: Optional[TimeSeriesData] = None
# Top-Erkenntnisse
insights: List[str] = field(default_factory=list)
recommendations: List[str] = field(default_factory=list)
def __post_init__(self):
"""Validierung der Report-Daten"""
if self.start_date > self.end_date:
raise ValueError("Start date kann nicht nach end date liegen")
if not 0.0 <= self.overall_success_rate <= 1.0:
raise ValueError("Success rate muss zwischen 0.0 und 1.0 liegen")
if self.avg_creation_time < 0:
raise ValueError("Average creation time kann nicht negativ sein")
@property
def duration(self) -> timedelta:
"""Dauer des Berichtszeitraums"""
return self.end_date - self.start_date
@property
def accounts_per_day(self) -> float:
"""Durchschnittliche Accounts pro Tag"""
days = self.duration.days or 1
return self.total_accounts_created / days
def get_metric(self, metric_type: MetricType) -> Optional[Metric]:
"""Holt eine spezifische Metrik"""
for metric in self.metrics:
if metric.name == metric_type.value:
return metric
return None
def get_platform_stats(self, platform: str) -> Optional[PlatformStats]:
"""Holt Statistiken für eine spezifische Plattform"""
for stats in self.platform_stats:
if stats.platform.lower() == platform.lower():
return stats
return None
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'report_id': self.report_id,
'report_type': self.report_type.value,
'start_date': self.start_date.isoformat(),
'end_date': self.end_date.isoformat(),
'generated_at': self.generated_at.isoformat(),
'duration_days': self.duration.days,
'total_accounts_created': self.total_accounts_created,
'total_attempts': self.total_attempts,
'overall_success_rate': self.overall_success_rate,
'avg_creation_time': self.avg_creation_time,
'accounts_per_day': self.accounts_per_day,
'metrics': [
{
'name': m.name,
'value': m.value,
'unit': m.unit,
'trend': m.trend,
'is_improving': m.is_improving
}
for m in self.metrics
],
'platform_stats': [
{
'platform': ps.platform,
'total_attempts': ps.total_attempts,
'successful_accounts': ps.successful_accounts,
'failed_attempts': ps.failed_attempts,
'success_rate': ps.success_rate,
'avg_duration_seconds': ps.avg_duration_seconds,
'error_distribution': ps.error_distribution
}
for ps in self.platform_stats
],
'error_summaries': self.error_summaries,
'success_rate_timeline': {
'timestamps': [t.isoformat() for t in self.success_rate_timeline.timestamps],
'values': self.success_rate_timeline.values,
'label': self.success_rate_timeline.label,
'average': self.success_rate_timeline.get_average(),
'trend': self.success_rate_timeline.get_trend()
} if self.success_rate_timeline else None,
'insights': self.insights,
'recommendations': self.recommendations
}