replay_historical_analysis.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. #!/usr/bin/env python3
  2. import asyncio
  3. import json
  4. import os
  5. import sqlite3
  6. from pathlib import Path
  7. from collections import Counter
  8. from app.kontor_mcp import KontorMCPClient
  9. from app.kontor_resolver import (
  10. resolve_customer_number,
  11. resolve_order_number,
  12. resolve_product_with_context,
  13. )
  14. DB = "data/telephony.sqlite3"
  15. OUTPUT = Path("data/historical_mcp_replay.json")
  16. def extract_analysis(row):
  17. try:
  18. return json.loads(row["analysis_json"])
  19. except Exception:
  20. return {}
  21. async def main():
  22. con = sqlite3.connect(DB)
  23. con.row_factory = sqlite3.Row
  24. rows = con.execute("""
  25. SELECT
  26. a.id AS analysis_id,
  27. a.cdr_row_id,
  28. a.transcript_id,
  29. a.analysis_json,
  30. t.rec_id,
  31. t.transcript_json,
  32. c.source_caller_id,
  33. c.destination_caller_id,
  34. c.start_time
  35. FROM analyses a
  36. JOIN transcripts t
  37. ON t.id = a.transcript_id
  38. JOIN cdr_calls c
  39. ON c.id = a.cdr_row_id
  40. WHERE a.cdr_row_id IS NOT NULL
  41. ORDER BY a.id
  42. """).fetchall()
  43. con.close()
  44. print(f"Historische CDR-Analysen: {len(rows)}")
  45. if not rows:
  46. raise SystemExit("Keine historischen Analysen gefunden.")
  47. client = KontorMCPClient(
  48. os.environ["KONTOR_MCP_URL"],
  49. os.environ["KONTOR_MCP_TOKEN"],
  50. float(os.getenv("KONTOR_MCP_TIMEOUT", "10")),
  51. )
  52. await client.initialize()
  53. results = []
  54. stats = Counter()
  55. for index, row in enumerate(rows, 1):
  56. print(
  57. f"\n[{index}/{len(rows)}] "
  58. f"analysis={row['analysis_id']} "
  59. f"cdr={row['cdr_row_id']} "
  60. f"rec={row['rec_id']}"
  61. )
  62. analysis = extract_analysis(row)
  63. result = {
  64. "analysis_id": row["analysis_id"],
  65. "cdr_row_id": row["cdr_row_id"],
  66. "transcript_id": row["transcript_id"],
  67. "rec_id": row["rec_id"],
  68. "start_time": row["start_time"],
  69. "caller": row["source_caller_id"],
  70. "destination": row["destination_caller_id"],
  71. "resolved": {
  72. "customer": None,
  73. "customer_number": None,
  74. "order": None,
  75. "products": [],
  76. },
  77. "errors": [],
  78. }
  79. # ---------------------------------------------------------
  80. # 1. Kunde anhand der CDR-Telefonnummer
  81. # ---------------------------------------------------------
  82. try:
  83. phone = row["source_caller_id"]
  84. if phone:
  85. raw = await client.find_customer_by_phone(phone)
  86. customer = raw
  87. result["resolved"]["customer"] = customer
  88. if customer:
  89. stats["customer_found"] += 1
  90. else:
  91. stats["customer_not_found"] += 1
  92. else:
  93. stats["customer_no_phone"] += 1
  94. except Exception as exc:
  95. stats["customer_error"] += 1
  96. result["errors"].append(
  97. f"customer: {exc!r}"
  98. )
  99. # ---------------------------------------------------------
  100. # 2. Kundennummer aus bestehender Qwen-Analyse validieren
  101. # ---------------------------------------------------------
  102. customer_data = analysis.get("customer") or {}
  103. customer_number = (
  104. customer_data.get("customer_number")
  105. )
  106. if customer_number:
  107. try:
  108. resolution = await resolve_customer_number(
  109. client,
  110. str(customer_number),
  111. )
  112. result["resolved"]["customer_number"] = {
  113. "status": resolution.status,
  114. "value": resolution.value,
  115. "confidence": resolution.confidence,
  116. "source": resolution.source,
  117. "data": resolution.data,
  118. }
  119. stats[
  120. f"customer_number_{resolution.status}"
  121. ] += 1
  122. except Exception as exc:
  123. stats["customer_number_error"] += 1
  124. result["errors"].append(
  125. f"customer_number: {exc!r}"
  126. )
  127. # ---------------------------------------------------------
  128. # 3. Bestellnummer validieren
  129. # ---------------------------------------------------------
  130. order_number = (
  131. customer_data.get("order_number")
  132. or analysis.get("order_number")
  133. )
  134. if order_number:
  135. try:
  136. resolution = await resolve_order_number(
  137. client,
  138. str(order_number),
  139. )
  140. result["resolved"]["order"] = {
  141. "status": resolution.status,
  142. "value": resolution.value,
  143. "confidence": resolution.confidence,
  144. "source": resolution.source,
  145. "data": resolution.data,
  146. }
  147. stats[
  148. f"order_{resolution.status}"
  149. ] += 1
  150. except Exception as exc:
  151. stats["order_error"] += 1
  152. result["errors"].append(
  153. f"order: {exc!r}"
  154. )
  155. # ---------------------------------------------------------
  156. # 4. Produkte mit Bestellkontext auflösen
  157. # ---------------------------------------------------------
  158. order_context = None
  159. if result["resolved"]["order"]:
  160. order_context = (
  161. result["resolved"]["order"]
  162. .get("data")
  163. )
  164. products = analysis.get("products") or []
  165. for product in products:
  166. if not isinstance(product, dict):
  167. continue
  168. raw_product = (
  169. product.get("raw_text")
  170. or product.get("name")
  171. )
  172. if not raw_product:
  173. continue
  174. try:
  175. resolution = (
  176. await resolve_product_with_context(
  177. client,
  178. str(raw_product),
  179. order_context,
  180. )
  181. )
  182. result["resolved"]["products"].append({
  183. "raw_text": raw_product,
  184. "status": resolution.status,
  185. "value": resolution.value,
  186. "confidence": resolution.confidence,
  187. "source": resolution.source,
  188. "candidates": resolution.candidates,
  189. "data": resolution.data,
  190. })
  191. stats[
  192. f"product_{resolution.status}"
  193. ] += 1
  194. except Exception as exc:
  195. stats["product_error"] += 1
  196. result["errors"].append(
  197. f"product {raw_product!r}: {exc!r}"
  198. )
  199. results.append(result)
  200. print(
  201. " Kunde:",
  202. "FOUND"
  203. if result["resolved"]["customer"]
  204. else "NOT FOUND",
  205. "| Produkte:",
  206. len(result["resolved"]["products"]),
  207. "| Fehler:",
  208. len(result["errors"]),
  209. )
  210. payload = {
  211. "generated_at": __import__("datetime").datetime.now().isoformat(),
  212. "database": DB,
  213. "count": len(results),
  214. "statistics": dict(sorted(stats.items())),
  215. "results": results,
  216. }
  217. OUTPUT.write_text(
  218. json.dumps(
  219. payload,
  220. ensure_ascii=False,
  221. indent=2,
  222. )
  223. )
  224. print("\n" + "=" * 70)
  225. print("HISTORICAL MCP REPLAY FERTIG")
  226. print("=" * 70)
  227. print(f"Calls: {len(results)}")
  228. print(f"Output: {OUTPUT}")
  229. print()
  230. for key, value in sorted(stats.items()):
  231. print(f"{key:35} {value}")
  232. if __name__ == "__main__":
  233. asyncio.run(main())