184 lines
5.9 KiB
Python
184 lines
5.9 KiB
Python
"""新旧解析器对比工具。
|
||
|
||
对比 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')}")
|