Files

90 lines
5.7 KiB
Python

"""Independent arithmetic and serialized-output checks for the audited 12 samples."""
import json, math, hashlib
from pathlib import Path
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from sample_support import ROOT, BASE
out=ROOT/'추출결과_12개교량_20261029'
summary=json.loads((out/'추출요약.json').read_text(encoding='utf-8'))
assert len(summary)==12
checked=0;piers=0;bearings=0;issues=[]
for s in summary:
path=Path(s['file']);data=json.loads(path.with_suffix('.json').read_text(encoding='utf-8'))
cells=data['cells'];wb=load_workbook(path)
for sheet,values in cells.items():
for addr,value in values.items():
actual=wb[sheet][addr].value
assert actual==value,(path.name,sheet,addr,actual,value)
if isinstance(value,(int,float)):assert math.isfinite(value)
checked+=1
for addr,p in cells['Pier'].items():
if not addr.startswith('B'):continue
r=addr[1:];piers+=1
assert cells['Pier']['K'+r]==(2 if cells['Pier']['C'+r]=='π형' else 1)
br=[a[1:] for a,v in cells['Bearing'].items() if a.startswith('B') and v==p]
ip=[a[1:] for a,v in cells['IP'].items() if a.startswith('B') and v==p]
assert len(ip)==3
def xy(sheet,r):return tuple(cells[sheet][c+r] for c in 'DE')
pts=[xy('Bearing',b) for b in br]
assert all(all(isinstance(x,(int,float)) for x in point) for point in pts)
maximum=max(math.dist(a,b) for a in pts for b in pts)*1000
assert cells['Pier']['I'+r]==(math.floor(round(maximum,6)/500)+1)*500
ips=[xy('IP',i) for i in ip]
assert len(set(ips))==3
length=math.dist(ips[0],ips[2])*1000
assert cells['Pier']['J'+r]==(math.floor(round(length,6)/500)+1)*500
for b in br:assert cells['Bearing']['H'+b] in ['전열','후열']
bearings+=s['bearings']
for rec in data['records']:
if rec['value'] is None and not(rec['sheet']=='IP' and rec['cell'].startswith('F')):
row=rec['cell'][1:];p=cells[rec['sheet']].get('B'+row)
issues.append([s['bridge'],p,rec['sheet'],rec['cell'],wb[rec['sheet']][rec['cell'][0]+'2'].value,rec['status'],' / '.join(rec['messages'])])
for e in rec['evidence']:assert e['doc'] in data['sources']
wb.close()
inventory=json.loads((BASE/'inventory.json').read_text(encoding='utf-8'))
assert len(inventory)==127
for src in inventory:
assert 'error' not in src,src
assert hashlib.sha256(Path(src['path']).read_bytes()).hexdigest()==src['sha256']
assert (piers,bearings)==(64,652)
book=Workbook();ws=book.active;ws.title='추출현황'
ws.append(['Sample','교량','교각 수','받침 수','미확정 셀 수','결과 파일'])
for s in summary:
ws.append([s['sample'],s['bridge'],s['piers'],s['bearings'],s['missing'],Path(s['file']).name])
ws.cell(ws.max_row,6).hyperlink=Path(s['file']).name
ws.cell(ws.max_row,6).style='Hyperlink'
ws.append(['합계','',piers,bearings,len(issues)])
review=book.create_sheet('미확정목록');review.append(['교량','교각','시트','셀','항목','상태','사유']);
for row in issues:review.append(row)
rules=book.create_sheet('적용기준')
for row in [
['항목','기준 / 범위'],
['교각형식','정면/평면 대표 형상 판독; T형 1개, π형 2개. 산외교 P9~P11 π형, 나머지 T형.'],
['두부보 EL / 받침 Z','ELEVATION 교좌면 EL 최소 / 교량받침 EL(몰탈상단).'],
['폭 / 길이','최대 받침 XY 거리 / IP1~IP3 XY 거리를 엄격히 초과하는 다음 500mm. 설계 확정 치수 아님.'],
['말뚝','길이 최소값 mm. 직접기초 확인 시 0, 길이 미확인/VAR은 빈칸.'],
['IP','도면 좌표 지시점 위→아래. 계문철교 원본 두 번째 P1 블록은 도면 위치에 따라 P2로 보정.'],
['기초 출처','종평면도. 목동교 좌/우 EL 및 기양천교 상충 EL은 병기 후 미확정.'],
['대호지교','종평면도 및 ELEVATION 산출근거도 부재: 관련 수치 빈칸.'],
['기존 엑셀 사용 범위','대상 교각/방향/지시점 대조용. 숫자는 CAD 문자에서 재추출. 이 배치 스크립트는 12개 샘플에 맞춘 판독 보정 포함.'],
['검증 범위','12개 XLSX 재읽기, 수치/단위/치수 산식/전후열/64교각·652받침/127원본 DWG 해시 검사.'],
['검증 한계','동일 작성자의 자기검증. 형상은 대표도면 표본 판독이며 모든 원도면의 독립 전수 육안 검증은 아님. 미확정값은 추정 대입하지 않음.'],
['연동','각 교량 XLSX 및 동일 이름 JSON/CSV 제공. 배포 wheel 재빌드 및 GUI 통합은 이번 배치에 포함하지 않음.']]:rules.append(row)
for sh in book:
sh.freeze_panes='A2';sh.auto_filter.ref=sh.dimensions
for c in sh[1]:c.font=Font(name='맑은 고딕',bold=True,color='FFFFFF');c.fill=PatternFill('solid',fgColor='203864')
for row in sh.iter_rows(min_row=2):
for c in row:c.font=Font(name='맑은 고딕',size=10);c.alignment=Alignment(vertical='top',wrap_text=True)
for col in 'ABCDEFG':sh.column_dimensions[col].width=18
sh.row_dimensions[1].height=28
ws.column_dimensions['F'].width=55;review.column_dimensions['G'].width=100
rules.column_dimensions['A'].width=27;rules.column_dimensions['B'].width=115
for r in range(2,rules.max_row+1):rules.row_dimensions[r].height=42
book.save(out/'00_추출현황_미확정목록.xlsx')
report=dict(xlsx_count=12,piers=piers,bearings=bearings,cells_checked=checked,source_dwg_hashes_checked=len(inventory),unresolved_cells=len(issues),checks='PASS',independent_review=False)
(out/'검증결과.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps(report,ensure_ascii=False))