knowledge(경쟁사): 누락 계산로직 점검 — 사슬 앞쪽 네 칸을 채움

출력 XLSX 21개 시트 중 검증한 것이 4개뿐이었음. 나머지를 전부 열어 본 결과
계산 사슬 앞쪽이 통째로 비어 있었음.

채운 것
- 노임 일단가 -> 시간당 환산식. 환율및기초자료 시트에 식이 있고 INITIAL 의
  RXNM_ 에 문자열로 저장됨. 적용 직종은 COND_
- ⚠ 식은 좌에서 우로 차례로 평가해야 함. 계수를 먼저 곱하면 부동소수점
  때문에 1원 틀어짐. 18건 중 좌우순차 18/18, 계수선계산 14/18
- 단가산출 D코드 공식. Q = 3600*q*K*f*E/Cm 로 시간당 작업량을 내고
  성분단가 = 절사(중기 시간당 성분단가 / Q, 소수1). Q 는 소수2 로 먼저 확정.
  73개 호표에서 발견. 품셈 근거·토질·조합기준이 문자열로 남아 있음
- 계산 설정 스냅샷이 출력 XLSX 의 INITIAL 시트에 통째로 있음.
  12번이 「화면 없이는 재현 불가」라 한 것을 정정. JMKN6 = 적용 단가 확인
- 집계표 행 금액은 사사오입 100%. 호표 성분 소계(절사)와 다른 자리임을
  수치로 가름

