Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,17 @@
"""
Domain Value Objects - Unveränderliche Wertobjekte ohne Identität
"""
from .action_timing import ActionTiming, ActionType
from .error_summary import ErrorSummary
from .report import Report, ReportType
from .login_credentials import LoginCredentials
__all__ = [
'ActionTiming',
'ActionType',
'ErrorSummary',
'Report',
'ReportType',
'LoginCredentials'
]

Datei anzeigen

@ -0,0 +1,120 @@
"""
Typsichere Parameter für Account-Erstellung
"""
from dataclasses import dataclass
from typing import Optional, Dict, Any, List
from domain.entities.browser_fingerprint import BrowserFingerprint
@dataclass
class ValidationResult:
"""Ergebnis einer Validierung"""
is_valid: bool
errors: List[str]
def get_error_message(self) -> str:
"""Gibt eine formatierte Fehlermeldung zurück"""
if self.is_valid:
return ""
return "\n".join(self.errors)
@dataclass
class AccountCreationParams:
"""Typsichere Parameter für Account-Erstellung"""
full_name: str
age: int
registration_method: str = "email"
show_browser: bool = False
proxy_type: Optional[str] = None
fingerprint: Optional[BrowserFingerprint] = None
email_domain: str = "z5m7q9dk3ah2v1plx6ju.com"
username: Optional[str] = None
password: Optional[str] = None
phone_number: Optional[str] = None
imap_handler: Optional[Any] = None
phone_service: Optional[Any] = None
additional_params: Dict[str, Any] = None
# Platform-spezifische Konstanten
MIN_AGE: int = 13
MAX_AGE: int = 99
def __post_init__(self):
if self.additional_params is None:
self.additional_params = {}
def validate(self) -> ValidationResult:
"""Validiert alle Parameter"""
errors = []
# Name validieren
if not self.full_name or len(self.full_name.strip()) < 2:
errors.append("Der Name muss mindestens 2 Zeichen lang sein")
# Alter validieren
if self.age < self.MIN_AGE:
errors.append(f"Das Alter muss mindestens {self.MIN_AGE} sein")
elif self.age > self.MAX_AGE:
errors.append(f"Das Alter darf maximal {self.MAX_AGE} sein")
# Registrierungsmethode validieren
if self.registration_method not in ["email", "phone"]:
errors.append("Ungültige Registrierungsmethode")
# Telefonnummer bei Phone-Registrierung
if self.registration_method == "phone" and not self.phone_number:
errors.append("Telefonnummer erforderlich für Phone-Registrierung")
# E-Mail-Domain validieren
if self.registration_method == "email" and not self.email_domain:
errors.append("E-Mail-Domain erforderlich für Email-Registrierung")
return ValidationResult(is_valid=len(errors)==0, errors=errors)
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Kompatibilität"""
result = {
"full_name": self.full_name,
"age": self.age,
"registration_method": self.registration_method,
"show_browser": self.show_browser,
"proxy_type": self.proxy_type,
"fingerprint": self.fingerprint,
"email_domain": self.email_domain,
"username": self.username,
"password": self.password,
"phone_number": self.phone_number,
"imap_handler": self.imap_handler,
"phone_service": self.phone_service
}
# Additional params hinzufügen
result.update(self.additional_params)
return result
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'AccountCreationParams':
"""Erstellt aus Dictionary"""
# Bekannte Parameter extrahieren
known_params = {
"full_name": data.get("full_name", ""),
"age": data.get("age", 18),
"registration_method": data.get("registration_method", "email"),
"show_browser": data.get("show_browser", False),
"proxy_type": data.get("proxy_type"),
"fingerprint": data.get("fingerprint"),
"email_domain": data.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com"),
"username": data.get("username"),
"password": data.get("password"),
"phone_number": data.get("phone_number"),
"imap_handler": data.get("imap_handler"),
"phone_service": data.get("phone_service")
}
# Alle anderen Parameter als additional_params
additional = {k: v for k, v in data.items() if k not in known_params}
known_params["additional_params"] = additional
return cls(**known_params)

Datei anzeigen

@ -0,0 +1,102 @@
"""
Action Timing Value Object - Repräsentiert Timing-Informationen einer Aktion
"""
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Optional, Dict, Any
class ActionType(Enum):
"""Typen von Aktionen die getimed werden"""
# Navigation
PAGE_LOAD = "page_load"
PAGE_NAVIGATION = "page_navigation"
# Form-Interaktionen
FORM_FILL = "form_fill"
BUTTON_CLICK = "button_click"
INPUT_TYPE = "input_type"
DROPDOWN_SELECT = "dropdown_select"
CHECKBOX_TOGGLE = "checkbox_toggle"
# Verifizierung
EMAIL_CHECK = "email_check"
SMS_CHECK = "sms_check"
CAPTCHA_SOLVE = "captcha_solve"
# Account-Aktionen
REGISTRATION_START = "registration_start"
REGISTRATION_COMPLETE = "registration_complete"
LOGIN_ATTEMPT = "login_attempt"
LOGOUT = "logout"
# Daten-Operationen
SCREENSHOT = "screenshot"
DATA_SAVE = "data_save"
SESSION_SAVE = "session_save"
# Netzwerk
API_REQUEST = "api_request"
FILE_UPLOAD = "file_upload"
FILE_DOWNLOAD = "file_download"
@dataclass(frozen=True)
class ActionTiming:
"""
Repräsentiert Timing-Informationen einer Aktion.
Frozen dataclass macht es unveränderlich (Value Object).
"""
action_type: ActionType
timestamp: datetime
duration: float # in Sekunden
success: bool
# Optionale Metadaten
url: Optional[str] = None
element_selector: Optional[str] = None
error_message: Optional[str] = None
retry_count: int = 0
metadata: Optional[Dict[str, Any]] = None
def __post_init__(self):
"""Validierung der Timing-Daten"""
if self.duration < 0:
raise ValueError("Duration kann nicht negativ sein")
if self.retry_count < 0:
raise ValueError("Retry count kann nicht negativ sein")
@property
def duration_ms(self) -> float:
"""Gibt die Dauer in Millisekunden zurück"""
return self.duration * 1000
@property
def is_slow(self) -> bool:
"""Prüft ob die Aktion langsam war (> 3 Sekunden)"""
return self.duration > 3.0
@property
def is_very_slow(self) -> bool:
"""Prüft ob die Aktion sehr langsam war (> 10 Sekunden)"""
return self.duration > 10.0
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'action_type': self.action_type.value,
'timestamp': self.timestamp.isoformat(),
'duration': self.duration,
'duration_ms': self.duration_ms,
'success': self.success,
'url': self.url,
'element_selector': self.element_selector,
'error_message': self.error_message,
'retry_count': self.retry_count,
'metadata': self.metadata or {},
'is_slow': self.is_slow,
'is_very_slow': self.is_very_slow
}

Datei anzeigen

@ -0,0 +1,29 @@
"""Browser protection style value object."""
from enum import Enum
from dataclasses import dataclass
class ProtectionLevel(Enum):
"""Defines the level of browser protection during automation."""
NONE = "none" # No protection
LIGHT = "light" # Visual indicator only
MEDIUM = "medium" # Transparent overlay with interaction blocking
STRONG = "strong" # Full blocking with opaque overlay
@dataclass
class BrowserProtectionStyle:
"""Configuration for browser protection during automation."""
level: ProtectionLevel = ProtectionLevel.MEDIUM
show_border: bool = True # Show animated border
show_badge: bool = True # Show info badge
blur_effect: bool = False # Apply blur to page content
opacity: float = 0.1 # Overlay opacity (0.0 - 1.0)
badge_text: str = "🔒 Automatisierung läuft - Nicht eingreifen"
badge_position: str = "top-right" # top-left, top-right, bottom-left, bottom-right
border_color: str = "rgba(255, 0, 0, 0.5)"
overlay_color: str = "rgba(0, 0, 0, {opacity})" # {opacity} will be replaced
def get_overlay_color(self) -> str:
"""Get the overlay color with the configured opacity."""
return self.overlay_color.format(opacity=self.opacity)

Datei anzeigen

@ -0,0 +1,98 @@
"""
Error Summary Value Object - Zusammenfassung von Fehlerinformationen
"""
from dataclasses import dataclass
from datetime import datetime
from typing import List, Dict, Any
@dataclass(frozen=True)
class ErrorSummary:
"""
Zusammenfassung von Fehlerinformationen für Berichte und Analysen.
Frozen dataclass macht es unveränderlich (Value Object).
"""
error_type: str
error_count: int
first_occurrence: datetime
last_occurrence: datetime
affected_sessions: List[str]
affected_accounts: List[str]
# Statistiken
avg_recovery_time: float # in Sekunden
recovery_success_rate: float # 0.0 - 1.0
# Häufigste Kontexte
most_common_urls: List[str]
most_common_actions: List[str]
most_common_steps: List[str]
# Impact
total_user_impact: int
total_system_impact: int
data_loss_incidents: int
def __post_init__(self):
"""Validierung der Summary-Daten"""
if self.error_count < 0:
raise ValueError("Error count kann nicht negativ sein")
if not 0.0 <= self.recovery_success_rate <= 1.0:
raise ValueError("Recovery success rate muss zwischen 0.0 und 1.0 liegen")
if self.first_occurrence > self.last_occurrence:
raise ValueError("First occurrence kann nicht nach last occurrence liegen")
@property
def duration(self) -> float:
"""Zeitspanne zwischen erstem und letztem Auftreten in Stunden"""
delta = self.last_occurrence - self.first_occurrence
return delta.total_seconds() / 3600
@property
def frequency(self) -> float:
"""Fehler pro Stunde"""
if self.duration > 0:
return self.error_count / self.duration
return self.error_count
@property
def severity_score(self) -> float:
"""
Berechnet einen Schweregrad-Score basierend auf:
- Häufigkeit
- Impact
- Wiederherstellungsrate
"""
frequency_factor = min(self.frequency / 10, 1.0) # Normalisiert auf 0-1
impact_factor = min((self.total_user_impact + self.total_system_impact) / 100, 1.0)
recovery_factor = 1.0 - self.recovery_success_rate
data_loss_factor = min(self.data_loss_incidents / 10, 1.0)
return (frequency_factor * 0.3 +
impact_factor * 0.3 +
recovery_factor * 0.2 +
data_loss_factor * 0.2)
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'error_type': self.error_type,
'error_count': self.error_count,
'first_occurrence': self.first_occurrence.isoformat(),
'last_occurrence': self.last_occurrence.isoformat(),
'duration_hours': self.duration,
'frequency_per_hour': self.frequency,
'affected_sessions': self.affected_sessions,
'affected_accounts': self.affected_accounts,
'avg_recovery_time': self.avg_recovery_time,
'recovery_success_rate': self.recovery_success_rate,
'most_common_urls': self.most_common_urls[:5],
'most_common_actions': self.most_common_actions[:5],
'most_common_steps': self.most_common_steps[:5],
'total_user_impact': self.total_user_impact,
'total_system_impact': self.total_system_impact,
'data_loss_incidents': self.data_loss_incidents,
'severity_score': self.severity_score
}

Datei anzeigen

@ -0,0 +1,44 @@
"""
Login Credentials Value Object - Repräsentiert Login-Daten mit Session-Status
"""
from dataclasses import dataclass
from typing import Optional
from datetime import datetime
@dataclass(frozen=True)
class LoginCredentials:
"""Unveränderliche Login-Daten für einen Account"""
username: str
password: str
platform: str
session_status: str # ACTIVE, EXPIRED, LOCKED, REQUIRES_2FA, UNKNOWN
last_successful_login: Optional[datetime] = None
session_id: Optional[str] = None
fingerprint_id: Optional[str] = None
def is_session_active(self) -> bool:
"""Prüft ob die Session aktiv ist"""
return self.session_status == "ACTIVE"
def requires_manual_login(self) -> bool:
"""Prüft ob manueller Login erforderlich ist"""
return self.session_status in ["EXPIRED", "LOCKED", "REQUIRES_2FA", "UNKNOWN"]
def has_session_data(self) -> bool:
"""Prüft ob Session-Daten vorhanden sind"""
return self.session_id is not None and self.fingerprint_id is not None
def to_dict(self) -> dict:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'username': self.username,
'password': self.password,
'platform': self.platform,
'session_status': self.session_status,
'last_successful_login': self.last_successful_login.isoformat() if self.last_successful_login else None,
'session_id': self.session_id,
'fingerprint_id': self.fingerprint_id
}

Datei anzeigen

@ -0,0 +1,229 @@
"""
Operation Result Value Object - Standardisierte Ergebnisstruktur
Backward-compatible Wrapper für konsistente Fehlerbehandlung
"""
from dataclasses import dataclass
from typing import Optional, Any, Dict, Union
from datetime import datetime
import traceback
@dataclass
class OperationResult:
"""
Standardisierte Ergebnisstruktur für alle Operationen.
Kompatibel mit bestehenden boolean und dict returns.
"""
success: bool
data: Optional[Any] = None
error_message: Optional[str] = None
error_code: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
timestamp: Optional[datetime] = None
legacy_result: Optional[Any] = None # Für backward compatibility
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now()
@classmethod
def success_result(cls, data: Any = None, metadata: Dict[str, Any] = None, legacy_result: Any = None):
"""Erstellt ein Erfolgsergebnis"""
return cls(
success=True,
data=data,
metadata=metadata or {},
legacy_result=legacy_result if legacy_result is not None else data
)
@classmethod
def error_result(cls, message: str, code: str = None,
metadata: Dict[str, Any] = None, legacy_result: Any = None):
"""Erstellt ein Fehlerergebnis"""
return cls(
success=False,
error_message=message,
error_code=code,
metadata=metadata or {},
legacy_result=legacy_result if legacy_result is not None else False
)
@classmethod
def from_exception(cls, exception: Exception, code: str = None,
metadata: Dict[str, Any] = None):
"""Erstellt Fehlerergebnis aus Exception"""
metadata = metadata or {}
metadata.update({
'exception_type': type(exception).__name__,
'traceback': traceback.format_exc()
})
return cls(
success=False,
error_message=str(exception),
error_code=code or type(exception).__name__,
metadata=metadata,
legacy_result=False
)
@classmethod
def from_legacy_boolean(cls, result: bool, success_data: Any = None, error_message: str = None):
"""Konvertiert legacy boolean zu OperationResult"""
if result:
return cls.success_result(data=success_data, legacy_result=result)
else:
return cls.error_result(
message=error_message or "Operation failed",
legacy_result=result
)
@classmethod
def from_legacy_dict(cls, result: Dict[str, Any]):
"""Konvertiert legacy dict zu OperationResult"""
success = result.get('success', False)
if success:
return cls.success_result(
data=result.get('data'),
metadata=result.get('metadata', {}),
legacy_result=result
)
else:
return cls.error_result(
message=result.get('error', 'Operation failed'),
code=result.get('error_code'),
metadata=result.get('metadata', {}),
legacy_result=result
)
def is_success(self) -> bool:
"""Prüft ob Operation erfolgreich war"""
return self.success
def is_error(self) -> bool:
"""Prüft ob Operation fehlgeschlagen ist"""
return not self.success
def get_legacy_result(self) -> Any:
"""Gibt das ursprüngliche Result-Format zurück für backward compatibility"""
return self.legacy_result
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für API/JSON Serialisierung"""
result = {
'success': self.success,
'timestamp': self.timestamp.isoformat() if self.timestamp else None
}
if self.data is not None:
result['data'] = self.data
if self.error_message:
result['error'] = self.error_message
if self.error_code:
result['error_code'] = self.error_code
if self.metadata:
result['metadata'] = self.metadata
return result
def to_legacy_dict(self) -> Dict[str, Any]:
"""Konvertiert zu legacy dict format"""
return {
'success': self.success,
'data': self.data,
'error': self.error_message,
'error_code': self.error_code,
'metadata': self.metadata
}
def __bool__(self) -> bool:
"""Ermöglicht if result: syntax"""
return self.success
def __str__(self) -> str:
if self.success:
return f"Success: {self.data}"
else:
return f"Error: {self.error_message} ({self.error_code})"
class ResultWrapper:
"""
Utility-Klasse für backward-compatible Wrapping von bestehenden Methoden
"""
@staticmethod
def wrap_boolean_method(method, *args, **kwargs) -> OperationResult:
"""Wrapper für bestehende boolean-Methoden"""
try:
success = method(*args, **kwargs)
return OperationResult.from_legacy_boolean(
result=success,
success_data=success,
error_message="Operation failed" if not success else None
)
except Exception as e:
return OperationResult.from_exception(e)
@staticmethod
def wrap_dict_method(method, *args, **kwargs) -> OperationResult:
"""Wrapper für bestehende dict-Methoden"""
try:
result = method(*args, **kwargs)
if isinstance(result, dict):
return OperationResult.from_legacy_dict(result)
else:
return OperationResult.success_result(data=result, legacy_result=result)
except Exception as e:
return OperationResult.from_exception(e)
@staticmethod
def wrap_any_method(method, *args, **kwargs) -> OperationResult:
"""Universal wrapper für beliebige Methoden"""
try:
result = method(*args, **kwargs)
if isinstance(result, bool):
return OperationResult.from_legacy_boolean(result)
elif isinstance(result, dict) and 'success' in result:
return OperationResult.from_legacy_dict(result)
elif result is None:
return OperationResult.error_result("Method returned None", legacy_result=result)
else:
return OperationResult.success_result(data=result, legacy_result=result)
except Exception as e:
return OperationResult.from_exception(e)
# Error Codes für häufige Fehlertypen
class CommonErrorCodes:
"""Häufig verwendete Fehlercodes"""
# Instagram spezifisch
CAPTCHA_REQUIRED = "CAPTCHA_REQUIRED"
EMAIL_TIMEOUT = "EMAIL_TIMEOUT"
SMS_NOT_IMPLEMENTED = "SMS_NOT_IMPLEMENTED"
USERNAME_TAKEN = "USERNAME_TAKEN"
SELECTOR_NOT_FOUND = "SELECTOR_NOT_FOUND"
BIRTHDAY_SELECTOR_FAILED = "BIRTHDAY_SELECTOR_FAILED"
# Allgemein
PROXY_ERROR = "PROXY_ERROR"
RATE_LIMITED = "RATE_LIMITED"
NETWORK_TIMEOUT = "NETWORK_TIMEOUT"
BROWSER_ERROR = "BROWSER_ERROR"
# Fingerprint spezifisch
FINGERPRINT_GENERATION_FAILED = "FINGERPRINT_GENERATION_FAILED"
FINGERPRINT_RACE_CONDITION = "FINGERPRINT_RACE_CONDITION"
FINGERPRINT_NOT_FOUND = "FINGERPRINT_NOT_FOUND"
# Session spezifisch
SESSION_EXPIRED = "SESSION_EXPIRED"
SESSION_INVALID = "SESSION_INVALID"
SESSION_SAVE_FAILED = "SESSION_SAVE_FAILED"

204
domain/value_objects/report.py Normale Datei
Datei anzeigen

@ -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
}