초기 정리: PIT3000 분석 자료 업로드
- 좌표/SQL/API 분석 문서화 - 재현 스크립트와 예시 응답 추가 - 개인정보/원본 실행파일 제외
This commit is contained in:
110
scripts/summarize_quickreport_receipts.py
Normal file
110
scripts/summarize_quickreport_receipts.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user