| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- # -*- coding: utf-8 -*-
- """
- 配对审计 — 验证 Excel 每行的 (店名, 药品标题, 价格) 是否真的来自同一张卡片
- =============================================================================
- 方法: 取行的来源截图 → PP-OCR 独立重读 → 几何枚举卡片(标题/价格/店铺) →
- 行的三元组与图上卡片逐一配对(价格精确 + 店名容错 + 标题重叠)。
- 当前图配不上时,依次尝试该批次的时间戳备份图(防批次覆盖)。
- 用法: python _tmp_pairing_audit.py [样本行数,默认60]
- """
- import random
- import re
- import sys
- from pathlib import Path
- ROOT = Path(__file__).parent
- sys.path.insert(0, str(ROOT))
- from audit_ocr import load_excel, norm, price_num, shop_match, enumerate_cards
- from ai_helper_vision1 import VisionParser1
- def title_overlap(a, b, min_len=6):
- """最长公共子串 >= min_len 视为同一商品标题(容忍前后缀差异)"""
- a, b = norm(a), norm(b)
- best = 0
- prev = [0] * (len(b) + 1)
- for ca in a:
- cur = [0]
- for j, cb in enumerate(b, 1):
- cur.append(prev[j - 1] + 1 if ca == cb else 0)
- best = max(best, cur[-1])
- prev = cur
- return best >= min_len
- def main():
- n_sample = int(sys.argv[1]) if len(sys.argv) > 1 else 60
- rows = load_excel(ROOT / "采集数据" / "20260909.xlsx")
- src_rows = [r for r in rows if r["src"]]
- # 分层抽样: 按来源排序后等距取,保证4台设备/各药品都覆盖
- src_rows_sorted = sorted(src_rows, key=lambda r: r["src"])
- step = max(1, len(src_rows_sorted) // n_sample)
- sample = src_rows_sorted[::step][:n_sample]
- print(f"可追溯行 {len(src_rows)}, 抽样 {len(sample)} 行进行配对审计\n")
- parser = VisionParser1()
- card_cache = {} # 图片路径 -> 枚举卡片
- confirmed = unconfirmed = 0
- fail_rows = []
- def cards_of(dev, batch):
- """该批次所有候选图(当前图+时间戳副本)的枚举卡片(带缓存)"""
- out = []
- sdir = ROOT / "screenshots" / dev / "step3"
- cands = [sdir / f"step3_b{batch}.png"]
- cands += sorted(sdir.glob(f"step3_b{batch}_*.png"), key=lambda p: p.stat().st_mtime)
- for img in cands:
- if not img.exists():
- continue
- key = str(img)
- if key not in card_cache:
- try:
- card_cache[key] = enumerate_cards(parser._ppocr_blocks(key, 0))
- except Exception as e:
- print(f" [OCR失败] {img.name}: {e}")
- card_cache[key] = []
- out.append((key, card_cache[key]))
- return out
- for idx, r in enumerate(sample, 1):
- dev, batch = r["src"].split("/step3_b")
- pn = price_num(r["price"])
- print(f"[{idx}/{len(sample)}] {r['src']} ¥{pn} {r['shop'][:12]} | {r['title'][:22]}", flush=True)
- hit = False
- tried = 0
- for key, cards in cards_of(dev, batch):
- tried += 1
- for c in cards:
- if (c["price"] == pn and shop_match(c["shop"], r["shop"])
- and title_overlap(c["title"], r["title"])):
- hit = True
- break
- if hit:
- break
- if hit:
- confirmed += 1
- print(f" ✓ 配对确认 (试了{tried}张候选图)")
- else:
- unconfirmed += 1
- fail_rows.append(r)
- print(f" ✗ 未能在来源图上找到配对卡片")
- if idx % 10 == 0:
- print(f" --- 进度: 确认{confirmed} 未确认{unconfirmed} ---", flush=True)
- n = confirmed + unconfirmed
- print("\n" + "=" * 70)
- print(f"配对审计结果: {confirmed}/{n} = {confirmed/n*100:.1f}% 的行 (店名+药品+价格) 三元组能在来源截图的同一张卡上得到验证")
- if fail_rows:
- print("未确认行(需人工看图/可能是批次覆盖或真错配):")
- for r in fail_rows:
- print(f" [{r['sheet']}] {r['title'][:30]} {r['price']} 店:{r['shop'][:16]} 来源:{r['src']}")
- if __name__ == "__main__":
- main()
|