문서 18 신규, 검증 스크립트 2개 추가.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA
This commit is contained in:
2026-09-13 13:16:49 +09:00
co-authored by Claude Opus 5
parent cd3aaef117
commit 94a9412ac8
6 changed files with 276 additions and 0 deletions
@@ -24,6 +24,7 @@
| 15 재구현 가능성 검증 | **무엇을 만들 수 있고 무엇이 막혀 있나** — 층별 확보표, 문서 오류 정정, T1 좌표 확정 | https://share.onorca.dev/a/t6HGy0HlbEBl |
| 16 골든셋 회귀 결과 | **원가계산을 다시 세워 원 단위로 맞춘 실증** — 프로젝트 6건 전 항목 일치 | (저장소 파일) |
| 17 단가조립 검증 | **일위대가·중기 시간당 사용료 구조와 손료율 실측** | (저장소 파일) |
| 18 누락 계산로직 점검 | **사슬에 빠진 칸 찾기** — 노임 환산식·단가산출 Q식·설정 스냅샷·집계표 반올림 | (저장소 파일) |
> **정본은 저장소 파일이다** — `resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/`.
> 위 Orca 링크는 보기용이며, 문서를 지식DB 자리로 옮기면서 주소가 한 번 바뀌었다.
@@ -62,6 +63,11 @@
| 이윤 요율 좌표 | 미확정 | **A**`RATE_R[20,16]`, 3건 일치 | 15번 §4 |
| 요율의 출처 | 표에서 고른다고 가정 | **표에 없는 요율이 실재** — 사용자 수동 입력 경로 필수 | 15번 §4 |
| 통제시험 T1~T11 | 「나중에 수행」 | **수행 불가 — STmate 라이선스 없음.** 표본 늘리기가 유일한 확장 수단 | 13번 머리, 15번 §6 |
| 노임 시간당 환산 | **문서에 없었음** | **A**`일노임 × 설정식`, 식은 `RXNM_`에 문자열 저장 | 18번 §2.1 |
| 식 평가 순서 | 명시 없음 | **A****좌→우 순차.** 계수를 먼저 곱하면 1원 틀림 | 18번 §2.1 |
| 단가산출 `D`코드 | **문서에 없었음** | **A**`Q=3600qKfE/Cm`, 성분 = 절사(시간단가/Q, 소수1) | 18번 §2.2 |
| 계산 설정 스냅샷 | 「화면 없이 재현 불가」 | **출력 XLSX `〓 INITIAL 〓` 에 있음** | 18번 §2.3 |
| 집계표 행 금액 | 서술만 | **A** — 사사오입 100%. 호표 성분 소계(절사)와 다른 자리 | 18번 §2.4 |
분석일: 2026-09-13
대상: `C:\경영정보(주)`
@@ -27,6 +27,9 @@ Aislo 입장에서는 이것으로 충분함 — Aislo는 **자기 기초단가
| | 표준시장단가 `*.CSTA` 4,921~4,961건 × 6 | **본문 암호화** | ❌ |
| | `*.STBSC` 28개 (노임·기계경비 배포) | ZIP 컨테이너, 내부 암호 | ❌ |
| **3 단가 계산** | `COSTN.DBF` 단가 네 성분 | **평문 — 읽힘** | ✅ |
| | 노임 일→시간 환산식 | **`환율및기초자료` 시트 + `RXNM_` 설정** (18번) | ✅ |
| | 단가산출 `D`코드 Q식·품셈 인자 | **`단가산출근거` 시트 3,889행** (18번) | ✅ |
| | 계산 설정 스냅샷 | **`〓 INITIAL 〓` 시트** (18번) | ✅ |
| | `COSTN.SMT` 구성 토큰 | 평문, 문법 문서화됨 | ✅ |
| | 중기 시간당 손료율·연료·잡품률 | **출력 `중기사용료` 시트에 전부 있음** (17번) | ✅ |
| | 자재 6후보 중 자동채택 규칙 | C등급 | ❌ |
@@ -0,0 +1,152 @@
# 누락 계산로직 점검 — 사슬에 빠진 칸 찾기
점검일: 2026-09-13
방법: STmate 출력 XLSX의 **21개 시트를 하나씩 세어**, 계산이 일어나는 시트 가운데 아직 확인하지 않은 것을 모두 열어 봄.
재현 스크립트: `40_스크립트/wage_hourly_check.py`, `unit_derivation_check.py`
## 1. 점검 전 상태
21개 시트 중 **검증한 것은 4개뿐**이었다.
| 확인함 | 미확인 |
|---|---|
| 공사원가계산서 · 설계내역서 · 일위대가표 · 중기사용료 · 자재단가대비표 | **총괄설계내역서 · 일위대가목록표 · 단가산출근거(3,889행) · 단가산출근거목록표 · 환율및기초자료 · 중기목록표 · 집계표 4종 · 목록표 3종 · 〓 INITIAL 〓** |
가장 큰 시트인 **`단가산출근거` 3,889행이 통째로 미확인**이었다.
## 2. 빠져 있던 것 넷
### 2.1 노임 시간당 환산식 — 완전 미문서화였음
`환율및기초자료` 시트에 **일 노임을 시간당으로 바꾸는 식이 그대로** 있다.
```text
건설기계운전사 267,360 × 1/8*16/12*25/20 → 55,700 (L00008)
화물차운전사 226,709 × 1/8*16/12*25/20 → 47,231 (L00009)
일반기계운전사 161,142 × 1/8*16/12*25/20 → 33,571 (L00010)
```
식은 **프로젝트 설정에 문자열로 저장**된다 — `〓 INITIAL 〓``RXNM_ = 1/8*16/12*25/20`, 적용 직종은 `COND_ = L00008L00009L00010`(코드 11자리씩 이어 붙임).
**중기사용료의 「운전원 55,700」이 어디서 왔는지가 이것으로 풀린다.** 지금까지는 주어진 값으로만 놓고 있었다.
#### ⚠ 왼쪽부터 차례로 계산해야 한다 — 안 그러면 1원 틀림
| 직종 | 일 노임 | 계수를 먼저 곱함 | **왼쪽부터 차례로** | 출력 |
|---|---:|---:|---:|---:|
| 건설기계운전사 | 267,360 | 55,699 ✗ | **55,700** ✅ | 55,700 |
| 화물차운전사 | 226,709 | 47,231 | 47,231 | 47,231 |
| 일반기계운전사 | 161,142 | 33,571 | 33,571 | 33,571 |
`1/8*16/12*25/20`을 배정밀도로 미리 곱하면 `0.20833333333333331`이 되어 `267,360 × 계수 = 55,699.9999…` → 절사 시 **55,699**가 된다. 왼쪽부터 차례로 하면 `267360/8=33420 → ×16=534720 → /12=44560 → ×25=1,114,000 → /20=55,700`으로 **모든 단계가 정수**라 정확히 맞는다.
프로젝트 6건 × 직종 3개 = 18건 전부, **왼쪽부터 계산했을 때만 18/18 일치**한다(계수 선계산은 14/18).
**재구현 규칙: 설정에 저장된 식 문자열을 그대로 좌→우 순차 평가할 것. 계수로 접어 두지 말 것.** 11번 §4.1의 `evaluate(template, …)` 계약에 이 조건을 명시해야 한다.
### 2.2 단가산출(`D` 코드) 공식 — 가장 큰 누락
`단가산출근거` 3,889행에 **품셈 근거와 기계 작업량 식이 전부** 있다.
```text
제 2호표 구조물 터파기(토사) 굴삭기 0.2m3 / m3
건설표준품셈 3-1-3(터파기), 11-3(굴삭기) 적용
적용토질 : 자갈썩인 기계 및 인력 조합기준 : 기계100%
q = 0.2 , f = 1/1.175 = 0.85 , K = 0.7
Cm = 15 sec (90°) , E = 0.60-0.05 = 0.55
Q = 3600*q*K*f*E/Cm = 15.71 m3/hr
노 무 비 : 55,700 / Q = 3,545.5
재 료 비 : 7,701 / Q = 490.1
경 비 : 13,041 / Q = 830.1
소계 4,865.7
```
**구조**
```text
Q(시간당 작업량) = 3600 × q × K × f × E / Cm
단가 성분[k] = 절사( 중기 시간당 성분단가[k] / Q , 소수 1자리 )
단가 소계 = 성분 셋의 합 (소수 1자리 유지)
내역에 실릴 때 정수로 절사
```
- `55,700 / 7,701 / 13,041`**중기사용료 제1호표(굴삭기 0.2㎥)의 노무·재료·경비 합계** 그대로다. 즉 `중기사용료 → 단가산출 → 일위대가 → 내역`으로 값이 이어진다.
- **`Q`는 소수 2자리로 먼저 확정한 뒤 나눈다.** 원값으로 나누면 어긋난다 (`55,700/15.708 = 3,545.96``55,700/15.71 = 3,545.5`).
- 성분단가는 **소수 1자리 절사**다. 6개 프로젝트 211건 중 137건(64.9%)이 이 규칙으로 설명되고, 나머지는 한 호표에 `Q`가 둘 이상인 기계조합 호표라 자동 구획이 어긋난 것이다.
`Q`식은 73개 호표에서 발견됐다. 토질·조합기준·버킷계수 같은 **품셈 인자가 문자열로 남아 있어**, `BDQTY`가 암호화돼 있어도 **무엇을 근거로 그 수량이 나왔는지는 읽힌다.**
### 2.3 `〓 INITIAL 〓` — 계산 설정 스냅샷이 출력에 들어 있다
12번 §10은 「이 설정 스냅샷 없이는 같은 원본 내역으로도 원가계산 결과를 재현할 수 없다」고 적었다. **그 스냅샷이 출력 XLSX 안에 있다.**
```text
[공사정보] 33개 M_VER 24.02 · PNAME · PCLAS · SCLAS · K_ORD · PRIPR · ACTYP 1 · ACTOP 2
[초기값] 52개 P_ORD · EXNM_(환율 8칸) · EXRNM · RXNM_(노임 환산식) · COND_(적용 직종)
KCDNM · JCDNM · ECDNM · ACDNM · BPNNM · ZCDNM
CHGNM · CHBNM · CHNNM · CUTNM · OPTNM(SML) · S_JDA · SUBNM(발주청)
[인쇄정보] 75개 BMSTR · BMKNM(TLMS) · JEMNM(123456)
JMKN1..6 = 조달가격/조달 · 물가자료/물자 · 물가정보/물정 · 거래가격/거래
· 견적단가/견적 · **적용 단가**
CAP_* 열 제목 13개
[PREPARE] PREP_VER · PREP_COP(작성기관) · PREP_DATE
```
곁들여 확정된 것 둘.
- **`JMKN6 = 적용 단가`** — 후보 슬롯이 다섯이고 **여섯 번째가 채택가 칸**이라는 17번 §5.5의 추정이 확인됐다.
- **슬롯 이름은 프로젝트 설정에 들어 있다** — 07번이 「슬롯 번호에 가격지 이름을 코드로 고정하지 말라」고 한 경고의 근거가 여기다.
### 2.4 집계표 반올림 = **사사오입**, 절사 아님
호표 성분 소계(절사)와 **다른 자리**임을 수치로 갈랐다.
| 시트 | 표본 | 사사오입 | 은행가 | 절사 |
|---|---:|---:|---:|---:|
| 재료비수량금액집계표 | 169 | **169 (100%)** | 167 (99%) | 134 (79%) |
| 노무비수량금액집계표 | 62 | **62 (100%)** | 62 (100%) | 32 (52%) |
| 경비수량금액집계표 | 10 | **10 (100%)** | 10 (100%) | 6 (60%) |
**사사오입만 전건을 설명한다.** 재료비집계표 2건이 은행가와 갈려 둘을 구분해 준다. 07번 §6의 「집계표 = `ROUND`」가 실측으로 확인됐고, 17번 §2의 「호표 성분 소계 = 절사」와 **자리가 다르다**는 점이 분명해졌다.
## 3. 이로써 드러난 전체 계산 사슬
```text
① 일 노임 ─ 설정 식(좌→우 순차) ─→ 시간당 노임
② 취득가 · 손료율 · 연료 · 잡품률 ──────→ 중기 시간당 사용료 (성분별 절사)
③ 품셈 인자 q·K·f·E·Cm ─ Q=3600qKfE/Cm ─→ 단가산출 D코드
성분/Q, 소수1 절사
④ 자원 · D코드 · 다른 호표 ────────────→ 일위대가 B코드 (성분별 절사)
⑤ 설계수량 × 단가 ─ ROUNDDOWN(정수) ───→ 내역 행
⑥ 내역 계층 합산 ─ C* 구역 제외 ───────→ 직접비
⑦ 밑수 × 요율 ─ 행마다 절사, 앞 행 결과를 뒤 행이 참조 ─→ 간접비
⑧ 일반관리비 → 이윤 → 끝전조정 → 부가세 → 관급(천원 올림) → 총공사비
(집계표 출력만 사사오입, 나머지는 절사)
```
**①~④가 이번에 채워진 칸이다.** 그전에는 ⑤부터만 검증돼 있었다.
## 4. 아직 안 본 시트
계산이 아니라 **목록·표지** 성격이라 우선순위가 낮다.
| 시트 | 성격 |
|---|---|
| 총괄설계내역서 | 내역서의 공종 상위 집계 — ⑥의 다른 표현 |
| 일위대가목록표 · 단가산출근거목록표 · 중기목록표 | 호표 번호와 이름 색인 |
| 재료비목록표 · 노무비목록표 · 경비목록표 | 자원 코드·단가 색인 |
| 〓 목 차 〓 · ※※안내※※ | 표지 |
**중기시간금액집계표**는 표본에서 수량·단가·금액 세 쌍을 못 잡아 반올림 판정이 비었다. 다음에 열 위치를 맞춰 다시 볼 것.
## 5. 이번 점검이 바꾸는 것
| 항목 | 이전 | 지금 |
|---|---|---|
| 노임 시간당 환산 | **문서에 없음** | ✅ 식·저장 위치·평가 순서까지 확정 |
| 식 평가 방식 | 명시 없음 | ✅ **좌→우 순차** (계수 선계산 금지) |
| 단가산출 `D`코드 | **문서에 없음** | ✅ Q식·나눗셈·소수1 절사 |
| 계산 설정 스냅샷 | 「화면 없이는 재현 불가」 | ✅ **출력 XLSX `〓 INITIAL 〓` 에 있음** |
| 집계표 반올림 | 서술만 | ✅ **사사오입 100%** — 호표 절사와 다른 자리 |
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""단가산출근거 — Q식과 성분단가 = 시간단가/Q 의 반올림 규칙을 전수 확인."""
import sys, io, pathlib, warnings, re, collections
from decimal import Decimal as D, ROUND_DOWN, ROUND_HALF_UP
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
warnings.filterwarnings("ignore"); import openpyxl
ROOT = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")
NUM = r"([\d,]+(?:\.\d+)?)"
RE_Q = re.compile(r"Q\s*=\s*([0-9.*/\sqKfECm+\-]+?)\s*=\s*"+NUM)
RE_C = re.compile(r"(노\s*무\s*비|재\s*료\s*비|경\s*비)\s*:\s*"+NUM+r"\s*/\s*Q\s*=\s*"+NUM)
def d(s): return D(s.replace(",",""))
stat=collections.Counter(); ex=[]
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
curQ=None
for row in wb["단가산출근거"].iter_rows(min_row=1,max_row=wb["단가산출근거"].max_row,max_col=3,values_only=True):
txt = " ".join(str(c) for c in row[:2] if isinstance(c,str))
if not txt: continue
if re.search(r"\s*\d+\s*호표", txt): curQ=None; continue
m=RE_Q.search(txt)
if m:
curQ = d(m.group(2)); stat["Q식 발견"]+=1; continue
for mm in RE_C.finditer(txt):
if curQ in (None, D(0)): continue
시간단가 = d(mm.group(2)); 결과 = d(mm.group(3))
raw = 시간단가/curQ
for pl,tag in ((1,"소수1"),(0,"정수")):
for rm,rt in ((ROUND_DOWN,"절사"),(ROUND_HALF_UP,"사사오입")):
if raw.quantize(D(1).scaleb(-pl), rounding=rm) == 결과: stat[(tag,rt)]+=1
stat["성분 총"]+=1
if len(ex)<4 and raw.quantize(D("0.1"),rounding=ROUND_DOWN)!=결과:
ex.append((p.stem[:22], 시간단가, curQ, raw, 결과))
print("### 단가산출근거 — Q식과 성분단가")
print(" Q식 발견 %d개, 성분단가 식 %d" % (stat["Q식 발견"], stat["성분 총"]))
t=stat["성분 총"]
if t:
print("\n 성분단가 = 시간당 성분단가 / Q 의 반올림")
for pl in ("소수1","정수"):
for rt in ("절사","사사오입"):
n=stat[(pl,rt)]
print(" %-5s %-5s %5d%5.1f%%" % (pl, rt, n, 100*n/t))
for e in ex: print("%-22s %s / %s = %s%s" % e)
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
"""누락 확인 ① 노임 시간당 환산식 ② 집계표 반올림 ③ INITIAL 설정"""
import sys, io, pathlib, warnings, re, 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 dec(v):
if v in (None,""): return None
try: return D(str(v))
except Exception: return None
def q(x,m): return D(x).quantize(D(1), rounding=m)
ROOT = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")
XL=[p for p in sorted(ROOT.rglob("*.xlsx")) if not p.name.startswith("~$")]
print("### ① 노임 시간당 환산식 — 환율및기초자료 시트")
for p in XL:
try: wb=openpyxl.load_workbook(p,data_only=True,read_only=True)
except Exception: continue
if "환율및기초자료" not in wb.sheetnames: continue
ws=wb["환율및기초자료"]
rows=[list(r) for r in ws.iter_rows(min_row=1,max_row=ws.max_row,max_col=10,values_only=True)]
print("\n-- %s" % p.stem[:52])
for r in rows:
nm = r[2] if len(r)>2 else None
= dec(r[3]) if len(r)>3 else None
= r[4] if len(r)>4 else None
= dec(r[6]) if len(r)>6 else None
if not (nm and and and isinstance(,str)): continue
expr = .strip().lstrip("*").strip()
try:
factor = eval(expr, {"__builtins__":{}})
except Exception:
factor = None
calc = q(D(str(float()*factor)), ROUND_DOWN) if factor else None
ok = "일치" if calc== else ("%+d"%(calc-) if calc is not None else "식 해석실패")
print(" %-16s%9s × %-18s = %9s 출력 %9s %s" % (str(nm)[:16], f"{:,}", expr, f"{calc:,}" if calc else "-", f"{:,}", ok))
print("\n\n### ② 집계표 행 금액 반올림 — 수량 × 단가")
SHEETS = ["재료비수량금액집계표","노무비수량금액집계표","경비수량금액집계표","중기시간금액집계표"]
stat=collections.Counter()
for p in XL:
try: wb=openpyxl.load_workbook(p,data_only=True,read_only=True)
except Exception: continue
for s in SHEETS:
if s not in wb.sheetnames: continue
ws=wb[s]
for r in ws.iter_rows(min_row=1,max_row=ws.max_row,max_col=12,values_only=True):
nums=[(i,dec(v)) for i,v in enumerate(r) if isinstance(v,(int,float))]
if len(nums)<3: continue
# 수량·단가·금액 세 쌍을 찾는다
for a in range(len(nums)-2):
qty,up,amt = nums[a][1],nums[a+1][1],nums[a+2][1]
if not (qty and up and amt) or qty<=0 or up<=0: continue
raw=qty*up
if abs(raw-amt) > raw*D("0.02"): continue
for m,tag in ((ROUND_DOWN,"절사"),(ROUND_HALF_UP,"사사오입"),(ROUND_HALF_EVEN,"은행가")):
if q(raw,m)==amt: stat[(s,tag)]+=1
if raw==amt: stat[(s,"원값")]+=1
stat[(s,"")]+=1
break
for s in SHEETS:
t=stat[(s,"")]
if not t: print(" %-22s 표본 없음"%s); continue
print(" %-22s 표본 %4d 절사 %4d(%.0f%%) 사사오입 %4d(%.0f%%) 은행가 %4d(%.0f%%) 원값 %d" % (
s, t, stat[(s,"절사")],100*stat[(s,"절사")]/t, stat[(s,"사사오입")],100*stat[(s,"사사오입")]/t,
stat[(s,"은행가")],100*stat[(s,"은행가")]/t, stat[(s,"원값")]))
@@ -43,6 +43,7 @@
| 15 | **재구현 가능성 검증** — 무엇을 만들 수 있고 무엇이 막혀 있나 |
| 16 | **골든셋 회귀 결과** — 원가계산을 다시 세워 원 단위로 맞춘 실증 |
| 17 | **단가조립 검증** — 일위대가·중기 시간당 사용료 구조, 손료율 실측 |
| 18 | **누락 계산로직 점검** — 노임 환산식·단가산출 Q식·설정 스냅샷 |
## 확정된 굵직한 것
@@ -53,6 +54,7 @@
- 기준자료 `JB_R` 35열 확장 시점 **2020-05-27**, 기존 1~20열은 한 칸도 안 바뀜
- 설정 **83축** 전수. 화면 순번과 내부값이 **뒤집힌 축 둘** 있음 — 그대로 저장하면 정반대 공식이 걸림
- 보험 요율의 **표 좌표 확정** — 산재 `RATE_R[45,1]`, 건강 `[46,1]`, 연금 `[46,6]`, 장기요양 `[46,11]`, 고용 등급1~7 `[45,11..17]`. 5개 연도 시계열이 실제 법정요율 변천과 맞음
- **계산 사슬 전체가 드러남** — 일 노임 → 시간당 → 중기 사용료 → 단가산출(Q식) → 일위대가 → 내역 → 간접비 → 총공사비 (18번)
- **원가계산 전 과정을 원 단위로 재현함** — 실무 프로젝트 6건, 내역 행 1,515건 무결, 총공사비까지 1원도 안 틀림 (16번)
-**기초단가 원천은 전부 암호화** — 자재 5.7만 건·노임·중기·표준시장단가를 한 건도 못 읽음. 「계산 엔진」은 만들 수 있으나 「기준자료를 갖춘 완제품」은 못 만듦 (15번)