rebuild_zammad_cases.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env python3
  2. import re
  3. import sqlite3
  4. DB = "data/zammad_analysis.sqlite3"
  5. db = sqlite3.connect(DB)
  6. db.row_factory = sqlite3.Row
  7. # Felder bei Bedarf anlegen
  8. columns = {
  9. row[1]
  10. for row in db.execute("PRAGMA table_info(cases)")
  11. }
  12. if "analysis_status" not in columns:
  13. db.execute("""
  14. ALTER TABLE cases
  15. ADD COLUMN analysis_status TEXT
  16. """)
  17. if "analysis_reason" not in columns:
  18. db.execute("""
  19. ALTER TABLE cases
  20. ADD COLUMN analysis_reason TEXT
  21. """)
  22. db.commit()
  23. # Eindeutig automatische/spamartige Inhalte
  24. spam_patterns = [
  25. r"\bspam\b",
  26. r"\bwerbe[- ]?mail\b",
  27. r"\bmarketing[- ]?mail\b",
  28. r"\bunsubscribe\b",
  29. r"\bnewsletter\b",
  30. ]
  31. auto_patterns = [
  32. r"wir haben ihre anfrage erhalten",
  33. r"ihre anfrage wurde.{0,80}(erhalten|erstellt|angelegt)",
  34. r"ticket.{0,80}(erstellt|angelegt|eröffnet)",
  35. r"ticket.{0,80}(verfolgen|verfolgung)",
  36. r"link.{0,80}(ticket|anfrage)",
  37. r"dies ist eine automatische",
  38. r"automatische nachricht",
  39. r"vielen dank für ihre anfrage",
  40. ]
  41. spam_re = [re.compile(x, re.I | re.S) for x in spam_patterns]
  42. auto_re = [re.compile(x, re.I | re.S) for x in auto_patterns]
  43. tickets = db.execute("""
  44. SELECT ticket_id, ticket_number, title
  45. FROM cases
  46. ORDER BY ticket_id
  47. """).fetchall()
  48. stats = {}
  49. def count(status):
  50. stats[status] = stats.get(status, 0) + 1
  51. for pos, ticket in enumerate(tickets, 1):
  52. articles = db.execute("""
  53. SELECT
  54. role,
  55. content_role,
  56. analysis_body
  57. FROM articles
  58. WHERE ticket_id = ?
  59. ORDER BY id
  60. """, (ticket["ticket_id"],)).fetchall()
  61. customer = []
  62. agent = []
  63. for article in articles:
  64. text = (article["analysis_body"] or "").strip()
  65. if not text:
  66. continue
  67. if article["role"] == "CUSTOMER":
  68. customer.append(text)
  69. elif article["role"] == "AGENT":
  70. agent.append(text)
  71. elif article["content_role"] == "AGENT_RESPONSE":
  72. agent.append(text)
  73. # AUTOMATION / INTERNAL / sonstige UNKNOWN:
  74. # nicht in die Analyse übernehmen.
  75. customer_text = "\n\n".join(customer).strip()
  76. agent_text = "\n\n".join(agent).strip()
  77. # --------------------------------------------------------
  78. # Status bestimmen
  79. # --------------------------------------------------------
  80. if not customer_text:
  81. status = "AUTOMATION_ONLY"
  82. reason = "Kein verwertbarer Kundenbeitrag"
  83. elif (
  84. len(customer_text) < 30
  85. and not agent_text
  86. ):
  87. status = "NON_ANALYZABLE"
  88. reason = "Kundenbeitrag zu kurz"
  89. elif (
  90. any(p.search(customer_text) for p in auto_re)
  91. and len(customer_text) < 250
  92. ):
  93. status = "AUTOMATION_ONLY"
  94. reason = "Automatische Ticketnachricht"
  95. else:
  96. status = "ANALYZABLE"
  97. reason = "Verwertbarer Kundenfall"
  98. count(status)
  99. classification_status = (
  100. "NEW"
  101. if status == "ANALYZABLE"
  102. else "SKIP"
  103. )
  104. db.execute("""
  105. UPDATE cases
  106. SET
  107. customer_text = ?,
  108. agent_text = ?,
  109. unknown_text = '',
  110. analysis_status = ?,
  111. analysis_reason = ?,
  112. classification_status = ?
  113. WHERE ticket_id = ?
  114. """, (
  115. customer_text,
  116. agent_text,
  117. status,
  118. reason,
  119. classification_status,
  120. ticket["ticket_id"],
  121. ))
  122. if pos % 500 == 0:
  123. db.commit()
  124. print(
  125. f"[{pos}/{len(tickets)}] "
  126. f"ANALYZABLE={stats.get('ANALYZABLE', 0)} "
  127. f"SKIP={pos - stats.get('ANALYZABLE', 0)}",
  128. flush=True,
  129. )
  130. db.commit()
  131. print()
  132. print("=" * 72)
  133. print("ZAMMAD CASE REBUILD FERTIG")
  134. print("=" * 72)
  135. for status, number in sorted(
  136. stats.items(),
  137. key=lambda x: x[1],
  138. reverse=True,
  139. ):
  140. print(f"{status:24} {number:6}")
  141. print()
  142. row = db.execute("""
  143. SELECT COUNT(*)
  144. FROM cases
  145. WHERE analysis_status = 'ANALYZABLE'
  146. """).fetchone()
  147. print(
  148. f"Für Qwen vorgesehen: {row[0]}"
  149. )
  150. print()
  151. print("Kontrolle #91005 / #91006")
  152. print("-" * 72)
  153. for row in db.execute("""
  154. SELECT
  155. ticket_number,
  156. title,
  157. analysis_status,
  158. analysis_reason,
  159. customer_text
  160. FROM cases
  161. WHERE ticket_number IN ('91005', '91006')
  162. ORDER BY ticket_number
  163. """):
  164. print(
  165. f"#{row['ticket_number']} "
  166. f"{row['analysis_status']} "
  167. f"({row['analysis_reason']})"
  168. )
  169. print(
  170. f" {row['title'] or ''}"
  171. )
  172. print(
  173. f" {(row['customer_text'] or '')[:300]}"
  174. )
  175. db.close()