Initial commit
Dieser Commit ist enthalten in:
204
domain/value_objects/report.py
Normale Datei
204
domain/value_objects/report.py
Normale Datei
@ -0,0 +1,204 @@
|
||||
"""
|
||||
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
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren