clients.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import csv
  2. import re
  3. import xml.etree.ElementTree as ET
  4. from dataclasses import dataclass
  5. from datetime import datetime
  6. from pathlib import Path
  7. @dataclass(frozen=True)
  8. class ClientsResult:
  9. xml_count: int
  10. row_count: int
  11. def extract_clients(xml_path: Path) -> list[dict[str, str]]:
  12. root = ET.parse(xml_path).getroot()
  13. session = root.find("Session")
  14. if session is None:
  15. return []
  16. client_description = session.find("SessionDescription[@Name='ClientName']")
  17. client_name = "" if client_description is None else client_description.get("Description", "")
  18. start_time = session.get("StartTime", "")
  19. return [
  20. {
  21. "ParticipantName": participant.get("Name", ""),
  22. "ClientName": client_name,
  23. "StartTime": start_time,
  24. }
  25. for participant in session.findall("Participant")
  26. if participant.get("Role") not in {"ROLE_SUPPORTER", "ROLE_UNKNOWN"}
  27. and participant.get("Name", "").strip()
  28. and client_name.strip()
  29. ]
  30. def calculate_customer(client_name: str) -> str:
  31. match = re.search(r"\bC(?:7|11)\b", client_name, re.IGNORECASE)
  32. customer = client_name[: match.end()] if match else client_name
  33. customer = (
  34. customer.replace("&", "u.")
  35. .replace("ä", "ae")
  36. .replace("ö", "oe")
  37. .replace("ü", "ue")
  38. .replace("Ä", "Ae")
  39. .replace("Ö", "Oe")
  40. .replace("Ü", "Ue")
  41. .replace("ß", "ss")
  42. )
  43. customer = re.sub(r'[<>:"/\\|?*\x00-\x1f]', "_", customer)
  44. customer = customer.rstrip(" .")
  45. return customer or "_"
  46. def write_clients_csv(rows: list[dict[str, str]], csv_path: Path) -> None:
  47. csv_path.parent.mkdir(parents=True, exist_ok=True)
  48. fieldnames = ["ParticipantName", "ClientName", "StartTime", "Kunde"]
  49. with csv_path.open("w", newline="", encoding="latin-1", errors="replace") as csv_file:
  50. writer = csv.DictWriter(csv_file, fieldnames=fieldnames, delimiter=";")
  51. writer.writeheader()
  52. writer.writerows(rows)
  53. def read_clients_csv(csv_path: Path) -> list[dict[str, str]]:
  54. if not csv_path.exists():
  55. return []
  56. with csv_path.open("r", newline="", encoding="latin-1") as csv_file:
  57. reader = csv.DictReader(csv_file, delimiter=";")
  58. return [
  59. {
  60. "ParticipantName": row.get("ParticipantName", "").strip(),
  61. "ClientName": row.get("ClientName", ""),
  62. "StartTime": row.get("StartTime", ""),
  63. }
  64. for row in reader
  65. if row.get("ParticipantName", "").strip() and row.get("ClientName", "").strip()
  66. ]
  67. def parse_start_time(start_time: str) -> datetime:
  68. if not start_time:
  69. return datetime.min
  70. try:
  71. return datetime.fromisoformat(start_time.replace("Z", "+00:00")).replace(tzinfo=None)
  72. except ValueError:
  73. return datetime.min
  74. def merge_clients(existing_rows: list[dict[str, str]], new_rows: list[dict[str, str]]) -> list[dict[str, str]]:
  75. newest_rows: dict[str, dict[str, str]] = {}
  76. for row in [*existing_rows, *new_rows]:
  77. participant_name = row["ParticipantName"].strip()
  78. client_name = row["ClientName"].strip()
  79. if not participant_name or not client_name:
  80. continue
  81. normalized_row = {
  82. "ParticipantName": participant_name,
  83. "ClientName": client_name,
  84. "StartTime": row.get("StartTime", ""),
  85. }
  86. current = newest_rows.get(participant_name)
  87. if current is None or parse_start_time(normalized_row["StartTime"]) >= parse_start_time(current["StartTime"]):
  88. newest_rows[participant_name] = normalized_row
  89. return [
  90. {**row, "Kunde": calculate_customer(row["ClientName"])}
  91. for row in sorted(newest_rows.values(), key=lambda row: row["ParticipantName"].casefold())
  92. ]
  93. def find_xml_files(directory: Path) -> list[Path]:
  94. return sorted(
  95. path
  96. for path in directory.rglob("*")
  97. if path.is_file() and path.suffix.lower() == ".xml" and not path.name.lower().startswith("sessions")
  98. )
  99. def run(directory: Path, output: Path) -> ClientsResult:
  100. if not directory.is_dir():
  101. raise ValueError(f"Das Verzeichnis existiert nicht: {directory}")
  102. xml_files = find_xml_files(directory)
  103. new_rows = [row for xml_file in xml_files for row in extract_clients(xml_file)]
  104. rows = merge_clients(read_clients_csv(output), new_rows)
  105. write_clients_csv(rows, output)
  106. return ClientsResult(xml_count=len(xml_files), row_count=len(rows))