cases.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. """异常 Case 闭环与审计日志。"""
  2. import sqlite3
  3. from pathlib import Path
  4. import pandas as pd
  5. from defect_analysis.database import init_database
  6. VALID_CASE_STATUSES = {"OPEN", "IN_PROGRESS", "IMPROVED", "CLOSED", "REJECTED"}
  7. def _connect(db_path):
  8. conn = sqlite3.connect(Path(db_path))
  9. conn.row_factory = sqlite3.Row
  10. return conn
  11. def _write_audit_log(conn, *, entity_type, entity_id, action, actor, details):
  12. conn.execute(
  13. """
  14. INSERT INTO audit_logs (entity_type, entity_id, action, actor, details)
  15. VALUES (?, ?, ?, ?, ?)
  16. """,
  17. (entity_type, int(entity_id), action, actor, details),
  18. )
  19. def create_root_cause_case(
  20. db_path,
  21. *,
  22. title,
  23. candidate_type,
  24. candidate_value,
  25. defect_type,
  26. panel_zone,
  27. owner,
  28. created_by,
  29. recommendation,
  30. ):
  31. """从根因候选创建异常 Case。"""
  32. init_database(db_path)
  33. with _connect(db_path) as conn:
  34. cursor = conn.execute(
  35. """
  36. INSERT INTO root_cause_cases (
  37. title, status, candidate_type, candidate_value, defect_type,
  38. panel_zone, owner, recommendation, created_by
  39. )
  40. VALUES (?, 'OPEN', ?, ?, ?, ?, ?, ?, ?)
  41. """,
  42. (
  43. title,
  44. candidate_type,
  45. candidate_value,
  46. defect_type,
  47. panel_zone,
  48. owner,
  49. recommendation,
  50. created_by,
  51. ),
  52. )
  53. case_id = int(cursor.lastrowid)
  54. _write_audit_log(
  55. conn,
  56. entity_type="case",
  57. entity_id=case_id,
  58. action="CREATE_CASE",
  59. actor=created_by,
  60. details=f"创建 Case: {title}; 建议: {recommendation}",
  61. )
  62. return case_id
  63. def update_case_status(db_path, *, case_id, status, actor, note=""):
  64. """更新 Case 状态并记录审计日志。"""
  65. if status not in VALID_CASE_STATUSES:
  66. raise ValueError(f"无效 Case 状态: {status}")
  67. init_database(db_path)
  68. with _connect(db_path) as conn:
  69. current = conn.execute(
  70. "SELECT status FROM root_cause_cases WHERE case_id = ?",
  71. (int(case_id),),
  72. ).fetchone()
  73. if current is None:
  74. raise ValueError(f"未找到 Case: {case_id}")
  75. closed_at_expr = "CURRENT_TIMESTAMP" if status == "CLOSED" else "closed_at"
  76. conn.execute(
  77. f"""
  78. UPDATE root_cause_cases
  79. SET status = ?, updated_at = CURRENT_TIMESTAMP, closed_at = {closed_at_expr}
  80. WHERE case_id = ?
  81. """,
  82. (status, int(case_id)),
  83. )
  84. _write_audit_log(
  85. conn,
  86. entity_type="case",
  87. entity_id=case_id,
  88. action="UPDATE_STATUS",
  89. actor=actor,
  90. details=f"{current['status']} -> {status}; {note}",
  91. )
  92. def list_cases(db_path, *, status=None):
  93. """列出异常 Case。"""
  94. init_database(db_path)
  95. params = []
  96. where = ""
  97. if status is not None:
  98. where = "WHERE status = ?"
  99. params.append(status)
  100. with _connect(db_path) as conn:
  101. rows = conn.execute(
  102. f"""
  103. SELECT case_id, title, status, candidate_type, candidate_value,
  104. defect_type, panel_zone, owner, recommendation,
  105. created_by, created_at, updated_at, closed_at
  106. FROM root_cause_cases
  107. {where}
  108. ORDER BY case_id
  109. """,
  110. params,
  111. ).fetchall()
  112. return pd.DataFrame([dict(row) for row in rows])
  113. def get_audit_logs(db_path, *, entity_type=None, entity_id=None):
  114. """读取审计日志。"""
  115. init_database(db_path)
  116. clauses = []
  117. params = []
  118. if entity_type is not None:
  119. clauses.append("entity_type = ?")
  120. params.append(entity_type)
  121. if entity_id is not None:
  122. clauses.append("entity_id = ?")
  123. params.append(int(entity_id))
  124. where = "WHERE " + " AND ".join(clauses) if clauses else ""
  125. with _connect(db_path) as conn:
  126. rows = conn.execute(
  127. f"""
  128. SELECT audit_id, entity_type, entity_id, action, actor, details, created_at
  129. FROM audit_logs
  130. {where}
  131. ORDER BY audit_id
  132. """,
  133. params,
  134. ).fetchall()
  135. return pd.DataFrame([dict(row) for row in rows])