111 lines
5.9 KiB
Python
111 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
import csv, json, re, subprocess
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
|
|
ROOT=Path('/root/work/pharmit-ghidra')
|
|
CAND=ROOT/'out/a4_receipt_template_candidates'
|
|
OUT=CAND/'quickreport_receipt_deep'
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
PARSER=ROOT/'scripts/parse_fastreport_bin.py'
|
|
QR_CLASSES={'TQuickRep','TQRBand','TQRChildBand','TQRLabel','TQRDBText','TQRShape','TQRImage','TQRDBImage','TQRRichText','TQRExpr','TQRCompositeReport'}
|
|
KEYWORDS=['약제비 계산서','약제비','영수증','진료비','납입확인서','납입 확인서','소득공제','요양기관 종류','환 자','환자성명','약 제 비 총 액','총 수 납 금 액']
|
|
|
|
def text(o):
|
|
pr=o.get('props',{})
|
|
for k in ['Caption','Text','DataField','FieldName','Memo.UTF8W']:
|
|
v=pr.get(k)
|
|
if isinstance(v,str) and v.strip(): return v.strip()
|
|
if isinstance(v,list):
|
|
s='\n'.join(str(x) for x in v if str(x).strip()).strip()
|
|
if s: return s
|
|
return ''
|
|
|
|
def parse_file(p:Path):
|
|
jpath=OUT/(p.name+'.objects.json')
|
|
if not jpath.exists():
|
|
subprocess.run(['python3',str(PARSER),str(p)], stdout=jpath.open('w'), stderr=subprocess.DEVNULL, check=False)
|
|
try: return json.loads(jpath.read_text(encoding='utf-8'))
|
|
except Exception: return {'objects':[]}
|
|
|
|
def bounds(objs):
|
|
xs=[]; ys=[]
|
|
for o in objs:
|
|
pr=o.get('props',{})
|
|
try:
|
|
if 'Left' in pr and 'Width' in pr:
|
|
xs += [float(pr['Left']), float(pr['Left'])+float(pr['Width'])]
|
|
if 'Top' in pr and 'Height' in pr:
|
|
ys += [float(pr['Top']), float(pr['Top'])+float(pr['Height'])]
|
|
except Exception: pass
|
|
return ([round(min(xs),3),round(max(xs),3)] if xs else None, [round(min(ys),3),round(max(ys),3)] if ys else None)
|
|
|
|
def group_reports(objs):
|
|
q=[o for o in objs if o.get('class') in QR_CLASSES]
|
|
starts=[i for i,o in enumerate(q) if o.get('class') in {'TQuickRep','TQRCompositeReport'}]
|
|
if not starts:
|
|
return [('qr_blob', q)] if q else []
|
|
groups=[]
|
|
for n,si in enumerate(starts):
|
|
ei=starts[n+1] if n+1<len(starts) else len(q)
|
|
name=q[si].get('name') or f'report_{n}'
|
|
groups.append((name,q[si:ei]))
|
|
return groups
|
|
|
|
def score_group(g):
|
|
txt=' '.join(text(o) for o in g)
|
|
return sum(10 for k in KEYWORDS if k in txt) + sum(1 for o in g if o.get('class')=='TQRLabel')
|
|
|
|
def main():
|
|
all_rows=[]; summaries=[]
|
|
for p in sorted(CAND.glob('*.bin')):
|
|
if not any(s in p.name for s in ['진료비','소득공제','요양기관','현금영수증']):
|
|
continue
|
|
j=parse_file(p); objs=j.get('objects',[])
|
|
groups=group_reports(objs)
|
|
for gi,(gname,gobjs) in enumerate(groups):
|
|
captions=[text(o) for o in gobjs if text(o)]
|
|
joined=' | '.join(captions)
|
|
if not any(k in joined for k in KEYWORDS):
|
|
continue
|
|
bx,by=bounds(gobjs)
|
|
classes=Counter(o.get('class') for o in gobjs)
|
|
summ={'source_bin':p.name,'group_index':gi,'group_name':gname,'objects':len(gobjs),'classes':dict(classes),'bounds_x':bx,'bounds_y':by,'score':score_group(gobjs),'key_captions':[c for c in captions if any(k in c for k in KEYWORDS)][:80]}
|
|
summaries.append(summ)
|
|
for idx,o in enumerate(gobjs):
|
|
pr=o.get('props',{})
|
|
if o.get('class') not in QR_CLASSES: continue
|
|
all_rows.append({
|
|
'source_bin':p.name,'group_index':gi,'group_name':gname,'idx':idx,'class':o.get('class'),'name':o.get('name'),'offset':o.get('offset'),
|
|
'left':pr.get('Left',''),'top':pr.get('Top',''),'width':pr.get('Width',''),'height':pr.get('Height',''),
|
|
'caption':pr.get('Caption',''),'text':text(o),'datafield':pr.get('DataField') or pr.get('FieldName') or '', 'dataset':pr.get('DataSet') or pr.get('DataSetName') or '',
|
|
'font_height':pr.get('Font.Height',''),'alignment':pr.get('Alignment') or pr.get('HAlign') or '',
|
|
'props':json.dumps(pr,ensure_ascii=False,sort_keys=True)
|
|
})
|
|
summaries=sorted(summaries,key=lambda x:(-x['score'],x['source_bin'],x['group_index']))
|
|
(OUT/'quickreport_receipt_summary.json').write_text(json.dumps(summaries,ensure_ascii=False,indent=2),encoding='utf-8')
|
|
with (OUT/'quickreport_receipt_objects_all.csv').open('w',newline='',encoding='utf-8-sig') as f:
|
|
fields=list(all_rows[0].keys()) if all_rows else ['source_bin']
|
|
w=csv.DictWriter(f,fieldnames=fields); w.writeheader(); w.writerows(all_rows)
|
|
md=['# QuickReport receipt templates deep summary','']
|
|
for s in summaries:
|
|
md += [f"## score={s['score']} {s['source_bin']} group={s['group_index']} `{s['group_name']}`",
|
|
f"- objects: {s['objects']}",f"- bounds_x: {s['bounds_x']}",f"- bounds_y: {s['bounds_y']}",f"- classes: `{s['classes']}`",'- key captions:']
|
|
md += [f" - {c}" for c in s['key_captions'][:40]]
|
|
md += ['']
|
|
(OUT/'quickreport_receipt_summary.md').write_text('\n'.join(md),encoding='utf-8')
|
|
# Write top group's canonical CSV for immediate renderer work.
|
|
if summaries:
|
|
top=summaries[0]
|
|
top_rows=[r for r in all_rows if r['source_bin']==top['source_bin'] and int(r['group_index'])==int(top['group_index'])]
|
|
with (OUT/'quickreport_receipt_canonical_top.csv').open('w',newline='',encoding='utf-8-sig') as f:
|
|
w=csv.DictWriter(f,fieldnames=list(top_rows[0].keys())); w.writeheader(); w.writerows(top_rows)
|
|
(OUT/'quickreport_receipt_canonical_top.json').write_text(json.dumps(top,ensure_ascii=False,indent=2),encoding='utf-8')
|
|
print('TOP',top)
|
|
print(OUT/'quickreport_receipt_summary.md')
|
|
print(OUT/'quickreport_receipt_objects_all.csv')
|
|
print(OUT/'quickreport_receipt_canonical_top.csv')
|
|
|
|
if __name__=='__main__': main()
|