Merge remote-tracking branch 'origin/dev' into sub_laptop_1

This commit is contained in:
2026-09-13 14:23:28 +09:00
52 changed files with 4116 additions and 17 deletions
@@ -6,21 +6,36 @@
| 문서 | 용도 | Orca 아티팩트 |
|---|---|---|
| 00 분석 요약과 읽는 법 (이 문서) | 전체 인덱스, 결론, Aislo 비교 | https://share.onorca.dev/a/uHEsfWTvedw1 |
| 01 전체 구조 | 실행 기술, 모듈, 파일 흐름, 기능 구조 | https://share.onorca.dev/a/jjxDJTOtRGq7 |
| 02 데이터셋과 파일형식 | DBF 스키마, 프로젝트 패키지, 기준자료, 암호화 경계 | https://share.onorca.dev/a/boO2-Fo4CFNi |
| 03 계산로직과 Aislo 비교 | 확인된 공식, 계층 합산, 단가·제비율 모델, 차이와 권고 | https://share.onorca.dev/a/fyN0SVUu3Oum |
| 04 계산 데이터계보 | 입력 필드 → 계산 → 결과 필드 단계별 추적 | https://share.onorca.dev/a/S1E38cIhGiz6 |
| 05 재구현 판정기준과 근거목록 | 확정등급, 조사 표본, 파이프라인, 중간값 계약 | https://share.onorca.dev/a/8ktCQIIDvMuP |
| 06 규칙1·7 | RATE 좌표, 간접비 순서, 끝전·VAT | https://share.onorca.dev/a/z2v9o5Wfaim0 |
| 07 규칙2·3·4·5 | 직접비 단가, 중기, BDQTY, 자재 채택 | https://share.onorca.dev/a/o0HLxAXG8Lhg |
| 08 규칙6·8·9 | 재계산, 변경설계, 일괄변경 | https://share.onorca.dev/a/z58ZjH9Nu_Pb |
| 00 분석 요약과 읽는 법 (이 문서) | 전체 인덱스, 결론, Aislo 비교 | https://share.onorca.dev/a/jAFkUlfWjAOv |
| 01 전체 구조 | 실행 기술, 모듈, 파일 흐름, 기능 구조 | https://share.onorca.dev/a/YrTXvA_VN7OI |
| 02 데이터셋과 파일형식 | DBF 스키마, 프로젝트 패키지, 기준자료, 암호화 경계 | https://share.onorca.dev/a/dkOGvoT4jO8S |
| 03 계산로직과 Aislo 비교 | 확인된 공식, 계층 합산, 단가·제비율 모델, 차이와 권고 | https://share.onorca.dev/a/CPOaNTIjmPDA |
| 04 계산 데이터계보 | 입력 필드 → 계산 → 결과 필드 단계별 추적 | https://share.onorca.dev/a/j231bE3HQqNw |
| 05 재구현 판정기준과 근거목록 | 확정등급, 조사 표본, 파이프라인, 중간값 계약 | https://share.onorca.dev/a/NbIR-TMqLPjc |
| 06 규칙1·7 | RATE 좌표, 간접비 순서, 끝전·VAT | https://share.onorca.dev/a/U1-yAvd5qfbj |
| 07 규칙2·3·4·5 | 직접비 단가, 중기, BDQTY, 자재 채택 | https://share.onorca.dev/a/c7n8XytHiqGt |
| 08 규칙6·8·9 | 재계산, 변경설계, 일괄변경 | https://share.onorca.dev/a/G21A30euiRNl |
| 09 통합 재구현 명세 | 내부 데이터셋, 계산 순서, 호환 어댑터, 골든시험 | https://share.onorca.dev/a/7eULzjbfyZHM |
| 10 System 186개 파일 분석장부 | 전 파일별 판독 상태, 실제 형식, 핵심 모듈과 기준자료 구조 | https://share.onorca.dev/a/zHj5IbV4dfnL |
| 11 CPM 계산코어 정적분석 | 식 평가, 반올림, 간접비·안전비·변경설계·끝전조정 재구현 명세 | https://share.onorca.dev/a/U6HIpHCbt9XF |
| 12 원가계산 4형식과 RATE표 복원 | 일반·수공·실적일반·실적수공 공식, 화면 열, 내부 선택값 | https://share.onorca.dev/a/y-R9TQqgEFfo |
| 13 통제시험 목록 | 정적 분석으로 못 가르는 경계를 시험 11개로 분리, 우선순위·완료판정 | https://share.onorca.dev/a/lcmxdrG2SWvE |
| 14 최종 분석완료 보고서 | 도달 수준, A등급 확정 목록, 남은 것, 완료 판정 기준, 산출물 | https://share.onorca.dev/a/AmgldP7q_Qzc |
| 15 재구현 가능성 검증 | **무엇을 만들 수 있고 무엇이 막혀 있나** — 층별 확보표, 문서 오류 정정, T1 좌표 확정 | https://share.onorca.dev/a/t6HGy0HlbEBl |
| 16 골든셋 회귀 결과 | **원가계산을 다시 세워 원 단위로 맞춘 실증** — 프로젝트 6건 전 항목 일치 | (저장소 파일) |
| 17 단가조립 검증 | **일위대가·중기 시간당 사용료 구조와 손료율 실측** | (저장소 파일) |
| 18 누락 계산로직 점검 | **사슬에 빠진 칸 찾기** — 노임 환산식·단가산출 Q식·설정 스냅샷·집계표 반올림 | (저장소 파일) |
| 19 진행률 장부 | **어디까지 봤고 무엇이 남았나** — 원천별 실측 진행률, 차단점 현황 | (저장소 파일) |
| 20 설정파일과 보고서 카탈로그 | **`Start8x.Ini`·`SET_REP`** — 간접비 코드 사전 301~370, 서식 75종 | (저장소 파일) |
| 21 정정과 트랙 통합 | **`SMT` 판정 정정 · 지식DB 트랙 셋 통합 · `PRJLST.SMT` 설정 원장 · 암호화의 성격** | (저장소 파일) |
| 22 UI 폼 분석 | **폼 233개 중 3개만 봤던 것** — 선택축 272개, `ACTTYP`·자리수 키·일괄변경 규칙 | (저장소 파일) |
| 23 `COSTN.SMT` 전수파싱 | **단가산출 원장** — 참조 1,570·품 1,460·Q식 187개(10꼴)·운반 사이클 | (저장소 파일) |
| 24 모듈구조 전수 | **46개 모듈 export**`DP_Common` 73함수, 플러그인 규약, 동글, 8차 해소 | (저장소 파일) |
| 25 설정키 전수와 식템플릿 | **설정 사슬 닫기**`PRJLST.SMT` 키 241종, 간접비 식 26개 | (저장소 파일) |
> **정본은 저장소 파일이다** — `resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/`.
> 위 Orca 링크는 보기용이며, 문서를 지식DB 자리로 옮기면서 주소가 한 번 바뀌었다.
> 09·10·11·12·13·14번 링크는 재발행이 속도제한에 걸려 **아직 옛 주소**다 — 그 여섯은 저장소 파일을 볼 것.
중요: C등급으로 표시한 규칙은 “분석 누락”이 아니라 보유 정적 자료에 입력 전후 대응값이 없거나 레코드가 난독화되어 확정 불가능한 경계다. 문서에는 각 경계를 확정할 STmate 통제 대조시험까지 명시했다. System 폴더는 186/186개 모두 내부 구조까지 판독했으며 미판독 파일은 0개다.
@@ -40,6 +55,55 @@
| `JB_R` 35열 확장 시점 | "2020 이후" | **2020-05-27 확정** — 기존 1~20열은 한 셀도 안 바뀜 | 10번 §4 |
| `JB_R` 열 의미 | 미상 | **1~5 간접노무비 / 6~10 기타경비** (공종 5개 순서 확정), 21·26 국가유산 | 10번 §4 |
| 원가계산 설정 선택축 | 16축 목록 | **83축 전수** — 공통 29 + 형식별 12/11/16/15 | 12번 §10.1-2 |
| `COSTN.DBF` 암호화 | 본문 암호화라고 적음 | **평문 — 읽힘.** 암호화는 `BDQTY`·`SYSINFO` 둘뿐 | 02번, 15번 §3 |
| `TAMT=MAMT+LAMT+SAMT` | 예외 없는 불변조건 | **예외 있음** — 59행 중 2행(관급 흄관) | 11번 §3, 15번 §3 |
| 프로젝트 RATE 표 | 4종 | **6종**`RATE_A`·`RATE_B` 추가 발견 | 02번, 15번 §3 |
| 보험 요율 좌표 | 미확정 | **A** — 산재·건강·연금·장기요양·고용 등급1~7·산안비 좌표 확정 | 12번 §9.1, 15번 §4 |
| 원가계산 전 과정 | 규칙 서술 | **A 실증** — 프로젝트 6건을 원 단위로 다시 세워 전 항목 일치 | 16번 |
| 내역 말단 행 판별 | 없음 | **A**`BDCODE1` 접두 `T/E/C/:` 가 구조 행 | 16번 §4.1 |
| 제외금액 구역 | 서술만 | **A**`C*` 로 닫히는 구역은 직접비에서 뺌 | 16번 §4.2 |
| 끝전조정 | B | **A 실증** — 차액 → `ROUND(차액/1.1)` → 이윤 차감 | 16번 §4.4 |
| 차수 인계 | 「가정하지 말 것」 경고만 | **A 실증**`BOQ12`가 현재안, 다음 파일 차수1로 인계 | 16번 §4.5·§4.6, 08번 |
| 중기 시간당 사용료 | 손료 계수 없음 | **A** — 손료율·취득가·연료·잡품률 25개 장비 실측 | 17번 §3·§4 |
| 호표 성분 소계 반올림 | 미확정 | **A** — 절사 (중기 398건 100%) | 17번 §2 |
| 자재 6후보 자동채택 | C | **A 실증** — 0 아닌 후보 중 **최저가**. 복수후보 76/76 | 17번 §5.5, 07번 §5 |
| 이윤 요율 좌표 | 미확정 | **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 |
| 변경설계 간접비 모드 | 「모드가 여럿」까지만 | **A 실증** — 건강·장기요양·연금은 **당초액 유지**, 나머지는 재계산 | 19번 §6, 08번 §2 |
| 간접비 항목 순서·코드 | 화면 텍스트 복원 | **A**`KAN_ORD_1/2` 코드 목록, 301~370 사전 | 20번 §2.1·§2.2 |
| 후보 슬롯 이름 | 「설정에 있을 것」 | **설치본마다 다름을 실물 확인** (유통 물가 ↔ 견적단가) | 20번 §2.3 |
| 보고서 서식 | 30종 관찰 | **카탈로그 75종** — 수공양식·표준시장 서식 존재 | 20번 §3 |
| `*.SMT` | ⛔ 암호화라고 적었음 | **정정 — 평문.** `COSTN.SMT` 단가 전개, `PRJLST.SMT` 설정 원장 | 21번 §1 |
| 프로젝트 요율 | 기준표 좌표로 찾아야 한다고 봄 | **`PRJLST.SMT`에 값으로 저장** — 좌표는 읽기에 불필요 | 21번 §3 |
| 수공형식 세부경비 8개 | 표본 없어 미상 | **항목 확정**`R_S_SUDO`·`BOKRI`·`SOMO`·`TONGSIN`·`SEGUM`·`PEGI`·`DOSEO`·`JIGUP` | 21번 §3.2 |
| 암호화의 성격 | 「기초단가가 막혔다」 | **사온 물가지 데이터 보호** — 뚫을 일이 아니라 살 일 | 21번 §4 |
| 지식DB STmate 트랙 | 하나로 알고 있었음 | **셋이 서로 몰랐음** — ①계산로직 ②STC포맷 ③프로그램분석 | 21번 §2 |
| UI 폼 | 3개만 뽑아 씀 | **선택축 272개 전수** — 폼 40개 | 22번 |
| 일괄변경 수동값 (T10) | ⛔ 실행 필요 | **화면 안내문에 있음** — 보호표시·0원 제외 | 22번 §3.1 |
| `ACTTYP`·`ACTOPT` | 뜻 모름 | **A** — 표준시장 적용 방식·끝전 보정 우선순위 | 22번 §3.3 |
| `KCDNM` 등 6키 | 미확정 | **A** — 소수 자리수 설정 | 22번 §3.4 |
| 변경 차수 | 최대 3차로 봄 | ⚠ **화면은 8차** — 저장 구조 재확인 필요 | 22번 §3.3 |
| `COSTN.SMT` 내용 | 「평문」까지만 | **전수 파싱** — 중기 참조 1,504건, 성분 셋을 각각 | 23번 |
| `Q` 식 | 한 꼴로 알았음 | ⚠ **최소 10가지 꼴** — 하드코딩 금지 | 23번 §3 |
| 운반 사이클 | 안 봤음 | **적재+왕복+적하** 구조·인자 확인 | 23번 §4 |
| 할증률 | `BDQTY` 암호화로 막힘 | ◐ **적용값 92건은 읽힘**. 편집은 **금액·율 두 갈래** | 23번 §5, 24번 §5 |
| 모듈 분석 | 핵심 12개 | **46개 전수 export** | 24번 |
| 식 평가기 함수 | `CALC_STR` 만 | **A**`DP_Common` 73함수. 조건 `IIF`·중간변수·`MFRAC` 포함 | 24번 §2 |
| 재계산 진입점 | 한 곳으로 봄 | ⚠ **31개 모듈이 `Calc_Kanjub` 보유** | 24번 §1 |
| 라이선스 | 「없음」 | **Rainbow Sentinel SuperPro 하드웨어 동글** | 24번 §4 |
| 엑셀 | 단방향 출력 | **양방향**`CONV_XLS_2_ST`·`XLS_2_BDREC` | 24번 §3 |
| 변경 차수 8차 | ⚠ 미해결 | ✅ **해소** — 한 파일 4벌, 8차는 분리형 파일 선택 | 24번 §7 |
| 프로젝트 저장키 | 일부만 | **241종 전수**`C_`금액 62·`R_`요율 39·`I_`선택 41·`D_`밑수·`S_`결과 | 25번 §1 |
| 밑수 저장 | 안 남는 줄 알았음 | **`D_`(밑수)·`S_`(결과) 짝으로 저장** | 25번 §1.1 |
| 추가금액 | 화면 문자열만 | **저장 구조** `ADD_AMT_단계_행_칸` | 25번 §1.3 |
| 간접비 식 템플릿 | 일부 | **26개 전수** | 25번 §2 |
| 식에 조건 분기 | 몰랐음 | ⚠ **`IF(...)` 존재** — 평가기 필수 요건 | 25번 §2.2 |
분석일: 2026-09-13
대상: `C:\경영정보(주)`
@@ -47,7 +47,9 @@ STini.MEM, COSTN.SMT, PRJLST.SMT
- 6개 원천/4개 안: `AMT_11..64`, `PG_11..54`
- 표시·분류: `GUN`, `GUN2`, `IFORM`, `ACTTYP`, `EDT_DATE`
본문은 암호화되어 있으나 스키마는 평문이다. memo 내용은 `COSTN.SMT`에 저장된다.
**2026-09-13 정정 — `COSTN.DBF` 본문은 암호화돼 있지 않다.** 맛보기 STC의 71개 레코드를 그대로 읽었다(`B00001 흄관기초 D=800mm M TAMT1=23441 MAMT1=2719 LAMT1=20722 SAMT1=0`). 실무 STC 7건도 같다. 암호화된 것은 `BDQTY.DBF``SYSINFO.DBF` 둘뿐이다. memo 내용은 `COSTN.SMT`에 저장된다.
이 정정은 재구현 가능성에 크게 작용한다 — **단가의 네 성분이 프로젝트 파일 안에 평문으로 들어 있으므로 골든셋을 단가 수준까지 뽑을 수 있다.**
### `COSTA.DBF` — 별도 단가 자료
@@ -74,6 +76,25 @@ STini.MEM, COSTN.SMT, PRJLST.SMT
15필드. `CODE`, `NAME`, `BQCODE`, `QTY0..9`, `IFORM`, `XCODE`로 구성된다. 맛보기에는 레코드가 없으나 수량 계산식 또는 수량 슬롯을 코드화하는 자리로 보인다.
### `RATE*.DBF` — 프로젝트에 복사된 제비율 표 (6종)
맛보기 STC에는 **여섯 종**이 들어 있다. 기존 문서가 다루던 넷 말고 `RATE_A`·`RATE_B`가 더 있다.
| 테이블 | 형태 | 값이 든 행 | 대응 기준파일 | 비고 |
|---|---|---:|---|---|
| `RATE.DBF` | 41행 × 10열 | 37 | `JB_1` | |
| `RATE_M.DBF` | 25행 × 10열 | 11 | `JB_M` | 행자부기준 간접노무비. 값이 `15.0`처럼 **백분율** |
| `RATE_R.DBF` | 50행 × 35열 | 38 | `JB_R` 신형 | |
| `RATE_RA.DBF` | 50행 × 20열 | 27 | `JB_RA` | |
| **`RATE_A.DBF`** | 25행 × 10열 | 21 | **대응 JB 파일 없음** | 값이 `0.642 / 0.232 / 0.270 / 0.875 / 0.912`처럼 **0~1 계수**. 앞 5열만 씀 |
| **`RATE_B.DBF`** | 35행 × 10열 | 35 | **대응 JB 파일 없음** | 같은 0~1 계수를 **10열 모두** 씀 |
`RATE_A``RATE_M`과 형태가 같지만 **값의 성격이 다르다**(계수 vs 백분율). 전 행이 다르므로 같은 표가 아니다.
`RATE_B`의 10열은 RATE 편집 화면 `rGrd_RateB`의 숫자열 개수와 정확히 맞는다 — `재 / 직노 / 노 / 재+직노 / 재+노`가 토목·건축 두 열군으로 10개다. 따라서 **`RATE_B`는 수공형식(`S`)의 경비별 밑수 배분 계수표**로 보인다. **등급 B.**
**두 표는 맛보기 STC에만 있고 실무 STC 7건에는 없다.** 수공형식을 쓰지 않는 프로젝트에는 만들어지지 않는 것으로 보이며, 이는 **수공형식 재현 시험(T11)의 전제 자료가 실무 표본에 없다**는 뜻이다.
### `KONGJONG.DBF` — 공종 분류
`CODE`, `NAME`, `SIZE` 세 필드의 작은 분류표다.
@@ -89,7 +89,13 @@ price_candidate(material_id, revision_id, slot_no, source_name, page, observed_p
price_adoption(material_id, revision_id, chosen_slot_no?, adopted_price, method, user_override)
```
자동선정은 현재 C등급이다. 최저가·최신가·특정 슬롯 우선 중 어느 하나라고 단정할 근거가 없다. 동일 구현 전에는 후보 조합을 통제한 STmate 시험이 필수다: 6개 슬롯에 서로 다른 값, 동률, 0, 공란을 배치하고 채택값과 `PG_1` 변화를 기록한다.
**2026-09-13 — 자동선정은 「0이 아닌 후보 중 최저가」로 확정됐다. A등급 실증.**
출력 XLSX의 `자재단가대비표`에 후보 다섯 칸과 적용 단가·출처가 나란히 있어 직접 검정했다. 실무 6건 전수에서 **후보 값이 2종 이상인 76건 전부, 3종 이상인 36건 전부 최저가**였고, 최저가가 아닌 후보를 택한 건은 0이다. 상세는 17번 §5.5.
`COSTN`의 여섯 칸 중 **여섯 번째가 적용 단가 칸**으로 보이며, 이는 「노무 L은 후보6과 채택가가 거의 일치」라는 위 관찰과 맞는다.
남은 것은 **사용자 수동 고정이 걸렸을 때의 우선순위**다. 표본에는 수동 고정 흔적이 없었다. 통제시험(13번 T10)에서 6개 슬롯에 서로 다른 값·동률·0·공란을 배치하고 수동 고정을 섞어 채택값과 `PG_1` 변화를 기록한다.
## 6. 반올림 경계
@@ -35,7 +35,22 @@ STC에는 두 형태가 동시에 확인된다.
1. 한 파일 안에 `BOQ11`, `BOQ12`를 함께 저장하는 통합형: 소광 표본은 각각 90행, 89행이다.
2. 변경 차수를 별도 STC로 저장하면서 그 파일의 `BOQ11`을 현재안으로 쓰는 분리형: 소광 변경1차 표본은 115행이다.
또한 `COSTN`과 여러 필드의 접미사 1~4는 원설계와 최대 3개 변경차수의 값을 나란히 담을 수 있다. 따라서 “접미사 n=항상 BOQ1n”으로 기계적으로 가정하지 말고 프로젝트 메타데이터와 실제 존재 테이블을 함께 해석해야 한다.
또한 `COSTN`과 여러 필드의 접미사 1~4는 원설계와 최대 3개 변경차수의 값을 나란히 담을 수 있다.
### 실증으로 확정된 차수 규칙 — A등급 (2026-09-13, 16번 §4.5·§4.6)
```text
통합형 한 STC 안에 BOQ11(당초) + BOQ12(현재안)
⚠ STmate 출력은 BOQ12 를 쓴다. BOQ11 을 현재안으로 잡으면 통째로 틀린다.
분리형 변경 차수를 새 STC 로 저장
새 파일 BOQ11 차수1 = 앞 파일의 BOQ12 ← 소광 표본에서 원 단위 일치 확인
새 파일 BOQ11 차수2 = 이번 변경분
```
`PRJLST.TCOSTn = MCOSTn + LCOSTn + SCOSTn` 으로 **직접공사비 합계**다. 소광 변경1차는 당초 173,311,386 → 변경 257,410,433, 증감 +84,099,047이다.
따라서 “접미사 n=항상 BOQ1n”으로 기계적으로 가정하지 말고 **실제 존재하는 테이블과 `PRJLST` 를 함께 보고 현재안을 고를 것.**
### STmate의 실제 변경설계 계산모드 — A등급
@@ -67,7 +82,24 @@ STC에는 두 형태가 동시에 확인된다.
- 비교표는 `original`, `previous`, `current`, `delta`를 모두 계산한다.
- 삭제 항목은 물리 삭제하지 않고 현재 차수에 0/제외 상태로 남겨 코드 대응을 보존한다.
- 신규 항목은 안정적인 내부 ID와 차수별 표시 순서를 가진다.
- 간접비 항목마다 `recalculated`, `proportional_from_original`(방법 A), `delta_times_rate`(방법 B), `carry_original`, `manual`을 **계산모드 값으로 저장**한다. 절사 비선형성 때문에 모드가 다르면 원 단위 결과가 갈리므로, 모드를 하나로 고정하면 STmate 결과를 재현할 수 없다.
- 간접비 항목마다 `recalculated`, `proportional_from_original`(방법 A), `delta_times_rate`(방법 B), `carry_original`, `manual`을 **계산모드 값으로 저장**한다.
### 실데이터로 확인된 모드 — A등급 (2026-09-13)
계류보전 2025(변경)의 `공사원가계산서`는 당초·변경·증감을 나란히 낸다. **직접노무비가 `32,976,276 → 41,778,438`(+8,802,162)로 늘었는데 세 항목은 증감이 0이다.**
| 항목 | 당초 | 변경 | 증감 | 재계산했다면 | 모드 |
|---|---:|---:|---:|---:|---|
| 간접노무비 | 4,715,607 | 5,974,316 | +1,258,709 | 5,974,316 ✅ | `recalculated` |
| 산재보험료 | 1,341,831 | 1,699,998 | +358,167 | 1,699,998 ✅ | `recalculated` |
| 고용보험료 | 380,688 | 482,302 | +101,614 | 482,302 ✅ | `recalculated` |
| **건강보험료** | 1,328,500 | 1,328,500 | **0** | 1,683,108 | **`carry_original`** |
| **노인장기요양보험료** | 172,040 | 172,040 | **0** | 217,961 | **`carry_original`** |
| **연금보험료** | 1,686,389 | 1,686,389 | **0** | 2,136,526 | **`carry_original`** |
요율은 당초·변경이 같다(간접노무비 14.300%, 산재 3.560%, 고용 1.010%). 요율을 바꾼 것이 아니라 **항목마다 모드가 다르다.** 건강·장기요양·연금은 **사후정산 대상**이라 설계 단계에서 당초액을 그대로 가져간다 — §10의 「직접입력」 옵션이 이것이다.
**전부 재계산으로 두면 이 세 항목에서 85만 원이 어긋난다.** 모드를 하나로 고정하면 STmate 결과를 재현할 수 없다.
- Aislo 자체 기본값으로는 전 차수 독립 재계산(`recalculated`)을 권장하되, **STmate 호환 모드와 구분해서** 둔다.
## 3. 일괄 단가변경·물가변동
@@ -60,6 +60,19 @@ CODE="W" 또는 복합행 → TAMT=MAMT+LAMT+SAMT
따라서 총액을 네 번째 독립 비목으로 저장하거나 계산하면 안 된다. 총액은 세 성분의 합이며, 단일 비목행에서는 해당 성분만 총액과 같고 나머지는 0이다. **등급 A**.
#### ⚠ 실데이터에는 예외가 있다 (2026-09-13 실측)
맛보기 STC의 `COSTN.DBF` 71행 중 값이 있는 59행을 대조하면 **57행은 맞고 2행이 어긋난다.**
| CODE | 명칭 | 규격 | `TAMT1` | `MAMT1+LAMT1+SAMT1` | 차이 |
|---|---|---|---:|---:|---:|
| `M00010` | 흄관 | 800mm | 65,880 | 41,608 | **+24,272** |
| `M00011` | 흄관 | 900mm | 83,920 | 53,784 | **+30,136** |
둘 다 `CODE``M`(자재)이고 흄관 — 관급자재로 다루는 품목이다. 즉 **바이너리의 조건식은 「항상 참인 불변조건」이 아니라 「어긋나면 경고할 검사식」**이며, 관급·비과세처럼 별도로 취급하는 자재에는 예외가 있다.
**재구현 지침** — 이 관계를 **하드 오류로 막으면 실데이터를 거부하게 된다.** 경고로 두고 `component_sum_exempt` 같은 사유 필드를 함께 저장할 것. 예외의 정확한 조건은 통제시험 **T7**(13번 문서) 대상이다.
## 4. 공통 계산 함수의 의미
### 4.1 식 평가
@@ -254,7 +254,32 @@ DBF 필드는 `RATE0..RATE9` 또는 `RATE_01..RATE_35`, 레코드는 고정 25/4
- 2020-05-27 확장으로 **열 21 = 국가유산 간접노무비, 열 26 = 국가유산 기타경비**가 붙었고 기존 1~20열은 한 셀도 바뀌지 않았다.
- `JB_RA`는 43개 전 기간 20열 그대로다. 실적형식에는 국가유산 공종이 없다.
**남은 것** — 각 부속표 그리드(고용보험·환경보전·보증수수료 계열)가 `JB_1`/`JB_R`의 몇 번째 행부터 적재되는지. 열 접근이 실행 중 문자열 조립이라 코드 상수로 읽을 수 없으므로, 저장 이벤트 역추적 또는 통제 동적 시험이 필요하다.
### 9.1 확정된 좌표 (2026-09-13, 15번 문서)
출력 XLSX 「공사원가계산서」 비고 칸에 **실제 쓰인 밑수와 요율이 적혀 있다.** 그 값을 같은 프로젝트의 `RATE_*.DBF`에서 되찾아 좌표를 역추적하고, 실무 7건 교차로 후보가 하나뿐인 자리만 취했다.
| 항목 | 좌표 | 근거 |
|---|---|---|
| 간접노무비 | `RATE_R[10, 1]` | 2건 유일 |
| 산재보험료 | `RATE_R[45, 1]` | 3건 유일 |
| 건강보험료 | `RATE_R[46, 1]` | 2건 유일 |
| 연금보험료 | `RATE_R[46, 6]` | 시계열 |
| 노인장기요양보험료 | `RATE_R[46, 11]` | 1건 유일 + 시계열 |
| 고용보험료 등급 1~7 | `RATE_R[45, 11..17]` | 4건 유일 + 시계열 |
| 산업안전보건관리비 | `RATE_R[20, 17]` 율 · `[20, 18]` 기초액 | 3건 유일 |
**시계열이 실제 법정요율 변천과 일치해 A등급이다.**
| 좌표 | 2019 | 2022 | 2024 | 2025 | 2026 |
|---|---:|---:|---:|---:|---:|
| `[45,1]` 산재 | 3.75 | 3.70 | 3.56 | 3.56 | 3.56 |
| `[46,1]` 건강 | 3.23 | 3.495 | 3.545 | 3.545 | 3.595 |
| `[46,6]` 연금 | 4.5 | 4.5 | 4.5 | 4.5 | 4.75 |
| `[46,11]` 장기요양 | 8.51 | 12.27 | 12.95 | 12.95 | 13.14 |
**`RATE_R` 행 45~49는 요율 행렬이 아니라 부속표 영역**이고, 행 0~44가 규모·기간 행렬이다. `[45,11..17]``rGrd_Rate_Kobo`(7행, 등급·요율) 그대로다.
**남은 것**`연금보험료`·`일반관리비`·`이윤`·`환경보전비`·`부가가치세`는 같은 값이 표 안 여러 곳에 있어 단독으로는 못 가른다. 그 값만 특이한 숫자로 바꿔 저장하는 통제시험(13번 T1)이 필요하다. 부속표 그리드(환경보전·보증수수료 계열)의 시작 행도 같은 시험 대상이다.
## 10. 계산 설정 입력
@@ -2,8 +2,31 @@
> 정적 분석만으로는 확정할 수 없는 경계를 **시험 단위로 갈라 놓은 유일한 목록**이다. 05~12번 문서에 흩어져 있던 "미확정·C등급" 항목을 여기로 모았다. 각 문서는 이제 이 목록을 가리킨다.
기준일: 2026-09-13
대상: `C:\경영정보(주)`**원본은 읽기만 한다.** 시험은 프로젝트 **복제본**에서 한다.
기준일: 2026-09-13 (전제 갱신 2026-09-13)
대상: `C:\경영정보(주)`**원본은 읽기만 한다.**
## ⛔ 전제 정정 — 이 시험들은 **실행할 수 없다**
**STmate 라이선스가 없어 프로그램을 띄울 수 없다.** 따라서 아래 T1~T11은 **수행 불가**이며, 「나중에 시험으로 확정한다」는 계획은 성립하지 않는다.
이 목록은 버리지 않고 **두 가지 용도로 남긴다.**
1. **무엇이 왜 확정 불가인지**를 항목별로 밝혀 둔 장부 — 어떤 값이 「모르는 것」인지 분명히 해 두어야 Aislo가 그 자리를 정책 플래그로 열어 둘 수 있다.
2. **라이선스가 생기거나 실무자가 대신 눌러 줄 수 있게 되면** 그대로 쓸 절차서.
**대신 쓸 수 있는 길**은 세 갈래이며, 이미 상당 부분 성과가 났다.
| 대체 경로 | 무엇을 풀었나 |
|---|---|
| **출력 XLSX 역추적** — 비고 칸에 밑수·요율이 적혀 있음 | 보험 요율 좌표 6개, 이윤 좌표 (15번 §4) |
| **골든셋 회귀** — 내역으로 원가계산을 다시 세워 대조 | 원가계산 전 과정, 끝전조정, 차수 규칙 (16번) |
| **출력 시트 전수 검정** — 일위대가·중기·자재단가대비표 | 성분 절사, 중기 손료율, 자재 채택=최저가 (17번) |
**표본을 늘리는 것이 통제시험을 대신하는 유일한 확장 수단이다.** 프로젝트마다 공사규모·기간·공종이 다르므로, 표본이 늘수록 좌표와 조건이 저절로 좁혀진다.
---
아래는 라이선스가 생겼을 때를 위한 원 절차서다. 시험은 프로젝트 **복제본**에서 한다.
## 0. 왜 시험이 필요한가
@@ -28,7 +51,19 @@
| 확정되는 것 | 그리드 → 행렬 좌표의 완전 매핑. `rate_standard()` 기준표를 하드코딩 없이 만들 수 있게 됨 |
| 근거 문서 | 12번 §8·§9, 10번 §4 |
이미 잡아 둔 것 — 행은 `0,5,10,…,45` 시작의 **5행 묶음**, `JB_R` 열 1~5=간접노무비·6~10=기타경비(토목/산설(토목)/조경/건축/산설(건축)), 신형 열 21·26=국가유산. **T1은 나머지 부속표의 시작 행만 남긴 시험이다.**
**2026-09-13 — T1 의 절반이 정적으로 풀렸다.** 출력 XLSX 비고 칸의 실제 요율을 프로젝트 `RATE_*`에서 역추적해 다음이 확정됐다(15번·12번 §9.1).
```text
간접노무비 RATE_R[10, 1]
산재보험료 RATE_R[45, 1] 건강보험료 RATE_R[46, 1]
연금보험료 RATE_R[46, 6] 장기요양 RATE_R[46, 11]
고용보험 등급1~7 RATE_R[45, 11..17]
산업안전보건관리비 RATE_R[20, 17] 율 · [20, 18] 기초액
```
시계열이 실제 법정요율 변천과 맞아 **A등급**이다. 행 45~49가 부속표 영역, 행 0~44가 규모·기간 행렬임도 갈렸다.
**T1 에 남은 것**`연금보험료`·`일반관리비`·`이윤`·`환경보전비`·`부가가치세`처럼 **같은 값이 표 안 여러 곳에 있는 항목**과, 환경보전·보증수수료 부속표의 시작 행. 이것들은 그 칸만 특이한 값(예: `99.99`)으로 바꿔 저장해 어디에 앉는지 보는 수밖에 없다.
### T2 — 금액·기간 구간 경계 방향
@@ -0,0 +1,188 @@
# 재구현 가능성 검증 — 모인 정보로 같은 프로그램을 만들 수 있나
검증일: 2026-09-13
방법: 05~14번 문서의 주장을 믿지 않고, **구현에 꼭 필요한 것을 층별로 세어 원본 파일과 직접 대조**함.
대상: `C:\경영정보(주)` 설치 폴더 186개 + 실무 STC 8건 + STmate 생성 XLSX 29건. **읽기만 함.**
## 1. 한 줄 답
**계산 엔진은 만들 수 있음. 기준자료를 갖춘 완제품은 못 만듦.**
STmate가 배포하는 **기초단가 원천이 전부 암호화**되어 있어 자재·노임·중기 단가를 한 건도 못 읽음. 반면 **프로젝트 파일(STC) 안에는 단가 결과가 평문으로 들어 있어** 「이 단가를 넣으면 이 내역·이 간접비가 나온다」는 재현은 가능함.
Aislo 입장에서는 이것으로 충분함 — Aislo는 **자기 기초단가 원천**(노임·자재·유가·환율)을 이미 따로 갖고 있으므로, STmate에서 가져올 것은 **계산 규칙**이지 기초자료가 아님.
## 2. 층별 확보 상태
| 층 | 구현에 필요한 것 | 확보 | 판정 |
|---|---|---|---|
| **1 파일 읽기** | STC = 무암호 ZIP | 8건 모두 암호 없음 | ✅ |
| | 프로젝트 표 18종 스키마·본문 | 16종 평문 | ✅ |
| | `BDQTY.DBF` (수량·할증 상세) | **본문 암호화** | ❌ |
| | `SYSINFO.DBF` | **본문 암호화** | ❌ |
| **2 기초단가 원천** | `JDCODE_M.BSC` 자재 57,580건 | **본문 암호화** | ❌ **치명** |
| | `JDCODE_L.BSC` 노임 460건 | **본문 암호화** | ❌ |
| | `JDCODE_X.BSC` 중기 833건 | **본문 암호화** | ❌ |
| | `JDCODE_Z.BSC` 33,927건 · `GRPCOD_Z` 12,125건 | **본문 암호화** | ❌ |
| | 표준시장단가 `*.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등급 | ❌ |
| **4 내역** | `BOQ11.DBF` 수량·단가·금액 | 평문 | ✅ |
| | 계층 집계(`SORTCODE`·`ENDCODE`·`ITM_TYP`) | 문서화됨 | ✅ |
| **5 간접비** | 항목 사전 30여 개 | 문서화됨 | ✅ |
| | 형식별 계산 순서 | 네 형식 모두 번호 매겨 복원 | ✅ |
| | 항목별 밑수 공식 | 화면 묶음 제목에서 확정 | ✅ |
| | 요율표 좌표 | **6개 항목 확정** (아래 4장), 나머지 진행 중 | ⚠→✅ |
| | `RATE_A`·`RATE_B` 표 | 이번에 발견, 의미 B등급 | ⚠ |
| **6 반올림** | 내부 함수·음수·절반값 | 은행가 반올림 확정 | ✅ |
| | 출력별 정책 | 확정 | ✅ |
| **7 출력** | `SET_REP.DBF` 75행 × 28열 | 평문 | ✅ |
## 3. 이번 검증에서 나온 문서 오류 셋
기존 문서가 실제와 어긋난 자리를 찾아 고침.
### ① `COSTN.DBF`는 암호화돼 있지 않음
02번이 「본문은 암호화되어 있으나 스키마는 평문」이라 적었으나 **사실이 아님.** 맛보기 71행, 봉화 248행을 그대로 읽음.
```text
B00001 흄관기초 D=800mm M TAMT1=23,441 MAMT1=2,719 LAMT1=20,722 SAMT1=0
```
**이 정정이 결론을 바꿈** — 단가의 네 성분이 프로젝트 안에 평문으로 있으므로 **골든셋을 단가 수준까지 뽑을 수 있음.** 암호화된 것은 `BDQTY``SYSINFO` 둘뿐임.
### ② `TAMT = MAMT + LAMT + SAMT` 는 예외가 있음
A등급 불변조건으로 적혀 있으나 실데이터에 **59행 중 2행이 어긋남.**
| CODE | 명칭 | `TAMT1` | `M+L+S` | 차이 |
|---|---|---:|---:|---:|
| `M00010` | 흄관 800mm | 65,880 | 41,608 | +24,272 |
| `M00011` | 흄관 900mm | 83,920 | 53,784 | +30,136 |
둘 다 관급으로 다루는 자재임. **하드 오류로 막으면 실데이터를 거부하게 됨** — 경고로 두고 사유를 함께 저장할 것.
### ③ 프로젝트 RATE 표는 넷이 아니라 여섯
맛보기 STC에 **`RATE_A`(25×10)·`RATE_B`(35×10)** 가 더 있음. 값이 `0.642 / 0.232 / 0.875` 같은 **0~1 계수**로, 백분율인 다른 표와 성격이 다름. `RATE_B`의 10열은 화면 `rGrd_RateB`의 숫자열 개수(`재/직노/노/재+직노/재+노` × 토목·건축)와 정확히 맞아 **수공형식 경비 밑수 배분표**로 보임.
**두 표는 맛보기에만 있고 실무 STC 7건에는 없음** — 수공형식 재현 시험의 전제 자료가 표본에 없다는 뜻임.
## 4. T1(요율 좌표)을 정적으로 깬 결과
**출력 엑셀 「공사원가계산서」의 비고 칸에 쓰인 밑수와 요율이 그대로 적혀 있음.**
```text
간접노무비 직접노무비 x 14.5%
산재보험료 (직접노무비+간접노무비) x 3.56%
노인장기요양보험료 건강보험료 x 12.95%
이윤 (노무비+경비+일반관리비) x 15%
```
이 값을 그 프로젝트의 `RATE_*.DBF` 안에서 되찾아 좌표를 역추적함. 실무 7건 전수 교차로 **후보가 하나뿐인 자리**만 취함.
| 항목 | 확정 좌표 | 근거 |
|---|---|---|
| 간접노무비 | `RATE_R[10, 1]` | 2건에서 유일 |
| 산재보험료 | `RATE_R[45, 1]` | 3건에서 유일 |
| 건강보험료 | `RATE_R[46, 1]` | 2건에서 유일 |
| 고용보험료 | `RATE_R[45, 17]` | 4건에서 유일 |
| 노인장기요양보험료 | `RATE_R[46, 11]` | 1건에서 유일 |
| 산업안전보건관리비 | `RATE_R[20, 17]` (율), `[20, 18]` (기초액) | 3건에서 유일 |
### 시계열 교차 검증 — A등급
기준파일 `JB_R`의 같은 자리를 연도별로 늘어놓으면 **실제 법정요율 변천과 정확히 일치함.**
| 좌표 | 2019 | 2022 | 2024 | 2025 | 2026 | 무엇인가 |
|---|---:|---:|---:|---:|---:|---|
| `[45, 1]` | 3.75 | 3.70 | 3.56 | 3.56 | 3.56 | 산재보험료율 |
| `[46, 1]` | 3.23 | 3.495 | 3.545 | 3.545 | 3.595 | 건강보험료율 |
| `[46, 6]` | 4.5 | 4.5 | 4.5 | 4.5 | 4.75 | 연금보험료율 |
| `[46, 11]` | 8.51 | 12.27 | 12.95 | 12.95 | 13.14 | 노인장기요양보험료율 |
`[45, 11..17]`은 **고용보험 등급 1~7**임. 값이 내림차순이고(2024: `1.57 / 1.30 / 1.13 / 1.06 / 1.03 / 1.02 / 1.01`) 개수가 화면 `rGrd_Rate_Kobo`의 7행과 맞음. 검증한 프로젝트는 모두 `열17`(7등급 이하)을 씀.
**`RATE_R`의 행 45~49는 요율 행렬이 아니라 부속표 영역**이고, 행 0~44가 규모·기간 행렬임.
### 항목이 두 부류로 갈림 — 통제시험 없이 밝힌 것
프로젝트 5건의 요율을 각자의 `RATE_*` 표에 겹쳐 보면 항목이 두 갈래로 나뉨.
**① 고정 칸** — 공사규모·기간과 무관. 전 프로젝트에서 같은 좌표.
```text
산재보험료 RATE_R[45, 1] 건강보험료 RATE_R[46, 1]
연금보험료 RATE_R[46, 6] 장기요양 RATE_R[46, 11]
고용보험 등급1~7 RATE_R[45, 11..17]
산업안전보건관리비 RATE_R[20, 17] 율 · [20, 18] 기초액 ← 3건 전부 유일
이윤 RATE_R[20, 16] ← 3건 전부 일치
```
**② 규모·기간에 따라 칸이 움직임** — 간접노무비·기타경비·일반관리비. 전 프로젝트에 공통인 좌표가 **없음.** 행이 `(공사규모 밴드, 공사기간 밴드)`로 정해지므로 고정 좌표라는 모델 자체가 틀림. 열은 공종(토목·산설·조경·건축·산설건축)이고 행이 움직인다.
### ⚠ 요율이 표에 아예 없는 경우가 실재함
| 프로젝트 | 항목 | 쓴 요율 | 그 프로젝트 `RATE_R` 안에 |
|---|---|---:|---|
| 2024 간선임도 (기번3) | 간접노무비 | 14.5% | **없음** |
| 2025 소광 (기번8) | 이윤 | 13.435% | **없음** |
즉 **사용자가 표를 거치지 않고 요율을 직접 써 넣은 것**이다. 11번 §8이 「순환이 생기면 사용자 수동 확정으로 넘긴다」고 한 경로가 실데이터에 그대로 나타났다.
**재구현 필수 조건** — 요율을 표에서 못 찾았다고 오류로 막으면 안 된다. `rate_source ∈ {table(coord), manual}` 을 항상 함께 저장하고, 수동값이면 좌표 없이 값만 보존해야 한다.
## 5. 그래서 무엇을 만들 수 있나
### 지금 자료로 가능한 것
1. **STC 읽기·쓰기 어댑터** — 표 16종 평문, ZIP 무암호
2. **내역 계산기** — 수량 × 단가 → 행 금액 → 계층 집계. 반올림 정책 확정
3. **간접비 계산기** — 항목 사전·형식별 순서·밑수·반올림 확정, 보험 요율 좌표 6개 확정
4. **원설계 골든셋 회귀** — 실무 7건의 `BOQ11` + `COSTN` + `RATE_*` + 출력 XLSX를 짝지어 **원 단위 대조** 가능
5. **변경설계 비교** — 소광 원설계/통합변경/변경1차 세 벌 보유
### 지금 자료로 불가능한 것
1. **기초단가 배포 체계** — 자재 5.7만 건·노임·중기·표준시장단가를 못 읽음. 암호를 풀지 않는 한 영구 불가
2. **수량 할증 재현**`BDQTY` 암호화로 `X_QTY`·`X_HAL` 실값을 못 봄. 출력에는 **할증 후 수량만** 남음
3. ~~중기 시간당 단가 재현~~**가능해짐** (17번). 출력 `중기사용료` 호표에 손료율·취득가·연료·잡품률이 다 들어 있음
4. **자재 자동채택** — 6후보 중 무엇을 고르는지 규칙 없음
5. **수공형식 검증**`RATE_A`·`RATE_B`가 실무 표본에 없음
## 6. 판정
| 물음 | 답 |
|---|---|
| STmate와 **똑같은 프로그램**을 만들 수 있나 | ❌ — 기초단가 원천이 암호화됨 |
| STmate와 **같은 답을 내는 계산 엔진**을 만들 수 있나 | ✅ **원가계산 부분은 실증 완료** (16번, 프로젝트 6건 원 단위 일치). 단가 조립·할증·자재채택은 여전히 불가 |
| STmate 프로젝트 파일을 **읽고 검산**할 수 있나 | ✅ — `BDQTY` 빼고 전부 |
| **Aislo 원가계산에 쓸 규칙**을 얻었나 | ✅ — 밑수·순서·반올림·요율 좌표 확보 |
**Aislo 목적으로는 필요한 정보가 모였음.** 「STmate 복제」는 애초에 불가능하고 목표도 아님.
### ⛔ 통제시험은 못 함 — 라이선스 없음
13번의 T1~T11은 **STmate를 띄워야 하는데 라이선스가 없어 수행 불가**다. 따라서 「나중에 시험으로 확정」이라 미뤄 둔 항목은 **다른 길로 풀거나, 모르는 채로 두고 Aislo에서 정책으로 여는 수밖에 없다.**
실제로 대체 경로가 잘 들었다 — 출력 XLSX 역추적·골든셋 회귀·출력 시트 전수 검정으로 09번이 꼽은 여섯 차단점 중 **둘을 완전히, 하나를 절반** 풀었다(17번 §7).
**남은 것을 늘릴 방법은 표본을 늘리는 것 하나뿐이다.** 공사규모·기간·공종·계산형식이 다른 프로젝트가 들어올수록 좌표와 조건이 저절로 좁혀진다. 특히 **수공형식(`S`)·실적형식(`TA`/`SA`) 표본이 하나도 없어** 그 세 형식은 지금 전혀 검증되지 않았다.
## 7. 다음에 할 것
| 순위 | 할 일 | 왜 |
|---:|---|---|
| ~~1~~ | ~~골든셋 회귀 구현~~ | ✅ **완료 — 16번.** 프로젝트 6건 전 항목 원 단위 일치 |
| 2 | 남은 요율 좌표(연금·일반관리비·이윤·환경보전비) 확정 | 통제시험 T1 나머지 |
| 3 | 중기 손료 계수를 **법령·품셈과 대조** | 값은 17번에서 확보했으나, Aislo 기본값은 현행 기준에서 가져와야 함 |
| 4 | `BDQTY` 대조시험 (T7) | 할증 규칙 — 여전히 막힌 유일한 입력 |
3번의 값 자체는 17번 §4에 25개 장비분이 있음. 다만 **Aislo 기본값은 STmate 값이 아니라 현행 「건설기계 경비 산정 기준」에서** 확정해야 하며, 17번 표는 대조용임.
@@ -0,0 +1,167 @@
# 골든셋 회귀 결과 — 원가계산을 원 단위로 다시 세워 맞췄다
실행일: 2026-09-13
방법: 프로젝트 STC의 내역 표(`BOQ11`/`BOQ12`)만 입력으로 삼아 **직접비 → 간접비 → 이윤 → 끝전조정 → 총공사비**를 처음부터 다시 계산하고, STmate가 뽑은 XLSX 「공사원가계산서」와 **원 단위로 대조**함.
재현 스크립트: `40_스크립트/golden_regression.py`
## 1. 결과 한 줄
**실무 프로젝트 6건 전부, 모든 항목이 1원도 틀리지 않고 맞았다.**
09번 문서가 정한 「완료 정의」 중 **원설계 재현 조건이 실증으로 충족**됐다. 이제 규칙은 문서상의 주장이 아니라 **돌아가는 계산**이다.
## 2. 전수 결과
| 프로젝트 | 말단 행 | 행 금액 대조 | 직접재료비 | 직접노무비 | 산출경비 | 간접비 |
|---|---:|---|:--:|:--:|:--:|:--:|
| 봉화 2024 (기번41 현동) | 72 | 216건 일치 | ✅ | ✅ | ✅ | ✅ |
| 영월 2024 (기번6) | 96 | 288건 일치 | ✅ | ✅ | ✅ | ✅ |
| 울진 대흥 2024 (기번3) | 129 | 387건 일치 | ✅ | ✅ | ✅ | ✅ |
| 영덕 계류보전 2025 (변경) | 98 | 294건 일치 | ✅ | ✅ | ✅ | — |
| 소광 2025 원설계 (기번8) | 43 | 129건 일치 | ✅ | ✅ | ✅ | ✅ |
| 소광 2025 변경 1차 (기번8) | 67 | 201건 일치 | ✅ | ✅ | ✅ | ✅ |
**행 금액 `ROUNDDOWN(수량 × 단가)` 대조 1,515건 중 불일치 0.**
## 3. 봉화 2024 — 처음부터 끝까지
| 항목 | 다시 계산한 값 | STmate 출력 | |
|---|---:|---:|:--:|
| 직접재료비 | 191,867,113 | 191,867,113 | ✅ |
| 직접노무비 | 426,802,539 | 426,802,539 | ✅ |
| 산출경비 | 162,613,763 | 162,613,763 | ✅ |
| 간접노무비 | 61,886,368 | 61,886,368 | ✅ |
| 산재보험료 | 17,397,325 | 17,397,325 | ✅ |
| 고용보험료 | 4,935,757 | 4,935,757 | ✅ |
| 건강보험료 | 15,130,150 | 15,130,150 | ✅ |
| 노인장기요양보험료 | 1,959,354 | 1,959,354 | ✅ |
| 연금보험료 | 19,206,114 | 19,206,114 | ✅ |
| 산업안전보건관리비 | 19,270,590 | 19,270,590 | ✅ |
| 기타경비 | 40,833,361 | 40,833,361 | ✅ |
| 환경보전비 | 6,250,267 | 6,250,267 | ✅ |
| 건설기계대여금 지급보증 | 3,125,133 | 3,125,133 | ✅ |
| 경비 소계 | 290,721,814 | 290,721,814 | ✅ |
| 일반관리비 | 58,276,670 | 58,276,670 | ✅ |
| **이윤 (끝전보정 후)** | **125,652,769** | **125,652,769** | ✅ |
| 총원가 | 1,155,207,273 | 1,155,207,273 | ✅ |
| 부가가치세 | 115,520,727 | 115,520,727 | ✅ |
| 도급공사비 | 1,270,728,000 | 1,270,728,000 | ✅ |
| 관급자재대 | 143,555,000 | 143,555,000 | ✅ |
| **총공사비** | **1,414,283,000** | **1,414,283,000** | ✅ |
## 4. 이번에 새로 밝혀진 규칙 일곱
문서에 없던 것들이다. 이것이 없으면 위 표가 맞지 않는다.
### 4.1 말단 행 판별 — `BDCODE1` 접두가 구조를 말한다
`BOQ11`은 말단 공종과 계층 제목·계를 한 테이블에 섞어 담는다. 구분은 `BDCODE1`의 접두다.
```text
T1~T5 계층 제목 행 (그 구역 소계를 담고 있음)
E1~E5 계(End) 행
C1~C5 제외금액 총계 행
: 페이지 넘김
그 외 실제 단가 코드 (D00021, M00053, B00012 …) → 말단 행
```
제목 행과 계 행이 **소계를 그대로 담고 있으므로** 전체를 더하면 9배 가까이 부풀어 오른다.
### 4.2 `C*` 로 닫히는 구역은 제외금액 구역
`T4 관급자재대` 로 열리고 `C4 총계(제외금액)` 로 닫히는 구역 안의 행은 **직접비에 넣지 않는다.** 봉화의 경우 이 구역이 143,554,309원이고, 빼지 않으면 직접재료비가 정확히 그만큼씩 두 번 더해진다(구역 안 품목 행 + `C4` 총계 행).
구역 경계는 `C*` 행의 `ENDCODE`가 여는 `T*` 행의 `SORTCODE`를 가리키는 것으로 찾는다.
### 4.3 산업안전보건관리비의 관급재 밑수 = 관급자재대 − 조달수수료
화면 수식은 `(직노+직재+간재+관급재/1.1) × 율 + 기초액`인데, 여기서 **관급재는 조달수수료를 뺀 원자재대**다.
| 무엇을 관급재로 넣나 | 율 부분 | 판정 |
|---|---:|---|
| 관급자재대 143,554,309 그대로 | 13,934,628 | ✗ +13,038 |
| **관급자재대 조달수수료 771,029 = 142,783,280** | **13,921,590** | **✅** |
### 4.4 끝전조정 — 차액을 세전으로 환산해 이윤에서 뺀다
봉화에서 보정 전후는 이렇다.
```text
보정 전 이윤 125,653,108
보정 전 총원가 1,155,207,612
보정 전 부가세 115,520,761
보정 전 도급공사비 1,270,728,373 ← 천원 단위가 아님
차액 = 1,270,728,373 1,270,728,000 = 373
세전조정 = ROUND(373 / 1.1) = 339 ← 사사오입
이윤 = 125,653,108 339 = 125,652,769 ✅
총원가 = 1,155,207,273 ✅
부가세 = ROUNDDOWN(총원가 × 10%) = 115,520,727 ✅
도급 = 1,270,728,000 ✅
```
**06번 §4가 세운 모델(`차액 → 세전 상당액 → 이윤·VAT 배분`)이 실데이터로 확인됐다.** 재구현은 `raw_amount / rounded_amount / adjustment_amount`를 반드시 분리해 저장해야 한다.
### 4.5 한 파일에 `BOQ11`·`BOQ12`가 같이 있으면 **`BOQ12`가 현재안**
소광 원설계 STC는 두 표를 함께 담는다.
| 표 | 행 | 말단 | 재료비 | 노무비 | 경비 | 출력과 |
|---|---:|---:|---:|---:|---:|:--:|
| `BOQ11` | 90 | 43 | 54,699,532 | 93,741,594 | 48,693,912 | ✗ |
| `BOQ12` | 89 | 43 | 47,987,107 | 82,246,518 | 43,077,761 | **✅** |
**STmate가 뽑은 XLSX는 `BOQ12`와 맞는다.** `BOQ11`을 무조건 현재안으로 잡으면 이 프로젝트만 통째로 틀린다. 처음 회귀에서 소광 한 건이 어긋난 원인이 이것이었다.
### 4.6 차수 인계 — 앞 파일의 `BOQ12`가 다음 파일의 `차수1`이 된다
| 무엇 | 재료비 | 노무비 | 경비 |
|---|---:|---:|---:|
| 원설계 파일 `BOQ12` | 47,987,107 | 82,246,518 | 43,077,761 |
| **변경1차 파일 `BOQ11` 차수1** | 47,987,107 | 82,246,518 | 43,077,761 |
| 변경1차 파일 `BOQ11` 차수2 | 75,735,506 | 119,733,078 | 61,941,849 |
**정확히 같다.** 08번이 「접미사 n = 항상 `BOQ1n` 으로 기계적으로 가정하지 말라」고 경고한 것이 옳았고, 실제 규칙은 이렇다.
```text
통합형 : 한 STC 안에 BOQ11(당초) + BOQ12(현재안). 출력은 BOQ12.
분리형 : 변경 차수를 새 STC 로 저장.
그 파일의 BOQ11 차수1 = 앞 파일의 BOQ12
그 파일의 BOQ11 차수2 = 이번 변경분
```
`PRJLST.TCOSTn = MCOSTn + LCOSTn + SCOSTn` 이며 **직접공사비 합계**다. 소광 변경1차는 당초 173,311,386 → 변경 257,410,433, 증감 **+84,099,047**이다.
### 4.7 관급자재대는 천원 **올림**
```text
관급자재대 = ROUNDUP(원자재대 143,554,309, 천원) = 143,555,000 ✅
총공사비 = 도급공사비 + 관급자재대 = 1,414,283,000 ✅
```
## 5. 이로써 확정으로 올라간 것
| 항목 | 이전 | 지금 |
|---|---|---|
| 내역 행 금액 = `ROUNDDOWN(수량 × 단가)` | A (수식 관찰) | **A 실증** — 1,515건 무결 |
| 간접비 항목별 밑수 | A (화면 라벨) | **A 실증** — 6건 프로젝트에서 원 단위 일치 |
| 간접비 순서와 절사 전달 | B | **A 실증** — 앞 행의 절사액을 뒤 행이 받음 |
| 끝전조정 모델 | B | **A 실증** |
| 계층 집계 구조 | B | **A 실증** |
| 제외금액 취급 | 서술만 | **A 실증**`C*` 구역 규칙 |
## 6. 아직 이 시험으로 못 건드린 것
골든셋은 **내역 표의 단가를 주어진 것으로 놓고** 그 위를 검증한다. 따라서 아래는 여전히 열려 있다.
- 단가가 **어떻게 만들어지는가** — 일위대가·중기 시간당 단가 (`BDQTY` 암호화)
- 자재 6후보 중 **무엇을 채택하는가**
- 수량 **할증** 계산
- 수공·실적 형식(`S`/`TA`/`SA`) — 표본이 전부 일반형식 `T`
- 변경설계 **간접비**의 계산모드 — 차수 인계와 직접비 증감(+84,099,047)까지는 확인했으나, 변경분 XLSX 출력이 표본에 없어 간접비 쪽 모드(방법 A/B/직접입력)는 아직 못 가름
## 7. 다음
1. **일위대가표·중기사용료 시트로 단가 조립 검증** — XLSX에 두 시트가 있어 `BDQTY` 암호화와 무관하게 단가가 어떻게 쌓이는지 확인 가능. 지금 자료로 되는 다음 칸
2. 변경설계 **간접비** 모드 — 변경분 출력 XLSX 한 건만 있으면 정적으로 갈림
3. 수공·실적 형식 표본 확보 — 현재 표본이 전부 일반형식 `T`
@@ -0,0 +1,169 @@
# 단가 조립 검증 — 일위대가와 중기 시간당 사용료
실행일: 2026-09-13
방법: STmate가 뽑은 XLSX의 `일위대가표`·`중기사용료` 시트를 호표 단위로 갈라, **구성행의 성분별 금액 합이 합계행과 맞는지** 전수 대조함.
재현 스크립트: `40_스크립트/unit_assembly_check.py`, `machine_rate_extract.py`
## 1. 왜 이게 되는가 — 막힌 줄 알았던 길
15번은 단가 조립을 「`BDQTY.DBF` 암호화로 불가」로 적었다. **틀렸다.**
`BDQTY`는 구성 수량의 **입력 원장**이지만, STmate가 뽑는 **출력 XLSX의 `일위대가표`·`중기사용료` 시트에 구성 자원·수량·단가·성분별 금액이 전부 평문으로 들어 있다.** 입력 원장은 못 읽어도 **조립 결과와 조립 규칙은 읽힌다.**
## 2. 성분 소계는 **절사**다
호표마다 구성행의 노무·재료·경비 금액을 각각 더하고 합계행과 대조했다.
| 시트 | 성분 소계 건수 | 절사 | 사사오입 | 은행가 |
|---|---:|---:|---:|---:|
| **중기사용료** | 398 | **398 (100%)** | 265 (66.6%) | 280 (70.4%) |
| **일위대가표** | 345 | **325 (94.2%)** | 198 (57.4%) | 211 (61.2%) |
**절사가 유일하게 전건을 설명한다.** 사사오입·은행가는 소수부가 없는 건에서만 우연히 맞는다.
> 07번 §6의 「자원/일위대가/중기 **집계표** 행 금액 = `ROUND`」와 헷갈리면 안 된다. 그것은 **집계표**(자원집계표·일위대가수량금액집계표·중기시간금액집계표)의 행 금액이고, 여기서 말하는 것은 **호표 안 성분 소계**다. 둘은 다른 자리이고 **반올림 방식도 다르다.**
일위대가표의 나머지 5.8%는 호표 머리·부분계 서식이 달라 자동 구획이 어긋난 것으로, 반올림 방식이 다르다는 증거는 아니다.
## 3. 중기 시간당 사용료의 구조 — A등급
호표 한 벌이 그대로 공식이다.
```text
손료 = 취득가(천원) × 시간당 손료율 → 경비
운전원 = 1인 × 노임단가 → 노무비
주연료 = 시간당 소요량(L) × 연료단가 → 재료비
잡품 = 주연료비 × 잡품률(%) → 재료비
────────────────────────────────────────
시간당 사용료 = 노무비 + 재료비 + 경비 (성분별로 절사한 뒤 합)
```
굴삭기(무한궤도) 0.7㎥ 실제 값으로 확인하면 이렇다.
```text
손료 110,926천원 × 0.2085 = 23,128.07 → 경비 23,128
운전원 1인 × 55,700 = 55,700 → 노무 55,700
경유 11.6 L × 1,273 = 14,766.8 → 재료
잡품 14,766.8 × 22% = 3,248.6 → 재료
재료 소계 = 절사(14,766.8 + 3,248.6) = 18,015
합계 = 55,700 + 18,015 + 23,128 = 96,843 ✅ 시트와 일치
```
**`잡품`의 단가 칸에는 연료 금액이 그대로 들어간다.** 즉 잡품은 수량×단가가 아니라 **연료비에 비율을 곱하는 가산 행**이다. 07번 §4가 「재료 수량 할증과 잡재료 금액 가산은 다른 연산」이라 한 것이 이 자리다.
## 4. 시간당 손료율 실측 — 25개 장비 (봉화 2024)
15번이 「중기 손료 계수 실제 값이 어디에도 없음 ❌」이라 적은 것을 **정정한다. 자료에 있다.**
| 장비 | 규격 | 손료율 | 취득가(천원) | 손료(원/h) | 연료(L/h) | 잡품(%) |
|---|---|---:|---:|---:|---:|---:|
| 굴삭기(무한궤도) | 0.2㎥ | 0.2085 | 62,550 | 13,042 | 5 | 21 |
| 굴삭기(무한궤도) | 0.4㎥ | 0.2085 | 78,556 | 16,379 | 9.9 | 22 |
| 굴삭기(무한궤도) | 0.7㎥ | 0.2085 | 110,926 | 23,128 | 11.6 | 22 |
| 굴삭기(무한궤도) | 0.4㎥ 암석 | 0.2405 | 78,556 | 18,893 | 9.9 | 22 |
| 굴삭기(무한궤도) | 0.7㎥ 암석 | 0.2405 | 110,926 | 26,678 | 11.6 | 22 |
| 굴삭기(무한궤도) | 1.0㎥ 암석 | 0.2405 | 133,819 | 32,184 | 19.5 | 22 |
| 굴삭기(무한궤도) | 0.4㎥ 브레이커조합 | 0.6601 | 7,882 | 5,203 | 9.9 | 16 |
| 굴삭기(무한궤도) | 0.7㎥ 브레이커조합 | 0.6601 | 16,315 | 10,770 | 11.6 | 16 |
| 굴삭기(타이어) | 0.6㎥ | 0.2279 | 112,684 | 25,681 | 11.6 | 24 |
| 굴삭기(타이어) | 0.6㎥ 부착용집게 | 0.7435 | 7,381 | 5,488 | 11.6 | 24 |
| 유압식 진동콤팩터 | 굴삭기 0.7㎥ 부착용 | 0.3193 | 11,046 | 3,527 | — | — |
| 불도저(무한궤도) | 19톤 | 0.1811 | 184,499 | 33,413 | 25 | 16 |
| 덤프트럭 | 2.5톤 암석 | 0.3533 | 20,793 | 7,346 | 2.9 | 38 |
| 덤프트럭 | 15톤 | 0.2279 | 86,142 | 19,632 | 15.9 | 38 |
| 덤프트럭 | 15톤 암석 | 0.2679 | 86,142 | 23,077 | 15.9 | 38 |
| 덤프트럭 | 24톤 | 0.2229 | 140,899 | 31,406 | 23 | 38 |
| 덤프트럭 자동덮개시설 | 15톤용 | 0.2684 | 1,546 | 415 | — | — |
| 트럭탑재형 크레인 | 5톤 | 0.2598 | 38,469 | 9,994 | 5.1 | 20 |
| 트럭 트랙터 및 평판트레일러 | 20톤 | 0.2576 | 63,483 | 16,353 | 16.5 | 39 |
| 콘크리트 믹서 | 0.45㎥ | 0.3039 | 5,409 | 1,644 | 3.9 | 2 |
| 커터(콘크리트·아스팔트용) | 320~440mm | 0.6354 | 2,954 | 1,877 | 5.6 | 20 |
| 물탱크(살수차) | 5,500 | 0.2113 | 44,820 | 9,471 | 9.3 | 30 |
| 동력분무기 | 4.85㎾ | 0.2799 | 875 | 245 | 1.3 | — |
| 진동롤러(자주식) | 12톤 | 0.2825 | 97,000 | 27,403 | 15.8 | 30 |
| 크롤러드릴(탑승유압식) | 110㎾ | 0.1548 | 150,666 | 23,323 | 18.6 | 23 |
읽는 법 몇 가지.
- **같은 장비라도 「암석」이 붙으면 손료율이 오른다** — 굴삭기 0.2085 → 0.2405. 작업 조건이 손료율을 바꾼다.
- **부착 장비는 취득가가 부착물만의 값이다** — 굴삭기 0.7㎥ 브레이커조합은 취득가 16,315천원에 손료율 0.6601이다. 본체와 별도 행으로 쌓는다.
- **손료율은 취득가 1천원당 시간 손료(원)** 이다. `손료 = 취득가(천원) × 손료율`.
- 잡품률은 장비마다 다르다(2~39%). 일괄 적용하면 안 된다.
⚠ 이 값들은 **STmate가 담고 있는 값**이다. Aislo는 같은 수치를 **법령·품셈**(건설기계 경비 산정 기준)에서 확보해야 하며, 여기 표는 **대조용**으로만 쓴다.
## 5. 일위대가 조립
호표는 구성 자원을 나열하고 각 행이 `수량 × 단가`로 성분별 금액을 만든다. 구성 자원은 **다른 호표를 참조할 수 있다.**
```text
제 6 호표 기슭막이(깬잡석,찰쌓기 L3=45) H=2.0m
깬잡석채집 2.09 M2 × 30,302 = 63,331 ← 다른 호표
깬잡석찰쌓기 2.09 M2 × 58,889 = 123,078 ← 제 1 호표
고임돌채집 0.31 M3 × 17,416 = 5,399
```
`깬잡석찰쌓기 58,889`가 제 1 호표의 합계와 같다. **호표 사이에 참조가 걸리므로 위상순서로 계산해야 한다** — 09번 §3이 말한 의존 그래프가 실제로 필요하다.
## 5.5 자재 채택 규칙 — **최저가**, A등급 실증
출력 XLSX의 `자재단가대비표` 시트에 **후보 다섯 칸과 적용 단가·출처가 나란히** 있다.
```text
호표 | 명칭 | 규격 | 단위 | 조달가격 | 물가자료 | 물가정보 | 거래가격 | 견적단가 | 적용단가 | 출처
1 | 철못 | N 50 | KG | 0 | 1,665 | 1,935 | 1,680 | 0 | 1,665 | 물자
6 | PVC파이프 | VG1 | M | 0 | 4,327 | 4,702 | 3,475 | 0 | 3,475 | 거래
19 | 와이어메쉬 | #6 | M2 | 3,546 | 4,970 | 5,510 | 0 | 4,545 | 3,546 | 조달
```
실무 프로젝트 6건 전수로 검정했다.
| 구분 | 건수 | 적용단가 = 후보 최저 |
|---|---:|---:|
| 전체 (후보 1개 이상) | 186 | **186 (100%)** |
| **후보 값이 2종 이상** | 76 | **76 (100%)** |
| **후보 값이 3종 이상** | 36 | **36 (100%)** |
| 최저가가 아닌 후보를 택한 건 | — | **0** |
| 어느 후보와도 다른 건 | — | **0** |
**자동채택은 「0이 아닌 후보 중 최저가」다.** 07번 §5가 「최저가·최신가·특정 슬롯 우선 중 어느 하나라고 단정할 근거가 없다」며 C등급으로 둔 항목이 **실증으로 확정**됐다.
곁들여 확인된 것.
- 후보 슬롯은 화면상 **다섯**(조달가격·물가자료·물가정보·거래가격·견적단가)이고, `COSTN``AMT_11..AMT_61` 여섯 칸 중 **여섯 번째가 적용 단가 칸**으로 보인다. 07번이 「노무 L은 후보6과 채택가가 거의 일치」라 관찰한 것과 맞는다.
- 후보가 하나도 없는데 적용 단가만 있는 행이 25건 있다(경유·휘발유 등 **유가 자료**, 별도 배포 단가).
- 적용 단가가 0인 행 49건은 `별도계상`(이형철근·레미콘)·`조달수수료(재료비×0.54%)`처럼 **다른 자리에서 계산되는** 것이다.
- **출처 표시는 슬롯 이름의 축약**이다(물자=물가자료, 물정=물가정보, 거래, 조달, 견적). 슬롯 번호에 기관명을 코드로 박으면 안 된다는 07번 경고는 그대로 유효하다 — `Start8x.Ini`가 표시명을 바꾼다.
**사용자 수동 고정이 있으면 이 규칙을 벗어난다.** 표본 6건에는 수동 고정 흔적이 없었을 뿐이며, 수동 우선순위는 여전히 통제시험(T10) 대상이다.
## 6. 이로써 바뀌는 판정
| 항목 | 15번의 판정 | 지금 |
|---|---|---|
| 중기 시간당 단가 재현 | ❌ 손료 계수가 어디에도 없음 | ✅ **호표에 손료율·취득가·연료·잡품률이 전부 있음** |
| 단가 조립 규칙 | ❌ `BDQTY` 암호화로 불가 | ✅ **출력 시트로 조립 규칙·반올림 확인됨** |
| 성분 소계 반올림 | 미확정 | ✅ **절사** (중기 398건 100%) |
| 자재 6후보 채택 | ❌ C등급, 규칙 없음 | ✅ **최저가** — 복수후보 76/76 |
여전히 막힌 것은 **입력 쪽**이다.
- `BDQTY`의 수량 **할증** 필드(`X_QTY`·`X_HAL`) 실값 — 출력에는 할증 후 수량만 남는다
- 품셈에서 구성 수량이 **어떻게 뽑혔는가**
- 사용자가 **수동 고정**했을 때의 우선순위 (T10)
## 7. 다음
1. 일위대가 호표 사이 **참조 그래프**를 복원해 위상순서 계산을 재현 — 지금 자료로 됨
2. `단가산출근거` 시트 구조 확인 — 시트는 있으나 이번 구획 규칙으로는 안 잡힘
3. 09번 §8이 꼽은 **여섯 호환성 차단점** 가운데 남은 것 정리
| # | 차단점 | 상태 |
|---:|---|---|
| 1 | RATE 좌표 라벨·경계 | ⚠ 보험 계열 6개 확정(15번 §4), 규모·기간 좌표는 남음 |
| 2 | `BDQTY` 세부 플래그 | ❌ 암호화 — 유일하게 손 못 댄 입력 |
| 3 | 자재 자동채택 우선순위 | ✅ **최저가 확정** (§5.5) |
| 4 | 중기 내부 절사 시점 | ✅ **성분별 절사 확정** (§2·§3) |
| 5 | UI 명령별 재계산 범위 | ❌ 통제시험 T8 |
| 6 | 일괄변경 수동값 우선순위 | ❌ 통제시험 T10 |
@@ -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,173 @@
# 진행률 장부 — 어디까지 봤고 무엇이 남았나
갱신일: 2026-09-13
세는 법: 눈대중이 아니라 **파일·표·시트를 실제로 세어** 적음. 재현 스크립트 `40_스크립트/coverage_ledger.py`
## 1. 한눈에
| 원천 | 전체 | 본 것 | 남은 것 | 진행 |
|---|---:|---:|---:|---|
| 설치 폴더 `System` 파일 | 186 | 186 등록·형식·내부구조 | 암호화 13개는 **영구 불가** | ▓▓▓▓▓▓▓▓▓▓ 100% |
| 기준 요율표 `JB_*` | 43 | 43 전수 파싱·블록·확장차이 | 셀 좌표 일부 | ▓▓▓▓▓▓▓▓░░ 85% |
| 실무 프로젝트 `STC` | 7 | 7 전부 | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
| STC 안의 표 | 16종 | **10종 읽고 씀** · 4종 빈 표 | **2종 암호화** (`BDQTY`·`SYSINFO`) | ▓▓▓▓▓▓▓▓▓░ 88% |
| STmate 출력 `XLSX` | 6 | 6 전부 | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
| 출력 시트 | **30종** | **30종 전부 훑음** (계산 있는 것 15종 규칙 확정) | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
| 계산 사슬 단계 | 8 | **8 전부** (④는 참조 그래프까지 실증) | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
| 변경설계 재현 | 직접비+간접비 | **전 항목 원 단위 일치** | — | ▓▓▓▓▓▓▓▓▓▓ 100% |
| 09번이 꼽은 호환성 차단점 | 6 | **3 해결 · 1 부분** | 2 (실행 필요) | ▓▓▓▓▓▓░░░░ 58% |
## 2. 설치 폴더 186개
| 갈래 | 개수 | 상태 |
|---|---:|---|
| PE 실행모듈 (`*.CPM`·`*.BPL`·`*.EXE`) | 82 | 헤더·섹션·임포트·문자열 전수. 핵심 12개는 호출자리까지 |
| ZIP 계열 (`*.STBSC` 등) | 28 | 컨테이너 확인, 내부는 암호 |
| JB 요율표 | 43 | **전수 파싱** — 형태·블록·2020-05-27 확장 차이 |
| 암호화 기준단가 (`*.BSC`·`*.CSTA`) | 13 | ⛔ **본문 암호화 — 영구 불가** |
| 기타 | 20 | 형식 판정 완료 |
⛔ 13개 안에 **자재 57,580건·노임 460건·중기 833건·표준시장단가 4,921건**이 들어 있다. 이것이 「완제품 복제 불가」의 유일한 근거다.
## 3. 출력 시트 30종 — 가장 많이 남은 곳
### ✅ 계산 검증 완료 14종
```
공사원가계산서 · 설계내역서 · 일위대가표 · 중기사용료 · 자재단가대비표
단가산출근거 · 환율및기초자료 · 〓 INITIAL 〓
재료비수량금액집계표 · 노무비수량금액집계표 · 경비수량금액집계표
설계변경내역서 · 설계변경금액대비표 · 설계변경증감대비표
```
### ✅ 나머지 16종도 훑음 (2026-09-13)
전부 열어 본 결과 **새 계산 규칙은 나오지 않았고**, 대신 두 가지가 밝혀졌다.
| 갈래 | 시트 | 결과 |
|---|---|---|
| 색인·목록 | 일위대가목록표 · 단가산출근거목록표 · 중기목록표 · 재료비목록표 · 노무비목록표 · 경비목록표 · 일식견적목록표 | **계산 쌍 없음** — 호표 번호·코드·단가 색인 |
| 표지 | 〓 목 차 〓 · ※※안내※※ | 계산 없음 |
| 내역서 변형 | **착공내역서** | 178건 **절사 100%** — 설계내역서와 같은 규칙 |
| | 총괄설계내역서 · 총괄내역서 | 공종 상위 집계, 새 규칙 없음 |
| | 설계변경내역서 | 당초·변경 두 벌이라 단순 쌍 검출로는 안 갈림. 금액 자체는 19번 §6에서 확정 |
| 집계표 | 중기시간금액집계표 · 일위대가수량금액집계표 · 단가산출근거수량금액집계표 · 일식견적수량금액집계표 | **단가 열이 없음** — 아래 |
#### 집계표에는 단가 열이 없다
```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단계 — 전부 확인
| # | 단계 | 근거 문서 |
|---:|---|---|
| ① | 일 노임 → 시간당 (설정식, 좌→우 순차) | 18번 §2.1 |
| ② | 중기 시간당 사용료 (손료·운전원·연료·잡품, 성분별 절사) | 17번 §3 |
| ③ | 단가산출 `D`코드 (`Q=3600qKfE/Cm`, 성분/Q 소수1 절사) | 18번 §2.2 |
| ④ | 일위대가 `B`코드 (구성 조립, 성분별 절사, 호표 간 참조) | 17번 §2·§5 |
| ⑤ | 내역 행 (`ROUNDDOWN(수량×단가)`) | 16번 |
| ⑥ | 계층 합산·제외금액 구역 | 16번 §4.1·§4.2 |
| ⑦ | 간접비 (밑수×요율, 행마다 절사, 앞 행 참조) | 16번 §3, 06번 §3 |
| ⑧ | 일반관리비 → 이윤 → 끝전조정 → 부가세 → 관급 → 총공사비 | 16번 §4.4 |
## 5. 호환성 차단점 여섯 — 현황
| # | 차단점 | 상태 | 근거 |
|---:|---|---|---|
| 1 | RATE 좌표 라벨·경계 | ◐ **부분** — 고정 칸(보험·산안비·이윤) 확정, 규모·기간 의존 칸은 남음 | 15번 §4 |
| 2 | `BDQTY` 세부 플래그 | ⛔ **암호화 — 불가** | 15번 §2 |
| 3 | 자재 자동채택 우선순위 | ✅ **최저가** | 17번 §5.5 |
| 4 | 중기 내부 절사 시점 | ✅ **성분별 절사** | 17번 §2 |
| 5 | UI 명령별 재계산 범위 | ⛔ 실행 필요 | 13번 T8 |
| 6 | 일괄변경 수동값 우선순위 | ⛔ 실행 필요 | 13번 T10 |
**추가로 09번 목록 밖에서 푼 것** — 변경설계 계산모드(아래 6장), 노임 환산식, 단가산출 Q식, 계산 설정 스냅샷, 집계표 반올림.
## 6. 변경설계 계산모드 — 이번에 확정 (T9)
계류보전 2025(변경) 프로젝트의 `공사원가계산서`**당초·변경·증감을 나란히** 낸다. 직접노무비가 `32,976,276 → 41,778,438`(+8,802,162)로 크게 늘었는데도 **세 항목은 증감이 0이다.**
| 항목 | 당초 | 변경 | 증감 | 재계산했다면 | 판정 |
|---|---:|---:|---:|---:|---|
| 간접노무비 | 4,715,607 | 5,974,316 | +1,258,709 | 5,974,316 ✅ | **재계산** |
| 산재보험료 | 1,341,831 | 1,699,998 | +358,167 | 1,699,998 ✅ | **재계산** |
| 고용보험료 | 380,688 | 482,302 | +101,614 | 482,302 ✅ | **재계산** |
| **건강보험료** | 1,328,500 | 1,328,500 | **0** | 1,683,108 (+354,608) | **당초액 유지** |
| **노인장기요양보험료** | 172,040 | 172,040 | **0** | 217,961 (+45,921) | **당초액 유지** |
| **연금보험료** | 1,686,389 | 1,686,389 | **0** | 2,136,526 (+450,137) | **당초액 유지** |
요율은 당초·변경이 같다(간접노무비 14.300%, 산재 3.560%, 고용 1.010%). 즉 요율을 바꾼 것이 아니라 **항목마다 모드가 다르다.**
**건강보험료·노인장기요양보험료·연금보험료는 사후정산 대상이라 설계 단계에서 당초액을 그대로 가져간다.** 11번 §10의 「보험료 사후정산 항목은 당초금액과 동일하게 직접 입력하는 옵션」이 실데이터로 확인됐다.
**재구현 필수** — 간접비 항목마다 `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. 남은 일 — 할 수 있는 것과 없는 것
### 지금 자료로 할 수 있는 것
| 순위 | 할 일 | 왜 |
|---:|---|---|
| ~~1~~ | ~~남은 시트 훑기~~ | ✅ **완료** — 30종 전부. 새 규칙 없음, 집계표 ±1 원인 밝힘 |
| ~~2~~ | ~~중기시간금액집계표~~ | ✅ **완료** — 절사(수량 × 시간당 성분단가) |
| ~~1~~ | ~~일위대가 참조 그래프~~ | ✅ **완료** — 순환 없음, 깊이 2단, 불일치 0 (§6.5) |
| ~~2~~ | ~~설계변경 직접비 증감~~ | ✅ **완료** — 당초·변경 전 항목 원 단위 일치 (§6.5) |
| 1 | **표본 늘리기** | 지금 자료로 할 수 있는 것은 사실상 다 함. 규모·기간 의존 요율 좌표와 수공·실적 형식은 **새 표본이 있어야** 열림 |
### 끝내 못 하는 것
| 무엇 | 왜 |
|---|---|
| 기초단가 원천 (자재 5.7만 건 등) | 본문 암호화 |
| `BDQTY` 수량 할증 | 본문 암호화 |
| UI 재계산 범위·일괄변경 우선순위 | **STmate 실행 필요 — 라이선스 없음** |
| 규모·기간 의존 요율 좌표 | 같음. 표본을 늘리면 좁혀지기는 함 |
| 수공 `S`·실적 `TA`/`SA` 형식 | **표본이 하나도 없음.** 그 형식 설계서가 한 건 생기면 바로 가능 |
@@ -0,0 +1,156 @@
# 설정파일과 보고서 카탈로그 — 마지막으로 남아 있던 자리
점검일: 2026-09-13
계기: 「추가로 더 있는지」 확인. **`System` 폴더만 보고 `STmate 자료`·`STmate 압축자료` 폴더를 통째로 안 봤다.**
재현 스크립트: `40_스크립트/ini_and_report_catalog.py`
## 1. 안 보고 있던 자리
`C:\경영정보(주)` 아래에는 폴더가 넷이다.
| 폴더 | 내용 | 그동안 |
|---|---|---|
| `System` | 설치 파일 186개 | ✅ 전수 |
| `STmate 자료\설계자료\실습(맛보기)` | 맛보기 프로젝트를 **푼 것** 23개 | ⚠ **STC와 같다고 넘겼음** |
| `STmate 압축자료` | `실습(맛보기).STC` | ✅ |
| `(TMP)` | 비어 있음 | — |
DBF는 STC와 같지만 **STC에 없는 파일 셋이 있었다**`Start8x.Ini` · `STini.MEM` · `QCODE.SMT`.
## 2. `Start8x.Ini` — 설치본 전역 설정
### 2.1 ⭐ 간접비 계산 순서가 코드 목록으로 저장돼 있다
```ini
KAN_ORD_1=;301;302;303;310;304;305;306;312;313;321;307;308;309;311;361;362;363;364;365;366;367;368;369;370;
KAN_ORD_2=;351;301;302;303;310;304;305;306;312;313;331;332;333;334;335;336;337;338;307;308;309;311;361;…;370;
KAN_TYP=0
```
**두 목록의 차이가 12번 §7의 「기타경비: 일반=묶음 1개 / 수공=세부 8개」와 정확히 맞는다.**
- `KAN_ORD_1` (24개) — `313` 다음에 **`321` 하나**
- `KAN_ORD_2` (32개) — 같은 자리에 **`331`~`338` 여덟 개**, 그리고 맨 앞에 `351` 추가
**`KAN_ORD_1` = 일반형식(`T`) 순서, `KAN_ORD_2` = 수공형식(`S`) 순서**이고 `KAN_TYP`가 고른다.
### 2.2 코드 → 항목 사전 (봉화 2024 출력과 대조해 확정)
`KAN_ORD_1` 순서대로 놓고, 값이 0인 항목은 출력에서 빠진다는 규칙으로 봉화 `공사원가계산서`와 맞추면 하나도 어긋나지 않는다.
| 순서 | 코드 | 항목 | 봉화에 출력됨 |
|---:|---|---|:--:|
| 1 | `301` | 간접노무비 | ✅ |
| 2 | `302` | 산재보험료 | ✅ |
| 3 | `303` | 고용보험료 | ✅ |
| 4 | `310` | 건강보험료 | ✅ |
| 5 | `304` | 노인장기요양보험료 | ✅ |
| 6 | `305` | 연금보험료 | ✅ |
| 7 | `306` | 퇴직공제부금비 | ✗ (0) |
| 8 | `312` | 산업안전보건관리비 | ✅ |
| 9 | `313` | 안전·품질관리비 (별도내역) | ✗ (0) |
| 10 | `321` | **기타경비** (일반형식 묶음) | ✅ |
| 11 | `307` | 공사이행보증수수료 | ✗ (0) |
| 12 | `308` | 하도급대금지급보증수수료 | ✗ (0) |
| 13 | `309` | 환경보전비 | ✅ |
| 14 | `311` | 건설기계대여금 지급보증 | ✅ |
| 15~24 | `361`~`370` | 후단 공통 (일반관리비·이윤·부가세·도급·관급 계열) | — |
| — | `331`~`338` | **수공형식 세부 경비 8개** | 표본 없음 |
| — | `351` | 수공형식 선두 항목 | 표본 없음 |
출력 순서가 **정확히 이 순서**다. 등급 **A**.
**재구현은 항목을 이름이 아니라 코드로 다루고, 순서를 목록 데이터로 둘 것.** 형식을 바꾸면 목록을 갈아 끼우면 된다. `if 항목명 == "기타경비"` 같은 분기를 심으면 수공형식에서 깨진다.
### 2.3 ⚠ 후보 슬롯 이름이 설치본마다 다르다
| | 슬롯1 | 슬롯2 | 슬롯3 | 슬롯4 | **슬롯5** | 슬롯6 |
|---|---|---|---|---|---|---|
| 설치 기본 (`Start8x.Ini`) | 가격 정보 | 물가 자료 | 물가 정보 | 거래 가격 | **유통 물가** | 적용 단가 |
| 실무 프로젝트 (`INITIAL`) | 조달가격 | 물가자료 | 물가정보 | 거래가격 | **견적단가** | 적용 단가 |
**다섯 번째가 통째로 다르다.** 07번의 「슬롯 번호에 가격지 이름을 코드로 고정하지 말라」가 이보다 분명할 수 없다. 축약도 다르다(`가.정/물.자/물.정/거.래/유.통``조달/물자/물정/거래/견적`).
`JEMNM=123456`이 표시 순서다.
### 2.4 코드 종류 사전
```ini
COD_FMT_1=노무 COD_FMT_2=자재 COD_FMT_3=경비 COD_FMT_4=대가
COD_FMT_5=중기 COD_FMT_6=산근 COD_FMT_7=기타 COD_FMT_8=실적
```
설계변경증감대비표 비고 칸의 `산근 4호표` · `대가 1호표` · `M00760`이 이 사전을 쓴다. **`대가`=일위대가(`B`), `산근`=단가산출근거(`D`)**다.
`BCDNM=10 · DCDNM=14 · LCDNM=9 · MCDNM=13 · PCDNM=11 · SCDNM=15 · XCDNM=14`는 코드 종류별 기본값이며 의미는 아직 미확정(B등급).
### 2.5 `[EQUIPMENT]` — 노임 환산식과 환율의 설치 기본값
```ini
RXXNM=1/8 * 16/12 * 25/20 ← 18번 §2.1의 그 식
RXTNM=1/8*16/12*25/20
CONLD=L00007L00008L00009 ← 적용 직종 (설치 기본)
EXLNM1~4 / EXHNM1~4 = 1088 · 1289 · 1134.5 · 1134.5 ← 달러 구간별 환율
EXEUR1/2=1000 EXYEN1/2=1000
```
프로젝트의 `COND_``L00008L00009L00010`으로 **설치 기본과 다르다** — 프로젝트마다 적용 직종을 바꿀 수 있다.
### 2.6 그 밖
| 키 | 값 | 뜻 |
|---|---|---|
| `DISP_EX` | `제외금액` | 16번 §4.2의 `C*` 구역 이름이 여기서 온다 |
| `DISP_TOT` | `총 계` | |
| `UPDNCAP` | `상단:당초 , 하단:변경` | 변경설계 표시 |
| `BMKNM` | `TLMS` | 비목 코드 순서 (총계·노무·재료·경비) |
| `[NAG_HAL]` | `ST_MHAL` · `EQ_LHAL` · `EQ_MHAL` · `EQ_SHAL` = 0 | **할증률 설정 자리** — 표본은 전부 0 |
| `LMGNM=5.0` · `MKRNM=4.0` · `TMGNM=0.0` | | 미확정 (B) |
## 3. `SET_REP.DBF` — 보고서 서식 75종 카탈로그
우리 표본의 XLSX에는 **30종**이 나왔는데, 카탈로그에는 **75종**이 있다. 45종은 표본에 안 나온 것뿐이다.
| 갈래 | 서식 |
|---|---|
| 기성 | 기성부분총괄내역서 · 기성내역서 · 기성내역서(사정) · 기성내역서(기성율) · 기성검사조서(을/병) · 기성부분수량조서 |
| 견적·계약 | 견적내역서 · 견적단가대비표 · 계약,실행내역서 |
| 준공 | 준공조서(을/병) |
| 변경 | 변경내역서 · 변경내역서(변경율) · 변경총괄내역서(구.시도/시도/구도) |
| 계획 | 장비투입계획 · 인력투입계획 · 자재사용계획 |
| 실행예산 | 실행예산서Ⅰ · Ⅱ |
| 양식 변형 | 종양식 · 도공양식 · 굴착 · 구.시도/시도/구도 · **수공양식** |
| 기타 | 간이공사비 산출명세서 · 총괄집계표 · 표준시장/시장시공 목록표·집계표 |
각 행은 `INDX`(서식번호) · `NAME` · `HEADTITLE` · 열 이름 11개와 폭을 가진다. **보고서 서식이 코드가 아니라 데이터**라는 00번의 관찰이 여기서 확인된다.
**`일위대가표(수공양식)`(`0303`)과 `표준시장/시장시공` 계열이 카탈로그에 있다** — 수공·실적 형식이 실제로 지원되며 서식도 따로 있다. 우리 표본에 그 형식 프로젝트가 없을 뿐이다.
**계산** 규칙은 없다. 같은 값을 다른 묶음·다른 열로 내는 출력 변형이다.
## 4. `COSTN.SMT` · `QCODE.SMT` · `STini.MEM`
> ⛔ **이 절의 첫 판정은 틀렸다. 21번 §1에서 정정했다.** 아래는 바로잡은 내용이다.
| 파일 | 크기 | 상태 |
|---|---:|---|
| `COSTN.SMT` | 10,177 B | ✅ **평문 — 본문 문자열 276개.** 단가산출 전개 텍스트 |
| `PRJLST.SMT` | 2,759 B | ✅ **평문 — 153개.** 프로젝트별 원가계산 설정 원장 |
| `QCODE.SMT` | 512 B | 머리만, 내용 없음 |
| `STini.MEM` | 512 B | 미해독 |
처음 훑을 때 **파일 앞 4KB만** 보고 필터를 세게 걸어 놓쳤다. **본문은 `0x200`부터** 시작한다. 07번이 기대한 `&M00034M&` 참조 토큰이 **평문으로 그대로 있다.**
암호화된 것은 `BDQTY.DBF`·`SYSINFO.DBF` 둘뿐이라는 판정은 그대로 맞다. 상세는 **21번 §1·§3**과 기존 `original/원가계산/STmate/SIxMemo_구조.md`.
## 5. 이번 점검이 바꾸는 것
| 항목 | 이전 | 지금 |
|---|---|---|
| 간접비 항목 순서 | 화면 텍스트로 복원 | ✅ **코드 목록으로 확정**, 형식별 두 벌 |
| 간접비 항목 코드 | 없었음 | ✅ **301~370 사전**, 봉화 출력과 무결 대조 |
| 후보 슬롯 이름 | 「설정에 있을 것」 | ✅ **설치본마다 다름을 실물로 확인** |
| 코드 종류 사전 | 추정 | ✅ `COD_FMT_1~8` |
| 보고서 서식 | 30종 봄 | ✅ **카탈로그 75종** 전체 |
| `*.SMT` 메모 | 「토큰이 있을 것」 | ✅ **평문 — 토큰·설정 원장이 그대로** (21번 §1·§3) |
| 할증률 설정 자리 | 없었음 | ◐ `[NAG_HAL]` 발견, 표본은 전부 0 |
@@ -0,0 +1,156 @@
# 정정과 트랙 통합 — 마지막 확인에서 드러난 것
작성일: 2026-09-13
계기: 「분석이 대부분 끝났나」 최종 확인. **끝나지 않았다.** 오히려 이 확인에서 **내 오류 하나와 큰 구멍 하나**가 나왔다.
## 1. ⛔ 정정 — `*.SMT` 는 암호화가 아니다. 읽힌다.
20번 §4에 「`COSTN.SMT`·`QCODE.SMT`·`PRJLST.SMT` 본문에 읽히는 문자열 없음 — 암호화/압축」이라 적었다. **틀렸다.**
앞선 훑기가 **파일 앞 4KB만** 본 데다 필터가 셌다. 본문은 **`0x200`부터** 시작한다.
| 파일 | 크기 | 읽히는 문자열 |
|---|---:|---:|
| `COSTN.SMT` | 10,177 B | **276개** |
| `PRJLST.SMT` | 2,759 B | **153개** |
| `QCODE.SMT` | 512 B | 1개 (비어 있음) |
암호화된 것은 **`BDQTY.DBF``SYSINFO.DBF` 둘뿐**이라는 앞선 판정은 그대로 맞다.
## 2. 큰 구멍 — 지식DB에 STmate 트랙이 **셋**이고 서로 몰랐다
| 트랙 | 자리 | 담당 | 만든 때 |
|---|---|---|---|
| ① **계산 로직** | `technical_info/01_임도/05_원가정보/` (문서 10개) | 밑수·단수처리·제비율·기계경비·노임·자재 적용 | 2026-08 |
| ② **STC 포맷** | `original/원가계산/STmate/` (문서 8개) | 컨테이너·필드사전·난독화·SIxMemo·INITIAL | 2026-08-15 |
| ③ **프로그램 분석** | `original/경쟁사 프로그램/STmate 분석/` (이 폴더) | 실행모듈·폼·계산사슬·골든셋 | 2026-09-12~13 |
②의 `_meta.md`**「계산 로직은 `technical_info/01_임도/05_원가정보/`가 담당, 이 폴더는 포맷만」** 이라고 트랙 분리를 명시해 놓았다. ③을 만들 때 이것을 못 보고 시작했다.
### 그 결과 겹치거나 어긋난 것
| 내가 「새로 찾았다」한 것 | 이미 있던 곳 | 판정 |
|---|---|---|
| 내역 행 절사 · 집계표 사사오입 · 관급 천원 올림 | ①`단수처리_규칙.md`**5공사지 교차로 이미 확정**. 봉화 216건까지 같은 숫자 | **중복** |
| `〓 INITIAL 〓` 시트 | ②`XLSX_INITIAL_구조.md` — 존재·성격 이미 규명. 「셀 전수 추출」만 미착수 | **부분 중복** (전수 추출은 18번이 처음) |
| `COSTN.SMT` 토큰 | ②`SIxMemo_구조.md`**헤더·문법 이미 해독**(`&M00034M&`, `:=A01`, `Q=…`) | **중복이자 내 오판** |
| `BDQTY`·`SYSINFO` 암호화 | ②`STC_난독화_상태.md`**XOR 키스트림 부분 검증까지** 완료 | **중복** |
**정직하게 말해 ③의 상당 부분이 ①②를 다시 한 것이다.** 다만 ③에서만 나온 것도 분명하다 — 실행모듈 정적분석(은행가 반올림), 폼 복원(밑수 공식·설정 83축·간접비 코드), 골든셋 회귀(원 단위 재현), 변경설계 모드, 단가산출 Q식 전수.
## 3. ⭐ `PRJLST.SMT` — 요율이 **값으로** 저장돼 있다
가장 중요한 발견. 프로젝트마다 원가계산 설정이 **INI 형식 평문**으로 들어 있다.
```ini
[KANJUB] JAB_TYPE=0 CAL_ACTL=0 VIS_TOTL=0
[1_KANJUB] ← 차수 1
[2_KANJUB] ← 차수 2
```
### 3.1 요율 `R_*` — 전부 값으로
```ini
1_R_KANNO=10.8 간접노무비 1_R_SANBO=3.8 산재보험료
1_R_KOBO=0.67 고용보험료 1_R_KUNBO=1.4 건강보험료
1_R_YUNBO=2.41 연금보험료 1_R_ANJUN=2.48 산업안전보건관리비
1_R_T_KITA=6.1 기타경비 1_R_HADOSU=0.018 하도급보증수수료
1_R_ILBAN=4.7 일반관리비 1_R_IYUN=15 이윤
1_R_KONGSA=0.7 공사보험료 1_R_TBUGUM=0 퇴직공제부금
```
### 3.2 ⭐ 수공형식 세부 경비 8개가 여기 있다
```ini
1_R_S_SUDO 수도광열비 1_R_S_BOKRI 복리후생비
1_R_S_SOMO 소모품·사무용품비 1_R_S_TONGSIN 여비·교통비·통신비
1_R_S_SEGUM 세금·공과금 1_R_S_PEGI 폐기물처리
1_R_S_DOSEO 도서·시공도작성비 1_R_S_JIGUP 지급수수료
```
**`KAN_ORD_2``331`~`338` 여덟 개와 정확히 맞는다**(20번 §2.1). 수공형식 표본이 없어도 **항목 구성은 확정**됐다.
### 3.3 직접입력 금액 `C_*` — 변경설계 「당초액 유지」의 실제 자리
```ini
1_C_KUNBO=649160 건강보험료 직접입력 금액
1_C_YUNBO=1117483 연금보험료 직접입력 금액
1_C_ANJUN=1994098 안전관리비
1_C_IYUN_M=37182 이윤 보정액 ← 16번 §4.4 끝전조정의 저장 자리
1_C_KONGSA_J=119300000
```
**19번 §6에서 「건강·장기요양·연금은 당초액 유지」라 실증한 것의 저장 자리가 이것이다.** 요율이 아니라 **금액을 직접 써 넣는다.**
### 3.4 선택 인덱스 `I_*` — 폼의 83축과 직결
```ini
1_I_KONG_TYPE=0 공사종류 1_I_KONG_KIKAN=2 공사기간
1_I_ANJUN_TYPE=0 안전비 공종 1_I_KUN_TYPE=6 건물종류
1_I_JEOLSA=5 공급가액 절사 1_I_TBUGUM=3 퇴직공제 구분
1_I_BUGA=0 부가세 방식 1_I_KOBO_TYPE=0 고용보험 등급
1_I_MTYP · 1_I_LTYP · 1_I_MXA · 1_I_MXK ← 실적형식 전용 (12번 §10.1-2)
```
12번이 폼에서 뽑은 축 이름(`cmBx_Kong_Type`·`cmBx_AnJun_Type`·`cmBx_JeolSa`…)과 **일대일로 맞는다.**
### 3.5 ⇒ 요율 좌표(T1)는 재구현 필수가 아니다
**프로젝트를 읽을 때는 `PRJLST.SMT``R_*` 값을 그대로 쓰면 된다.** 기준표 좌표는 「새 프로젝트를 만들며 기준표에서 고르는」 경로에만 필요하다.
| 쓰임 | 좌표 필요? |
|---|---|
| STmate 프로젝트 **읽기·검산** | ❌ — `PRJLST.SMT`에 값이 있음 |
| Aislo에서 **새로 요율을 고르기** | ⭕ — 다만 Aislo는 **현행 법령**에서 고르면 됨 |
| STmate와 **같은 기준표 선택 재현** | ⭕ — 하지만 목표가 아님 |
⇒ 13번 T1의 우선순위를 **1위에서 내린다.** 19번 §7의 「규모·기간 의존 요율 좌표」도 같은 이유로 격하한다.
## 4. 암호화의 성격 — 프로그램이 아니라 **사온 데이터**를 지킨다
| | 암호화 41개 | 평문 43개 |
|---|---|---|
| 무엇 | `*.BSC` · `*.CSTA` · `*.STBSC` 단가 자료 | `JB_*` 제비율 요율표 |
| 성격 | **유료 구독 데이터** | **법정 공개 자료** |
암호화된 파일은 **전부 파일명에 출처 기관과 반기가 박혀 있다.**
```
2025(상,국교,해수부,조달청)_표준시장.CSTA 국토부·해수부·조달청 표준시장단가
2026(상_노임,기계경비).STBSC 대한건설협회 노임 + 건설기계 경비
2026(상_표준일위대가_토목).STBSC 표준품셈 일위대가
2026-상-시설공통자재.STBSC 나라장터 시설공통자재
JDCODE_M.BSC 19 MB 자재 코드·단가 57,580건
```
**결정적 근거는 후보 슬롯 이름이 국내 물가지 이름 그대로**라는 점이다.
| 슬롯 | 이름 | 실체 |
|---|---|---|
| 1 | 조달가격 / 가격 정보 | 조달청 가격정보 |
| 2 | **물가자료** | 한국물가협회 |
| 3 | **물가정보** | 한국물가정보 |
| 4 | 거래가격 | 거래가격지 |
| 5 | 유통 물가 / 견적단가 | 유통물가 또는 업체 견적 |
즉 STmate는 **물가지 네댓 곳에서 사온 단가를 자기 포맷으로 담아 반기마다 배포**하며, 암호화는 **그 데이터의 저작권 보호**다. 프로그램 로직을 숨기려는 것이 아니다 — 계산 모듈(`*.CPM`)은 암호화돼 있지 않고 프로젝트 파일도 대부분 평문이다.
### Aislo 에 뜻하는 것 — 뚫을 일이 아니라 살 일
| 무엇 | STmate | Aislo 현재 |
|---|---|---|
| 계산 규칙 | 암호화 안 됨 | ✅ 다 읽었음 |
| 법정 요율(제비율) | 평문 | ✅ 현행법에서 직접 |
| 품셈 `q·K·f·E·Cm` | 출력에 평문 | ✅ `건설공사_표준품셈` 보유 |
| 노임 | 암호화 배포 | ✅ `노임단가_건설업_대한건설협회` |
| 유가·환율 | 암호화 배포 | ✅ `유가_오피넷` · `환율_한국은행ECOS` |
| 시설공통자재 | 암호화 배포 | ✅ `자재단가/나라장터_시설공통자재` |
| **물가지 자재단가** | 암호화 배포 | ⛔ **구독 필요 — 유일한 실질 격차** |
| 표준시장단가 | 암호화 배포 | ◐ 조달청 공표 자료로 대체 가능 |
## 5. 이 문서 이후 해야 할 것
1. **①②③ 트랙을 잇는 색인** — 세 폴더 머리에 서로를 가리키는 줄을 넣어 다시 갈라지지 않게 할 것
2. ③의 계산 규칙을 ①`technical_info/05_원가정보/`**승격** — ①이 Aislo 구현의 정본이므로
3. ②의 미해명(`SMT` 10바이트 인덱스, 레코드 경계)과 ③의 성과를 합칠 것
4. `COSTN.SMT` 전개 텍스트 **전수 파싱** — ②가 문법을 해독해 뒀으므로 바로 가능
@@ -0,0 +1,156 @@
# UI 폼 분석 — 화면에 남아 있던 계산 규칙
점검일: 2026-09-13
계기: 「추가 UI 정보도 분석했는지」 확인. **안 했다.** 폼 233개 중 **3개만** 뽑아 썼다.
원자료: `30_원자료/ui_form_catalog.txt` · 재현 스크립트: `40_스크립트/ui_form_sweep.py`, `form_extract.py`, `form_catalog.py`
## 1. 그동안 얼마나 봤나
| | 개수 |
|---|---:|
| 폼을 가진 PE 모듈 | 46 |
| 폼 자원 (중복 포함) | **233** |
| 서로 다른 폼 이름 | **190** |
| **그동안 뽑아 쓴 것** | **3**`TWM_KANJUB`·`TWM_KAN_RATE`·`TPM_KAN_RATE` |
계산 분석에 필요한 제비율·계산설정 화면만 뽑고 나머지는 손대지 않았다.
## 2. 이번에 전수로 훑음
파싱에 성공한 STmate 고유 폼에서 캡션과 선택지를 뽑았다.
| | 개수 |
|---|---:|
| 선택지를 가진 폼 | **40** |
| **선택축** | **272** |
12번 §10.1-2가 계산설정 폼 하나에서 뽑은 **83축의 세 배가 넘는다.**
### 폼 이름 규칙
```
PM_* 인쇄 폼 — SET_REP 의 보고서 서식 75종과 대응
w* / wM_* 편집 창·모달
frm* 보조 창
```
`ST_P_Nae`(22) · `STmate.Cpm`(30) · `ST_Boq`(13)에 폼이 몰려 있다.
## 3. 화면에서 새로 나온 계산 규칙
### 3.1 `wM_Edt_Hal` — 기준단가 수정 (단가 일괄보정)
```text
적용단가 = 기준단가 × [ ] % , 소수 [ ] 자리 미만 [절사 / 반올림]
※ 단【 적용단가 】가 "0" 원인것과 보호표시된 것은 수정안됨
```
**일괄변경에서 「보호표시(수동 고정)된 항목과 적용단가 0원인 항목은 건너뛴다.**」 13번 **T10(일괄변경 수동값 우선순위)**의 핵심이 화면 안내문에 적혀 있다. 등급 **A**(화면 문구 기준).
### 3.2 `wM_Rate` — 비목별 일괄 보정, 절사/반올림 × 소수 0~4위
`ST_Boq.CPM`의 노무비·재료비·경비·중기·일식 보정 창에 **비목마다 같은 짝**이 있다.
```text
wcBx_Pnt1_M / _L / _X / _W → 0 절사하여 · 1 반올림 하여
wcBx_Pnt2_M / _L / _X / _W → 0~4 소수 n 위값을 적용
rd_Qty1 / rd_Amt1 / rd_Amt2 → 내역서 전체 · 선택된 항목만
```
⇒ **단수처리가 전역 설정 하나가 아니라 비목(M·L·X·W)마다 따로**다. 09번 §4의 「출력별 정책을 데이터로」가 여기서도 확인된다.
### 3.3 `wM_Sel_Prn``ACTTYP`·`ACTOPT` 의 뜻이 풀렸다
`PRJLST.ACTTYP`·`ACTOPT``INITIAL``ACTYP`·`ACTOP`가 무엇인지 몰랐다. 화면이 답한다.
```text
cb_ACTTYP 노무비외 비목 적용안함 / 노무비외 재료비로 적용 / 노무비외 경비로 적용
cb_ACTOPT 노무비 1원미만 버림 / 소수 비교후 노무비 우선보정 / 소수 비교후 타금액 우선보정
```
**표준시장단가(실적) 적용 방식**이다. 설치 폴더의 파일명과 정확히 대응한다.
```
2025(상_표준시장[노임외 경비적용],국교,해수부).STBSC
2025(상_표준시장[노임외 재료비적용],국교,해수부).STBSC
```
봉화 2024는 `ACTYP=1`·`ACTOP=2`**「노임외 재료비로 적용」 + 「소수 비교후 타금액 우선보정」**이다.
`cb_PCLASS`(당초/변경/분할설계)와 `cb_CHASU`(1~8차변경)도 여기 있다 — **변경 차수가 최대 8차**까지다. `COSTN`의 접미사 1~4(최대 3차변경)와 다르므로, **차수 저장 구조가 4벌 슬롯만이 아니다.**
### 3.4 `wM_Set_Sys``Start8x.Ini` 의 미확정 키가 풀렸다
20번 §2.4에서 「`KCDNM`·`ACDNM`·`ZCDNM`·`JCDNM`·`ECDNM`·`BPNNM`은 의미 미확정(B)」이라 적었다. 화면이 답한다.
```text
cmBx_KCD / ACD / JCD / ECD / BPN → 0 자리 · 1 자리 · 2 자리
cmBx_ZCD → 2 자리 · 3 자리
```
**전부 소수 자리수 설정**이다. `Start8x.Ini`의 `KCDNM=1 ACDNM=1 ZCDNM=2 JCDNM=0 ECDNM=0 BPNNM=1`이 그대로 자리수다. 등급 **A**.
같은 화면에서 두 가지가 더 풀린다.
```text
cmBX_EXR 환율 적용 범위 — 시간당중기사용료 단독적용 / 중기 / 대가 / 산근 / 내역 전체
cmBx_Sel_RX 노임 환산 계수 갈래 — 계수 ; 터널구간 / 계수-1 ; 장대터널
/ 계수-2 ; 펌프준설선 / 계수-3 ; GRAB준설선 / 계수-4 ; 버켓준설선
```
`[EQUIPMENT]``RX1NM`~`RX4NM`은 **작업 조건별 노임 환산식 네 벌**이다(18번 §2.1의 `RXTNM`이 기본). `EXRNM`은 환율을 어디까지 적용할지다.
### 3.5 `wM_KanJub_K` — 부가세 방식이 하나 더 있다
기성 제잡비 계산서 화면의 부가세 선택지가 **12번 §10.1과 다르다.**
```text
(0) 직접입력 (1) 공급가액의 10% (2) 재료비의 10% (3) 재료비+산출경비의 10%
```
12번이 본 설계용 화면은 `공급가액의 10% / 재료비의 10% / 없음 / 산림조합 형식 / 산림조합-면세품`이었다. **기성 계산은 선택지 집합이 다르다.** 계류보전 프로젝트의 부가세가 10%가 아니었던 것(19번 §6 주석)과 이어질 수 있다.
`rd_Filter`는 집계 수준을 고르는 축이다 — `소계(+=) 이상 / 계(+==) / 합계(+===) / 총계(+====) / 총합계(+=====)`. **`BOQ11``T*`·`E*` 계층 깊이와 대응**한다(16번 §4.1).
### 3.6 `wM_Mrg_BSC` — 기준자료 갱신 매칭 규칙
```text
매칭 기준(5단계) 호환규정코드 / 시설코드 / 명칭·규격·단위 / 호환+명+규+단 / 명칭·단위 / 설정안함
명칭·단가·조달코드 업데이트 안함(복사처적용) / 업데이트(복사원적용)
+ 조달코드는 「복사처 우선」·「복사원 우선」까지 4택
```
⇒ 반기마다 새 기준자료가 오면 **무엇을 키로 잡아 기존 프로젝트 단가를 갱신할지**가 여기서 정해진다. 재구현 시 같은 축이 필요하다.
### 3.7 `wM_JabPum` · `wQtyList` — 코드 종류 전체 목록
```text
모든단가 · 노무비 · 재료비 · 경비 · 일위대가 · 중기비 · 단가산출 · 견적단가 · 대가,산근 · 그림코드
```
`COD_FMT_1~8`(20번 §2.4)보다 넓다. **`그림코드`**가 따로 있다 — 도면 참조 코드가 단가 체계 안에 있다.
## 4. 이번 점검이 바꾸는 것
| 항목 | 이전 | 지금 |
|---|---|---|
| UI 분석 범위 | 폼 3 / 190 | **선택축 272개 전수 카탈로그** |
| 일괄변경 수동값 우선순위 (T10) | ⛔ 실행 필요 | ✅ **화면 안내문에 있음** — 보호표시·0원은 제외 |
| 비목별 단수처리 | 전역 설정으로 봄 | ✅ **M·L·X·W 각각 절사/반올림 × 소수 0~4위** |
| `ACTTYP`·`ACTOPT` | 뜻 모름 | ✅ **표준시장단가 적용 방식·끝전 보정 우선순위** |
| `KCDNM` 등 6개 키 | 미확정 (B) | ✅ **소수 자리수 설정** |
| `RX1NM`~`RX4NM` | 미상 | ✅ **작업조건별 노임 환산식 4벌** (터널·준설선 계열) |
| 변경 차수 | 최대 3차(접미사 1~4)로 봄 | ⚠ **화면은 8차까지** — 저장 구조 재확인 필요 |
| 부가세 방식 | 설계용 5택 | ⚠ **기성용은 다른 4택** |
## 5. 남은 UI 작업
| 순위 | 할 일 | 왜 |
|---:|---|---|
| 1 | `PM_*` 인쇄 폼 ↔ `SET_REP` 75종 **대응표** | 어느 서식이 어느 계산을 쓰는지 |
| 2 | `wBoq`(398 KB)·`wM_Edit_San`(81 KB) 깊이 파싱 | 내역·단가산출 **편집 규칙** |
| 3 | 변경 차수 **8차** 저장 구조 확인 | `COSTN` 접미사 4벌과 어긋남 |
| 4 | 272개 선택축 ↔ `PRJLST.SMT` 키 대응 | 설정 저장 자리 완성 |
**UI 는 「화면 모양」이 아니라 계산 옵션의 원장이다.** 아직 캘 것이 남았다.
@@ -0,0 +1,117 @@
# `COSTN.SMT` 전수 파싱 — 단가산출 원장
파싱일: 2026-09-13
문법 출처: **`original/원가계산/STmate/SIxMemo_구조.md`** (②트랙, 2026-08-15) — 그쪽이 해독해 둔 문법을 그대로 씀.
대상: 맛보기 1건 + 실무 STC 7건의 `COSTN.SMT`
재현 스크립트: `40_스크립트/smt_expansion_parse.py`
## 1. 무엇이 들어 있나
`COSTN.SMT`는 **단가 하나하나의 산출 전개 텍스트**다. `BDQTY.DBF`가 암호화돼 막힌 자리를 **이 파일이 평문으로 대신 채운다.**
```text
'1. 포설(그레이다 3.6 m)'
L=2.9'm', H=0.2'm', D=50 , f=1.0
E=0.50 , N=4'회'
Cm=0.06*(50/4+50/4.0)+2*0.5=
Q=60*L*D*H*E*f/(N*Cm)='㎥/Hr'
'노무비' &X00006L& / Q =
'재료비' &X00006M& / Q =
'경 비' &X00006S& / Q =
```
## 2. 전수 집계
| 요소 | 검출 |
|---|---:|
| **자원 참조** `&코드&` | **1,570** |
| **품** (수량 + 단위) | **1,460** |
| **`Q` 식** | **187** |
| **중간 라벨** `:=A01` | **162** |
| **할증률** `n'%'` | **92** |
### 참조되는 자원 종류 — 중기가 압도적
| 앞글자 | 뜻 | 건수 |
|---|---|---:|
| **`X`** | **중기** | **1,504** |
| `L` | 노무 | 58 |
| `M` | 재료 | 7 |
| `S` | 경비 | 1 |
⇒ 단가산출(`D`코드)은 거의 전부 **중기 시간당 사용료를 작업량으로 나누는 계산**이다. 17번 §3(중기 호표)과 18번 §2.2(Q식)가 이 구조의 양 끝이다.
### 가져오는 성분 — 세 성분을 고루
| 끝글자 | 뜻 | 건수 |
|---|---|---:|
| `L` | 노무비 성분 | 548 |
| `S` | 경비 성분 | 524 |
| `M` | 재료비 성분 | 498 |
**중기 한 대를 참조하면 노무·재료·경비 세 성분을 각각 따로 끌어온다.** `&X00006L&` / `&X00006M&` / `&X00006S&`가 한 벌로 나온다. 07번 §1의 「총액 하나로만 저장하면 간접비 산정기초가 달라진다」가 여기서 확인된다.
## 3. `Q` 식은 한 꼴이 아니다 — 187개, 최소 10가지 꼴
18번 §2.2는 `Q = 3600·q·K·f·E/Cm` 한 꼴만 봤다. **실제로는 작업 종류마다 다르다.**
| 꼴 | 횟수 | 쓰임 |
|---|---:|---|
| `3600*q*K*f*E/Cm` | 56 | 굴삭 계열 (버킷) |
| `3600*q*k*E*f/Cm` | 26 | 같은 식, 인자 순서·대소문자만 다름 |
| `60*q1*F*E/Cm` | 16 | 분 단위 계열 |
| `60*P*f*E/Cm` | 12 | 출력(P) 기반 |
| `60*q1*f*E/Cm` | 10 | |
| `3600*q*K*f*E1/Cm1` | 4 | 인자에 번호가 붙은 변형 |
| `3600*q*K*f*E/Cms` | 4 | |
| `60*q*F*E/Cm` · `60*q*f*E1/Cm` | 각 4 | |
| `60*L*D*H*E*f/(N*Cm)` | — | 포설 계열 (길이·폭·두께) |
**재구현은 `Q` 식을 하드코딩하면 안 된다.** 텍스트에 적힌 식을 그대로 평가하는 경로가 필요하다 — 18번 §2.1의 「좌→우 순차 평가」와 같은 요구다.
`3600`(초→시간)과 `60`(분→시간) 두 계열이고, `Cm`(사이클 시간)의 단위가 각각 초·분이다.
## 4. 단위 분포 — 운반 계산 인자가 드러난다
| 단위 | 횟수 | | 단위 | 횟수 |
|---|---:|---|---|---:|
| 분 | 216 | | `m/min` | 44 |
| 인 | 115 | | `(적재시간)` | 35 |
| `km/hr` | 85 | | `(왕복시간)` | 35 |
| `km` | 69 | | `hr` | 34 |
| `(적하시간)` | 64 | | `sec(135)` | 31 |
| `m` | 56 | | `Km` | 29 |
**운반(덤프·도자) 계산이 `적재시간 + 왕복시간 + 적하시간`으로 사이클을 쌓는다.** `km/hr`(주행속도)·`km`(운반거리)가 함께 나오므로 `Cm = 적재 + 거리/속도 왕복 + 적하 + 대기` 꼴이다. 임도 설계에서 토공 운반이 큰 몫이라 Aislo에 직접 쓸모가 있다.
`sec(135)`처럼 **괄호 안에 조건(선회각 등)이 붙는다** — 18번 §2.2의 `Cm = 15 sec (90°)`와 같은 표기다.
## 5. 할증률 — 92건, 값이 다양
| 값 | 횟수 | | 값 | 횟수 |
|---|---:|---|---|---:|
| 2% | 33 | | 24% | 3 |
| 3% | 22 | | 8% | 3 |
| 5% | 9 | | 52% | 3 |
| 1% | 6 | | 11% | 2 |
| 9% | 6 | | 0.443% | 2 |
⇒ **할증은 품목·작업마다 제각각**이며 일괄 적용값이 아니다. `52%`·`24%` 같은 큰 값은 잡재료·공구손료 계열로 보인다(07번 §4의 「금액 가산」). `0.443%` 같은 값은 개별 산정 결과다.
`BDQTY``X_HAL` 실값은 여전히 못 보지만, **적용된 할증률 자체는 이 텍스트에 남는다.**
## 6. 이번 파싱이 바꾸는 것
| 항목 | 이전 | 지금 |
|---|---|---|
| `COSTN.SMT` | 21번에서 「평문」까지만 | ✅ **전수 파싱** — 참조 1,570·품 1,460·Q식 187 |
| 단가산출 참조 구조 | Q식 한 꼴만 봄 | ✅ **중기 참조 1,504건, 세 성분 각각** |
| `Q` 식 | 한 꼴로 알았음 | ⚠ **최소 10가지 꼴** — 하드코딩 금지 |
| 운반 사이클 | 안 봤음 | ✅ **적재+왕복+적하** 구조와 인자 |
| 할증률 | `BDQTY` 암호화로 막힘 | ◐ **적용값 92건은 읽힘** (입력 원장은 여전히 막힘) |
## 7. 남은 것
1. 호표 단위로 갈라 **`D`코드 ↔ 전개 텍스트 ↔ 참조 중기 호표**를 잇기 — 지금은 요소별 집계까지만
2. `Cm` 식 전수 수집 — 운반 사이클 공식을 꼴별로 정리
3. ②트랙의 미해명 **10바이트 메모 인덱스** — 이것이 풀리면 `COSTN.DBF` 행과 메모를 정확히 이을 수 있음
@@ -0,0 +1,121 @@
# 모듈 구조 전수 — 46개 모듈의 export 와 플러그인 규약
점검일: 2026-09-13
계기: 11번은 **핵심 CPM 12개**만 다뤘다. 나머지 **32개 모듈**이 손대지 않은 채였다.
재현 스크립트: `40_스크립트/module_export_sweep.py`
## 1. 플러그인 규약 — `Calc_Kanjub`
**46개 모듈 중 31개가 `Calc_Kanjub` 를 내보낸다.** 편집·인쇄·변환 무엇이든 **제비율 재계산을 스스로 부를 수 있는 공통 진입점**이다.
```text
Calc_Kanjub 31개 모듈이 공유
```
⇒ 「어느 화면에서 무엇을 고쳐도 제비율이 다시 돌 수 있다」는 뜻이다. 13번 **T8(UI 명령별 재계산 범위)**이 왜 어려운지가 여기서 보인다 — 재계산 진입점이 한 곳이 아니라 **모듈마다** 있다.
## 2. ⭐ `DP_Common.CPM` — 계산 공용 라이브러리 73함수
11번 §4.1은 `CALC_STR`만 언급했다. **실제 함수 집합 전체**는 이렇다.
### 2.1 식 평가
```text
CALC_STR 식 문자열 평가
CALC_STR_PART 부분 평가
CALC_STR_URC URC(자원참조) 포함 평가 ← &M00034M& 계열을 푸는 자리
GET_URC_TYP 참조 종류 판정
GET_VARI_VAL 변수 읽기 SET_VARI_VAL 변수 쓰기 ← :=A01 중간라벨
```
### 2.2 반올림·수치 — 11번 §4.2의 원 함수가 전부 여기 있다
```text
MROUNDUD 반올림 계열 MROUNDUP 올림 MTRUNC 절사
MINT 정수화 MFRAC 소수부
GET_ROUND_CUT 설정에 따라 위를 고르는 디스패처
MSTRTODBL · MSTRTOINT · ISNUM · ISINT · CH_FNTOVAL · FLOATTOSTR_ · EXTTOSTR
```
**`MFRAC`(소수부)가 새로 나왔다.** 18번 §2.2의 「`Q`를 소수 2자리로 먼저 확정」·17번 §2의 「성분 소계 절사」처럼 **소수부를 따로 다루는 자리**가 실제로 함수로 있다.
### 2.3 조건·논리
```text
IIF IFI IFO CHK_TRUE_CLSTR COUNT_LOGICAL
```
⇒ 식 문자열에 **조건 분기**가 들어간다. 11번 §4.1의 「선택 토큰(`[+LAB]`·`[+ANJ_B]`)을 옵션에 따라 포함/제거」가 이 함수들로 처리된다.
### 2.4 문자열·기타
`ADDBJVAL` · `ADDBSL` · `ADDSPSTR` · `CHANGE_STR` · `COPY_LEFT/RIGHT` · `LEFT_STR` · `RIGHT_STR` · `SEP_STR` · `REPLICA_STR` · `COUNT_STR` · `MPOS` · `NPOS` · `BPOS` · `DIS_FMT` · **`AMTTOHAN`**(금액 한글 변환 — 「금 일억삼천사백이십만원정」) · 파일 조작 20여 개.
**재구현 지침** — 식 평가기는 `+ - * /``ROUND` 계열만으로 부족하다. **`IIF`·`IFI`·`IFO` 조건 함수와 `GET_VARI_VAL`/`SET_VARI_VAL` 중간변수**까지 갖춰야 한다.
## 3. `ST_P_XLS.CPM` — XLSX 입출력 72함수
| 함수 | 뜻 |
|---|---|
| `MAKE_XLS` · `SHOW_XLS` · `FREE_XLS` | 엑셀 생성·표시·해제 |
| **`CONV_XLS_2_ST`** | **엑셀 → STmate 역등록** |
| **`XLS_2_BDREC`** | 엑셀 → `BDQTY` 레코드 |
| **`Prn_FMLA_INFO`** | **수식 정보 출력** — 출력 XLSX에 수식이 남는 까닭 |
| `MK_BSCBOQ_EXL` · `MK_EXL_BSCBOQ` | 기준 BOQ 양방향 |
| `Prn_*_EXL` 약 55개 | `SET_REP` 서식마다 엑셀판 |
⇒ **엑셀이 단순 출력이 아니라 양방향 교환 경로**다. `〓 INITIAL 〓` 시트(18번 §2.3)가 재등록 메타인 까닭이 `CONV_XLS_2_ST`에 있다.
**`XLS_2_BDREC`** — 암호화된 `BDQTY`**엑셀에서 만들 수 있다.** 뒤집으면 `BDQTY` 내용이 엑셀 수량산출 양식과 대응한다는 뜻이다.
## 4. `SX32W.CPM` — 라이선스는 하드웨어 동글
```text
RNBOsproInitialize · RNBOsproFindFirstUnit · RNBOsproQuery · RNBOsproRead
RNBOsproGetKeyInfo · RNBOsproGetSubLicense · RNBOsproSetDeveloperCode
RNBOsproCheckTerminalService · RNBOsproSetHeartBeat · RNBOsproReleaseLicense …29개
```
**Rainbow(현 Thales) Sentinel SuperPro** API다. `ST_BdSN.CPM``Chk_BD_SN`·`Chk_REG_SN`(시리얼 검사)이 함께 있다.
⇒ **라이선스가 소프트 키가 아니라 USB 동글**이다. 13번의 통제시험 T1~T11이 왜 「영구 불가」인지가 확정됐다 — 동글이 없으면 실행 자체가 안 된다.
## 5. 그 밖에 드러난 모듈
| 모듈 | export | 뜻 |
|---|---:|---|
| `ST_U_Archive.CPM` | 16 | **STC 압축·해제**`Chk_STC_Ver`(버전) · `ComP_Data` · `UnPack`/`UnPack1`/`UnPack_No` |
| `ST_P_Excav.CPM` | 15 | **굴착 전용 내역 양식**`Prn_Nae_Ex1_*` · `Ex2_*` 계열. 도로공사 양식 |
| `ST_P_Nae.CPM` | 30 | 내역·원가 인쇄 본진 |
| `ST_P_List.CPM` | 11 | 목록표 계열 |
| **`ST_Jabpum.CPM`** | 6 | **`Edt_Hal_Amt` vs `Edt_Hal_Rate`** — 할증을 **금액과 율 두 갈래**로 편집. 07번 §4의 「수량 할증 ≠ 금액 가산」이 export 이름에 그대로 |
| `ST_U_ADL.CPM` | 6 | 온라인 갱신 — `ST_Down_File` · `ST_UP_INFO_R` · `ST_SND_Mail` |
| `ST_Com_V.CPM` | 10 | `GET_*_R` 계열 — 모듈 간 자원 조회 규약 |
| `ST_Func_C.CPM` | 7 | `SUM_TOT` · `CON_TOT` · `ZERO_TOT` · `PCN_TO_T` · `OPTIMIZ`**계층 합산** |
| `CPMARC`/`CPMUNA` | 4+4 | ZIP 압축·해제 (Info-ZIP) |
| `STmexe.Cpm` | 2 | `Chk_Exe` · `Chk_New_Exe` — 자기 갱신 |
## 6. 이번 점검이 바꾸는 것
| 항목 | 이전 | 지금 |
|---|---|---|
| 모듈 분석 범위 | 핵심 12개 | **46개 전수 export** |
| 식 평가기 함수 집합 | `CALC_STR`만 알았음 | ✅ **73함수** — 조건(`IIF`)·중간변수·`MFRAC`까지 |
| 재계산 진입점 | 한 곳으로 봄 | ⚠ **31개 모듈이 `Calc_Kanjub` 보유** — T8이 어려운 까닭 |
| 라이선스 | 「없음」까지만 | ✅ **Rainbow Sentinel SuperPro 하드웨어 동글** |
| 엑셀 | 단방향 출력으로 봄 | ✅ **양방향**`CONV_XLS_2_ST` · `XLS_2_BDREC` |
| 할증 | 한 갈래로 봄 | ✅ **금액·율 두 갈래** (`Edt_Hal_Amt`/`Edt_Hal_Rate`) |
| 변경 차수 8차 의문 | 미해결 | ✅ **해소** — 아래 |
## 7. 변경 차수 8차 의문 해소
22번 §3.3에서 「화면은 8차인데 `COSTN` 접미사는 4벌」이라 남겨 둔 것을 확인했다.
| 근거 | 결과 |
|---|---|
| `PRJLST.SMT` 차수 섹션 | 표본 7건 모두 **최대 `[1_KANJUB]`·`[2_KANJUB]`** |
| DBF 접미사 | `PRJLST`·`BOQ11`·`COSTN` 모두 **1~4 네 벌** |
| 바이너리 문자열 | **`1차변경`~`3차변경`만** 존재. 4~8차 없음 |
| `BOQ1*` / `BOQ2*` | `BOQ1` 15개 모듈, **`BOQ2`는 0개** |
**한 파일이 담는 것은 4벌(원설계 + 3차변경)이 맞다.** 화면의 1~8차는 **분리형으로 파일을 이어 갔을 때 그 파일들을 고르는 인쇄 선택**이다(16번 §4.6의 차수 인계). 저장 구조 의문은 없다.
@@ -0,0 +1,149 @@
# 설정키 전수와 간접비 식 템플릿 — 설정 사슬 닫기
점검일: 2026-09-13
재현 스크립트: `40_스크립트/settings_key_map.py` · 원자료 `30_원자료/prjlst_smt_keys.txt`
## 1. `PRJLST.SMT` 저장키 **241종**
21번 §3에서 일부만 봤던 것을 전수로 세었다. 접두어가 뜻을 가른다.
| 접두 | 뜻 | 개수 |
|---|---|---:|
| `C_` | **금액 직접입력** | 62 |
| `R_` | **요율** | 39 |
| `I_` | **선택 인덱스** | 41 |
| `B_` | 켬/끔 | 12 |
| `D_` | **밑수** | 26 |
| `S_` | **산출액** | 30 |
| `ADD_*` | **추가금액** | 22 |
| 그 밖 | `JAB_TYPE` · `CAL_ACTL` · `VIS_TOTL` · `Y_KONGSU` | 9 |
### 1.1 ⭐ `D_``S_` 가 짝을 이룬다 — 밑수와 결과를 함께 저장
```text
D_KANNO / S_KANNO 간접노무비 D_SANBO / S_SANBO 산재보험료
D_KUNBO / S_KUNBO 건강보험료 D_YUNBO / S_YUNBO 연금보험료
D_ANJUN / S_ANJUN 안전관리비 D_T_KITA / S_T_KITA 기타경비
D_HWAN / S_HWAN 환경보전비 D_ILBAN / S_ILBAN 일반관리비
D_HADOSU / S_HADOSU 하도급보증 D_EQIPSU / S_EQIPSU 건설기계보증
D_S_SUDO / S_S_SUDO … 수공 세부경비 8종도 같은 짝
```
**항목마다 「어떤 밑수에 곱했고 얼마가 나왔는지」를 프로젝트에 남긴다.** 09번 §2의 `indirect_result(base_amount, rate, raw_amount, rounded_amount)` 계약이 실제 저장 구조와 맞는다.
`S_DAMT_L` · `S_DAMT_M` · `S_DAMT_S` · `S_KAMT_M` 은 직접비 성분 합계다.
### 1.2 고용개선지원비가 4종으로 나뉜다
```text
C_EIS_HOLLAB C_EIS_HOLPAY C_EIS_INCENT C_EIS_SOCINS C_EIS_SUPPAY
R_EIS_HOLIR1 R_EIS_HOLIR2 R_EIS_INCENT R_EIS_SOCIR2
D_EIS_* / S_EIS_*
```
12번 §3.2가 「일반형식에 별도 묶음이 있다」고만 적은 **고용개선지원비**가 실제로 **휴업수당·장려금·사회보험·지원금 네 갈래**다.
### 1.3 ⭐ 추가금액 저장 구조
```text
ADD_CNT_1 ~ ADD_CNT_4 단계별 행 수
ADD_NAME_2_01 · ADD_UNIT_2_01 · ADD_BIGO_2_01 이름·단위·비고
ADD_AMT_2_01_1 ~ ADD_AMT_2_01_5 금액 (단계별 5칸)
ADD_AMT_3_01_1 ~ ADD_AMT_3_01_5
ADD_BUGA_2_01 부가세 포함 여부
```
`_2_`·`_3_` = **삽입 단계**, `_01` = 행 번호, `_1~5` = **단계별 포함 칸**. 11번 §9가 화면 문자열로 본 「단계 3~6 삽입」이 저장 구조로 확인된다.
### 1.4 형식 전용 키
```text
실적형식만 I_MTYP · I_LTYP · I_MXA · I_MXK · R_R_DM · R_R_DL · R_R_L · R_R_DMDL · R_R_DML
수공형식만 C_S_* / R_S_* / D_S_* / S_S_* (수도광열·복리후생·소모품·통신·세금·폐기물·도서·지급수수료)
```
`R_R_DM`·`R_R_DL`·`R_R_L`·`R_R_DMDL`·`R_R_DML`**`rGrd_RateB`의 열 이름(`재`·`직노`·`노`·`재+직노`·`재+노`) 그대로**다(02번 §RATE_B). 수공형식 밑수 배분 계수가 여기 저장된다.
## 2. 간접비 식 템플릿 — 바이너리 전수 **26개**
11번 §6이 일부만 보여 준 것을 모두 뽑았다. **대괄호는 런타임 치환 변수**다.
### 2.1 밑수가 다른 기본 꼴
```text
ROUNDDOWN( [T_AMT] * [RAT_1] ) 총액 기반
ROUNDDOWN( [LAMT] * [RAT_1] ) 노무 기반
ROUNDDOWN( [M_AMT] * [RAT_1] ) 재료 기반
ROUNDDOWN( ([L_AMT][+LAB]) * [RAT_1] ) 노무 + 선택토큰
ROUNDDOWN( ([T_AMT]-[M_AMT]) * [RAT_1] ) 총액 − 재료 ← 이윤
ROUNDDOWN( ([M_AMT]+[L_AMT]+[S_AMT]) * … ) 세 성분 합
```
`[+LAB]`는 **옵션에 따라 켜고 끄는 항**이다 — `DP_Common``CHK_TRUE_CLSTR`가 판정한다(24번 §2.3).
### 2.2 ⭐ 조건 분기가 식 안에 있다
```text
IF( ([LAMT]+[M_AMT]) … )
IF( ([L_AMT]+[M_AMT]) … )
```
**식 평가기에 `IF`가 필요하다.** 24번 §2.3의 `IIF`·`IFI`·`IFO` 함수가 이것을 처리한다. 사칙연산과 `ROUND` 계열만으로는 재현이 안 된다.
### 2.3 앞 결과를 밑수로 쓰는 꼴
```text
ROUNDDOWN( [C_KUNBO] * [RAT_1] )
```
**건강보험료의 「직접입력 금액」(`C_KUNBO`)에 요율을 곱한다** — 노인장기요양보험료다. 16번 §3에서 `장기요양 = 건강보험료 × 12.95%`로 실증한 것의 식 원문이며, **밑수가 요율 계산 결과가 아니라 `C_` 직접입력 칸**이라는 점이 19번 §6의 「당초액 유지」와 이어진다.
### 2.4 복합 보험식 — 세 항을 각각 절사 후 다시 절사
```text
ROUNDDOWN( ROUNDDOWN([LAMT]*[RAT_1]*[RAT_2]/100*1.4, 0)
+ ROUNDDOWN([LAMT]*[RAT_KY]*[RAT_3]/100, 0)
+ ROUNDDOWN([LAMT]*[RAT_1]*[RAT_2]/100*[RAT_4]/100, 0), 자리 )
```
### 2.5 변경설계 두 꼴
```text
[C_AMT_B] + ROUNDDOWN( [C_AMT_B] * (([C_TAMT_C]-[C_TAMT_B])/[C_TAMT_B]) ) 방법 A
[C_AMT_B] + ROUNDDOWN( ([C_TAMT_C]-[C_TAMT_B]) * [RAT_1] ) 방법 B
```
### 2.6 사급자재 VAT 제외
```text
+ [C_SAGUP_C] / 1.1
```
`cmBx_Kwan_Sa``사급자재대/1.1` 선택(12번 §10.1)이 식으로 들어간다.
## 3. 설정 사슬이 닫혔다
```text
화면 선택축 272개 (22번)
↓ 고르면
PRJLST.SMT 저장키 241종 (이 문서 §1)
↓ C_/R_/I_/B_ 값이
간접비 식 템플릿 26개 (§2)의 [RAT_n]·[C_*] 자리에 치환
↓ DP_Common 73함수로 평가 (24번 §2)
↓ 항목마다 D_(밑수)·S_(결과) 로 되돌아 저장
공사원가계산서 출력 (16번에서 원 단위 재현 완료)
```
**입력(화면) → 저장(SMT) → 식(템플릿) → 평가기(함수) → 결과(출력)** 다섯 칸이 모두 이어졌다.
## 4. 이번 점검이 바꾸는 것
| 항목 | 이전 | 지금 |
|---|---|---|
| 프로젝트 저장키 | 일부만 봄 | ✅ **241종 전수**, 접두어 6갈래 |
| 밑수 저장 | 안 남는 줄 알았음 | ✅ **`D_`(밑수)·`S_`(결과) 짝으로 저장** |
| 추가금액 | 화면 문자열로만 | ✅ **저장 구조** `ADD_AMT_단계_행_칸` |
| 고용개선지원비 | 「별도 묶음」 | ✅ **4갈래** (휴업수당·장려금·사회보험·지원금) |
| 수공 밑수 배분 | `RATE_B` 표만 | ✅ **`R_R_DM`·`DL`·`L`·`DMDL`·`DML`** 저장키 |
| 간접비 식 | 일부 | ✅ **26개 전수** |
| 식에 조건 분기 | 몰랐음 | ⚠ **`IF(...)` 존재** — 평가기 필수 요건 |
@@ -0,0 +1,61 @@
### CPM/EXE export 전수 — 모듈 44개, export 408개(중복포함)
11번이 다룬 핵심 12개 말고 **32개 모듈**이 남아 있었음
CPMARC.CPM 4개 GetZipDllPrivVersion, GetZipDllVersion, ZipDllExec, ___CPPdebugHook
CPMUNA.CPM 4개 GetUnzDllPrivVersion, GetUnzDllVersion, UnzDllExec, ___CPPdebugHook
DP_Common.CPM 73개 ADDBJVAL, ADDBSL, ADDSPSTR, AMTTOHAN, BPOS, CALC_STR, CALC_STR_PART, CALC_STR_URC, CHANGE_STR, C
DP_MinMsg.CPM 3개 GETNEWCODE_NAME, GETSPCHAR, MINIMSG
ST_BdSN.CPM 3개 Chk_BD_SN, Chk_BD_USE, Chk_REG_SN
✅ ST_Boq.CPM 4개 Add_From_MRG, Calc_Kanjub, Edit_Boq, Edit_Bqm
ST_Com_V.CPM 10개 GET_CAL_R, GET_COM_R, GET_IMS_R, GET_MRG_R, GET_PBL_R, GET_PRN_R, GET_SFC_R, GET_STM_R, GET_SYS_
ST_EdSan.CPM 3개 Calc_Kanjub, Edit_EQ_SYNC, Edit_SanKun
✅ ST_EdiLWi.CPM 2개 Calc_Kanjub, Edit_iLW
ST_FindUse.CPM 3개 Calc_Kanjub, Find_BDQ, Find_BOQ
ST_Func_C.CPM 7개 CON_TOT, OPTIMIZ, PCN_TO_T, SUM_TOT, VALTOPOS, ZERO_TO, ZERO_TOT
ST_Jabpum.CPM 6개 Calc_Kanjub, Edit_Dan, Edit_Strg, Edt_Hal_Amt, Edt_Hal_Rate, View_JabPum
✅ ST_KanJub.CPM 5개 Calc_Kanjub, Edit_KanJub, Edit_KanJub_K, Edit_Kan_Rate, Prn_Kan_Rate
ST_Merge_Code.CPM 1개 Calc_Kanjub
ST_PList.CPM 3개 Calc_Kanjub, Call_MRG, Call_PLIST
✅ ST_P_Collect.CPM 5개 Calc_Kanjub, Prn_Col_Dan, Prn_Col_Dan_L, Prn_Col_X, Prn_Col_X_L
✅ ST_P_ConEst.CPM 9개 Calc_Kanjub, Prn_Jab_K, Prn_Nae_C1, Prn_Nae_C2, Prn_Nae_C3, Prn_Nae_Est, Prn_Nae_K1, Prn_Nae_K2,
ST_P_Excav.CPM 15개 Calc_Kanjub, Prn_Nae_Ex1_0, Prn_Nae_Ex1_1, Prn_Nae_Ex1_2, Prn_Nae_Ex1_C1, Prn_Nae_Ex1_C2, Prn_Na
✅ ST_P_Joong.CPM 7개 Calc_Kanjub, Prn_Joong, Prn_Joong2, Prn_Joong2_L, Prn_Joong_C, Prn_Joong_C_L, Prn_Joong_L
ST_P_List.CPM 11개 Calc_Kanjub, Prn_AList, Prn_Comp_M, Prn_Comp_M1, Prn_Comp_M1_L, Prn_Dan, Prn_Dan_L, Prn_SanMok,
ST_P_Nae.CPM 30개 Calc_Kanjub, Prn_Boq_Not, Prn_Bqm, Prn_Jab, Prn_Jab_P, Prn_Nae, Prn_NaeW, Prn_NaeW_P, Prn_Nae_B,
ST_P_San.CPM 3개 Calc_Kanjub, Prn_San_L, Prn_San_P
ST_P_XLS.CPM 72개 CONV_XLS_2_ST, Calc_Kanjub, Chk_Xls_File, FIND_PRT_INDX, FREE_XLS, MAKE_XLS, MK_BSCBOQ_EXL, MK_E
ST_P_iLWi.CPM 3개 Calc_Kanjub, Prn_iLWi, Prn_iLWi_S
ST_Prt.CPM 4개 Calc_Kanjub, GS_Prn_Rec, Prn_Prt, Rep_Print
✅ ST_SanCal.CPM 2개 Calc_Kanjub, Calq_San
ST_Sys.CPM 2개 Calc_Kanjub, Set_Sys_1
ST_U_ADL.CPM 6개 Get_Real_Time, ST_Down_File, ST_NEW_INFO, ST_SND_Mail, ST_UP_DNINFO, ST_UP_INFO_R
ST_U_Archive.CPM 16개 Calc_Kanjub, Chk_ArcName, Chk_Data, Chk_STC_Ver, Chk_STDir, ComP_CurBOQ, ComP_Data, ComP_FOLD, C
✅ ST_U_ChgAmt.CPM 2개 CHANGE_AMT, Calc_Kanjub
✅ ST_U_ChgCode.CPM 10개 Calc_Kanjub, Chg_Code_All, Chg_Code_Plus, Chg_Code_Sel, Chk_Chg_Code_1, Chk_True_Rep_Code, Exec_
✅ ST_U_ConBD.CPM 4개 CONBD_USD_QTY, CON_QTY, CON_QTY9, Calc_Kanjub
✅ ST_U_Convert.CPM 20개 Appn_Tag_REP, CHK_K_FLD, Calc_Kanjub, Conv_BOQ, Conv_Equip, Conv_Jodal_ST8, Conv_ST8_Jodal, Conv
ST_U_DelCode.CPM 6개 Calc_Kanjub, Chk_True_Del_1, Del_Code_A_T, Del_Code_A_X, Del_Code_Sel, Exec_Delete_Code
ST_U_DelPrj.CPM 3개 Calc_Kanjub, Delete_Prj, Merge_Prj
ST_U_Indx.CPM 2개 Calc_Kanjub, INDX_ALL
ST_U_Merge.CPM 2개 Calc_Kanjub, ST_MERGE
ST_VSetup.EXE 2개 _MainWndProc@16, _StubFileWrite@12
STmate.Cpm 1개 Calc_Kanjub
✅ STmateQty8.CPM 4개 Calc_Kanjub, FIND_QCODE, Free_Qty, ST_QTY
STmexe.Cpm 2개 Chk_Exe, Chk_New_Exe
SX32W.CPM 29개 RNBOsproActivate, RNBOsproCheckTerminalService, RNBOsproCleanup, RNBOsproDecrement, RNBOsproEnum
UNWISE.EXE 4개 _ItemDlg@16, _MainWndProc@16, _PromptDlg@16, _SharedDlg@16
View_Old_San.CPM 1개 Show_Old_San
### 여러 모듈이 공유하는 export (플러그인 공통 규약)
Calc_Kanjub 31개 모듈
___CPPdebugHook 2개 모듈
Prn_Boq_Not 2개 모듈
Prn_Nae_ColL 2개 모듈
Prn_Nae_ColM 2개 모듈
Prn_Nae_ColX 2개 모듈
Prn_Nae_Cont1 2개 모듈
Prn_Nae_Cont2 2개 모듈
_MainWndProc@16 2개 모듈
GetZipDllPrivVersion 1개 모듈
GetZipDllVersion 1개 모듈
ZipDllExec 1개 모듈
@@ -0,0 +1,95 @@
### PRJLST.SMT 저장키 241종 (접두 차수 제거)
■ C_ 금액 직접입력 62개
C_ANJUN C_ANJUN1_G C_ANJUN2_G C_ANJUNP C_ANJUN_B
C_ANJUN_G C_BUGA_M C_BUSAN C_BUSAN_B C_DOSEOH
C_EIS_HOLLAB C_EIS_HOLPAY C_EIS_INCENT C_EIS_SOCINS C_EIS_SUPPAY
C_EQIPSU C_EXCEPT_B C_HADOSU C_HWAN C_ILBAN
C_IYUN_M C_KANNO C_KAN_M C_KN_SA C_KOBO
C_KONGSA C_KONGSA_J C_KONGSU C_KONGSU_A C_KONGSU_MA
C_KUNBO C_KWANGUP C_KWANGUP_D C_KWANGUP_K C_NBUGAM
C_PSAMT_B C_PUMJIL_B C_PURCHAS C_SAGUP C_SANBO
C_SANGSI C_SLVBO C_S_ANJUN_B C_S_BOKRI C_S_DOSEO
C_S_JIGUP C_S_PEGI C_S_PUMJIL C_S_SEGUM C_S_SIGONG
C_S_SOMO C_S_SUDO C_S_TONGSIN C_TBUGUM C_T_ANJUN_N
C_T_JODAL C_T_KITA C_T_KOLJAE C_T_K_MAT C_T_PEGI
C_UPDNKWN C_YUNBO
■ R_ 요율 39개
R_ANJUN R_ANJUN1 R_ANJUN2 R_ANJUNP R_DOSEOH
R_EIS_HOLIR1 R_EIS_HOLIR2 R_EIS_INCENT R_EIS_SOCIR2 R_EQIPSU
R_HADOSU R_HWAN R_ILBAN R_IYUN R_KANNO
R_KOBO R_KONGSA R_KONGSU R_KUNBO R_R_DL
R_R_DM R_R_DMDL R_R_DML R_R_L R_SANBO
R_SLVBO R_S_BOKRI R_S_DOSEO R_S_JIGUP R_S_PEGI
R_S_SEGUM R_S_SIGONG R_S_SIGONG_1 R_S_SOMO R_S_SUDO
R_S_TONGSIN R_TBUGUM R_T_KITA R_YUNBO
■ I_ 선택 인덱스 41개
I_ANJUNAUTO I_ANJUN_B I_ANJUN_K I_ANJUN_TYPE I_ANPUM
I_AN_DN_TYPE I_BF_CH_BO I_BID_TYPE I_BUGA I_COST_BO
I_DOGUP I_DOG_JEOL I_EQIP I_HWAN I_ILBAN
I_IYUN I_JEOLSA I_KAN_JEOLSA I_KN_SA_TYPE I_KOBO_TYPE
I_KONG_KIKAN I_KONG_TYPE I_KOSU_TYPE I_KUNBO I_KUN_TYPE
I_KWN_COMB I_KWN_JEOLSA I_KWN_K_JEOLSA I_KWN_K_UPDN I_KWN_UPDN
I_LTYP I_MTYP I_MXA I_MXK I_NAG_RATE
I_POS_SA I_SET_BASE I_TBUGUM I_TOT_ADD I_TOT_JEOL
I_YUNBO
■ B_ 켬/끔 12개
B_ANJUN_B B_BUGA_UP B_BUSAN_B B_CHANGE B_EXCEPT_B
B_KONGSA B_KWANGUP_D B_KWANGUP_K B_NBUGAM B_PSAMT_B
B_PUMJIL_B B_T_JODAL
■ Y_ 연도·기타 1개
Y_KONGSU
■ 기타_ 섹션·기타 86개
ADD_AMT_2_01_1 ADD_AMT_2_01_2 ADD_AMT_2_01_3 ADD_AMT_2_01_4 ADD_AMT_2_01_5
ADD_AMT_3_01_1 ADD_AMT_3_01_2 ADD_AMT_3_01_3 ADD_AMT_3_01_4 ADD_AMT_3_01_5
ADD_BIGO_2_01 ADD_BIGO_3_01 ADD_BUGA_2_01 ADD_CNT_1 ADD_CNT_2
ADD_CNT_3 ADD_CNT_4 ADD_NAME_2_01 ADD_NAME_3_01 ADD_UNIT_2_01
ADD_UNIT_3_01 CAL_ACTL D_ANJUN D_ANJUNP D_EIS_HOLLAB
D_EIS_INCENT D_EIS_SOCINS D_EIS_SUPPAY D_EQIPSU D_HADOSU
D_HWAN D_ILBAN D_KANNO D_KOBO D_KONGSA
D_KONGSU D_KUNBO D_SANBO D_SLVBO D_S_BOKRI
D_S_DOSEO D_S_JIGUP D_S_PEGI D_S_SEGUM D_S_SIGONG
D_S_SOMO D_S_SUDO D_S_TONGSIN D_TBUGUM D_T_KITA
D_YUNBO JAB_TYPE S_ANJUN S_ANJUNP S_DAMT_L
S_DAMT_M S_DAMT_S S_EIS_HOLLAB S_EIS_INCENT S_EIS_SOCINS
S_EIS_SUPPAY S_EQIPSU S_HADOSU S_HWAN S_ILBAN
S_KAMT_M S_KANNO S_KOBO S_KONGSA S_KONGSU
S_KUNBO S_SANBO S_SLVBO S_S_BOKRI S_S_DOSEO
S_S_JIGUP S_S_PEGI S_S_SEGUM S_S_SIGONG S_S_SOMO
S_S_SUDO S_S_TONGSIN S_TBUGUM S_T_KITA S_YUNBO
VIS_TOTL
### 간접비 식 템플릿 — 바이너리에서 전수
서로 다른 템플릿 26개 (상위 30)
ROUNDDOWN( [T_AMT] * [RAT_1] 62
ROUNDDOWN( [LAMT] * [RAT_1] 31
ROUNDDOWN( ([L_AMT][+LAB]) * [RAT_1] 31
[C_AMT_B]+ROUNDDOWN( [C_AMT_B] * (([C_TAMT_C]-[C_TAMT_B])/[C_TAMT_B]) 31
(([LAMT][+LAB])- 31
[C_AMT_B]+ROUNDDOWN( ([C_TAMT_C]-[C_TAMT_B]) * [RAT_1] 31
([LAMT][+LAB]) 31
ROUNDDOWN( [C_KUNBO] * [RAT_1] 31
ROUNDDOWN( ([LAMT][+LAB]) * [RAT_1] 31
[LAMT][+LAB] 31
([LAMT]+[M_AMT] 31
IF(([LAMT]+[M_AMT]) 31
([LAMT]+[M_AMT])) 31
([L_AMT]+[M_AMT]) 31
IF(([L_AMT]+[M_AMT]) 31
([L_AMT]+[M_AMT])) 31
ROUNDDOWN( ([T_AMT]-[M_AMT]) * [RAT_1] 31
ROUNDDOWN( ROUNDDOWN([LAMT]*[RAT_1]*[RAT_2]/100*1.4 31
0) + ROUNDDOWN([LAMT]*[RAT_KY]*[RAT_3]/100 31
0) + ROUNDDOWN([LAMT]*[RAT_1]*[RAT_2]/100*[RAT_4]/100 31
ROUNDDOWN( ([M_AMT]+[L_AMT]+[S_AMT] 31
ROUNDDOWN([T_AMT] * [RAT_1] 31
ROUNDDOWN([M_AMT] * [RAT_1] 31
+[C_SAGUP_C]/1.1 31
[C_SAGUP_C]/1.1 31
ROUNDDOWN([TAMT] * [RATE] 3
@@ -0,0 +1,335 @@
### 파싱 성공한 STmate 고유 폼 150개
■ PM_Prn_Ord ST_Prt.CPM «보고서 파일의 인쇄순서를 지정합니다.»
cBx_BTyp 당초 설계, 변경 설계, 분할 설계, 실적 공사, 할증 내역, 견적 내역, 계약 내역, 실행 예산, 기성 내역, 설계 변경, 굴착/복구, 굴착 변경
■ PM_Prt ST_Prt.CPM «설계예산시스템 인쇄메뉴»
cmBx_FntTyp 굴림체, 궁서체, 바탕체, 돋움체
cmBx_BigoFld 코드번호 인쇄, 번호(호표) 인쇄, 분류번호 인쇄, 시설코드 인쇄, 호환규정코드, 적용기준일, 수공분류코드
cmBx_CodeFld 코드번호 인쇄, 번호(호표) 인쇄, 분류번호 인쇄, 시설코드 인쇄, 호환규정코드
cmBx_PosQty 수량 / 단위, 단위 / 수량, 표 준
cmBx_BigoFld2 코드번호 인쇄, 번호(호표) 인쇄, 분류번호 인쇄, 시설코드 인쇄, 호환규정코드, 적용기준일, 수공분류코드
cmBx_SHTyp 일반양식 인쇄, 토개공양식 인쇄, 수자원공사양식 인쇄
cmBx_SanLP 기본방향(세로) 인쇄, 횡 방향(가로) 인쇄
cmBx_SanMOK 일반양식 인쇄, 산근양식 인쇄
■ PM_Sel_St ST_Prt.CPM «인쇄할 공종을 선택하세요»
rd_Filter 소 계(+=) 이상, 계(+==) 이상, 합 계(+===) 이상, 총 계(+====) 이상, 총합계(+=====
Rg_Bk 아래로, 위로
■ mMinMsg_ DP_MinMsg.CPM «종류»
ListBox1 기호1, 기호2, 영어/숫자, 한글자모, 그리스어, 선문자, 단위기호, 원문자, 괄호문자, 히라가다, 가다가나, 러시아어, 특수외국어, 분수/첨자
■ uM_ConstType ST_U_Convert.CPM «건축공사 사업내용《 건축공사 중 신축 또는 신축에 준하»
cn_Cn_Code1 서울특별시; 11, 부산광역시; 26, 대구광역시; 27, 인천광역시; 28, 광주광역시; 29, 대전광역시; 30, 울산광역시; 31, 세종특별자치시; 36
cn_Cn_Code2 서울특별시; 11, 부산광역시; 26, 대구광역시; 27, 인천광역시; 28, 광주광역시; 29, 대전광역시; 30, 울산광역시; 31, 세종특별자치시; 36
cn_Bd_Type 일반청사; 연면적 10,000㎡ 미만 공용청사(경찰서, 우체국 포함), 대형청사; 연면적 10,000㎡ 이상 공용청사(경찰서, 우체국 포함), 일반형초중고;
cn_Dm_Const 철거공사 포함; 기존 부지에 있는 시설물을 철거하고 공사, 철거공사 미포함; 기존/신축 부지에 시설물만 공사
cn_Cl_Area 비혼잡지역(일반); 공사와 관련된 현장주변의 장애요인이 적고 공사수행환경이 복잡하지 않은 지역, 혼잡지역(특수); 공사에 따른 민원발생 소지가 높고, 특수지반
cn_Gr_Shape 일반지반; 터파기 등 기초공사 수행에 어려움이 없는 지반, 특수지반;
cn_Sh_Const 흙막이공사가 없는 경우, 흙막이공사가 있는 경우
cn_St_Type 철골조; 주요 구조부가 철골조로 구성된 경우, 철골철근콘크리트조; 주요 구조부가 철골철근콘크리트로 구성된 경우, 철근콘크리트조; 주요 구조부가 철근콘크리로 구
■ uM_Merge ST_U_Merge.CPM «데이타 병합 【 복사처 <= 복사원 】»
Rdo_BD 각 기준에 따라 설정, 일식단가(W코드)로 생성
Rdo_X 일위대가 기준대로 처리, 일식단가(W코드)로 생성
RdoL 복사처적용 (동일항목 코드치환), 복사원적용 (동일항목 내용복사), 신규등록 (단가다른코드 신규생성)
RdoM 복사처적용 (동일항목 코드치환), 복사원적용 (동일항목 내용복사), 신규등록 (단가다른코드 신규생성)
RdoS 복사처적용 (동일항목 코드치환), 복사원적용 (동일항목 내용복사), 신규등록 (단가다른코드 신규생성)
RdoB 복사처적용 (동일항목 코드치환), 복사원적용 (동일항목 내용복사), 신규등록 (단가다른코드 신규생성)
RdoD 복사처적용 (동일항목 코드치환), 복사원적용 (동일항목 내용복사), 신규등록 (단가다른코드 신규생성)
RdoW 복사처적용 (동일항목 코드치환), 복사원적용 (동일항목 내용복사), 신규등록 (단가다른코드 신규생성)
■ uM_XML_Info ST_U_Convert.CPM «공사원가호환규정 포맷(XML) 변환»
ed_PrjTyp 토목공사, 건축공사, 설비공사, 전기공사, 통신공사, 조경공사, 소방공사, 국가유산수리공사
■ wBoq ST_Boq.CPM «공종목록표»
cBox01 = 이상, == 이상, === 이상, ==== 이상 , ===== 이상, ====== 이상
RGrpTo 내역서, 열린 보관함
cBox02 = 이상, == 이상, === 이상, ==== 이상 , ===== 이상, ====== 이상
■ wBoqRate ST_Boq.CPM «할증율 적용:»
Rdo 전체(재,노,경 같은 비율), 비목별(재,노,경 각각)
■ wExpnd ST_Boq.CPM «일위대가표 풀기»
RGrpToT 소계(=), 계(==), 합계(===)
CMBoxUpDn 절삭하여, 반올림하여
CMBoxDot 소수 4위값을 적용, 소수 3위값을 적용, 소수 2위값을 적용, 소수 1위값을 적용, 소수 0위값을 적용
■ wITNum ST_Boq.CPM «내역서의 공종번호 자동입력»
RdoFirstChar 공란처리, . 입력, ( 입력, - 입력, 추가입력
RdoLastChar 공란처리, . 입력, ) 입력
CBoxType 가..하,거..허 ~ 그..흐,기..히 (14x10=140개 까지만 처리), a .. z,a1..z1 ~ a8..z8,a9..z9 (26x10=260개 까
CBoxLeft 여백 0 Byte, 여백 1 Byte, 여백 2 Byte, 여백 3 Byte, 여백 4 Byte, 여백 5 Byte, 여백 6 Byte, 여백 7 Byte,
Fmt_Typ 0, 00, 000
cBx_itNum_B 『직전공종(계표시)의 공종번호』, 『직전공종길이 여백으로 채움』
cBx_OnlyIT 공종(계표시) / 단가코드 모두적용., 공종(계표시)에만 구분적용., 단가코드에만 구분적용.
cBx_Bl_Lev 하위 전체공종 반영, 9레벨(=========) 미만에서 초기화, == 레벨 미만에서 초기화, === 레벨 미만에서 초기화,
■ wKongJong ST_Boq.CPM «공종 목록표»
RdoLevel 소계 (+=), 계 (+==), 합계 (+===), 총계 (+====), 총합계(+=====), 총대계(+======)
RdoFirstChar - 입력, 없슴
RdoIndex 코드번호 순, 명 칭 순, 분류번호 순
RdoFilt 일위대가/산근전체, 대공종 해당목록만
■ wMBoq ST_Boq.CPM «주공종 집계표(계표시 확인)»
CBox01 1레벨( 소 계 ) 이상, 2레벨( 계 ) 이상, 3레벨( 합 계 ) 이상, 4레벨( 총 계 ) 이상, 5레벨(총 합 계) 이상, 6레벨(총 대
■ wM_Boq_ExecX ST_Boq.CPM «시간/단위당 중기실행단가 계산 및 입력»
wCm_Jeol 1원, 10원, 100원, 1,000원, 10,000원, 100,000원
wCm_OPT 기본보정, 노 무 비, 재 료 비, 경 비
■ wM_Box_H STmate.Cpm «불러간 목록»
RG_MComp 변동없음, 1 단가, 2 단가, 3 단가, 4 단가, 5 단가, 최소단가
■ wM_Boxa STmate.Cpm «단가목록표»
RG_MComp 변동없음, 1 단가, 2 단가, 3 단가, 4 단가, 5 단가, 최소단가
rg_FLT_M_PP 모 두 보 임, 10 : 새들분수전, 11 : K.P 자재, 12 : 엘보, 13 : 직관, 14 : 소켓, 15 : S.T.S 자재, 16 : 니플, 17
rg_FLT_B_PP 모 두 보 임, 30 : 토공, 31 : 포장공, 32 : 포장깨기/절단, 30~32 : 토공사, 40 : 분기공, 41 : 관접합, 42 : 관연결, 43
■ wM_Conv_A2W STmate.Cpm «실적단가를 일식단가로 변환등록»
wcBx_SelJOD 비목율( 단가 => 노,재,경 ), 조달청 발표단가 변환적용
cBx_Stndard 공종(실적)코드 ↔ 분류번호로 연결, 공종(실적)코드 ↔ 시설코드로 연결, 명칭, 규격, 단위 로 연결
■ wM_Conv_Mode ST_PList.CPM «설계자료 → 계약자료로 변환등록»
cBx_Sel_QTY 당초 수량/단가 적용, 변경 수량/단가 적용
cBx_Lev 소계 이상, 계 이상, 합계 이상, 총계 이상, 총합계 이상, 총대계 이상, 광역계 이상, 광역총계이상, 광역대계이상
■ wM_Edit_EQ ST_EdSan.CPM «표준중기공식 편집.»
cBx_INS_X 공 식, 공식+코드
■ wM_Edit_San ST_EdSan.CPM «단가산출근거 수정[ D00001: 무근콘크리트 깨기 ]»
RdosmMode 열블럭, 행블럭, 일 반
cBoxFunc 각종 지원함수, ∏, INT(0), SIN(0), COS(0), TAN(0), SQRT(1) , √(1), ROUND(1,0), ROUNDDOWN(1,0),
cBox_Trans 코드 * 수량(0) =, 명칭 코드 * 수량(0) =, 명칭 규격 코드 * 수량(0) =, 명칭 규격 코드 * 수량(0) 단위 =, 코드번호
ComOp /, *
ComQty Q, A, T, N
cmBx_IFORCE XML비호환 메세지 숨김, XML비호환시 보이기
cBx_SBMOK 0 각 해당비목 적용, 1 재료비로 적용, 2 노무비로 적용, 3 경 비로 적용
■ wM_Edit_XRate ST_EdiLWi.CPM «시간당 손료계수 수정»
cBx_Sel 계 입력, 각계수 입력
■ wM_Edit_iLWi ST_EdiLWi.CPM «일위대가표 수정»
wcBx_RXX 초기값설정 개별적용, 기 본 노임산출계수, 기 타 노임산출계수, 기타-1노임산출계수, 기타-2노임산출계수, 기타-3노임산출계수, 기타-4노임산출계수
cBx_SBMOK 0 각 해당비목 적용, 1 재료비로 적용, 2 노무비로 적용, 3 경 비로 적용
■ wM_Edt_Hal ST_Jabpum.CPM «기준단가 수정»
wcBx_Up 절사, 반올림
■ wM_Expnd ST_Boq.CPM «일위대가표 및 단가산출근거 풀어넣기»
wcBx_Pnt1_M 0 절사하여, 1 반올림 하여
wcBx_Pnt2_M 0 소수 0위값을 적용, 1 소수 1위값을 적용, 2 소수 2위값을 적용, 3 소수 3위값을 적용, 4 소수 4위값을 적용
wcBx_Pnt1_L 0 절사하여, 1 반올림 하여
wcBx_Pnt2_L 0 소수 0위값을 적용, 1 소수 1위값을 적용, 2 소수 2위값을 적용, 3 소수 3위값을 적용, 4 소수 4위값을 적용
wcBx_Pnt1_X 0 절사하여, 1 반올림 하여
wcBx_Pnt2_X 0 소수 0위값을 적용, 1 소수 1위값을 적용, 2 소수 2위값을 적용, 3 소수 3위값을 적용, 4 소수 4위값을 적용
wcBx_Pnt1_W 0 절사하여, 1 반올림 하여
wcBx_Pnt2_W 0 소수 0위값을 적용, 1 소수 1위값을 적용, 2 소수 2위값을 적용, 3 소수 3위값을 적용, 4 소수 4위값을 적용
■ wM_Expnd10 ST_Boq.CPM «일위대가표 풀기»
RGrpToT 입력안함, 소계(=), 계(==), 합계(===)
Add1_PRIN 재료비의, 노무비의, 경비의, 전체의
Add1_PROUT 재료비로 계산, 노무비로 계산, 경비로 계산, 해당비목 계산
Add2_PRIN 재료비의, 노무비의, 경비의, 전체의
Add2_PROUT 재료비로 계산, 노무비로 계산, 경비로 계산, 해당비목 계산
■ wM_GetAMT ST_KanJub.CPM «잡비 금액 불러오기»
rd_Filter 모두보기, 소 계(+=), 계(+==), 합 계(+===), 총 계(+====), 총합계(+=====)
■ wM_Host STmate8.Exe «서버의 IP주소를 입력하고 IP접속( 또는 Enter »
rgPrgTyp 일반, LAN, EDU, OnLine, 북측
■ wM_JDCode STmate.Cpm «공사원가 호환규정코드 찾기 / 입력»
wCb_Base 명칭, 명칭/규격, 분류번호, 시설코드, 선택안함
wCb_Srch 명칭/규격/단위, 호환규정코드, 군급목록번호, 중기품셈코드, 시설코드번호
wCb_Other 기본, 자재, 경비, 중기, 실적/기타
wCb_Method 분류별 조회, 코드..명칭 조회
■ wM_JabPum ST_Jabpum.CPM «닫기 (&Q)»
cBoxCode 모든단가, 노무비, 재료비, 경비, 일위대가, 중기비, 단가산출, 견적단가, 대가,산근, 그림코드
cBox_IFORM 명칭,규격 으로검색, 산근내용 으로검색
■ wM_KanJub ST_KanJub.CPM «제잡비 계산»
Cmbx_PORD1_H 당초 / 변경 대비인쇄, 변 경 분 만 인 쇄
cBx_K_JeolSa 간접비 절사없음, 간접비 10원미만 절사, 간접비 100원미만 절사, 간접비 1,000원미만 절사, 간접비 10,000원미
cBx_Chasu 원설계, 1차변경, 2차변경, 3차변경
cBx_W_Type 제잡비 원가계산방식, 제잡비 표준시장방식
cBx_Jeol_T 총공사비에서 조정, 10원 미만, 100원 미만, 1,000원 미만, 10,000원 미만, 100,
Ed_C_AnJun_K_T 없음, 관급/사급 내용, 사급자재대
Ed_C_AnJun_B_T , 의 1.2배
Ed_C_KUNBO_T ㄴ.1)직노, 당해 상시근로자노무비
■ wM_KanJub_K ST_KanJub.CPM «< 기성 제잡비 계산서 >»
rd_Filter 소 계(+=) 이상, 계(+==) 이상, 합 계(+===) 이상, 총 계(+====) 이상, 총합계(+=====) 이상
cmBx_Buga (0) 직접입력, (1) 공급가액의 10%, (2) 재료비의 10%, (3) 재료비+산출경비의 10%
cmBx_JeolSa 총공사비에서 조정, 10원 미만, 100원 미만, 1,000원 미만, 10,000원 미만, 100,
cmBx_Tot_Jeol 이윤금액 직접입력, 10원 미만, 100원 미만, 1,000원 미만, 10,000원 미만, 100,
■ wM_Mk_Cont STmate.Cpm «당초설계 → 계약내역 변환등록»
cBx_Lev 소계 이상, 계 이상, 합계 이상, 총계 이상, 총합계 이상, 총대계 이상, 광역계 이상, 광역총계이상, 광역대계이상
■ wM_Mrg_BSC STmate.Cpm «자료파일 업데이트»
cBx_Sel1 호환규정코드, 시설코드, 명칭/규격/단위, 호환+명+규+단, 명칭/단위, 설정안함
cBx_Sel2 호환규정코드, 시설코드, 명칭/규격/단위, 호환+명+규+단, 명칭/단위, 설정안함
cBx_Sel3 호환규정코드, 시설코드, 명칭/규격/단위, 호환+명+규+단, 명칭/단위, 설정안함
cBx_Sel4 호환규정코드, 시설코드, 명칭/규격/단위, 호환+명+규+단, 명칭/단위, 설정안함
cBx_Sel5 호환규정코드, 시설코드, 명칭/규격/단위, 호환+명+규+단, 명칭/단위, 설정안함
rg_Name 업데이트 안함(복사처적용), 업데이트 (복사원적용)
rg_Amt 업데이트 안함(복사처적용), 업데이트 (복사원적용)
rg_JDCOD 업데이트 안함(복사처적용), 업데이트 (복사원적용), 복사처 우선적용 업데이트, 복사원 우선적용 업데이트
■ wM_Rate ST_Boq.CPM «노무비»
rd_Qty1 내역서 전체, 선택된 항목만
rd_Amt1 내역서 전체, 선택된 항목만
rd_Amt2 내역서 전체, 선택된 항목만
wcBx_Pnt1 0 절사하여, 1 반올림 하여
wcBx_Pnt2 0 소수 0위값을 적용, 1 소수 1위값을 적용, 2 소수 2위값을 적용, 3 소수 3위값을 적용, 4 소수 4위값을 적용
wcBx_Pnt1_M 0 절사하여, 1 반올림 하여
wcBx_Pnt2_M 0 소수 0위값을 적용, 1 소수 1위값을 적용, 2 소수 2위값을 적용, 3 소수 3위값을 적용, 4 소수 4위값을 적용
wcBx_Pnt1_L 0 절사하여, 1 반올림 하여
■ wM_Req_ON STmate.Cpm «유지보수기간 연장신청»
wEd_Sub 12개월, 6개월, 3개월
■ wM_Sel_Prn STmate.Cpm «인쇄할 영역을 선택하세요»
cb_PCLASS 당초설계, 변경설계, 분할설계
cb_PRTYPE 해당코드 상위의 7레벨공종이하 내역전체, 해당코드 상위의 해당레벨(하위레벨제외)
cb_ACTTYP 노무비외 비목 적용안함, 노무비외 재료비로 적용, 노무비외 경비로 적용
cb_ACTOPT 노무비 1원미만 버림, 소수 비교후 노무비 우선보정, 소수 비교후 타금액 우선보정
cb_CHASU 1차변경, 2차변경, 3차변경, 4차변경, 5차변경, 6차변경, 7차변경, 8차변경
■ wM_Set_Sys ST_Sys.CPM «초기값 입력및 수정»
cmBx_Sel_RX 계수 ;터널구간, 계수-1;장대터널, 계수-2;펌프준설선, 계수-3;GRAB준설선, 계수-4;버켓준설선
cmBX_EXR 시간당중기사용료 단독적용, 중기, 대가, 산근, 내역 전체
cmBx_KCD 0 자리, 1 자리, 2 자리
cmBx_ACD 0 자리, 1 자리, 2 자리
cmBx_ZCD 2 자리, 3 자리
cmBx_JCD 0 자리, 1 자리, 2 자리
cmBx_ECD 0 자리, 1 자리, 2 자리
cmBx_BPN 0 자리, 1 자리, 2 자리
■ wM_Stmate_Merge_Code ST_Merge_Code.CPM «설계예산시스템 코드병합»
rg_Set_Sys 선택코드만 병합, 선택코드+하위기초단가코드 병합, 모든코드(하위 산근,대가포함) 병합, 코드병합 취소
■ wQtyList STmateQty8.CPM «수량산출서»
cBoxCode 모든단가, 노무비, 재료비, 경비, 일위대가, 중기비, 단가산출, 견적단가, 대가,산근
■ wSrch_PL ST_PList.CPM «프로젝트 DATA검색(명칭, 규격)»
wcBx_Type 노무비, 재료비, 경비, 중기비, 일위대가, 단가산출, 일식/표준시장, 내역서
선택지를 가진 폼 40개, 선택축 272개
### STmate 고유 폼 — 모듈별
■ STmate.Cpm 30개
M_Chk_Pw PM_Req_AS frmAddTip frmEditor
frmTips wM_About wM_Box_H wM_Boxa
wM_Conv_A2W wM_DM_Com wM_D_Restore wM_Dm_1
wM_Dm_2 wM_Down_Center wM_Get_Info wM_Get_Value
wM_JDCode wM_JajeXLS wM_Mk_Cont wM_Mrg_BSC
wM_Req_AS wM_Req_ON wM_Rw_On wM_Sel_Prn
wM_Show_Info wM_Status wM_Stmate_Start_8x wM_Xls_Comp
wM_iLWi_San wPrjList
■ ST_P_Nae.CPM 22개
PM_Boq_Not PM_Bqm PM_Jab PM_Jab_P
PM_Nae PM_NaeW PM_NaeW_P PM_Nae_B
PM_Nae_Col PM_Nae_Col_ML PM_Nae_Cont PM_Nae_K0
PM_Nae_P PM_Nae_P_K0 PM_Nae_Plan_A PM_Nae_Plan_K
PM_Won_A PM_Won_A_C PM_Won_A_L PM_Wonga
PM_Wonga_C PM_Wonga_L
■ ST_Boq.CPM 13개
wBoq wBoqRate wExpnd wITNum
wKongJong wMBoq wM_Boq_ExecX wM_Expnd
wM_Expnd10 wM_From_XLS wM_Rate wM_Ref_Lst
wSameCode
■ ST_P_ConEst.CPM 8개
PM_JAB_K PM_NAE_C1 PM_NAE_C2 PM_NAE_C3
PM_NAE_K1 PM_NAE_K2 PM_NAE_K3 PM_Nae_Est
■ ST_PList.CPM 8개
wM_Add_Prj wM_Conv_Mode wM_Get_Node wM_Get_Value
wM_ST_Restore wPList wST_Dir wSrch_PL
■ ST_P_List.CPM 7개
PM_Comp_M PM_Dan PM_Dan_L PM_SanMok
PM_SanMok_L PM_SanMok_P PM_iLMok
■ ST_P_Excav.CPM 6개
PM_NAE_Ex2 PM_NAE_Ex2T PM_Nae_Ex1 PM_Nae_Ex1T
PM_Nae_ExC PM_Nae_ExCT
■ ST_P_Joong.CPM 6개
PM_JOONG PM_JOONG2 PM_JOONG2_L PM_JOONG_L
PM_Joong_C PM_Joong_C_L
■ ST_KanJub.CPM 5개
PM_KAN_RATE wM_GetAMT wM_KAN_RATE wM_KanJub
wM_KanJub_K
■ ST_Jabpum.CPM 4개
wM_Edit_Strorage wM_Edt_Hal wM_Inst_CD wM_JabPum
■ ST_P_Collect.CPM 4개
PM_Col_Dan PM_Col_Dan_L PM_Col_X PM_Col_X_L
■ ST_Prt.CPM 4개
PM_Jab_Ord PM_Prn_Ord PM_Prt PM_Sel_St
■ ST_Viewer.exe 4개
wM_About wM_Extr wM_Main__Viewer wM_Sub
■ DP_MinMsg.CPM 3개
mMinMsg_ wInDlgImg wInDlgMemo
■ ST_EdSan.CPM 3개
wM_Edit_EQ wM_Edit_San wM_Edit_USER
■ ST_Sys.CPM 3개
wM_Set_Init wM_Set_JLab wM_Set_Sys
■ ST_U_ChgCode.CPM 3개
uM_ChgCode uM_Chg_Plus uM_ReplCode
■ ST_U_ConBD.CPM 3개
uM_ConBD uM_ConQTY uM_ConQTY9
■ ST_U_Convert.CPM 3개
uM_BSC_M uM_ConstType uM_XML_Info
■ ST_Merge_Code.CPM 3개
wM_Stmate_Merge_Code wM_Sub wSrch_PL
■ JDLCHK.Size.dll 2개
frm_CheckProgress frm_JDLAnno
■ ST_Com_V.CPM 2개
wInDlgImg wInDlgMemo
■ ST_EdiLWi.CPM 2개
wM_Edit_XRate wM_Edit_iLWi
■ ST_FindUse.CPM 2개
wM_FindBoq wM_FindUse
■ ST_P_iLWi.CPM 2개
PM_iLWi PM_iLWi_S
■ ST_P_San.CPM 2개
PM_San_L PM_San_P
■ ST_U_ADL.CPM 2개
NMShow wM_ADL
■ ST_Func_C.CPM 2개
wInDlgImg wInDlgMemo
■ ST_Wal.CPM 2개
wM_Get_GJ wM_Stmate_Get_Wal
■ JDLCHK.Tab.dll 1개
frm_CheckProgress
■ JDLCHK.Tab_33.dll 1개
frm_CheckProgress
■ ST_U_Archive.CPM 1개
uM_Archive
■ ST_U_ChgAmt.CPM 1개
uM_Chg_Amt
■ ST_U_DelCode.CPM 1개
uM_DelCode
■ ST_U_DelPrj.CPM 1개
uM_DelPrj
■ ST_U_Indx.CPM 1개
uM_Indx
■ ST_U_Merge.CPM 1개
uM_Merge
■ STmateQty8.CPM 1개
wQtyList
■ ST_U_APatch.CPM 1개
wM_Stmate_APatch
■ STmate8.Exe 1개
wM_Host
■ View_Old_San.CPM 1개
M_Hrp_San
### 갈래별 (총 172개)
모달 편집 66개
인쇄 63개
편집 창 19개
기타 17개
보조 창 7개
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""기준자료를 실제로 읽을 수 있는지 판정. 복호화 시도는 하지 않음 — 읽히나만 봄."""
import pathlib, struct, sys, io, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = pathlib.Path(r"C:\경영정보(주)\System")
def probe(p):
try: b = p.read_bytes()
except Exception as e: return "열기실패", 0, 0
if b[:2] == b"PK": return "ZIP", 0, 0
if b[:1] not in (b"\x03", b"\x04", b"\x05", b"\x30", b"\xE5", b"\x06", b"\xF5", b"\x8B"):
return "DBF아님", 0, 0
try:
nrec, hlen, rlen = struct.unpack_from("<IHH", b, 4)
n = 0; o = 32
while o < len(b) and b[o] != 0x0D: n += 1; o += 32
if not (1 <= n <= 300 and hlen and rlen): return "머리깨짐", 0, 0
# 첫 레코드가 사람이 읽을 수 있는 문자인가
rec = b[hlen:hlen+rlen]
printable = sum(1 for c in rec if 32 <= c < 127 or c >= 0xA1)
ratio = printable / max(len(rec), 1)
return ("평문" if ratio > 0.85 else "본문암호화"), nrec, n
except Exception:
return "해석실패", 0, 0
groups = collections.defaultdict(lambda: collections.Counter())
detail = collections.defaultdict(list)
for p in sorted(ROOT.rglob("*")):
if not p.is_file(): continue
ext = p.suffix.upper() or "(없음)"
if ext not in (".DBF",".BSC",".STBSC",".CSTA",".JB_R",".JB_RA",".JB_1",".JB_M",".SMT",".MEM"): continue
st, nrec, nfld = probe(p)
groups[ext][st] += 1
detail[ext].append((p.name, st, nrec, nfld))
print("### 기준자료·프로젝트 테이블의 읽힘 상태 (복호화 시도 없음)")
print("%-8s %-5s %s" % ("확장자","개수","판정"))
for ext in sorted(groups):
tot = sum(groups[ext].values())
print("%-8s %-5d %s" % (ext, tot, dict(groups[ext])))
print("\n### 단가 원천으로 쓰이는 대형 기준파일")
for ext in (".BSC", ".CSTA", ".STBSC"):
for nm, st, nrec, nfld in detail.get(ext, [])[:8]:
print(" %-42s %-10s rec=%-7s fld=%s" % (nm[:42], st, nrec or "-", nfld or "-"))
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
import pathlib, zipfile, struct, sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
def rd(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]; v=[]; c=1
for nm,ln in f: v.append(r[c:c+ln].decode("cp949","replace").strip()); c+=ln
rows.append(v)
return [x[0] for x in f], rows
Z = zipfile.ZipFile(pathlib.Path(r"C:\경영정보(주)\STmate 압축자료\실습(맛보기).STC"))
fn, rows = rd(Z, "COSTN.DBF")
ix = {n:i for i,n in enumerate(fn)}
def g(r,k):
try: return float(r[ix[k]] or 0)
except: return 0.0
print("### TAMT ≠ MAMT+LAMT+SAMT 인 행")
bad=0
for r in rows:
T,M,L,S = g(r,"TAMT1"), g(r,"MAMT1"), g(r,"LAMT1"), g(r,"SAMT1")
if not (T or M or L or S): continue
if abs(T-(M+L+S)) >= 0.005:
bad+=1
print(" %-8s %-14s %-12s T=%10.0f M+L+S=%10.0f 차=%+.0f" % (
r[ix["CODE"]], r[ix["NAME"]][:14], r[ix["SIZE"]][:12], T, M+L+S, T-(M+L+S)))
print(" 불일치 %d" % bad)
print("\n### 프로젝트 RATE 표 전수 (맛보기)")
import re
for nm in sorted(n for n in Z.namelist() if re.match(r"RATE", n, re.I)):
f2, r2 = rd(Z, nm)
nz = sum(1 for r in r2 if any((x and x not in ("0.0000","0.00","0")) for x in r))
print(" %-14s %2d× %2d열 값이 든 행 %d" % (nm, len(r2), len(f2), nz))
print("\n### RATE_A 와 RATE_M 이 같은 표인가")
fa, ra = rd(Z,"RATE_A.DBF"); fm, rm = rd(Z,"RATE_M.DBF")
same = sum(1 for a,b in zip(ra,rm) if a==b)
print("%d개 중 완전히 같은 행 %d개 → %s" % (len(ra), same, "같은 표 아님" if same<len(ra) else "같은 표"))
print(" RATE_A 첫행: %s" % " ".join(x or "·" for x in ra[0]))
print(" RATE_M 첫행: %s" % " ".join(x or "·" for x in rm[0]))
print("\n### 다른 프로젝트에도 RATE_A/RATE_B 가 있나")
for p in sorted(pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서").rglob("*.STC")):
try:
zz = zipfile.ZipFile(p)
rs = sorted(n for n in zz.namelist() if re.match(r"RATE", n, re.I))
print(" %-46s %s" % (p.name[:46], ", ".join(rs)))
except Exception as e: print(" %s 열기실패" % p.name)
@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""진행률 장부 — 원천별로 무엇을 어디까지 봤나 실제로 센다."""
import sys, io, pathlib, zipfile, struct, re, warnings, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
warnings.filterwarnings("ignore"); import openpyxl
SYS = pathlib.Path(r"C:\경영정보(주)\System")
PRAC = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")
# ── ① 설치 폴더 186개 ──
files = [p for p in SYS.rglob("*") if p.is_file()]
kinds = collections.Counter()
for p in files:
try: head = p.open("rb").read(2)
except Exception: head = b""
ext = p.suffix.upper()
if head == b"MZ": kinds["PE 실행모듈"] += 1
elif head == b"PK": kinds["ZIP 계열"] += 1
elif ext in (".JB_1",".JB_M",".JB_R",".JB_RA"): kinds["JB 요율표"] += 1
elif ext in (".BSC",".CSTA"): kinds["암호화 기준단가"] += 1
else: kinds["기타"] += 1
print("### ① 설치 폴더 `C:\경영정보(주)\System` 전체 %d" % len(files))
for k,v in kinds.most_common(): print(" %-16s %3d" % (k,v))
# ── ② 실무 STC ──
stcs = sorted(PRAC.rglob("*.STC"))
tabset = collections.Counter()
for s in stcs:
try: z = zipfile.ZipFile(s)
except Exception: continue
for n in z.namelist():
if n.upper().endswith(".DBF"): tabset[n.upper()] += 1
print("\n### ② 실무 프로젝트 STC %d건, 담긴 표 %d" % (len(stcs), len(tabset)))
# ── ③ STmate 생성 XLSX 시트 ──
sheets = collections.Counter(); nx = 0
for p in PRAC.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
nx += 1
for s in wb.sheetnames: sheets[s] += 1
print("\n### ③ STmate 생성 XLSX %d건, 시트 %d" % (nx, len(sheets)))
DONE_SHEET = {"공사원가계산서","설계내역서","일위대가표","중기사용료","자재단가대비표",
"환율및기초자료","단가산출근거","〓 INITIAL 〓",
"재료비수량금액집계표","노무비수량금액집계표","경비수량금액집계표"}
PART_SHEET = {"중기시간금액집계표"}
print(" %-24s %s" % ("시트","상태"))
for s,_ in sheets.most_common():
st = "✅ 계산 검증" if s in DONE_SHEET else ("◐ 부분" if s in PART_SHEET else "· 미확인(목록·표지)")
print(" %-24s %s" % (s[:24], st))
d=len(DONE_SHEET & set(sheets)); pt=len(PART_SHEET & set(sheets))
print(" → 계산 검증 %d / %d 시트 (부분 %d)" % (d, len(sheets), pt))
DONE_TAB = {"BOQ11.DBF","BOQ12.DBF","COSTN.DBF","PRJLST.DBF","RATE.DBF","RATE_M.DBF",
"RATE_R.DBF","RATE_RA.DBF","RATE_A.DBF","RATE_B.DBF","SET_REP.DBF","KONGJONG.DBF"}
print("\n STC 표별 상태")
for t,c in sorted(tabset.items()):
st = "✅ 읽고 씀" if t in DONE_TAB else ("⛔ 암호화" if t in ("BDQTY.DBF","SYSINFO.DBF") else "· 빈 표/미사용")
print(" %-16s %d%s" % (t, c, st))
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""STmate 고유 폼 목록을 모듈·갈래별로 정리."""
import sys, io, pathlib, re, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = pathlib.Path(r"C:\경영정보(주)\System")
THIRD = re.compile(r"^(Dclhrp60|designide60|RTL60|VCL60|DBRTL60|BDERTL60|RealGrid|Apollo|dsnide)", re.I)
HRLIB = {"HReportUf","HRPageRf","HRPreVUf","HRSaveUf","LoginDialog","PasswordDialog","HRGridCUf",
"HRGridFUf","HRGroupUf","HRLinkFf","HRPrintUf","HRExprUf","HRFieldUf","HRSortUf"}
def forms(b):
out=[]
for m in re.finditer(rb"TPF0", b):
s=m.end()
if s>=len(b): continue
n=b[s]
if not (3<=n<=40): continue
cls=b[s+1:s+1+n]
if not re.fullmatch(rb"T[A-Za-z0-9_]+", cls): continue
s2=s+1+n
if s2>=len(b): continue
n2=b[s2]
if not (1<=n2<=40): continue
nm=b[s2+1:s2+1+n2]
if not re.fullmatch(rb"[A-Za-z_][A-Za-z0-9_]*", nm): continue
out.append(nm.decode("latin1"))
return out
per={}
for p in sorted(ROOT.rglob("*")):
if not p.is_file() or THIRD.match(p.stem): continue
try: b=p.read_bytes()
except Exception: continue
if b[:2]!=b"MZ": continue
f=[x for x in forms(b) if x not in HRLIB]
if f: per[p.name]=sorted(set(f))
def kind(n):
if n.startswith("PM_"): return "인쇄"
if n.startswith(("wM_","M_")): return "모달 편집"
if n.startswith("w"): return "편집 창"
if n.startswith("frm"): return "보조 창"
return "기타"
cnt=collections.Counter(); tot=0
print("### STmate 고유 폼 — 모듈별")
for nm,f in sorted(per.items(), key=lambda x:-len(x[1])):
if not f: continue
tot+=len(f)
for x in f: cnt[kind(x)]+=1
print("\n%-24s %2d" % (nm, len(f)))
for i in range(0,len(f),4):
print(" " + " ".join("%-22s"%x for x in f[i:i+4]))
print("\n\n### 갈래별 (총 %d개)"%tot)
for k,v in cnt.most_common(): print(" %-10s %3d" % (k,v))
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""PE 안의 이름 붙은 Delphi 폼(TPF0)을 뽑아 .dfm 으로 저장. 원본은 읽기만."""
import sys, io, re, pathlib
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = pathlib.Path(r"C:\경영정보(주)\System")
OUT = pathlib.Path(r"C:\Users\umsan\AppData\Local\Temp\claude\D--aislo-wt-sub\df75230a-b05b-4088-ad34-bb0c9da0a312\scratchpad\forms")
OUT.mkdir(exist_ok=True)
want = sys.argv[1:] or ["wM_Edt_Hal","wM_JabPum","wM_Edit_San","wM_Set_Init","wM_Set_Sys","wBoq","wBoqRate"]
found = {}
for p in sorted(ROOT.rglob("*")):
if not p.is_file(): continue
try: b=p.read_bytes()
except Exception: continue
if b[:2]!=b"MZ": continue
for m in re.finditer(rb"TPF0", b):
s=m.end()
if s>=len(b): continue
n=b[s]
if not (3<=n<=40): continue
cls=b[s+1:s+1+n]
if not re.fullmatch(rb"T[A-Za-z0-9_]+", cls): continue
s2=s+1+n; n2=b[s2] if s2<len(b) else 0
if not (1<=n2<=40): continue
nm=b[s2+1:s2+1+n2]
try: nms=nm.decode("latin1")
except Exception: continue
if nms not in want or nms in found: continue
# 끝 찾기: 다음 TPF0 또는 넉넉히 자름
nxt=b.find(b"TPF0", m.end())
end = nxt if nxt>0 else min(len(b), m.start()+400000)
blob = b[m.start():end]
f = OUT/("%s.dfm"%nms)
f.write_bytes(blob)
found[nms]=(p.name, len(blob))
for k in want:
if k in found: print("%-16s %-22s %7d B" % (k, found[k][0], found[k][1]))
else: print("%-16s 못 찾음" % k)
@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
"""골든셋 회귀 — 내역 → 직접비 → 간접비 → 총공사비를 다시 세워 XLSX 와 원 단위 대조."""
import sys, io, pathlib, re, zipfile, struct, warnings
from decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP, ROUND_UP
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
warnings.filterwarnings("ignore")
import openpyxl
BASE = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서\04.산불 봉화\01. 내역서")
STC = BASE/"13-2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.STC"
XLS = BASE/"2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.xlsx"
D = Decimal
def dn(x, q=0): return (D(x)).quantize(D(1).scaleb(-q), 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
z = zipfile.ZipFile(STC); boq = dbf(z,"BOQ11.DBF")
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|:)$")
# C* 로 닫히는 구역 = 제외금액 구역(관급자재대 등). 그 안의 행은 직접비에서 뺀다.
excl_span = set()
for i, r in enumerate(boq):
bd = (r.get("BDCODE1") or "").strip()
if bd.startswith("C"):
end = r.get("ENDCODE"); start = None
for j, q in enumerate(boq):
if q.get("SORTCODE") == end: start = j; break
if start is not None:
for k in range(min(start, i), max(start, i)+1): excl_span.add(k)
leaf = [r for i, r in enumerate(boq)
if r.get("BDCODE1") and not STRUCT.match(r["BDCODE1"].strip()) and i not in excl_span]
excl = [r for i, r in enumerate(boq)
if r.get("BDCODE1") and not STRUCT.match(r["BDCODE1"].strip()) and i in excl_span]
print("### 전체 %d행 → 말단 %d행 (제목 T*·계 E*·제외총계 C*·페이지 : 제외)" % (len(boq), len(leaf)))
print(" 제외금액 구역 %d행 (합 재료 %s)" % (len(excl), f"{sum(g(r,'M_AMT1') for r in excl):,}"))
M = sum(g(r,"M_AMT1") for r in leaf); L = sum(g(r,"L_AMT1") for r in leaf); S = sum(g(r,"S_AMT1") for r in leaf)
# 행 금액이 ROUNDDOWN(수량 x 단가) 인지 검사
ok=bad=0; badrows=[]
for r in leaf:
for comp,amt in (("MAMT1","M_AMT1"),("LAMT1","L_AMT1"),("SAMT1","S_AMT1")):
exp = dn(g(r,"QTY1")*g(r,comp))
act = g(r,amt)
if exp == act: ok+=1
else:
bad+=1
if len(badrows)<5: badrows.append((r.get("NAME1","")[:20], comp, exp, act))
print("행 금액 = ROUNDDOWN(수량 × 단가) : 일치 %d / 불일치 %d" % (ok,bad))
for nm,c,e,a in badrows: print(" %-20s %-7s 계산 %s ≠ 장부 %s" % (nm,c,e,a))
wb = openpyxl.load_workbook(XLS, data_only=True, read_only=True)
ws = wb["공사원가계산서"]
T={}; notes={}
for row in ws.iter_rows(min_row=1,max_row=40,max_col=7,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(" ","")
amt=next((c for c in cells[3:5] if isinstance(c,(int,float))),None)
note=next((str(c) for c in cells[4:7] if isinstance(c,str) and "%" in str(c)),"")
if lab and amt is not None: T[lab]=D(str(amt)); notes[lab]=note
def cmp(label, calc, key):
exp = T.get(key)
if exp is None: print(" %-22s %14s (목표 없음)" % (label, calc)); return
d = calc-exp
print(" %-22s 계산 %14s 출력 %14s %s" % (label, f"{calc:,}", f"{exp:,}", "일치" if d==0 else "%+d" % d))
print("\n### 직접비")
cmp("직접재료비", M, "순공사원가재료비직접재료비")
cmp("직접노무비", L, "노무비직접노무비")
cmp("산출경비", S, "경비산출경비")
print("\n### 간접비 — 문서의 밑수·요율 그대로 적용")
간노 = dn(L*D("0.145")); cmp("간접노무비", 간노, "간접노무비")
노무 = L + 간노
산재 = dn(노무*D("0.0356")); cmp("산재보험료", 산재, "산재보험료")
고용 = dn(노무*D("0.0101")); cmp("고용보험료", 고용, "고용보험료")
건강 = dn(L*D("0.03545")); cmp("건강보험료", 건강, "건강보험료")
장기 = dn(건강*D("0.1295")); cmp("노인장기요양보험료", 장기, "노인장기요양보험료")
연금 = dn(L*D("0.045")); cmp("연금보험료", 연금, "연금보험료")
기타 = dn((노무+M)*D("0.06")); cmp("기타경비", 기타, "기타경비")
환경 = dn((M+L+S)*D("0.008")); cmp("환경보전비", 환경, "환경보전비")
기계 = dn((M+L+S)*D("0.004")); cmp("건설기계대여금지급보증금액", 기계, "건설기계대여금지급보증금액")
관급 = D("143554309")
안전 = dn((L+M+관급/D("1.1"))*D("0.0186")) + D("5349000")
cmp("산업안전보건관리비", 안전, "산업안전보건관리비")
print(" (비고 원문: %s)" % notes.get("산업안전보건관리비","")[:70])
경비계 = S+산재+고용+건강+장기+연금+T.get("산업안전보건관리비",D(0))+기타+환경+기계
cmp("경비 소계", 경비계, "소계")
순공사원가 = M + 노무 + 경비계
일반 = dn(순공사원가*D("0.06")); cmp("일반관리비", 일반, "일반관리비")
이윤 = dn((노무+경비계+일반)*D("0.15")); cmp("이윤", 이윤, "이윤")
총원가 = 순공사원가+일반+이윤; cmp("총원가", 총원가, "총원가")
부가 = dn(T.get("총원가",총원가)*D("0.1")); cmp("부가가치세", 부가, "부가가치세")
# ── 산업안전보건관리비의 관급재 밑수를 역산 ───────────────────────
print("\n### 산업안전보건관리비 — 관급재 밑수 역산")
목표 = T["산업안전보건관리비"] - D("5349000")
관급대 = D("143554309"); 조달 = D("771029")
for 이름, in [("관급자재대 그대로", 관급대), ("관급자재대 조달수수료", 관급대-조달), ("조달수수료만", 조달)]:
밑수 = L + M + /D("1.1")
계산 = dn(밑수*D("0.0186"))
print(" %-22s 밑수 %16s 율부분 %12s %s" % (이름, f"{밑수.quantize(D('1')):,}", f"{계산:,}",
"일치" if 계산==목표 else "%+d" % (계산-목표)))
# ── 끝전조정 ─────────────────────────────────────────────
print("\n### 끝전조정 — 이윤 차 339원의 정체")
안전 = T["산업안전보건관리비"]
경비계2 = S+산재+고용+건강+장기+연금+안전+기타+환경+기계
순2 = M + 노무 + 경비계2
일반2 = dn(순2*D("0.06"))
이윤원 = dn((노무+경비계2+일반2)*D("0.15"))
총원가원 = 순2+일반2+이윤원
vat원 = dn(총원가원*D("0.1"))
도급원 = 총원가원+vat원
도급목표 = T["도급공사비"]
차액 = 도급원 - 도급목표
세전 = (D(차액)/D("1.1")).quantize(D("1"), rounding=ROUND_HALF_UP)
print(" 보정 전 이윤 %14s" % f"{이윤원:,}")
print(" 보정 전 총원가 %14s" % f"{총원가원:,}")
print(" 보정 전 부가세 %14s" % f"{vat원:,}")
print(" 보정 전 도급공사비 %14s 목표 %14s 차액 %s" % (f"{도급원:,}", f"{도급목표:,}", f"{차액:,}"))
print(" 세전조정 = ROUND(차액/1.1) = %s" % f"{세전:,}")
이윤2 = 이윤원 - 세전
총2 = 순2+일반2+이윤2
vat2 = dn(총2*D("0.1"))
print()
for lab,calc,key in [("이윤(보정후)",이윤2,"이윤"),("총원가",총2,"총원가"),
("부가가치세",vat2,"부가가치세"),("도급공사비",총2+vat2,"도급공사비")]:
exp=T[key]; print(" %-14s 계산 %16s 출력 %16s %s" % (lab, f"{calc:,}", f"{exp:,}",
"일치" if calc==exp else "%+d" % (calc-exp)))
관급자재대 = (관급대/D("1000")).quantize(D("1"), rounding=ROUND_UP)*D("1000")
print("\n 관급자재대 = ROUNDUP(%s, 천원) = %s 출력 %s %s" % (
f"{관급대:,}", f"{관급자재대:,}", f"{T['관급자재대']:,}", "일치" if 관급자재대==T["관급자재대"] else "불일치"))
총공 = 총2+vat2+관급자재대
print(" 총공사비 = 도급 + 관급 = %s 출력 %s %s" % (
f"{총공:,}", f"{T['총공사비']:,}", "일치" if 총공==T["총공사비"] else "%+d"%(총공-T["총공사비"])))
@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
"""골든셋 회귀 — 실무 STC 전수에 같은 규칙을 걸어 원 단위 대조."""
import sys, io, pathlib, re, zipfile, struct, warnings, difflib
from decimal import Decimal as D, ROUND_DOWN, ROUND_HALF_UP, ROUND_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\실무문서")
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):
excl=set()
for i,r in enumerate(boq):
bd=(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("BDCODE1") and not STRUCT.match(r["BDCODE1"].strip()) and i not in excl]
rowok=rowbad=0
for r in leaf:
for c,a in (("MAMT1","M_AMT1"),("LAMT1","L_AMT1"),("SAMT1","S_AMT1")):
if dn(g(r,"QTY1")*g(r,c))==g(r,a): rowok+=1
else: rowbad+=1
return (sum(g(r,"M_AMT1") for r in leaf), sum(g(r,"L_AMT1") for r in leaf),
sum(g(r,"S_AMT1") for r in leaf), len(leaf), rowok, rowbad)
def targets(xp):
wb=openpyxl.load_workbook(xp,data_only=True,read_only=True)
if "공사원가계산서" not in wb.sheetnames: return {},{}
ws=wb["공사원가계산서"]; T={};N={}
for row in ws.iter_rows(min_row=1,max_row=45,max_col=7,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(" ","")
amt=next((c for c in cells[3:5] if isinstance(c,(int,float))),None)
note=next((str(c) for c in cells[4:7] if isinstance(c,str) and "%" in str(c)),"")
if lab and amt is not None and lab not in T: T[lab]=D(str(amt)); N[lab]=note
return T,N
RE_R = re.compile(r"([\d.]+)\s*%")
pairs=[]
for stc in sorted(ROOT.rglob("*.STC")):
best=None;bs=0
for x in list(stc.parent.rglob("*.xlsx"))+list(stc.parent.parent.rglob("*.xlsx")):
if x.name.startswith("~$"): continue
s=difflib.SequenceMatcher(None,stc.stem,x.stem).ratio()
if s>bs: bs,best=s,x
if best and bs>0.55: pairs.append((stc,best))
for stc,xp in pairs:
try: T,N = targets(xp)
except Exception as e: print("%-42s 엑셀 실패" % stc.stem[:42]); continue
if not T: continue
zf = zipfile.ZipFile(stc)
have = {n.upper() for n in zf.namelist()}
# 한 파일에 BOQ11·BOQ12 가 함께 있으면 BOQ12 가 현재안(출력 기준)이다
tbl = "BOQ12.DBF" if "BOQ12.DBF" in have and dbf(zf,"BOQ12.DBF") else "BOQ11.DBF"
boq = dbf(zf, tbl)
M,L,S,nleaf,ok,bad = direct(boq)
def get(*keys):
for k in keys:
if k in T: return T[k]
return None
tM=get("순공사원가재료비직접재료비","재료비직접재료비"); tL=get("노무비직접노무비"); tS=get("경비산출경비")
print("\n== %s" % stc.stem[:60])
print(" 말단 %d행 행금액 ROUNDDOWN 일치 %d / 불일치 %d" % (nleaf, ok, bad))
for lab,c,e in (("직접재료비",M,tM),("직접노무비",L,tL),("산출경비",S,tS)):
if e is None: print(" %-10s 계산 %16s (목표 없음)" % (lab, f"{c:,}")); continue
print(" %-10s 계산 %16s 출력 %16s %s" % (lab, f"{c:,}", f"{e:,}", "일치" if c==e else "%+d"%(c-e)))
# 요율은 비고에서 읽어 그대로 적용
def rate(key):
m=RE_R.search(N.get(key,"") or ""); return D(m.group(1))/100 if m else None
r간=rate("간접노무비")
if r간 and tL is not None:
=dn(tL*r간); e=T.get("간접노무비")
print(" 간접노무비 계산 %16s 출력 %16s %s" % (f"{:,}", f"{e:,}", "일치" if ==e else "%+d"%(-e)))
=tL+
for lab,base,key in (("산재보험료",,"산재보험료"),("고용보험료",,"고용보험료"),
("건강보험료",tL,"건강보험료"),("연금보험료",tL,"연금보험료")):
rr=rate(key)
if rr is None or key not in T: continue
v=dn(base*rr); e=T[key]
print(" %-10s 계산 %16s 출력 %16s %s" % (lab, f"{v:,}", f"{e:,}", "일치" if v==e else "%+d"%(v-e)))
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
"""Start8x.Ini 설정과 SET_REP 보고서 카탈로그를 뽑는다. 원본은 읽기만."""
import sys, io, struct, pathlib, re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
D = pathlib.Path(r"C:\경영정보(주)\STmate 자료\설계자료\실습(맛보기)")
print("### Start8x.Ini — 절별 키")
txt = (D/"Start8x.Ini").read_bytes().decode("cp949","replace")
sec="(머리)"
for line in txt.splitlines():
line=line.strip()
if not line: continue
if line.startswith("["): sec=line; print("\n"+sec); continue
print(" %s" % line[:110])
print("\n\n### 간접비 순서 코드")
for k in ("KAN_ORD_1","KAN_ORD_2"):
m=re.search(k+r"=(.*)", txt)
if m:
codes=[c for c in m.group(1).split(";") if c.strip()]
print(" %s %d개: %s" % (k, len(codes), ", ".join(codes)))
a=set(re.search(r"KAN_ORD_1=(.*)",txt).group(1).split(";"))
b=set(re.search(r"KAN_ORD_2=(.*)",txt).group(1).split(";"))
print(" 1에만: %s" % sorted(x for x in a-b if x.strip()))
print(" 2에만: %s" % sorted(x for x in b-a if x.strip()))
print("\n\n### SET_REP.DBF — 보고서 서식 카탈로그")
b2=(D/"SET_REP.DBF").read_bytes(); nrec,hlen,rlen=struct.unpack_from("<IHH",b2,4)
f=[];o=32
while b2[o]!=0x0D: f.append((b2[o:o+11].rstrip(b"\0").decode("cp949","replace"),b2[o+16]));o+=32
n=0
for i in range(nrec):
r=b2[hlen+i*rlen:hlen+(i+1)*rlen]; v=[];c=1
for nm,ln in f: v.append(r[c:c+ln].decode("cp949","replace").strip()); c+=ln
if v[1]: n+=1; print(" %-5s %-24s %s" % (v[0], v[1][:24], v[2][:30]))
print(" → 서식 %d" % n)
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""중기사용료 호표에서 시간당 손료율·연료·잡품률을 뽑는다."""
import sys, io, pathlib, warnings, re, collections
from decimal import Decimal as D
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
P = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서\04.산불 봉화\01. 내역서\2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.xlsx")
ws = openpyxl.load_workbook(P, data_only=True)["중기사용료"]
rows=[list(r) for r in ws.iter_rows(min_row=1,max_row=ws.max_row,max_col=12,values_only=True)]
호표=[]; cur=None; title=None
for r in rows:
nm=(str(r[0]) if r[0] is not None else "").strip()
k=nm.replace(" ","")
if re.match(r"^제?\d+호표", k): cur=[]; title=None; 호표.append((None,cur)); continue
if cur is None: continue
if k=="합계":
호표[-1]=(title,cur+[("SUM",r)]); cur=None; continue
if title is None and r[3] and not r[2]:
title=(nm, str(r[1] or "")); continue
cur.append(("ROW",r))
print("### 중기 시간당 사용료 구성 — 봉화 2024")
print("%-24s %-14s %9s %12s %10s %8s %8s" % ("장비","규격","손료율","취득가(천원)","손료(원)","연료L/h","잡품%"))
for title, blk in 호표:
if not title: continue
손료율=취득가=연료=잡품=None
for k,r in blk:
if k!="ROW": continue
u=(str(r[3]) if r[3] else "").strip()
nm=(str(r[0]) if r[0] else "").strip()
if u=="천원": 손료율, 취득가 = dec(r[2]), dec(r[4])
elif u=="L": 연료 = dec(r[2])
elif u=="%" and "잡품" in nm: 잡품 = dec(r[2])
if 손료율 is None: continue
print("%-24s %-14s %9s %12s %10s %8s %8s" % (
title[0][:24], title[1][:14], 손료율, f"{취득가:,}" if 취득가 else "-",
f"{(손료율*취득가):,.1f}" if (손료율 and 취득가) else "-",
연료 if 연료 is not None else "-", 잡품 if 잡품 is not None else "-"))
print("\n### 잡품 = 주연료비 × 율 검산")
ok=bad=0
for title, blk in 호표:
연료액=None
for k,r in blk:
if k!="ROW": continue
u=(str(r[3]) if r[3] else "").strip(); nm=(str(r[0]) if r[0] else "").strip()
if u=="L": 연료액 = dec(r[9]) or dec(r[5])
elif u=="%" and "잡품" in nm:
=dec(r[2]); =dec(r[4]); =dec(r[9]) or dec(r[5])
if 연료액 is None or is None: continue
exp = 연료액*/100
same_base = ( == 연료액)
if abs(exp-( or D(0))) < D("0.05") and same_base: ok+=1
else: bad+=1
print(" 잡품 금액 = 연료비 × 율/100 : 일치 %d / 불일치 %d (잡품 단가칸 = 연료비)" % (ok,bad))
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""자재 채택 규칙 검정 — 적용단가가 후보 중 무엇인가."""
import sys, io, pathlib, warnings, collections
from decimal import Decimal as D
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
ROOT = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")
SLOTS = [("조달가격",5),("물가자료",7),("물가정보",9),("거래가격",11),("견적단가",13)]
APPLY = 15
stat = collections.Counter(); examples=[]
files=0
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
files += 1
ws = wb["자재단가대비표"]
for row in ws.iter_rows(min_row=5, max_row=ws.max_row, max_col=17, values_only=True):
name = row[1]
if not name or not str(name).strip(): continue
cands = {nm: dec(row[i-1]) for nm,i in SLOTS}
nz = {k:v for k,v in cands.items() if v not in (None, D(0))}
app = dec(row[APPLY-1]); src = (str(row[APPLY]) if len(row)>APPLY and row[APPLY] else "").strip()
if app in (None, D(0)):
stat["적용단가 없음(별도계상 등)"] += 1; continue
if not nz:
stat["후보 없이 적용단가만(유가·별도자료)"] += 1; continue
mn = min(nz.values()); mx = max(nz.values())
if len(set(nz.values())) >= 2:
stat["복수후보 총"] += 1
if app == mn: stat["복수후보 최저"] += 1
elif app in nz.values(): stat["복수후보 최저아닌 후보"] += 1
else: stat["복수후보 후보밖"] += 1
if len(set(nz.values())) >= 3:
stat["3개이상 총"] += 1
if app == mn: stat["3개이상 최저"] += 1
if app == mn: stat["최저가와 일치"] += 1
elif app == mx: stat["최고가와 일치"] += 1
elif app in nz.values(): stat["후보 중 하나지만 최저 아님"] += 1
else:
stat["어느 후보와도 다름"] += 1
if len(examples)<6: examples.append((p.stem[:26], str(name)[:18], nz, app, src))
stat[""] += 1
print("### 자재 채택 규칙 검정 — 파일 %d" % files)
tot = stat[""]
for k in ("최저가와 일치","후보 중 하나지만 최저 아님","최고가와 일치","어느 후보와도 다름"):
print(" %-24s %4d%5.1f%%" % (k, stat[k], 100*stat[k]/tot if tot else 0))
print(" %-24s %4d" % ("(후보 없이 적용만)", stat["후보 없이 적용단가만(유가·별도자료)"]))
print(" %-24s %4d" % ("(적용단가 0/별도계상)", stat["적용단가 없음(별도계상 등)"]))
print(" ── 검정 대상 합계 %d" % tot)
print()
for k in ("복수후보 총","복수후보 최저","복수후보 최저아닌 후보","복수후보 후보밖","3개이상 총","3개이상 최저"):
print(" %-22s %4d" % (k, stat[k]))
if examples:
print("\n### 어느 후보와도 다른 예")
for f,n,nz,app,src in examples:
print(" %-26s %-18s 적용 %-10s 출처 %-6s 후보 %s" % (f,n,app,src,
", ".join("%s=%s"%(k,v) for k,v in nz.items())))
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
"""CPM 46개 모듈의 export 함수 전수 — 11번은 핵심 12개만 했음."""
import sys, io, pathlib, struct, collections, re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = pathlib.Path(r"C:\경영정보(주)\System")
def exports(b):
try:
e=struct.unpack_from("<I",b,0x3C)[0]
if b[e:e+4]!=b"PE\0\0": return []
nsec=struct.unpack_from("<H",b,e+6)[0]; optsz=struct.unpack_from("<H",b,e+20)[0]
opt=e+24; magic=struct.unpack_from("<H",b,opt)[0]
dd=opt+(96 if magic==0x10b else 112)
erva,esz=struct.unpack_from("<II",b,dd)
if not erva: return []
secs=[]; so=opt+optsz
for i in range(nsec):
s=b[so+i*40:so+i*40+40]
vsz,va,rsz,ptr=struct.unpack_from("<IIII",s,8); secs.append((va,vsz,ptr,rsz))
def r2o(r):
for va,vsz,ptr,rsz in secs:
if va<=r<va+max(vsz,rsz): return ptr+(r-va)
eo=r2o(erva)
if eo is None: return []
nnames=struct.unpack_from("<I",b,eo+24)[0]
namp=struct.unpack_from("<I",b,eo+32)[0]
no=r2o(namp)
out=[]
for i in range(min(nnames,4000)):
nr=struct.unpack_from("<I",b,no+4*i)[0]
o=r2o(nr)
if o is None: continue
end=b.index(b"\0",o)
out.append(b[o:end].decode("latin1"))
return out
except Exception: return []
per={}; allx=collections.Counter()
for p in sorted(ROOT.rglob("*")):
if not p.is_file() or p.suffix.upper() not in (".CPM",".EXE"): continue
try: b=p.read_bytes()
except Exception: continue
if b[:2]!=b"MZ": continue
x=exports(b)
if x: per[p.name]=sorted(x)
for n in x: allx[n]+=1
DONE={"ST_Boq.CPM","ST_EdiLWi.CPM","ST_KanJub.CPM","ST_SanCal.CPM","STmateQty8.CPM","ST_P_Joong.CPM",
"ST_P_Collect.CPM","ST_P_ConEst.CPM","ST_U_ChgAmt.CPM","ST_U_ChgCode.CPM","ST_U_ConBD.CPM","ST_U_Convert.CPM"}
print("### CPM/EXE export 전수 — 모듈 %d개, export %d개(중복포함)" % (len(per), sum(len(v) for v in per.values())))
print(" 11번이 다룬 핵심 12개 말고 **%d개 모듈**이 남아 있었음\n" % len([k for k in per if k not in DONE]))
for nm,x in sorted(per.items()):
mark = "" if nm in DONE else " "
print("%s %-22s %2d%s" % (mark, nm[:22], len(x), ", ".join(x)[:96]))
print("\n### 여러 모듈이 공유하는 export (플러그인 공통 규약)")
for n,c in allx.most_common(12): print(" %-22s %2d개 모듈" % (n,c))
@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
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
B = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서\25년 산불진화임도(기번8)(울진소광)")
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)
def dn(x): return D(x).quantize(D(1), rounding=ROUND_DOWN)
STRUCT = re.compile(r"^(T\d|E\d|C\d|:)$")
def direct(boq, sfx="1"):
excl=set()
for i,r in enumerate(boq):
bd=(r.get("BDCODE"+sfx) 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"+sfx) or r.get("BDCODE1")) and not STRUCT.match((r.get("BDCODE"+sfx) or r.get("BDCODE1")).strip()) and i not in excl]
return (sum(g(r,"M_AMT"+sfx) for r in leaf), sum(g(r,"L_AMT"+sfx) for r in leaf),
sum(g(r,"S_AMT"+sfx) for r in leaf), len(leaf))
orig = zipfile.ZipFile(B/"2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC")
chg = zipfile.ZipFile(B/"변경 1차분-2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC")
b11 = dbf(orig,"BOQ11.DBF"); b12 = dbf(orig,"BOQ12.DBF"); c11 = dbf(chg,"BOQ11.DBF")
print("### 원설계 파일 안의 두 표")
for nm,t in (("BOQ11",b11),("BOQ12",b12)):
M,L,S,n = direct(t)
print(" %-6s %3d행 → 말단 %3d 재료 %13s 노무 %13s 경비 %13s" % (nm,len(t),n,f"{M:,}",f"{L:,}",f"{S:,}"))
wb = openpyxl.load_workbook(B/"2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).xlsx", data_only=True, read_only=True)
ws = wb["공사원가계산서"]; T={}
for row in ws.iter_rows(min_row=1,max_row=45,max_col=7,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(" ","")
amt=next((c for c in cells[3:5] if isinstance(c,(int,float))),None)
if lab and amt is not None and lab not in T: T[lab]=D(str(amt))
print("\n### 출력 XLSX 가 어느 표를 썼나")
tM=T.get("순공사원가재료비직접재료비"); tL=T.get("노무비직접노무비"); tS=T.get("경비산출경비")
print(" 출력 재료 %13s 노무 %13s 경비 %13s" % (f"{tM:,}",f"{tL:,}",f"{tS:,}"))
for nm,t in (("BOQ11",b11),("BOQ12",b12)):
M,L,S,_ = direct(t)
print(" %-6s %s" % (nm, "✅ 일치" if (M,L,S)==(tM,tL,tS) else "불일치"))
print("\n### 변경1차 파일의 BOQ11 차수1 이 원설계 파일의 BOQ12 와 같은가")
M1,L1,S1,n1 = direct(c11,"1"); M2,L2,S2,n2 = direct(c11,"2")
M12,L12,S12,_ = direct(b12)
print(" 변경파일 차수1 말단 %3d 재료 %13s 노무 %13s 경비 %13s" % (n1,f"{M1:,}",f"{L1:,}",f"{S1:,}"))
print(" 원설계 BOQ12 재료 %13s 노무 %13s 경비 %13s %s" % (f"{M12:,}",f"{L12:,}",f"{S12:,}",
"✅ 같음" if (M1,L1,S1)==(M12,L12,S12) else "다름"))
print(" 변경파일 차수2 말단 %3d 재료 %13s 노무 %13s 경비 %13s" % (n2,f"{M2:,}",f"{L2:,}",f"{S2:,}"))
print("\n 직접공사비 당초 %s → 변경 %s 증감 %s" % (
f"{M1+L1+S1:,}", f"{M2+L2+S2:,}", f"{(M2+L2+S2)-(M1+L1+S1):+,}"))
pl = dbf(chg,"PRJLST.DBF")[0]
print(" PRJLST 1차 T=%s 2차 T=%s (직접비 합과 %s)" % (
f"{g(pl,'TCOST1'):,}", f"{g(pl,'TCOST2'):,}",
"일치" if (g(pl,'TCOST1')==M1+L1+S1 and g(pl,'TCOST2')==M2+L2+S2) else "불일치"))
@@ -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,49 @@
# -*- coding: utf-8 -*-
"""변경 차수 저장 구조 — DBF 슬롯 4벌 vs 화면 8차의 어긋남을 가른다."""
import sys, io, re, pathlib, zipfile, struct, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")
def fields(b):
f=[];o=32
while b[o]!=0x0D: f.append(b[o:o+11].rstrip(b"\0").decode("cp949","replace")); o+=32
return f
print("### ① PRJLST.SMT 의 차수 섹션")
for p in sorted(ROOT.rglob("*.STC")):
try: z=zipfile.ZipFile(p); b=z.read("PRJLST.SMT")
except Exception: continue
secs=sorted({s for s in re.findall(r"\[([0-9]+)_KANJUB\]",
"".join(ch for ch in b[0x200:].decode("cp949","replace")))}, key=int)
print(" %-46s 섹션 %s" % (p.stem[:46], secs or "없음"))
print("\n### ② DBF 의 차수 슬롯 (접미사 숫자)")
for p in list(sorted(ROOT.rglob("*.STC")))[:2]:
z=zipfile.ZipFile(p)
print("\n-- %s" % p.stem[:50])
for t in ("PRJLST.DBF","BOQ11.DBF","COSTN.DBF"):
try: f=fields(z.read(t))
except Exception: continue
sfx=collections.Counter()
for n in f:
m=re.search(r"(\d)$", n)
if m: sfx[m.group(1)]+=1
print(" %-12s 필드 %3d개 접미사별: %s" % (t, len(f), dict(sorted(sfx.items()))))
print("\n### ③ 화면·코드가 말하는 차수 상한")
SYS=pathlib.Path(r"C:\경영정보(주)\System")
pat=[rb"[1-9]\xec\xb0\xa8\xeb\xb3\x80\xea\xb2\xbd"] # UTF-8 'n차변경' — cp949 로 다시
import re as _re
need=[("%d차변경"%i).encode("cp949") for i in range(1,10)]
hit=collections.Counter()
for p in SYS.rglob("*"):
if not p.is_file(): continue
try: b=p.read_bytes()
except Exception: continue
if b[:2]!=b"MZ": continue
for i,n in enumerate(need,1):
if n in b: hit[i]+=1
print(" 'n차변경' 문자열을 가진 모듈 수: %s" % dict(sorted(hit.items())))
for tok in (b"BOQ1", b"BOQ2"):
c=sum(1 for p in SYS.rglob("*") if p.is_file() and p.suffix.upper()==".CPM" and tok in p.read_bytes())
print(" '%s' 를 가진 CPM 모듈 %d" % (tok.decode(), c))
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""화면 선택축 ↔ PRJLST.SMT 저장키 대응, 그리고 간접비 식 템플릿 전수."""
import sys, io, re, pathlib, zipfile, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
PRAC = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")
SYS = pathlib.Path(r"C:\경영정보(주)\System")
# ── PRJLST.SMT 키 모으기 ─────────────────────────
keys=collections.Counter()
for p in sorted(PRAC.rglob("*.STC")):
try: b=zipfile.ZipFile(p).read("PRJLST.SMT")
except Exception: continue
txt=b[0x200:].decode("cp949","replace")
for k in re.findall(r"([0-9]?_?[A-Z][A-Z0-9_]{2,})=", txt): keys[re.sub(r"^\d_","",k)]+=1
print("### PRJLST.SMT 저장키 %d종 (접두 차수 제거)" % len(keys))
grp=collections.defaultdict(list)
for k in keys:
m=re.match(r"^([CRIBY])_(.+)$", k)
grp[m.group(1) if m else "기타"].append(k)
LBL={"C":"금액 직접입력","R":"요율","I":"선택 인덱스","B":"켬/끔","Y":"연도·기타","기타":"섹션·기타"}
for g in ("C","R","I","B","Y","기타"):
if g not in grp: continue
v=sorted(grp[g])
print("\n%s_ %s %d" % (g, LBL[g], len(v)))
for i in range(0,len(v),5): print(" " + " ".join("%-20s"%x for x in v[i:i+5]))
# ── 간접비 식 템플릿 전수 (바이너리) ─────────────
print("\n\n### 간접비 식 템플릿 — 바이너리에서 전수")
TMPL=re.compile(rb"[A-Z_\[\]0-9+\-*/(). ]*\[[A-Z_0-9]{2,12}\][A-Z_\[\]0-9+\-*/(). ]{4,80}")
found=collections.Counter()
for p in SYS.rglob("*"):
if not p.is_file() or p.suffix.upper()!=".CPM": continue
try: b=p.read_bytes()
except Exception: continue
for m in TMPL.finditer(b):
s=m.group().decode("latin1").strip()
if s.count("[")<1 or len(s)<10: continue
if not re.search(r"[*/+]", s): continue
if re.search(r"[a-z]", s): continue
found[s]+=1
print(" 서로 다른 템플릿 %d개 (상위 30)" % len(found))
for s,c in found.most_common(30): print(" %-72s %2d" % (s[:72], c))
@@ -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)
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
"""SIxMemo(*.SMT) 본문 덤프 — 0x200 부터가 본문. 원본은 읽기만."""
import sys, io, re, pathlib
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
D = pathlib.Path(r"C:\경영정보(주)\STmate 자료\설계자료\실습(맛보기)")
for f in ("PRJLST.SMT","COSTN.SMT","QCODE.SMT"):
b=(D/f).read_bytes()
out=[]
for m in re.finditer(rb"[\x20-\x7e\xa1-\xfe]{3,}", b[0x200:]):
try: out.append((0x200+m.start(), m.group().decode("cp949")))
except Exception: pass
print("\n" + "="*92)
print("### %s %d B 본문 문자열 %d" % (f, len(b), len(out)))
for off,s in out: print(" 0x%05X %s" % (off, s[:86]))
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""COSTN.SMT 전개 텍스트 전수 파싱 — 자원 참조·품·할증·중간라벨·Q식.
문법 출처: original/원가계산/STmate/SIxMemo_구조.md (2026-08-15)."""
import sys, io, re, pathlib, zipfile, collections, json
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
SRC = [pathlib.Path(r"C:\경영정보(주)\STmate 자료\설계자료\실습(맛보기)\COSTN.SMT")]
for p in sorted(pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서").rglob("*.STC")):
SRC.append(p)
RE_REF = re.compile(r"&([A-Z])(\d{5})([A-Z])&") # &M00034M& 종류·번호·성분
RE_LABEL = re.compile(r":=\s*([A-Z]\d{2})") # :=A01
RE_QTY = re.compile(r"([\d.]+)\s*'([^']{1,8})'") # 0.31'Kg'
RE_PCT = re.compile(r"([\d.]+)\s*'%'") # 5'%'
RE_Q = re.compile(r"\bQ\s*=\s*([^=\n']{3,60})") # Q = 3600*q*K*f*E/Cm
def body(b):
out=[]
for m in re.finditer(rb"[\x20-\x7e\xa1-\xfe]{3,}", b[0x200:]):
try: out.append(m.group().decode("cp949"))
except Exception: pass
return out
tot=collections.Counter(); refs=collections.Counter(); comp=collections.Counter()
qforms=collections.Counter(); pcts=collections.Counter(); units=collections.Counter()
files=0; lines=0
for s in SRC:
try:
if s.suffix.upper()==".STC":
z=zipfile.ZipFile(s)
nm=next((n for n in z.namelist() if n.upper()=="COSTN.SMT"), None)
if not nm: continue
b=z.read(nm)
else: b=s.read_bytes()
except Exception: continue
if b[8:15]!=b"SIxMemo": continue
files+=1
for ln in body(b):
lines+=1
for k,n,c in RE_REF.findall(ln):
refs[k]+=1; comp[c]+=1; tot["참조"]+=1
for l in RE_LABEL.findall(ln): tot["중간라벨"]+=1
for v,u in RE_QTY.findall(ln):
if u!="%": units[u]+=1; tot["품(수량+단위)"]+=1
for v in RE_PCT.findall(ln): pcts[v]+=1; tot["할증%"]+=1
for q in RE_Q.findall(ln):
qq=re.sub(r"\s+","",q)
if len(qq)>4 and any(ch in qq for ch in "*/"): qforms[qq[:44]]+=1; tot["Q식"]+=1
print("### COSTN.SMT 전수 파싱 — 파일 %d개, 본문 줄 %d" % (files, lines))
print("\n요소별 검출")
for k,v in tot.most_common(): print(" %-14s %5d" % (k,v))
print("\n자원 참조 종류 (앞글자)")
for k,v in refs.most_common(): print(" %s %5d" % (k,v))
print("\n가져오는 성분 (끝글자)")
for k,v in comp.most_common(): print(" %s %5d" % (k,v))
print("\n할증률 값 분포 (상위 12)")
for k,v in pcts.most_common(12): print(" %-8s %4d" % (k+"%", v))
print("\n단위 분포 (상위 12)")
for k,v in units.most_common(12): print(" %-8s %4d" % (k,v))
print("\nQ식 꼴 (상위 10)")
for k,v in qforms.most_common(10): print(" %-46s %3d" % (k,v))
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""STC 프로젝트 안의 표들이 실제로 읽히는지 본다. 복호화 시도 없음."""
import pathlib, zipfile, struct, sys, io, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
roots = [pathlib.Path(r"C:\경영정보(주)"),
pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")]
stcs = []
for r in roots:
if r.exists(): stcs += sorted(r.rglob("*.STC"))
print("찾은 STC %d" % len(stcs))
for s in stcs[:3]: print(" %s" % s)
def dbf_state(b):
if len(b) < 64: return "짧음", 0, 0
try:
nrec, hlen, rlen = struct.unpack_from("<IHH", b, 4)
n = 0; o = 32
while o < len(b) and b[o] != 0x0D: n += 1; o += 32
if not (1 <= n <= 300 and hlen and rlen): return "머리깨짐", 0, 0
if nrec == 0: return "빈표", 0, n
rec = b[hlen:hlen+rlen]
pr = sum(1 for c in rec if 32 <= c < 127 or c >= 0xA1) / max(len(rec),1)
return ("평문" if pr > 0.85 else "본문암호화"), nrec, n
except Exception: return "해석실패", 0, 0
if stcs:
for s in stcs[:2]:
print("\n### %s" % s.name)
try:
z = zipfile.ZipFile(s)
except Exception as e:
print(" ZIP 열기 실패: %s" % e); continue
enc = any(i.flag_bits & 0x1 for i in z.infolist())
print(" ZIP 암호: %s 파일 %d" % ("걸림" if enc else "없음", len(z.infolist())))
for i in z.infolist():
if not i.filename.upper().endswith((".DBF",)): continue
try: b = z.read(i)
except Exception as e: print(" %-16s 읽기실패" % i.filename); continue
st, nrec, nf = dbf_state(b)
print(" %-16s %-10s rec=%-6s fld=%s" % (i.filename, st, nrec, nf))
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
import sys, io, pathlib, re, zipfile, struct, collections, warnings, difflib
warnings.filterwarnings("ignore"); import openpyxl
SRC = open(r"C:\Users\umsan\AppData\Local\Temp\claude\D--aislo-wt-sub\df75230a-b05b-4088-ad34-bb0c9da0a312\scratchpad\t1_consist.py",
encoding="utf-8").read().split("items = collections.Counter()")[0]
SRC = SRC.replace('sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")','')
SRC = "\n".join(l for l in SRC.split("\n") if not l.strip().startswith("print("))
ns = {}
exec(SRC, ns)
per = ns["per"]; tabs_by_proj = ns["tabs_by_proj"]
out = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
def P(*a): print(*a, file=out)
P("### 규모·기간 의존 항목 — 열이 고정인가 (RATE_R 행 0~44 안)")
for lab in ("간접노무비","기타경비","일반관리비","이윤"):
vals = {k: per[k][lab] for k in per if lab in per[k]}
if len(vals) < 3: continue
colsets=[]; detail=[]
for k,v in vals.items():
M = tabs_by_proj[k].get("RATE_R") or []
hits = {(i,j+1) for i,row in enumerate(M[:45]) for j,x in enumerate(row) if x and abs(x-v)<1e-6}
colsets.append({c for _,c in hits}); detail.append((k,v,sorted(hits)[:6]))
common = set.intersection(*colsets) if colsets else set()
P("\n%s 관측 %d건, 값 %s" % (lab, len(vals), sorted({v for v in vals.values()})))
P(" 모든 프로젝트 공통 열: %s" % (sorted(common) or "없음"))
for k,v,h in detail:
P(" %-30s %7.3f%% %s" % (k[:30], v, ", ".join("(%d,%d)"%x for x in h) or "없음"))
out.flush()
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
"""T1 대체 — 통제시험 없이, 모든 프로젝트에서 동시에 성립하는 좌표만 남긴다."""
import sys, io, pathlib, re, zipfile, struct, collections, warnings, difflib
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\실무문서")
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
M=[]
for i in range(nrec):
r=b[hlen+i*rlen:hlen+(i+1)*rlen]; v=[];c=1
for nm,ln in f:
s=r[c:c+ln].decode("cp949","replace").strip(); c+=ln
try: v.append(round(float(s),4) if s else 0.0)
except ValueError: v.append(0.0)
M.append(v)
return M
RE = re.compile(r"([\d.]+)\s*%")
def rates(xp):
wb=openpyxl.load_workbook(xp,data_only=True,read_only=True)
if "공사원가계산서" not in wb.sheetnames: return {}
ws=wb["공사원가계산서"]; out={}
for row in ws.iter_rows(min_row=1,max_row=45,max_col=7,values_only=True):
cells=["" if v is None else v for v in row]
lab=re.sub(r"\s+","",next((str(c) for c in cells[:3] if isinstance(c,str) and str(c).strip()),""))
note=next((str(c) for c in cells[4:7] if isinstance(c,str) and "%" in str(c)),"")
if not lab or not note: continue
m=RE.search(note)
if m and lab not in out: out[lab]=float(m.group(1))
return out
pairs=[]
for stc in sorted(ROOT.rglob("*.STC")):
best=None;bs=0
for x in list(stc.parent.rglob("*.xlsx"))+list(stc.parent.parent.rglob("*.xlsx")):
if x.name.startswith("~$"): continue
s=difflib.SequenceMatcher(None,stc.stem,x.stem).ratio()
if s>bs: bs,best=s,x
if best and bs>0.55: pairs.append((stc,best))
# 항목 -> {프로젝트: 요율}
per = {}
tabs_by_proj = {}
for stc,xp in pairs:
try: r = rates(xp)
except Exception: continue
if not r: continue
z = zipfile.ZipFile(stc); tabs={}
for n in z.namelist():
if re.match(r"RATE.*\.DBF$", n, re.I):
try: tabs[n.upper().replace(".DBF","")] = dbf(z,n)
except Exception: pass
key = stc.stem[:28]
tabs_by_proj[key]=tabs; per[key]=r
items = collections.Counter()
for k,r in per.items():
for lab in r: items[lab]+=1
print("프로젝트 %d건, 항목별 관측 수" % len(per))
print()
print("%-22s %5s %6s %s" % ("항목","관측","값종류","전 프로젝트에서 동시에 맞는 좌표"))
print("-"*96)
for lab, cnt in items.most_common():
vals = {k: per[k][lab] for k in per if lab in per[k]}
if len(vals) < 3: continue
# 후보 좌표: 첫 프로젝트에서 값이 맞는 모든 자리
first = next(iter(vals))
cand = set()
for tn, M in tabs_by_proj[first].items():
for i,row in enumerate(M):
for j,x in enumerate(row):
if x and abs(x-vals[first])<1e-6: cand.add((tn,i,j))
for k in list(vals)[1:]:
keep=set()
for (tn,i,j) in cand:
M = tabs_by_proj[k].get(tn)
if M and i<len(M) and j<len(M[i]) and abs(M[i][j]-vals[k])<1e-6: keep.add((tn,i,j))
cand = keep
if not cand: break
nv = len(set(vals.values()))
s = ", ".join("%s[%d,%d]"%(t,i,j+1) for t,i,j in sorted(cand)[:4]) or "없음"
if len(cand)>4: s += " …외 %d" % (len(cand)-4)
print("%-22s %5d %6d %s" % (lab[:22], len(vals), nv, s))
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import sys, io, pathlib, struct, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = pathlib.Path(r"C:\경영정보(주)\System\ST_BSC")
def rd(p):
b=p.read_bytes(); 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
M=[]
for i in range(nrec):
r=b[hlen+i*rlen:hlen+(i+1)*rlen]; v=[];c=1
for nm,ln in f:
s=r[c:c+ln].decode("cp949","replace").strip();c+=ln
try: v.append(round(float(s),4) if s else 0.0)
except ValueError: v.append(0.0)
M.append(v)
return M
print("### JB_R 45·46행을 연도별로 — 부속표 영역의 열 의미 굳히기\n")
for name in ["2019(상_0107)_제비율.JB_R","2022(상_0103)_제비율.JB_R",
"2024(상_0101)_제비율.JB_R","2025(상_0101)_제비율.JB_R","2026(상_0101)_제비율.JB_R"]:
p = ROOT/name
if not p.exists(): continue
M = rd(p)
for r in (45, 46):
vals = [(c+1, M[r][c]) for c in range(len(M[r])) if M[r][c]]
print("%-26s%2d : %s" % (name[:26], r, " ".join("%d=%g"%v for v in vals)))
print()
print("### 해석")
print("""
행45 열1 = 산재보험료 (%) 출력 엑셀에서 확인
행45 열11~17 = 고용보험료 등급 1~7 (%) rGrd_Rate_Kobo 7(등급·요율) 것과 일치,
값이 내림차순이고 프로젝트는 열17(7등급이하)
행46 열1 = 건강보험료 (%)
행46 열6 = 연금보험료 (%)
행46 열11 = 노인장기요양보험료 (%) 건강보험료에 곱함
행20 열17·18 = 산업안전보건관리비 (%)·기초액()
""")
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""T1 확장 — 짝을 폴더 기준으로 넓게 잡고, 항목별 좌표 교집합을 낸다."""
import sys, io, pathlib, re, zipfile, struct, collections, warnings, difflib
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\실무문서")
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
M=[]
for i in range(nrec):
r=b[hlen+i*rlen:hlen+(i+1)*rlen]; v=[]; c=1
for nm,ln in f:
s=r[c:c+ln].decode("cp949","replace").strip(); c+=ln
try: v.append(round(float(s),4) if s else 0.0)
except ValueError: v.append(0.0)
M.append(v)
return M
RATE_RE = re.compile(r"([\d]+(?:\.[\d]+)?)\s*%")
def sheet_rates(xp):
wb = openpyxl.load_workbook(xp, data_only=True, read_only=True)
if "공사원가계산서" not in wb.sheetnames: return []
ws = wb["공사원가계산서"]; out=[]; seen=set()
for row in ws.iter_rows(min_row=1, max_row=40, max_col=8, values_only=True):
cells = ["" if v is None else str(v) for v in row]
label = re.sub(r"\s+","", next((c for c in cells[:3] if c.strip()), ""))
note = next((c for c in cells[4:7] if "%" in c), "")
if not note or not label: continue
for m in RATE_RE.finditer(note):
k=(label, m.group(1))
if k in seen: continue
seen.add(k); out.append((label, float(m.group(1))))
return out
# 폴더 안의 STC 와 xlsx 를 이름 비슷한 순으로 짝지음
pairs=[]
for stc in sorted(ROOT.rglob("*.STC")):
best=None; bs=0
for x in list(stc.parent.rglob("*.xlsx")) + list(stc.parent.parent.rglob("*.xlsx")):
if x.name.startswith("~$"): continue
s = difflib.SequenceMatcher(None, stc.stem, x.stem).ratio()
if s > bs: bs, best = s, x
if best and bs > 0.55: pairs.append((stc, best, bs))
print("%d" % len(pairs))
for s,x,r in pairs: print(" %.2f %s" % (r, s.stem[:52]))
coord = collections.defaultdict(collections.Counter) # 항목 -> 좌표 카운트
uniq = collections.defaultdict(collections.Counter) # 항목 -> 유일히 잡힌 좌표
for stc, xp, _ in pairs:
try: rates = sheet_rates(xp)
except Exception: continue
if not rates: continue
z = zipfile.ZipFile(stc)
tabs = {}
for n in z.namelist():
if re.match(r"RATE.*\.DBF$", n, re.I):
try: tabs[n.upper().replace(".DBF","")] = dbf(z,n)
except Exception: pass
for label, v in rates:
found=[]
for tn,M in tabs.items():
for r,row in enumerate(M):
for c,x in enumerate(row):
if x and abs(x-v) < 1e-6: found.append("%s[%d,%d]"%(tn,r,c+1))
for f in found: coord[label][f]+=1
if len(found)==1: uniq[label][found[0]]+=1
print("\n=== 유일하게 잡힌 좌표 (다른 후보 없이 딱 하나) ===")
for lab in sorted(uniq):
for k,n in uniq[lab].most_common():
print(" %-22s%-16s 프로젝트 %d건에서 유일" % (lab[:22], k, n))
print("\n=== 여러 후보 중 모든 프로젝트에 공통인 좌표 ===")
N = len(pairs)
for lab in sorted(coord):
all_hit = [k for k,n in coord[lab].items() if n == max(coord[lab].values()) and n >= 3]
if all_hit and lab not in uniq:
print(" %-22s%s (각 %d건)" % (lab[:22], ", ".join(all_hit[:5]), max(coord[lab].values())))
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""STmate 고유 폼 전수 — 캡션·선택지를 뽑아 UI 카탈로그를 만든다. 원본은 읽기만."""
import sys, io, re, pathlib, importlib.util, collections
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
spec=importlib.util.spec_from_file_location("pd", r"D:\aislo-wt\sub\resources\knowledge\original\경쟁사 프로그램\STmate 분석\40_스크립트\parse_dfm.py")
pd=importlib.util.module_from_spec(spec); spec.loader.exec_module(pd)
ROOT=pathlib.Path(r"C:\경영정보(주)\System")
THIRD=re.compile(r"^(Dclhrp60|designide60|RTL60|VCL60|DBRTL60|BDERTL60|RealGrid|Apollo|dsnide|JDLCHK)",re.I)
HRLIB={"HReportUf","HRPageRf","HRPreVUf","HRSaveUf","LoginDialog","PasswordDialog"}
def blobs(b):
out=[]; pos=[m.start() for m in re.finditer(rb"TPF0", b)]
for i,s in enumerate(pos):
e = pos[i+1] if i+1<len(pos) else len(b)
p=s+4
if p>=len(b): continue
n=b[p]
if not (3<=n<=40): continue
cls=b[p+1:p+1+n]
if not re.fullmatch(rb"T[A-Za-z0-9_]+", cls): continue
p2=p+1+n; n2=b[p2] if p2<len(b) else 0
if not (1<=n2<=40): continue
nm=b[p2+1:p2+1+n2]
if not re.fullmatch(rb"[A-Za-z_][A-Za-z0-9_]*", nm): continue
out.append((nm.decode("latin1"), b[s:e]))
return out
def walk(n, out):
if isinstance(n,dict):
pr=n.get("properties") or {}
cap=pr.get("Caption")
items=None
for k,v in pr.items():
if k.lower().endswith("items.strings") or k=="Items": items=v
if isinstance(cap,str) and any(ord(c)>127 for c in cap): out["cap"].append(cap.strip())
if isinstance(items,list) and items: out["items"].append((n.get("name",""), [str(x) for x in items]))
for c in n.get("children",[]) or []: walk(c,out)
cat={}
for p in sorted(ROOT.rglob("*")):
if not p.is_file() or THIRD.match(p.stem): continue
try: b=p.read_bytes()
except Exception: continue
if b[:2]!=b"MZ": continue
for nm, blob in blobs(b):
if nm in HRLIB or nm in cat: continue
try:
r=pd.Reader(blob); r.take(4); root=r.component(root=True)
except Exception: continue
o={"cap":[],"items":[]}; walk(root,o)
if o["cap"] or o["items"]: cat[nm]=(p.name,o)
print("### 파싱 성공한 STmate 고유 폼 %d\n" % len(cat))
tot_i=0
for nm,(mod,o) in sorted(cat.items()):
if not o["items"]: continue
tot_i+=len(o["items"])
title=o["cap"][0] if o["cap"] else ""
print("%-20s %-20s %s" % (nm[:20], mod[:20], ("«%s»"%title[:30]) if title else ""))
for cn, its in o["items"][:8]:
print(" %-18s %s" % (cn[:18], ", ".join(its)[:88]))
print("\n선택지를 가진 폼 %d개, 선택축 %d" % (sum(1 for _,(_,o) in cat.items() if o["items"]), tot_i))
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""호표 성분 소계의 반올림 방식을 가른다 — 절사 / 사사오입 / 은행가 / 원값."""
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 q(x,m): return D(x).quantize(D(1), rounding=m)
def dec(v):
if v in (None,""): return D(0)
try: return D(str(v))
except Exception: return D(0)
ROOT = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서")
def scan(path, sheet, stat):
try: wb = openpyxl.load_workbook(path, data_only=True, read_only=True)
except Exception: return
if sheet not in wb.sheetnames: return
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)]
blk=None
for r in rows:
nm=(str(r[0]) if r[0] is not None else "").strip().replace(" ","")
if re.match(r"^제?\d+호표", nm): blk=[]; continue
if blk is None: continue
if nm=="합계":
# 합계행의 금액칸 위치를 찾는다: 성분 원합과 가장 가까운 칸
for idx,lab in ((7,"노무"),(9,"재료"),(11,"경비")):
raw = sum(dec(c[idx]) for c in blk)
if raw == 0: continue
cands = {i: dec(r[i]) for i in (idx-1, idx) if r[i] is not None}
for rm, tag in ((ROUND_DOWN,"절사"),(ROUND_HALF_UP,"사사오입"),(ROUND_HALF_EVEN,"은행가")):
v = q(raw, rm)
if v in cands.values(): stat[(sheet,tag)] += 1
if raw in cands.values(): stat[(sheet,"원값")] += 1
stat[(sheet,"")] += 1
blk=None; continue
blk.append(r)
stat = collections.Counter()
for p in ROOT.rglob("*.xlsx"):
if p.name.startswith("~$"): continue
for s in ("중기사용료","일위대가표"): scan(p, s, stat)
for sheet in ("중기사용료","일위대가표"):
tot = stat[(sheet,"")]
if not tot: continue
print("\n### %s — 성분 소계 %d" % (sheet, tot))
for tag in ("절사","사사오입","은행가","원값"):
n = stat[(sheet,tag)]
print(" %-8s %5d%5.1f%%" % (tag, n, 100*n/tot))
@@ -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,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()))))
@@ -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,"원값")]))
@@ -1,5 +1,18 @@
# STmate 분석
> ## 🔗 STmate 지식은 **세 폴더**에 나뉘어 있음 — 갈라지지 않게 서로를 가리킴
>
> | 트랙 | 자리 | 담당 |
> |---|---|---|
> | ① **계산 로직** | `technical_info/01_임도/05_원가정보/` | 밑수·단수처리·제비율·기계경비·노임·자재 적용. **Aislo 구현의 정본** |
> | ② **STC 포맷** | `original/원가계산/STmate/` | 컨테이너·필드사전·난독화·SIxMemo·INITIAL. import/export 명세 |
> | ③ **프로그램 분석** | `original/경쟁사 프로그램/STmate 분석/` | 실행모듈·UI 폼·계산사슬·골든셋 회귀 |
>
> ⚠ **새 STmate 작업을 시작하기 전에 세 폴더를 모두 볼 것.** 2026-09-13 에 ③이 ①②를 모르고 시작해
> 단수처리 규칙·`INITIAL` 시트·`SMT` 문법·`BDQTY` 난독화를 다시 분석한 일이 있음 (③의 `21_정정과_트랙통합.md`).
경쟁사 설계예산 프로그램 **STmate**(경영정보(주)) 를 정적으로 뜯어본 기록.
원본은 저장소 밖 `C:\경영정보(주)`**원본은 손대지 않음**, 여기에만 정리물을 둠.
옆 폴더 `../오솔길 분석/` 과 같은 얼개.
@@ -40,6 +53,17 @@
| 12 | 원가계산 4형식과 RATE표 복원 — **설정 83축** |
| 13 | 통제시험 목록 T1~T11 — 아직 못 가른 경계 |
| 14 | 최종 분석완료 보고서 |
| 15 | **재구현 가능성 검증** — 무엇을 만들 수 있고 무엇이 막혀 있나 |
| 16 | **골든셋 회귀 결과** — 원가계산을 다시 세워 원 단위로 맞춘 실증 |
| 17 | **단가조립 검증** — 일위대가·중기 시간당 사용료 구조, 손료율 실측 |
| 18 | **누락 계산로직 점검** — 노임 환산식·단가산출 Q식·설정 스냅샷 |
| 19 | **진행률 장부** — 어디까지 봤고 무엇이 남았나 |
| 20 | **설정파일과 보고서 카탈로그** — 간접비 코드 사전, 서식 75종 |
| 21 | **정정과 트랙 통합**`SMT` 정정, 지식DB 세 트랙, 암호화의 성격 |
| 22 | **UI 폼 분석** — 선택축 272개, 화면에 남아 있던 계산 규칙 |
| 23 | **`COSTN.SMT` 전수파싱** — 단가산출 원장, Q식 10꼴, 운반 사이클 |
| 24 | **모듈구조 전수** — 46개 export, 계산 공용 라이브러리 73함수, 동글 |
| 25 | **설정키 전수와 식템플릿** — 저장키 241종, 간접비 식 26개, 설정 사슬 닫힘 |
## 확정된 굵직한 것
@@ -49,6 +73,10 @@
- 원가계산 **네 형식**(일반·수공·실적일반·실적수공)은 출력 모양이 아니라 **공식 집합이 통째로 다름**
- 기준자료 `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번)
## 원자료 — `30_원자료/`
@@ -6,6 +6,19 @@ status: draft
목적: STC(STmate 프로젝트 파일) import/export 구현을 위한 컨테이너·DBF 명세·검증 근거 축적
---
> ## 🔗 STmate 지식은 **세 폴더**에 나뉘어 있음 — 갈라지지 않게 서로를 가리킴
>
> | 트랙 | 자리 | 담당 |
> |---|---|---|
> | ① **계산 로직** | `technical_info/01_임도/05_원가정보/` | 밑수·단수처리·제비율·기계경비·노임·자재 적용. **Aislo 구현의 정본** |
> | ② **STC 포맷** | `original/원가계산/STmate/` | 컨테이너·필드사전·난독화·SIxMemo·INITIAL. import/export 명세 |
> | ③ **프로그램 분석** | `original/경쟁사 프로그램/STmate 분석/` | 실행모듈·UI 폼·계산사슬·골든셋 회귀 |
>
> ⚠ **새 STmate 작업을 시작하기 전에 세 폴더를 모두 볼 것.** 2026-09-13 에 ③이 ①②를 모르고 시작해
> 단수처리 규칙·`INITIAL` 시트·`SMT` 문법·`BDQTY` 난독화를 다시 분석한 일이 있음 (③의 `21_정정과_트랙통합.md`).
> **왜 이 폴더인가**: STC는 설계자가 발주처에 제출하는 **최종 산출물**이며, Aislo도 STC **불러오기(import)·내보내기(export)가 필수 요구사항**이다(사용자 확정 2026-08-15). 따라서 STC 포맷 해석은 연구성 작업이 아니라 **납품 호환성 확보를 위한 구현 선행 명세**다.
> 계산 로직(밑수·절사·채택 규칙)은 `technical_info/01_임도/05_원가정보/`가 담당하고, 이 폴더는 **포맷 구조·필드 사전·검증 근거**만 담는다. 두 트랙은 분리한다.
> 실무 관측 진입점 = [original/실무문서/_STC포맷분석.md](../../실무문서/_STC포맷분석.md) (이 폴더로 명세 이관, 그 문서는 요약·링크로 유지).
@@ -9,6 +9,19 @@ status: draft
last_updated: 2026-08-14
---
> ## 🔗 STmate 지식은 **세 폴더**에 나뉘어 있음 — 갈라지지 않게 서로를 가리킴
>
> | 트랙 | 자리 | 담당 |
> |---|---|---|
> | ① **계산 로직** | `technical_info/01_임도/05_원가정보/` | 밑수·단수처리·제비율·기계경비·노임·자재 적용. **Aislo 구현의 정본** |
> | ② **STC 포맷** | `original/원가계산/STmate/` | 컨테이너·필드사전·난독화·SIxMemo·INITIAL. import/export 명세 |
> | ③ **프로그램 분석** | `original/경쟁사 프로그램/STmate 분석/` | 실행모듈·UI 폼·계산사슬·골든셋 회귀 |
>
> ⚠ **새 STmate 작업을 시작하기 전에 세 폴더를 모두 볼 것.** 2026-09-13 에 ③이 ①②를 모르고 시작해
> 단수처리 규칙·`INITIAL` 시트·`SMT` 문법·`BDQTY` 난독화를 다시 분석한 일이 있음 (③의 `21_정정과_트랙통합.md`).
관련 문서: [제비율](제비율.md) · [설계·감리대가](설계·감리대가.md) · [원가_기초데이터_원천](원가_기초데이터_원천.md) · [노임단가_적용](노임단가_적용.md) · [자재단가_적용](자재단가_적용.md) · [기계경비_산정](기계경비_산정.md) · [수량산출_일반](../04_수량분석정보/수량산출_일반.md) · [품셈_적용기준](../04_수량분석정보/품셈_적용기준.md)
## 1. 공사원가 구성 트리