summary.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import csv
  2. from collections import defaultdict
  3. from dataclasses import dataclass
  4. from datetime import date, datetime, timedelta
  5. from decimal import Decimal, InvalidOperation
  6. from pathlib import Path
  7. @dataclass(frozen=True)
  8. class SummaryResult:
  9. activity_count: int
  10. summary_count: int
  11. recent_summary: str
  12. def read_session_details(input_path: Path) -> list[dict[str, str]]:
  13. if not input_path.is_file():
  14. raise ValueError(f"Die CSV-Datei existiert nicht: {input_path}")
  15. with input_path.open("r", newline="", encoding="latin-1") as csv_file:
  16. return list(csv.DictReader(csv_file, delimiter=";"))
  17. def write_summary(rows: list[dict[str, str]], output: Path) -> None:
  18. output.parent.mkdir(parents=True, exist_ok=True)
  19. with output.open("w", newline="", encoding="latin-1", errors="replace") as csv_file:
  20. writer = csv.DictWriter(csv_file, fieldnames=["Datum", "Startzeit", "Kunde", "Dauer"], delimiter=";")
  21. writer.writeheader()
  22. writer.writerows(rows)
  23. def format_duration(duration: Decimal) -> str:
  24. value = format(duration, "f")
  25. if "." in value:
  26. value = value.rstrip("0").rstrip(".")
  27. return value or "0"
  28. def format_elapsed_seconds(duration: Decimal) -> str:
  29. total_seconds = max(int(duration), 0)
  30. minutes, seconds = divmod(total_seconds, 60)
  31. return f"{minutes}m" if seconds == 0 else f"{minutes}m {seconds:02d}s"
  32. def format_recent_summary(rows: list[dict[str, str]], days: int = 14, today: date | None = None) -> str:
  33. if days < 1:
  34. raise ValueError("Die Anzahl der Tage muss mindestens 1 sein.")
  35. today = today or date.today()
  36. first_day = today - timedelta(days=days - 1)
  37. weekday_names = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
  38. lines: list[str] = []
  39. current_date: date | None = None
  40. for row in rows:
  41. row_date = date.fromisoformat(row["Datum"])
  42. if row_date < first_day or row_date > today:
  43. continue
  44. if not row.get("_has_visible_activity", True):
  45. continue
  46. if row_date != current_date:
  47. if lines:
  48. lines.append("")
  49. lines.append(f"{weekday_names[row_date.weekday()]}, {row_date:%d.%m.%Y}")
  50. current_date = row_date
  51. lines.append(f"- {row['Startzeit'][:5]} {row['Kunde']} ({format_elapsed_seconds(Decimal(row['Dauer']))})")
  52. return "\n".join(lines)
  53. def run(input_path: Path, output: Path, days: int = 14) -> SummaryResult:
  54. totals: defaultdict[tuple[str, str], list[Decimal | str | bool]] = defaultdict(lambda: [Decimal("0"), "", False])
  55. activity_count = 0
  56. for row in read_session_details(input_path):
  57. date_time = row.get("DateTime", "").strip()
  58. customer = row.get("Kunde", "").strip()
  59. duration_text = row.get("Duration", "").strip()
  60. try:
  61. parsed_date_time = datetime.fromisoformat(date_time.replace("Z", "+00:00"))
  62. duration = Decimal(duration_text)
  63. except (InvalidOperation, ValueError):
  64. continue
  65. if not customer or not duration_text:
  66. continue
  67. date = parsed_date_time.date().isoformat()
  68. key = (date, customer)
  69. values = totals[key]
  70. values[0] += duration
  71. time = parsed_date_time.time().isoformat()
  72. if not values[1] or time < values[1]:
  73. values[1] = time
  74. if row.get("UserActivityType", "").strip() != "SupportAndPresentationMode" or duration != 0:
  75. values[2] = True
  76. activity_count += 1
  77. rows = [
  78. {
  79. "Datum": date,
  80. "Startzeit": start_time,
  81. "Kunde": customer,
  82. "Dauer": format_duration(duration),
  83. }
  84. for (date, customer), (duration, start_time, _) in sorted(
  85. totals.items(), key=lambda item: (item[0][0], item[1][1], item[0][1].casefold())
  86. )
  87. ]
  88. write_summary(rows, output)
  89. recent_rows = [{**row, "_has_visible_activity": totals[(row["Datum"], row["Kunde"])][2]} for row in rows]
  90. return SummaryResult(
  91. activity_count=activity_count,
  92. summary_count=len(rows),
  93. recent_summary=format_recent_summary(recent_rows, days),
  94. )