diff --git a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/19_진행률_장부.md b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/19_진행률_장부.md index 1aaaf00e..4d96c9b5 100644 --- a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/19_진행률_장부.md +++ b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/19_진행률_장부.md @@ -12,7 +12,7 @@ | 실무 프로젝트 `STC` | 7 | 7 전부 | — | ▓▓▓▓▓▓▓▓▓▓ 100% | | STC 안의 표 | 16종 | **10종 읽고 씀** · 4종 빈 표 | **2종 암호화** (`BDQTY`·`SYSINFO`) | ▓▓▓▓▓▓▓▓▓░ 88% | | STmate 출력 `XLSX` | 6 | 6 전부 | — | ▓▓▓▓▓▓▓▓▓▓ 100% | -| 출력 시트 | **30종** | **14종 계산 검증** · 1종 부분 | 15종 (목록·표지·변형 내역서) | ▓▓▓▓▓░░░░░ 48% | +| 출력 시트 | **30종** | **30종 전부 훑음** (계산 있는 것 15종 규칙 확정) | — | ▓▓▓▓▓▓▓▓▓▓ 100% | | 계산 사슬 단계 | 8 | **8 전부** | — | ▓▓▓▓▓▓▓▓▓▓ 100% | | 09번이 꼽은 호환성 차단점 | 6 | **3 해결 · 1 부분** | 2 (실행 필요) | ▓▓▓▓▓▓░░░░ 58% | @@ -39,20 +39,40 @@ 설계변경내역서 · 설계변경금액대비표 · 설계변경증감대비표 ``` -### ◐ 부분 1종 +### ✅ 나머지 16종도 훑음 (2026-09-13) -`중기시간금액집계표` — 수량·단가·금액 열 위치를 못 맞춰 반올림 판정이 비어 있음. +전부 열어 본 결과 **새 계산 규칙은 나오지 않았고**, 대신 두 가지가 밝혀졌다. -### · 남은 15종 — 전부 **목록·표지·내역서 변형** +| 갈래 | 시트 | 결과 | +|---|---|---| +| 색인·목록 | 일위대가목록표 · 단가산출근거목록표 · 중기목록표 · 재료비목록표 · 노무비목록표 · 경비목록표 · 일식견적목록표 | **계산 쌍 없음** — 호표 번호·코드·단가 색인 | +| 표지 | 〓 목 차 〓 · ※※안내※※ | 계산 없음 | +| 내역서 변형 | **착공내역서** | 178건 **절사 100%** — 설계내역서와 같은 규칙 | +| | 총괄설계내역서 · 총괄내역서 | 공종 상위 집계, 새 규칙 없음 | +| | 설계변경내역서 | 당초·변경 두 벌이라 단순 쌍 검출로는 안 갈림. 금액 자체는 19번 §6에서 확정 | +| 집계표 | 중기시간금액집계표 · 일위대가수량금액집계표 · 단가산출근거수량금액집계표 · 일식견적수량금액집계표 | **단가 열이 없음** — 아래 | -| 갈래 | 시트 | -|---|---| -| 색인·목록 (계산 없음) | 일위대가목록표 · 단가산출근거목록표 · 중기목록표 · 재료비목록표 · 노무비목록표 · 경비목록표 · 일식견적목록표 | -| 집계표 변형 | 일위대가수량금액집계표 · 단가산출근거수량금액집계표 · 일식견적수량금액집계표 | -| 내역서 변형 | 총괄설계내역서 · 총괄내역서 · 착공내역서 | -| 표지 | 〓 목 차 〓 · ※※안내※※ | +#### 집계표에는 단가 열이 없다 -**새 계산 규칙이 나올 가능성은 낮다.** 집계표 변형 3종은 이미 확인한 집계표와 같은 반올림(사사오입)일 것으로 보이고, 내역서 변형 3종은 같은 값을 다른 묶음으로 낸 것이다. 다만 **확인은 안 했으므로 「확인 안 함」으로 센다.** +```text +호표 | 명칭 | 규격 | 수량 | 단위 | 합계 | 노무비 | 재료비 | 경비 + 2 | 굴삭기(무한궤도) | 0.7㎥ | 940.69 | 시간 | 91,099,241 | 52,396,433 | 16,946,530 | 21,756,278 +``` + +금액은 **`절사(수량 × 해당 호표의 시간당 성분단가)`** 다. 굴삭기 0.7㎥의 시간당 성분(노무 55,700 / 재료 18,015 / 경비 23,128)으로 검산하면 맞는다. + +| 장비 | 시간 | 노무 | 재료 | 경비 | +|---|---:|---|---|---| +| 굴삭기 0.2㎥ | 17.47 | ✅ | ✅ | ✅ | +| 굴삭기 0.7㎥ | 940.69 | ✅ | ✅ | ✅ | +| 덤프 15톤 | 379.54 | ✅ | ✅ | −1 | +| 덤프 2.5톤 | 150.52 | ✅ | −1 | −1 | + +#### ⚠ 표시 수량은 반올림된 값이다 + +어긋나는 세 칸은 전부 **−1**이다. 경비로 역산하면 굴삭기 0.7㎥의 실제 시간은 `21,756,278 / 23,128 = 940.68998…`인데 시트에는 `940.69`로 찍힌다. + +⇒ **집계표의 수량을 그대로 역산하면 1원씩 어긋난다.** 집계는 표시 수량이 아니라 **원수량 또는 행별 금액의 합**에서 나온다. 09번 §4의 「입력 원값과 표시 반올림값을 분리한다」가 바로 이 자리다. ## 4. 계산 사슬 8단계 — 전부 확인 @@ -105,10 +125,10 @@ | 순위 | 할 일 | 왜 | |---:|---|---| -| 1 | 남은 시트 15종 훑기 | 새 규칙은 낮지만 **확인 안 한 칸을 0으로 만드는 값어치** | -| 2 | `중기시간금액집계표` 열 맞춰 반올림 판정 | 유일한 「부분」 | -| 3 | 일위대가 호표 **참조 그래프** 복원 → 위상순서 계산 재현 | ④단계를 실증으로 | -| 4 | 설계변경 **직접비 증감**까지 원 단위 재현 | 6장을 전 항목으로 넓힘 | +| ~~1~~ | ~~남은 시트 훑기~~ | ✅ **완료** — 30종 전부. 새 규칙 없음, 집계표 ±1 원인 밝힘 | +| ~~2~~ | ~~중기시간금액집계표~~ | ✅ **완료** — 절사(수량 × 시간당 성분단가) | +| 1 | 일위대가 호표 **참조 그래프** 복원 → 위상순서 계산 재현 | ④단계를 실증으로 | +| 2 | 설계변경 **직접비 증감**까지 원 단위 재현 | 6장을 전 항목으로 넓힘 | ### 끝내 못 하는 것 diff --git a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/40_스크립트/sheet_sweep.py b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/40_스크립트/sheet_sweep.py new file mode 100644 index 00000000..751b2790 --- /dev/null +++ b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/40_스크립트/sheet_sweep.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +"""남은 시트 전수 훑기 — 머리 몇 줄과 수량×단가=금액 반올림 판정.""" +import sys, io, pathlib, warnings, collections +from decimal import Decimal as D, ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") +warnings.filterwarnings("ignore"); import openpyxl +def q(x,m): return D(x).quantize(D(1), rounding=m) +def dec(v): + if v in (None,""): return None + try: return D(str(v)) + except Exception: return None +ROOT = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서") +REST = ["중기시간금액집계표","일위대가수량금액집계표","단가산출근거수량금액집계표","일식견적수량금액집계표", + "총괄설계내역서","총괄내역서","착공내역서","설계변경내역서", + "일위대가목록표","단가산출근거목록표","중기목록표","재료비목록표","노무비목록표","경비목록표","일식견적목록표"] +stat = collections.Counter(); head = {} +for p in sorted(ROOT.rglob("*.xlsx")): + if p.name.startswith("~$"): continue + try: wb=openpyxl.load_workbook(p,data_only=True,read_only=True) + except Exception: continue + if "공사원가계산서" not in wb.sheetnames: continue + for s in REST: + if s not in wb.sheetnames: continue + ws=wb[s] + rows=[list(r) for r in ws.iter_rows(min_row=1,max_row=min(ws.max_row,400),max_col=14,values_only=True)] + if s not in head: + head[s]=[r for r in rows[:8]] + for r in rows: + nums=[(i,dec(v)) for i,v in enumerate(r) if isinstance(v,(int,float))] + for a in range(len(nums)-2): + qy,up,amt = nums[a][1],nums[a+1][1],nums[a+2][1] + if not (qy and up and amt) or qy<=0 or up<=0 or amt<=0: continue + raw=qy*up + if raw==0 or abs(raw-amt) > raw*D("0.015"): continue + for m,tag in ((ROUND_DOWN,"절사"),(ROUND_HALF_UP,"사사오입"),(ROUND_HALF_EVEN,"은행가")): + if q(raw,m)==amt: stat[(s,tag)]+=1 + stat[(s,"총")]+=1; break + +print("### 남은 시트 — 수량×단가=금액 반올림 판정") +print("%-26s %6s %10s %10s %10s" % ("시트","표본","절사","사사오입","은행가")) +for s in REST: + t=stat[(s,"총")] + if not t: + print("%-26s %6s (계산 쌍 없음 = 색인·표지 성격)" % (s[:26],"-")); continue + f=lambda k: "%d(%.0f%%)"%(stat[(s,k)],100*stat[(s,k)]/t) + print("%-26s %6d %10s %10s %10s" % (s[:26],t,f("절사"),f("사사오입"),f("은행가"))) + +print("\n\n### 각 시트 머리 (구조 확인)") +for s in REST: + if s not in head: continue + print("\n-- %s" % s) + for r in head[s]: + line=" | ".join(("" if v is None else (("%g"%v) if isinstance(v,float) else str(v)))[:15] for v in r[:10]) + if line.strip(" |"): print(" %s" % line)