knowledge(경쟁사): 일위대가 참조 그래프·설계변경 직접비 실증 — 지금 자료로 할 것 마무리
일위대가 호표 참조 그래프 (④단계) - 봉화 2024 의 45개 호표를 그래프로 세움 - 구성행이 다른 호표를 가리키고 단가가 일치하는 것 22건, 이름은 같은데 단가가 다른 것 0건 - 순환 참조 없음 -> 위상순서 계산 가능. 깊이 최대 2단 - 09번의 「의존 그래프·위상순서·순환은 오류」가 실데이터에서 성립함 설계변경 직접비 - 계류보전 2025(변경) 의 BOQ11 은 한 표에 차수1(당초)·차수2(변경) 를 나란히 담음 - 양쪽을 각각 다시 세워 직접재료비·직접노무비·산출경비 전부 원 단위 일치 - 행금액 절사 대조 294건 x 2 = 588건 불일치 0 - 제외금액 구역 판정은 차수와 무관하게 같음 - 19번 6장의 간접비 모드와 합쳐 변경설계가 직접비부터 간접비까지 전부 재현됨 진행률 장부 갱신 — 지금 자료로 할 수 있는 일은 사실상 소진. 남은 것은 새 표본이 있어야 열림. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA
This commit is contained in:
@@ -13,7 +13,8 @@
|
||||
| STC 안의 표 | 16종 | **10종 읽고 씀** · 4종 빈 표 | **2종 암호화** (`BDQTY`·`SYSINFO`) | ▓▓▓▓▓▓▓▓▓░ 88% |
|
||||
| STmate 출력 `XLSX` | 6 | 6 전부 | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
|
||||
| 출력 시트 | **30종** | **30종 전부 훑음** (계산 있는 것 15종 규칙 확정) | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
|
||||
| 계산 사슬 단계 | 8 | **8 전부** | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
|
||||
| 계산 사슬 단계 | 8 | **8 전부** (④는 참조 그래프까지 실증) | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
|
||||
| 변경설계 재현 | 직접비+간접비 | **전 항목 원 단위 일치** | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
|
||||
| 09번이 꼽은 호환성 차단점 | 6 | **3 해결 · 1 부분** | 2 (실행 필요) | ▓▓▓▓▓▓░░░░ 58% |
|
||||
|
||||
## 2. 설치 폴더 186개
|
||||
@@ -119,6 +120,36 @@
|
||||
|
||||
**재구현 필수** — 간접비 항목마다 `calc_mode ∈ {recalculated, carry_original, proportional_from_original, delta_times_rate, manual}` 을 저장할 것. 전부 재계산으로 두면 이 세 항목에서 **85만 원**이 어긋난다.
|
||||
|
||||
## 6.5 이번에 마저 끝낸 둘
|
||||
|
||||
### 일위대가 호표 참조 그래프 — ④단계 실증
|
||||
|
||||
봉화 2024의 `일위대가표` 45개 호표를 그래프로 세웠다.
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| 구성행이 다른 호표를 가리키고 **단가가 일치** | **22건** |
|
||||
| 이름은 같은데 단가가 다름 | **0건** |
|
||||
| 호표 간 참조를 가진 호표 | 18 / 45 |
|
||||
| **순환 참조** | **없음 → 위상순서 계산 가능** |
|
||||
| 참조 깊이 | 최대 **2단** (잎 27 · 1단 14 · 2단 4) |
|
||||
|
||||
⇒ 09번 §3의 「의존 그래프를 만들고 위상순서로 계산, 순환은 오류」가 **실데이터에서 성립**한다. 깊이가 얕아 재귀 깊이 걱정은 없고, 이름+규격 짝으로 참조를 찾을 수 있다(불일치 0건).
|
||||
|
||||
### 설계변경 직접비 — 당초·변경 양쪽 원 단위 재현
|
||||
|
||||
계류보전 2025(변경)의 `BOQ11`은 한 표에 **차수1(당초)·차수2(변경)를 나란히** 담는다. 양쪽을 각각 다시 세웠다.
|
||||
|
||||
| | 직접재료비 | 직접노무비 | 산출경비 | 행금액 절사 |
|
||||
|---|---:|---:|---:|---|
|
||||
| 차수1 (당초) | 40,003,539 ✅ | 32,976,276 ✅ | 20,047,669 ✅ | 294건 무결 |
|
||||
| 차수2 (변경) | 26,448,347 ✅ | 41,778,438 ✅ | 16,915,256 ✅ | 294건 무결 |
|
||||
| **증감** | −13,555,192 | **+8,802,162** | −3,132,413 | |
|
||||
|
||||
**전 항목 1원도 안 틀림.** 같은 말단 행 집합(98행)에 차수별 수량·단가를 걸어 계산하면 되고, **제외금액 구역(`C*`) 판정도 차수와 무관하게 같다.**
|
||||
|
||||
이로써 6장의 간접비 모드와 합쳐 **변경설계가 직접비부터 간접비까지 전부 재현**된다.
|
||||
|
||||
## 7. 남은 일 — 할 수 있는 것과 없는 것
|
||||
|
||||
### 지금 자료로 할 수 있는 것
|
||||
@@ -127,8 +158,9 @@
|
||||
|---:|---|---|
|
||||
| ~~1~~ | ~~남은 시트 훑기~~ | ✅ **완료** — 30종 전부. 새 규칙 없음, 집계표 ±1 원인 밝힘 |
|
||||
| ~~2~~ | ~~중기시간금액집계표~~ | ✅ **완료** — 절사(수량 × 시간당 성분단가) |
|
||||
| 1 | 일위대가 호표 **참조 그래프** 복원 → 위상순서 계산 재현 | ④단계를 실증으로 |
|
||||
| 2 | 설계변경 **직접비 증감**까지 원 단위 재현 | 6장을 전 항목으로 넓힘 |
|
||||
| ~~1~~ | ~~일위대가 참조 그래프~~ | ✅ **완료** — 순환 없음, 깊이 2단, 불일치 0 (§6.5) |
|
||||
| ~~2~~ | ~~설계변경 직접비 증감~~ | ✅ **완료** — 당초·변경 전 항목 원 단위 일치 (§6.5) |
|
||||
| 1 | **표본 늘리기** | 지금 자료로 할 수 있는 것은 사실상 다 함. 규모·기간 의존 요율 좌표와 수공·실적 형식은 **새 표본이 있어야** 열림 |
|
||||
|
||||
### 끝내 못 하는 것
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""설계변경 직접비 — 차수1(당초)·차수2(변경)를 내역에서 다시 세워 출력과 대조."""
|
||||
import sys, io, pathlib, zipfile, struct, re, warnings
|
||||
from decimal import Decimal as D, ROUND_DOWN
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||||
warnings.filterwarnings("ignore"); import openpyxl
|
||||
def dn(x): return D(x).quantize(D(1), rounding=ROUND_DOWN)
|
||||
def dbf(zf,name):
|
||||
b=zf.read(name); nrec,hlen,rlen=struct.unpack_from("<IHH",b,4)
|
||||
f=[];o=32
|
||||
while b[o]!=0x0D: f.append((b[o:o+11].rstrip(b"\0").decode("cp949","replace"),b[o+16]));o+=32
|
||||
rows=[]
|
||||
for i in range(nrec):
|
||||
r=b[hlen+i*rlen:hlen+(i+1)*rlen]; d={};c=1
|
||||
for nm,ln in f: d[nm]=r[c:c+ln].decode("cp949","replace").strip(); c+=ln
|
||||
rows.append(d)
|
||||
return rows
|
||||
def g(r,k):
|
||||
try: return D(r.get(k) or "0")
|
||||
except Exception: return D(0)
|
||||
STRUCT=re.compile(r"^(T\d|E\d|C\d|:)$")
|
||||
def direct(boq, s):
|
||||
excl=set()
|
||||
for i,r in enumerate(boq):
|
||||
bd=(r.get("BDCODE"+s) or r.get("BDCODE1") or "").strip()
|
||||
if bd.startswith("C"):
|
||||
end=r.get("ENDCODE"); st=next((j for j,q in enumerate(boq) if q.get("SORTCODE")==end),None)
|
||||
if st is not None:
|
||||
for k in range(min(st,i),max(st,i)+1): excl.add(k)
|
||||
leaf=[r for i,r in enumerate(boq)
|
||||
if (r.get("BDCODE"+s) or r.get("BDCODE1")) and not STRUCT.match((r.get("BDCODE"+s) or r.get("BDCODE1")).strip()) and i not in excl]
|
||||
ok=bad=0
|
||||
for r in leaf:
|
||||
for c,a in (("MAMT","M_AMT"),("LAMT","L_AMT"),("SAMT","S_AMT")):
|
||||
if dn(g(r,"QTY"+s)*g(r,c+s))==g(r,a+s): ok+=1
|
||||
else: bad+=1
|
||||
return (sum(g(r,"M_AMT"+s) for r in leaf), sum(g(r,"L_AMT"+s) for r in leaf),
|
||||
sum(g(r,"S_AMT"+s) for r in leaf), len(leaf), ok, bad)
|
||||
|
||||
BASE=pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서\20250527 2025년 계류보전사업(기번1-영덕.병곡.영리.산214-1)(변경)")
|
||||
stc=next(BASE.rglob("*.STC")); xls=next(p for p in BASE.rglob("*.xlsx") if p.stem==stc.stem)
|
||||
boq=dbf(zipfile.ZipFile(stc),"BOQ11.DBF")
|
||||
ws=openpyxl.load_workbook(xls,data_only=True)["공사원가계산서"]
|
||||
T={}
|
||||
for row in ws.iter_rows(min_row=1,max_row=45,max_col=6,values_only=True):
|
||||
cells=["" if v is None else v for v in row]
|
||||
lab="".join(str(c) for c in cells[:3] if isinstance(c,str)).replace(" ","")
|
||||
b=next((c for c in cells[3:4] if isinstance(c,(int,float))),None)
|
||||
c2=next((c for c in cells[4:5] if isinstance(c,(int,float))),None)
|
||||
if lab and b is not None: T[lab]=(D(str(b)), D(str(c2)) if c2 is not None else None)
|
||||
|
||||
print("### 설계변경 직접비 — 내역에서 다시 세움 (%s)" % stc.stem[:44])
|
||||
for s,tag in (("1","당초"),("2","변경")):
|
||||
M,L,S,n,ok,bad = direct(boq,s)
|
||||
print("\n-- 차수%s (%s) 말단 %d행, 행금액 절사 일치 %d / 불일치 %d" % (s,tag,n,ok,bad))
|
||||
for lab,calc,key in (("직접재료비",M,"순공사원가재료비직접재료비"),("직접노무비",L,"노무비직접노무비"),("산출경비",S,"경비산출경비")):
|
||||
pair=T.get(key)
|
||||
exp = pair[0] if s=="1" else (pair[1] if pair else None)
|
||||
if exp is None: print(" %-10s 계산 %14s (목표 없음)"%(lab,f"{calc:,}")); continue
|
||||
print(" %-10s 계산 %14s 출력 %14s %s"%(lab,f"{calc:,}",f"{exp:,}","일치" if calc==exp else "차 %+d"%(calc-exp)))
|
||||
M1,L1,S1,*_=direct(boq,"1"); M2,L2,S2,*_=direct(boq,"2")
|
||||
print("\n-- 증감")
|
||||
for lab,a,b,key in (("직접재료비",M1,M2,"순공사원가재료비직접재료비"),("직접노무비",L1,L2,"노무비직접노무비"),("산출경비",S1,S2,"경비산출경비")):
|
||||
print(" %-10s %+15s" % (lab, f"{b-a:,}"))
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""일위대가 호표 참조 그래프 — 구성행이 다른 호표를 가리키는지, 위상순서가 성립하는지."""
|
||||
import sys, io, pathlib, warnings, re, collections
|
||||
from decimal import Decimal as D, ROUND_DOWN
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||||
warnings.filterwarnings("ignore"); import openpyxl
|
||||
def dn(x): return D(x).quantize(D(1), rounding=ROUND_DOWN)
|
||||
def dec(v):
|
||||
if v in (None,""): return None
|
||||
try: return D(str(v))
|
||||
except Exception: return None
|
||||
P = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서\04.산불 봉화\01. 내역서\2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.xlsx")
|
||||
wb = openpyxl.load_workbook(P, data_only=True)
|
||||
|
||||
def blocks(sheet):
|
||||
ws=wb[sheet]
|
||||
rows=[list(r) for r in ws.iter_rows(min_row=1,max_row=ws.max_row,max_col=12,values_only=True)]
|
||||
out=[]; cur=None; title=None
|
||||
for r in rows:
|
||||
t=(str(r[0]) if r[0] else "").strip()
|
||||
k=t.replace(" ","")
|
||||
if re.match(r"^제?\d+호표$", k): cur={"no":int(re.sub(r"\D","",k)),"title":None,"comp":[],"sum":None}; out.append(cur); continue
|
||||
if cur is None: continue
|
||||
if cur["title"] is None and t and r[3] and not r[2]:
|
||||
cur["title"]=(t, str(r[1] or "").strip(), str(r[3] or "")); continue
|
||||
if k=="합계": cur["sum"]=r; cur=None; continue
|
||||
if t: cur["comp"].append(r)
|
||||
return out
|
||||
|
||||
B = blocks("일위대가표")
|
||||
print("### 일위대가표 호표 %d개" % len(B))
|
||||
# 호표 합계(단가) 사전
|
||||
unit = {}
|
||||
for b in B:
|
||||
if b["title"] and b["sum"]:
|
||||
unit[(b["title"][0], b["title"][1])] = dec(b["sum"][5])
|
||||
print(" 제목으로 찾을 수 있는 호표 %d개" % len(unit))
|
||||
|
||||
edges=collections.Counter(); hit=miss=0; ex=[]
|
||||
for b in B:
|
||||
for c in b["comp"]:
|
||||
nm=(str(c[0]) if c[0] else "").strip(); sz=str(c[1] or "").strip()
|
||||
up=dec(c[4])
|
||||
if up is None: continue
|
||||
key=(nm,sz)
|
||||
if key in unit and unit[key]==up:
|
||||
edges[b["no"]]+=1; hit+=1
|
||||
elif key in unit:
|
||||
miss+=1
|
||||
if len(ex)<4: ex.append((b["no"],nm[:16],sz[:12],up,unit[key]))
|
||||
print("\n 구성행이 다른 호표를 가리키고 단가가 일치: %d건" % hit)
|
||||
print(" 이름은 같은데 단가가 다름: %d건" % miss)
|
||||
for e in ex: print(" 제%d호표 %-16s %-12s 구성 %s ≠ 호표합계 %s" % e)
|
||||
print("\n 호표 사이 참조를 가진 호표 %d개 (전체 %d개)" % (len(edges), len(B)))
|
||||
|
||||
# 순환 검사
|
||||
adj=collections.defaultdict(set)
|
||||
byname={}
|
||||
for b in B:
|
||||
if b["title"]: byname[(b["title"][0],b["title"][1])]=b["no"]
|
||||
for b in B:
|
||||
for c in b["comp"]:
|
||||
key=((str(c[0]) if c[0] else "").strip(), str(c[1] or "").strip())
|
||||
if key in byname and byname[key]!=b["no"]: adj[b["no"]].add(byname[key])
|
||||
color={}
|
||||
cyc=[]
|
||||
def dfs(u, path):
|
||||
color[u]=1
|
||||
for v in adj[u]:
|
||||
if color.get(v)==1: cyc.append(path+[u,v])
|
||||
elif color.get(v) is None: dfs(v, path+[u])
|
||||
color[u]=2
|
||||
for b in B:
|
||||
if color.get(b["no"]) is None: dfs(b["no"], [])
|
||||
print(" 순환 참조: %s" % ("없음 → 위상순서 계산 가능" if not cyc else cyc[:2]))
|
||||
depth={}
|
||||
def dep(u):
|
||||
if u in depth: return depth[u]
|
||||
depth[u]=0
|
||||
depth[u]= 1+max([dep(v) for v in adj[u]], default=-1)
|
||||
return depth[u]
|
||||
for b in B: dep(b["no"])
|
||||
mx=max(depth.values()) if depth else 0
|
||||
print(" 참조 깊이 최대 %d단 (분포: %s)" % (mx, dict(collections.Counter(depth.values()))))
|
||||
Reference in New Issue
Block a user