classify_zammad_unknown.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #!/usr/bin/env python3
  2. import re
  3. import sqlite3
  4. from collections import Counter, defaultdict
  5. DB = "data/zammad_analysis.sqlite3"
  6. AUTO_PATTERNS = [
  7. r"wir haben ihre anfrage erhalten",
  8. r"ihre anfrage wurde",
  9. r"ticket.{0,30}(erstellt|angelegt|eröffnet)",
  10. r"wurde.{0,30}(zugewiesen|weitergeleitet)",
  11. r"wird.{0,80}(bearbeitet|geprüft)",
  12. r"antworten sie bitte direkt auf diese e-mail",
  13. r"dies ist eine automatische",
  14. r"automatisch erstellt",
  15. r"automatische nachricht",
  16. r"vielen dank für ihre anfrage",
  17. ]
  18. AGENT_PATTERNS = [
  19. r"wie besprochen",
  20. r"wie vereinbart",
  21. r"wie telefonisch",
  22. r"ich habe",
  23. r"wir haben.{0,80}(erstellt|angepasst|geändert|veranlasst)",
  24. r"wir senden",
  25. r"wir schicken",
  26. r"wir erstatten",
  27. r"wir werden ihnen",
  28. r"ich lasse ihnen",
  29. r"ich habe ihnen",
  30. r"gutschein",
  31. r"gutscheincode",
  32. r"kulanz",
  33. r"nachforschungsauftrag",
  34. r"nachlieferung",
  35. r"ersatzpflanze",
  36. r"erstattung",
  37. r"rückerstattung",
  38. r"stornorechnung",
  39. r"rücksendelabel",
  40. r"retourenlabel",
  41. r"paketnummer",
  42. r"sendungsnummer",
  43. r"tracking",
  44. r"mit freundlichen grüßen",
  45. r"viele grüße",
  46. r"freundliche grüße",
  47. ]
  48. auto_re = [re.compile(x, re.I | re.S) for x in AUTO_PATTERNS]
  49. agent_re = [re.compile(x, re.I | re.S) for x in AGENT_PATTERNS]
  50. db = sqlite3.connect(DB)
  51. # Spalten nur anlegen, wenn sie noch nicht existieren.
  52. columns = {
  53. row[1]
  54. for row in db.execute("PRAGMA table_info(articles)")
  55. }
  56. if "content_role" not in columns:
  57. db.execute("""
  58. ALTER TABLE articles
  59. ADD COLUMN content_role TEXT
  60. """)
  61. if "classification_score" not in columns:
  62. db.execute("""
  63. ALTER TABLE articles
  64. ADD COLUMN classification_score INTEGER
  65. """)
  66. if "classification_reason" not in columns:
  67. db.execute("""
  68. ALTER TABLE articles
  69. ADD COLUMN classification_reason TEXT
  70. """)
  71. db.commit()
  72. rows = db.execute("""
  73. SELECT
  74. id,
  75. ticket_id,
  76. sender,
  77. clean_body
  78. FROM articles
  79. WHERE role = 'UNKNOWN'
  80. AND clean_body IS NOT NULL
  81. AND clean_body != ''
  82. """).fetchall()
  83. print("=" * 72)
  84. print("UNKNOWN → AGENT / AUTOMATION VORSORTIERUNG")
  85. print("=" * 72)
  86. print(f"UNKNOWN-Artikel: {len(rows)}")
  87. print()
  88. stats = Counter()
  89. examples = defaultdict(list)
  90. for article_id, ticket_id, sender, text in rows:
  91. text = text or ""
  92. auto_hits = [
  93. p.pattern
  94. for p in auto_re
  95. if p.search(text)
  96. ]
  97. agent_hits = [
  98. p.pattern
  99. for p in agent_re
  100. if p.search(text)
  101. ]
  102. score = 0
  103. # Automationssignale
  104. score -= min(len(auto_hits) * 3, 9)
  105. # Signale für individuell formulierte Agentenantwort
  106. score += min(len(agent_hits) * 2, 12)
  107. # Individuelle Antworten sind häufig etwas länger.
  108. if len(text) > 500:
  109. score += 1
  110. if len(text) > 1200:
  111. score += 1
  112. if score >= 3:
  113. content_role = "AGENT_RESPONSE"
  114. stats[content_role] += 1
  115. elif score <= -3:
  116. content_role = "AUTOMATION"
  117. stats[content_role] += 1
  118. else:
  119. content_role = "UNKNOWN_REVIEW"
  120. stats[content_role] += 1
  121. reason = (
  122. f"agent_hits={len(agent_hits)};"
  123. f"auto_hits={len(auto_hits)};"
  124. f"score={score}"
  125. )
  126. db.execute("""
  127. UPDATE articles
  128. SET content_role = ?,
  129. classification_score = ?,
  130. classification_reason = ?
  131. WHERE id = ?
  132. """, (
  133. content_role,
  134. score,
  135. reason,
  136. article_id,
  137. ))
  138. if len(examples[content_role]) < 12:
  139. examples[content_role].append((
  140. ticket_id,
  141. score,
  142. text[:800],
  143. ))
  144. db.commit()
  145. print("ERGEBNIS")
  146. print("-" * 72)
  147. for role, count in stats.most_common():
  148. print(f"{role:20} {count:6}")
  149. for role in (
  150. "AGENT_RESPONSE",
  151. "AUTOMATION",
  152. "UNKNOWN_REVIEW",
  153. ):
  154. print()
  155. print("=" * 72)
  156. print(role)
  157. print("=" * 72)
  158. for ticket_id, score, text in examples[role]:
  159. print(
  160. f"\nTicket #{ticket_id} "
  161. f"score={score}"
  162. )
  163. print(text)
  164. print()
  165. print("=" * 72)
  166. print("STATUSVERTEILUNG")
  167. print("=" * 72)
  168. for row in db.execute("""
  169. SELECT
  170. COALESCE(content_role, role),
  171. COUNT(*)
  172. FROM articles
  173. GROUP BY COALESCE(content_role, role)
  174. ORDER BY COUNT(*) DESC
  175. """):
  176. print(f"{row[0]:20} {row[1]:6}")
  177. db.close()