baodan/api/insurance/ppt/comparison_tool.py

184 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""新旧解析器对比工具。
对比 regex-only和 split-LLM两种提取方式的结果差异。
用于评估新解析器的改进效果。
用法:
# Python
from insurance.ppt.comparison_tool import run_comparison
report = run_comparison("path/to/plan.pdf", "savings")
# CLI
python -m insurance.ppt.comparison_tool path/to/plan.pdf --type savings
"""
import json
import time
import logging
import asyncio
from typing import Optional
logger = logging.getLogger(__name__)
async def _extract_regex_only(pdf_path: str, plan_type: str) -> tuple[dict, float]:
"""旧方式:纯正则提取(零 LLM 调用)。"""
from insurance.ppt.regex_extractor import extract_insurance_regex
start = time.time()
# 读取 PDF 文本
from insurance.ppt.extraction import _extract_pdf_text
pdf_text, _ = _extract_pdf_text(pdf_path)
if not pdf_text:
return {"error": "无法提取 PDF 文本"}, 0
data = extract_insurance_regex(pdf_text)
elapsed = (time.time() - start) * 1000
return data, elapsed
async def _extract_new(pdf_path: str, plan_type: str) -> tuple[dict, float, dict]:
"""新方式:质量门 + 分块 LLM 提取。"""
from insurance.ppt.extraction import ExtractionOrchestrator
start = time.time()
orchestrator = ExtractionOrchestrator(use_cache=False)
result = await orchestrator.extract_plan(pdf_path, plan_type, force_reparse=True)
elapsed = (time.time() - start) * 1000
return result.data or {}, elapsed, result.provenance or {}
def _diff_fields(old: dict, new: dict, path: str = "") -> list[dict]:
"""递归对比两个 dict返回差异列表。"""
diffs = []
all_keys = set(list(old.keys()) + list(new.keys()))
for key in sorted(all_keys):
full_path = f"{path}.{key}" if path else key
old_val = old.get(key)
new_val = new.get(key)
if old_val == new_val:
continue
if isinstance(old_val, dict) and isinstance(new_val, dict):
diffs.extend(_diff_fields(old_val, new_val, full_path))
elif isinstance(old_val, list) and isinstance(new_val, list):
# 对比列表长度
if len(old_val) != len(new_val):
diffs.append({
"path": full_path,
"type": "list_length",
"old": len(old_val),
"new": len(new_val),
})
# 逐元素对比(前 N 个)
for i in range(min(len(old_val), len(new_val))):
if isinstance(old_val[i], dict) and isinstance(new_val[i], dict):
diffs.extend(_diff_fields(old_val[i], new_val[i], f"{full_path}[{i}]"))
elif old_val[i] != new_val[i]:
diffs.append({
"path": f"{full_path}[{i}]",
"type": "value",
"old": old_val[i],
"new": new_val[i],
})
else:
diffs.append({
"path": full_path,
"type": "value",
"old": old_val,
"new": new_val,
})
return diffs
async def run_comparison(pdf_path: str, plan_type: str = "savings") -> dict:
"""运行新旧解析器对比。
返回:
{
"pdf_path": str,
"plan_type": str,
"old_result": dict,
"new_result": dict,
"old_ms": float,
"new_ms": float,
"diffs": list[dict],
"diff_count": int,
"old_benefit_rows": int,
"new_benefit_rows": int,
"provenance": dict,
"summary": str,
}
"""
logger.info(f"[对比工具] 开始对比: {pdf_path} ({plan_type})")
# 旧方式
old_data, old_ms = await _extract_regex_only(pdf_path, plan_type)
old_benefit_rows = len(old_data.get("benefit_illustration") or [])
# 新方式
new_data, new_ms, provenance = await _extract_new(pdf_path, plan_type)
new_benefit_rows = len(new_data.get("benefit_illustration") or [])
# 对比
diffs = _diff_fields(old_data, new_data)
# 生成摘要
summary_parts = [
f"PDF: {pdf_path}",
f"险种: {plan_type}",
f"旧方式: {old_ms:.0f}ms, {old_benefit_rows} 行利益表",
f"新方式: {new_ms:.0f}ms, {new_benefit_rows} 行利益表",
f"差异项: {len(diffs)}",
]
# 关键字段对比
old_name = old_data.get("product_name", "unknown")
new_name = new_data.get("product_name", "unknown")
old_age = (old_data.get("insured") or {}).get("age")
new_age = (new_data.get("insured") or {}).get("age")
summary_parts.append(f"产品名: 旧={old_name}, 新={new_name}")
summary_parts.append(f"年龄: 旧={old_age}, 新={new_age}")
report = {
"pdf_path": pdf_path,
"plan_type": plan_type,
"old_result": old_data,
"new_result": new_data,
"old_ms": round(old_ms, 1),
"new_ms": round(new_ms, 1),
"diffs": diffs,
"diff_count": len(diffs),
"old_benefit_rows": old_benefit_rows,
"new_benefit_rows": new_benefit_rows,
"provenance": provenance,
"summary": "\n".join(summary_parts),
}
logger.info(f"[对比工具] 完成:\n{report['summary']}")
return report
if __name__ == "__main__":
import sys
logging.basicConfig(level=logging.INFO)
if len(sys.argv) < 2:
print("用法: python -m insurance.ppt.comparison_tool <pdf_path> [--type savings|ci|iul]")
sys.exit(1)
pdf = sys.argv[1]
ptype = "savings"
if "--type" in sys.argv:
idx = sys.argv.index("--type")
if idx + 1 < len(sys.argv):
ptype = sys.argv[idx + 1]
report = asyncio.run(run_comparison(pdf, ptype))
print("\n" + report["summary"])
if report["diffs"]:
print(f"\n差异详情 ({report['diff_count']} 项):")
for d in report["diffs"][:20]:
print(f" {d['path']}: {d.get('old')}{d.get('new')}")