"""Audited sample extraction. Reference workbooks identify targets, never supply output numbers. The registration records preserve the CAD entities selected for each field. """ import sys,re,json,math,csv,copy from pathlib import Path from openpyxl import load_workbook from sample_support import ROOT,BASE,load_docs from bridge_drawing_extractor.semantic import classify,bearings,calculation_elevations,norm,distance,pile_minimum_m,angle_text from bridge_drawing_extractor.automatic import blank_result,setcell,combine_values,apply_derived_piers,apply_input_conventions,assemble_group from bridge_drawing_extractor.core import write_json,HEADERS from bridge_drawing_extractor.workbook import export_workbook OUT=ROOT/'추출결과_12개교량_20261029';OUT.mkdir(exist_ok=True) def scalar(text): t=re.sub(r'\s+','',text) m=re.fullmatch(r'(?:E\.?L\.?[=:]*)?([-+]?\d+(?:\.\d+)?)(?:\([좌우]\))?',t,re.I) return float(m[1]) if m else None def all_points(ts): out=[] for e in ts: x=re.search(r'X\s*[=:]\s*([-+]?\d+\.\d+)',e['t'],re.I) if not x:continue y=re.search(r'Y\s*[=:]\s*([-+]?\d+\.\d+)',e['t'],re.I);ye=e if not y: candidates=[a for a in ts if re.search(r'Y\s*[=:]',a['t'],re.I) and a['y']no['y'] and e['x']>no['x']] if not xheads:continue xh=min(xheads,key=lambda e:distance(e,no));same=[e for e in ts if abs(e['y']-no['y'])<.001] vals={};ev=list(row['evidence']) for field,title in [('x','X좌표'),('y','Y좌표'),('angle','일반각도계')]: heads=[e for e in ts if norm(e['t'])==title and abs(e['y']-xh['y'])<.01] if not heads:continue h=min(heads,key=lambda e:distance(e,xh)) cand=[e for e in same if abs(e['x']-h['x'])<25 and (scalar(e['t']) is not None if field!='angle' else '°' in angle_text(e['t']))] if cand: e=min(cand,key=lambda e:abs(e['x']-h['x']));vals[field]=scalar(e['t']) if field!='angle' else angle_text(e['t']);ev.extend([h,e]) sta=[e for e in same if 'STA.' in e['t'] and 01: gaps=[(b-a,(a+b)/2) for a,b in zip(ss,ss[1:])];gap,mid=max(gaps) if gap>.5: for r in rs:r['row_type']='전열' if r['station']1 else '')],matches) else:setcell(result,'Pier',f'{col}{row}',None,'미확인',['종평면도에서 대응 값 미확인']) if legacy: oldr=next((c[1:] for c,v in legacy['cells']['Pier'].items() if c.startswith('B') and v==p),None) for col in 'EFGH': rec=next((r for r in legacy['records'] if r['sheet']=='Pier' and r['cell']==col+str(oldr)),None) if rec: ev=[dict(e,doc=next(d['key'] for d in docs if e['doc'] in d['roles'])) for e in rec['evidence']] v=rec['value'];v=v/1000 if col=='H' and isinstance(v,(int,float)) else v setcell(result,'Pier',f'{col}{row}',v,rec['status'],rec['messages'],ev) else: a=anchors.get('E');pile=None;ptype=None if a: labels=[e for e in profiletexts if '말뚝' in e['t'] and len(e['t'])<45] if labels: nearest=min(labels,key=lambda e:distance(e,a)) # Metre-scale longitudinal views; limit the local foundation label search. if distance(nearest,a)<35:ptype=nearest lengths=[] if ptype: for e in profiletexts: m=re.search(r'(?