"""Sizing study for W4 (stale template values): how many fields of a new pier still equal the template, how many of them vary between verified piers, and how many of those were really wrong in the P4 12:11 state (restored P4 = truth). usage: python -X utf8 analysis_w4_residual_sizing.py (run from Code/교각모델링앱/data; needs the P4 backup before_restore_decisions_20261020) """ import json def L(p): return json.load(open(p,encoding="utf-8-sig")) def flat(o,p=()): if isinstance(o,dict): for k,v in o.items(): yield from flat(v,p+(str(k),)) elif isinstance(o,list): for i,v in enumerate(o): yield from flat(v,p+(str(i),)) else: yield p,o def num(v): return isinstance(v,(int,float)) and not isinstance(v,bool) def load(d): out={} for f in("params","expected"): for p,v in flat(L(f"{d}/{f}.json")): out[(f,)+p]=v return out T=load("_템플릿/운곡교P1형") ref={n:load(f"운곡교{n}") for n in("P1","P2","P3")} truth=load("운곡교P4") # restored, verified-equivalent cur=load("운곡교P4/backup/before_restore_decisions_20261020") # 12:11 state # variability from P1..P3 only (leave-P4-out) def varies(k): vals=[ref[n].get(k) for n in ref] return len(set(map(repr,vals)))>1 nums=[k for k,v in T.items() if num(v)] print("템플릿 숫자 필드:",len(nums)) var=[k for k in nums if varies(k)] print("P1~P3에서 교각마다 달라진 숫자 필드:",len(var)) wrong=[k for k in set(cur)|set(truth) if cur.get(k)!=truth.get(k)] print("12:11 판과 정답(복원판)이 다른 필드:",len(wrong)) resid=[k for k in nums if cur.get(k)==T.get(k)] # still template value in 12:11 state flag_old=[k for k in resid if k in var] # variable + still template tp=[k for k in flag_old if truth.get(k)!=cur.get(k)] fp=[k for k in flag_old if truth.get(k)==cur.get(k)] print("12:11 판에서 템플릿 값 그대로인 숫자 필드:",len(resid)) print(" 그중 '교각마다 달라지는' 필드(새 기준 후보):",len(flag_old),"→ 실제 틀림(TP)",len(tp),"/ 맞게 같은 값(FP)",len(fp)) missed=[k for k in wrong if k not in flag_old] print("정답과 달랐으나 새 기준이 못 잡은 필드:",len(missed)) from collections import Counter def grp(k): return ".".join(k[:3]) print("TP 분포:",Counter(grp(k) for k in tp).most_common(12)) print("FP 분포:",Counter(grp(k) for k in fp).most_common(12)) print("missed 분포:",Counter(grp(k) for k in missed).most_common(12)) # compare to current C3b height-only rule import sys; sys.path.insert(0,"../") from engine.verify import height_like, EL_MIN, EL_MAX c3b=[k for k in resid if EL_MIN<=abs(cur[k])<=EL_MAX and height_like(k[2:])] print("현행 C3b(높이형) 기준 후보:",len(c3b),"→ TP",sum(1 for k in c3b if truth.get(k)!=cur.get(k))) print("\n=== 정밀도 개선 시험") def distinct(k): return len(set(repr(ref[n].get(k)) for n in ref)) # bundle changed vs template (section = file + first key) def sec(k): return k[:2] changed_secs={sec(k) for k in set(cur)|set(T) if cur.get(k)!=T.get(k)} def stat(name,flag): tp=[k for k in flag if truth.get(k)!=cur.get(k)] print(f"{name}: 후보 {len(flag)} / 실제 틀림 {len(tp)} / 정밀도 {len(tp)/max(1,len(flag)):.0%}", [".".join(k[1:4]) for k in tp]) stat("A 교각마다 달라짐(2종 이상)", flag_old) stat("B A + 바뀐 묶음 안", [k for k in flag_old if sec(k) in changed_secs]) stat("C B + 3교각 모두 서로 다름", [k for k in flag_old if sec(k) in changed_secs and distinct(k)==3]) # C with the 3 mostly-structural groups removed skip=("translation_checks","layers","table","drawing_datums") stat("D C + 구조성 그룹 제외", [k for k in flag_old if sec(k) in changed_secs and distinct(k)==3 and not any(s in k for s in skip)]) print("C 후보 분포:",Counter(grp(k) for k in flag_old if sec(k) in changed_secs and distinct(k)==3).most_common(10))