feat: 添加 Cross 模式统计报告功能

This commit is contained in:
2026-03-09 16:53:24 +08:00
parent 3850bc33dc
commit 8e101491d2
3 changed files with 433 additions and 0 deletions
+280
View File
@@ -0,0 +1,280 @@
"""
Cross 模式统计报告功能
提供事件记录和统计报告生成功能,支持 Cross 和 Normal 两种模式。
"""
import json
import os
import fcntl
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
# 统计数据文件路径
STATS_FILE = Path(__file__).parent / ".stats_data.json"
# 敏感字段列表(用于过滤)
SENSITIVE_FIELDS = {"password", "token", "secret", "api_key", "apikey", "credential"}
def _filter_sensitive_data(data: dict) -> dict:
"""过滤敏感数据
递归过滤字典中的敏感字段,将值替换为 [REDACTED]
"""
if not isinstance(data, dict):
return data
filtered = {}
for key, value in data.items():
key_lower = key.lower().replace("-", "_").replace(" ", "_")
# 检查是否为敏感字段
is_sensitive = any(s in key_lower for s in SENSITIVE_FIELDS)
if is_sensitive:
filtered[key] = "[REDACTED]"
elif isinstance(value, dict):
filtered[key] = _filter_sensitive_data(value)
else:
filtered[key] = value
return filtered
def _ensure_stats_file():
"""确保统计数据文件存在,并设置正确权限"""
if not STATS_FILE.exists():
STATS_FILE.write_text(json.dumps({"events": []}, ensure_ascii=False, indent=2))
os.chmod(STATS_FILE, 0o600)
def _load_stats() -> dict:
"""加载统计数据(带文件锁)"""
_ensure_stats_file()
with open(STATS_FILE, "r") as f:
fcntl.flock(f.fileno(), fcntl.LOCK_SH) # 共享锁
try:
data = json.load(f)
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
return data
def _save_stats(data: dict):
"""保存统计数据(带文件锁)"""
_ensure_stats_file()
with open(STATS_FILE, "w") as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX) # 排他锁
try:
json.dump(data, f, ensure_ascii=False, indent=2)
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
# 确保文件权限为 600
os.chmod(STATS_FILE, 0o600)
def record_event(
event_type: str,
mode: str,
metadata: Optional[dict] = None,
status: str = "success"
) -> bool:
"""记录一个事件
Args:
event_type: 事件类型 (debate, review, audit)
mode: 运行模式 (cross, normal)
metadata: 事件元数据(可选)
status: 事件状态 (success, failed)
Returns:
bool: 记录是否成功
"""
# 验证参数
valid_types = {"debate", "review", "audit"}
valid_modes = {"cross", "normal"}
valid_status = {"success", "failed"}
if event_type not in valid_types:
print(f"错误: 无效的事件类型 '{event_type}',有效值: {valid_types}")
return False
if mode not in valid_modes:
print(f"错误: 无效的模式 '{mode}',有效值: {valid_modes}")
return False
if status not in valid_status:
print(f"错误: 无效的状态 '{status}',有效值: {valid_status}")
return False
# 过滤敏感数据
if metadata:
metadata = _filter_sensitive_data(metadata)
else:
metadata = {}
# 创建事件记录
event = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"event_type": event_type,
"mode": mode,
"status": status,
"metadata": metadata
}
try:
data = _load_stats()
data["events"].append(event)
_save_stats(data)
return True
except Exception as e:
print(f"记录事件失败: {e}")
return False
def get_stats() -> dict:
"""获取统计汇总数据
Returns:
dict: 统计汇总数据
"""
data = _load_stats()
events = data.get("events", [])
# 初始化统计结构
stats = {
"total_events": len(events),
"by_type": {
"debate": {"total": 0, "success": 0, "failed": 0},
"review": {"total": 0, "success": 0, "failed": 0},
"audit": {"total": 0, "success": 0, "failed": 0}
},
"by_mode": {
"cross": {"total": 0, "success": 0, "failed": 0},
"normal": {"total": 0, "success": 0, "failed": 0}
},
"cross_mode_breakdown": {
"debate": {"total": 0, "success": 0, "failed": 0},
"review": {"total": 0, "success": 0, "failed": 0},
"audit": {"total": 0, "success": 0, "failed": 0}
},
"normal_mode_breakdown": {
"debate": {"total": 0, "success": 0, "failed": 0},
"review": {"total": 0, "success": 0, "failed": 0},
"audit": {"total": 0, "success": 0, "failed": 0}
},
"success_rate": 0.0,
"first_event": None,
"last_event": None
}
if not events:
return stats
# 统计各类数据
for event in events:
event_type = event["event_type"]
mode = event["mode"]
status = event["status"]
# 按类型统计
stats["by_type"][event_type]["total"] += 1
stats["by_type"][event_type][status] += 1
# 按模式统计
stats["by_mode"][mode]["total"] += 1
stats["by_mode"][mode][status] += 1
# 按模式+类型交叉统计
breakdown_key = f"{mode}_mode_breakdown"
stats[breakdown_key][event_type]["total"] += 1
stats[breakdown_key][event_type][status] += 1
# 计算成功率
total = stats["total_events"]
success_count = sum(t["success"] for t in stats["by_type"].values())
stats["success_rate"] = round(success_count / total * 100, 2) if total > 0 else 0.0
# 时间范围
stats["first_event"] = events[0]["timestamp"]
stats["last_event"] = events[-1]["timestamp"]
return stats
def format_stats_report(stats: dict) -> str:
"""格式化统计报告为可读字符串
Args:
stats: get_stats() 返回的统计数据
Returns:
str: 格式化的统计报告
"""
lines = []
lines.append("=" * 50)
lines.append(" Cross 模式统计报告")
lines.append("=" * 50)
# 总览
lines.append(f"\n📊 总览")
lines.append(f" 总事件数: {stats['total_events']}")
lines.append(f" 成功率: {stats['success_rate']}%")
if stats['first_event']:
lines.append(f" 时间范围: {stats['first_event']} ~ {stats['last_event']}")
# 按模式统计
lines.append(f"\n📈 按模式统计")
for mode, data in stats["by_mode"].items():
mode_name = "Cross 模式" if mode == "cross" else "Normal 模式"
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
lines.append(f" {mode_name}:")
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
# 按事件类型统计
lines.append(f"\n📋 按事件类型统计")
for event_type, data in stats["by_type"].items():
type_name = {"debate": "辩论", "review": "评审", "audit": "审计"}.get(event_type, event_type)
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
lines.append(f" {type_name}:")
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
# Cross 模式详细分解
lines.append(f"\n🔍 Cross 模式详细分解")
for event_type, data in stats["cross_mode_breakdown"].items():
type_name = {"debate": "辩论", "review": "评审", "audit": "审计"}.get(event_type, event_type)
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
lines.append(f" {type_name}:")
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
# Normal 模式详细分解
lines.append(f"\n🔍 Normal 模式详细分解")
for event_type, data in stats["normal_mode_breakdown"].items():
type_name = {"debate": "辩论", "review": "评审", "audit": "审计"}.get(event_type, event_type)
rate = round(data["success"] / data["total"] * 100, 2) if data["total"] > 0 else 0
lines.append(f" {type_name}:")
lines.append(f" 总数: {data['total']} | 成功: {data['success']} | 失败: {data['failed']} | 成功率: {rate}%")
lines.append("\n" + "=" * 50)
return "\n".join(lines)
if __name__ == "__main__":
# 测试代码
print("测试 stats.py 模块...")
# 记录一些测试事件
record_event("debate", "cross", {"topic": "AI 安全"}, "success")
record_event("review", "normal", {"pr_id": 123}, "success")
record_event("audit", "cross", {"password": "secret123"}, "failed") # 敏感数据会被过滤
# 获取并打印统计
stats = get_stats()
print(format_stats_report(stats))