Add run_summary_export/src/run_summary_export/core.py
This commit is contained in:
parent
38842dd647
commit
0e457d84e3
1 changed files with 84 additions and 0 deletions
84
run_summary_export/src/run_summary_export/core.py
Normal file
84
run_summary_export/src/run_summary_export/core.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Union, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class RunSummary:
|
||||||
|
"""Repräsentiert die zentralen Kennwerte eines Runs."""
|
||||||
|
|
||||||
|
def __init__(self, run_id: str, p95: float, p99: float, retry_free_count: int, publish_reorder_count: int) -> None:
|
||||||
|
self.run_id = run_id
|
||||||
|
self.p95 = p95
|
||||||
|
self.p99 = p99
|
||||||
|
self.retry_free_count = retry_free_count
|
||||||
|
self.publish_reorder_count = publish_reorder_count
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self) -> None:
|
||||||
|
assert isinstance(self.run_id, str) and self.run_id, "run_id muss ein nicht-leerer String sein."
|
||||||
|
assert isinstance(self.p95, (float, int)), "p95 muss eine Zahl sein."
|
||||||
|
assert isinstance(self.p99, (float, int)), "p99 muss eine Zahl sein."
|
||||||
|
assert isinstance(self.retry_free_count, int) and self.retry_free_count >= 0, (
|
||||||
|
"retry_free_count muss eine nicht-negative ganze Zahl sein."
|
||||||
|
)
|
||||||
|
assert isinstance(self.publish_reorder_count, int) and self.publish_reorder_count >= 0, (
|
||||||
|
"publish_reorder_count muss eine nicht-negative ganze Zahl sein."
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"run_id": self.run_id,
|
||||||
|
"p95": float(self.p95),
|
||||||
|
"p99": float(self.p99),
|
||||||
|
"retry_free_count": int(self.retry_free_count),
|
||||||
|
"publish_reorder_count": int(self.publish_reorder_count),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def export_summary(
|
||||||
|
summary_data: Union[RunSummary, List[RunSummary]],
|
||||||
|
format: str,
|
||||||
|
output_path: Optional[str] = None,
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Exportiert eine RunSummary- oder RunSummary-Liste in CSV oder JSON.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
summary_data: Eine oder mehrere RunSummary-Instanzen.
|
||||||
|
format: 'csv' oder 'json'.
|
||||||
|
output_path: Zielpfad, optional. Wird standardmäßig aus 'output/run_summary.<format>' abgeleitet.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Pfad zur erstellten Datei oder None bei fehlerhaftem Format.
|
||||||
|
"""
|
||||||
|
if isinstance(summary_data, RunSummary):
|
||||||
|
data_list = [summary_data]
|
||||||
|
elif isinstance(summary_data, list) and all(isinstance(s, RunSummary) for s in summary_data):
|
||||||
|
data_list = summary_data
|
||||||
|
else:
|
||||||
|
raise TypeError("summary_data muss eine RunSummary-Instanz oder eine Liste davon sein.")
|
||||||
|
|
||||||
|
fmt = format.lower().strip()
|
||||||
|
if fmt not in {"csv", "json"}:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not output_path:
|
||||||
|
output_dir = Path("output")
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path = str(output_dir / f"run_summary.{fmt}")
|
||||||
|
|
||||||
|
# Serialisierung basierend auf Format
|
||||||
|
if fmt == "json":
|
||||||
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump([item.to_dict() for item in data_list], f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
elif fmt == "csv":
|
||||||
|
fieldnames = list(data_list[0].to_dict().keys())
|
||||||
|
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
for item in data_list:
|
||||||
|
writer.writerow(item.to_dict())
|
||||||
|
|
||||||
|
return output_path
|
||||||
Loading…
Reference in a new issue