knowledge(원가·STC): 계산로직 3건 신설·STC 포맷 명세 9문서 + BDQTY 난독화 오판 정정

계산 로직 트랙 (technical_info/05_원가정보):
- 단수처리_규칙.md 신설: 위치별 3함수(내역·제경비 ROUNDDOWN 원절사 / 집계표
  ROUND / 관급자재대 ROUNDUP 천원올림) — 5공사지 xlsx 수식 교차 추출,
  관급자재대 +620원 차이 = 천원올림 규명
- 조달수수료 계상 구조: base→fee→total 3변수 분리(순환 정의 방지),
  원가계산_체계·제비율·입출력변수사전 반영 (0.54%는 과거 관측 ⚠, 현행 원문 미수집)
- 자재단가_적용 §3-1: 적용단가 채택 규칙 후보 — COSTN PG_1 분포(자재만 출처
  선택 존재) 관측, 알고리즘 미확정 명시

STC 포맷 트랙 (original/원가계산/STmate/ 신설 — import/export 필수 요구사항):
- 컨테이너·필드사전 핵심/보조·난독화상태·교차비교·SIxMemo·INITIAL·왕복검증
  9문서 + 재현 스크립트 2본
- 7개 STC 전수 자동 비교: BDQTY·SYSINFO 난독화 전수 재현(플래그 0x45),
  SYSINFO 키스트림 파일 간 100% 동일, BOQ12(다공구)·차수 슬롯 신규 발견

정정:
- _STC포맷분석.md: BDQTY·SYSINFO "빈 것" 오판 정정(실재 667·8건 난독화),
  교차대조 7파일 확장
- _숨김탭분석.md: INITIAL "무가치" 철회 — STC/xlsx 입출력 매핑 참조로 재분류

미결 3건 등록: No.19 STmate 실기 환경, No.20 조달수수료 현행 원문,
No.21 포맷 역공학 라이선스

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 19:40:27 +09:00
co-authored by Claude Fable 5
parent a29ca1a11d
commit 89e3b034e9
21 changed files with 1002 additions and 24 deletions
@@ -95,27 +95,40 @@ status: draft
### 3-6. 기타 테이블
| 테이블 | 상태 | 스키마 요지 |
> **2026-08-15 정정 (BDQTY·SYSINFO 상태)**: 종전 "빈 것" 기재는 오류다. 표준 DBF 파서(dbfread 등)가 **비표준 삭제플래그 바이트 `E`로 시작하는 레코드를 유효 레코드로 읽지 않아** 빈 것처럼 보였을 뿐, 헤더상 레코드가 실재한다. 상세 = [STmate 포맷 명세](../원가계산/STmate/STC_난독화_상태.md). 아래 표의 "상태" 열을 헤더 실측 기준으로 정정.
| 테이블 | 상태 (헤더 실측) | 스키마 요지 |
|---|---|---|
| BOQ (83필드) | 빈 것 | BOQ11과 동일 구조 — 작업본/발행본 이중화로 추정 ⚠ |
| BDQTY (37필드) | 빈 것 | BDCODEㆍQTY1~4ㆍ할증(X_HAL1~3)ㆍ비목(BIMOK) — 수량ㆍ할증 연결 테이블 |
| COSTA (53필드) | 빈 것 | COSTN의 대안 단가 (RATE_L/M/Sㆍ차수별 단가ㆍ_R 반영단가) |
| QCODE (15필드) | 빈 것 | 수량코드 (QTY0~9 슬롯) |
| KONGJONG | 1~7건 | 공종 구분 (영월ㆍ계류보전은 7건 — 공구/공종 분할 시 사용) |
| GRASS | 빈 것 | BDCODE 1필드 — 용도 미상 ⚠ |
| SET_REP (75~92건) | 설정 | **출력 보고서 양식 정의** — INDX(0101 재료비목록표…)ㆍ컬럼명 FLD_N1~11ㆍ폭 FLD_W1~11 |
| SYSINFO | 빈 것 | PART/NUM/LOG/CHR/DATE — 이력 |
| BOQ (83/94필드) | **레코드 0 (진짜 빈 테이블)** | BOQ11과 동일 구조 — 작업본/발행본 이중화로 추정 ⚠. 다공구 STC엔 BOQ12도 존재(소광 신설) |
| BDQTY (37/38필드) | **레코드 존재·난독화** (기번3 667ㆍ봉화 845ㆍ영월 883 등, 전 레코드 플래그 `E`+고엔트로피 본문) | BDCODEㆍQTY1~4ㆍ할증(X_HAL1~3)ㆍ비목(BIMOK) — 수량ㆍ할증 연결 테이블. 해독 규칙 미확정 → [STC_난독화_상태](../원가계산/STmate/STC_난독화_상태.md) |
| COSTA (53필드) | 레코드 0 (진짜 빈 테이블) | COSTN의 대안 단가 (RATE_L/M/Sㆍ차수별 단가ㆍ_R 반영단가) |
| QCODE (15필드) | 레코드 0 (진짜 빈 테이블) | 수량코드 (QTY0~9 슬롯) |
| KONGJONG | 1~7건 (정상 판독) | 공종 구분 (영월ㆍ계류보전은 7건 — 공구/공종 분할 시 사용) |
| GRASS | 레코드 0 (진짜 빈 테이블) | BDCODE 1필드 — 용도 미상 ⚠ |
| SET_REP (75~92건) | 설정 (정상 판독) | **출력 보고서 양식 정의** — INDX(0101 재료비목록표…)ㆍ컬럼명 FLD_N1~11ㆍ폭 FLD_W1~11 |
| SYSINFO | **레코드 8ㆍ난독화** (전 파일 8건, 플래그 `E`+고엔트로피, 파일 간 키스트림 100% 동일) | PART/NUM/LOG/CHR/DATE — 이력. 해독 규칙 미확정 → [STC_난독화_상태](../원가계산/STmate/STC_난독화_상태.md) |
## 4. 4개 STC 교차 대조
> [해석] "표준 파서가 못 읽음" ≠ "빈 테이블". 4종(BOQ·COSTA·QCODE·GRASS)은 헤더 레코드수 0 = **진짜 빈 테이블**이고, BDQTY·SYSINFO는 **레코드 실재 + 난독화**다. 이 둘의 구분이 STC export 설계에 중요 — 진짜 빈 테이블은 빈 상태로 재현 가능하나, 난독화 2종은 STmate 내부 사용 여부가 미확정이라 빈 export 허용 여부를 실기 검증해야 한다.
| 항목 | 울진대흥 | 봉화 | 영월 | 계류보전 |
|---|---|---|---|---|
| BOQ11 | 261건/83필드 | 141건/83필드 | 225건/83필드 | **148건/94필드** |
| COSTN | 186건/101필드 | 248건/101필드 | 224건/**99필드** | 115건/101필드 |
| KONGJONG | 1 | 1 | 7 | 7 |
| RATE 계열 | 동일 (내장 기준표) | 동일 | 동일 | 동일 |
## 4. STC 교차 대조 (2026-08-15 — 보유 7개 전수로 확장)
- 스키마가 파일마다 다름 (STmate 버전 차이) → **변환기는 필드명 기반 동적 매핑** 필요, 위치 기반 금지.
> 종전 4개 → 보유 7개 전수 자동 비교로 갱신. 자동화 스크립트 = [STmate/_scripts/stc_cross_compare.py](../원가계산/STmate/_scripts/stc_cross_compare.py), 결과 정리 = [STC_교차비교](../원가계산/STmate/STC_교차비교.md).
| 항목 | 울진대흥(기번3) | 봉화 | 영월 | 계류보전(변경) | 소광 당초 | 소광 신설(다공구) | 소광 변경1차 |
|---|---|---|---|---|---|---|---|
| BOQ11 | 261/83 | 141/83 | 225/83 | 148/94 | 90/83 | 90/94 | 115/94 |
| BOQ12 | — | — | — | — | — | **89/94** | — |
| COSTN | 186/101 | 248/101 | 224/**99** | 115/101 | 168/101 | 209/101 | 211/101 |
| BDQTY (난독화) | 667/37 | 845/37 | 883/37 | 395/38 | 540/38 | 813/38 | 747/38 |
| SYSINFO (난독화) | 8/5 | 8/5 | 8/5 | 8/5 | 8/5 | 8/5 | 8/5 |
| KONGJONG | 1 | 1 | 7 | 7 | 1 | 1 | 1 |
| PRJLST 필드 | 48 | 48 | 47 | 55 | 48 | 55(2건) | 55 |
| RATE 계열 | 동일 (내장 기준표 — 7파일 전부 스키마·건수 동일) |
- **난독화 전수 재현**: BDQTY·SYSINFO는 **7개 파일 전부** 플래그 `E`+고엔트로피(본문 비ASCII 0.63~0.71). SYSINFO 키스트림은 파일 간 100% 동일 — 개별 파일 우연 아닌 STmate 포맷 고유 특성 확정.
- **스키마 가변**: BOQ11 83↔94(임도↔사방 확장)ㆍCOSTN 99/101ㆍPRJLST 47/48/55ㆍBDQTY 37/38. 같은 83필드라도 레코드길이 1214↔1222 차이(필드길이 배분 상이) → **위치 기반 파싱 절대 금지, 필드명 기반 동적 매핑 필수**.
- **신규 발견**: ① `BOQ12.DBF` — 소광 신설(다공구) STC에만 존재, 2공구 분할 시 BOQ11/BOQ12 병렬. ② 소광은 STC 3종(당초 신설·다공구 신설·변경 1차) 보유 = **당초↔변경 차분분석 소스**. ③ PRJLST 다공구본은 레코드 2건(공구별).
- DBF 버전 바이트: 0x03(대부분)ㆍ0x06(BDQTY·SYSINFO·COSTA)ㆍ0xE5(COSTN·PRJLST·QCODE — Visual FoxPro 계열). 난독화 2종은 0x06+`E`플래그 조합.
## 5. Aislo → STC 변환 관점 (설계 메모)
@@ -124,7 +137,9 @@ status: draft
1. 최소 유효 세트: PRJLST(1건) + BOQ11(내역 트리) + COSTN(사용 단가) + COSTN.SMT(일위대가 전개) + RATE 계열(내장 기준표 복사) + SET_REP(양식) + Ini 2종.
2. BOQ11 생성 규칙: SORTCODE 256 간격 부여, 제목 T5→T4→T3→T2 + E2 소계 + ENDCODE 블록 포인터. 금액 = 단가(TAMT)×수량(QTY) 정합 필수 (프로그램 검증 가능성).
3. SIxMemo 포맷은 추가 리버싱 필요 (레코드 경계ㆍ인덱스 규칙) — 현재는 헤더ㆍ텍스트 구조만 확인.
4. 미해명 항목(⚠): BOQ vs BOQ11 이중화 규칙, GRASS 용도, RATE 행 라벨, SMT 인덱스 바이너리(10바이트) 구조, ENDCODE 정확한 산식.
4. 미해명 항목(⚠): BOQ vs BOQ11 이중화 규칙, BOQ12 분할 규칙(다공구), GRASS 용도, RATE 행 라벨, SMT 인덱스 바이너리(10바이트) 구조, ENDCODE 정확한 산식, **BDQTY·SYSINFO 난독화 해독**, PRJLST 차수 슬롯(~1..4) 저장 규칙. → 전량 [STmate 포맷 명세](../원가계산/STmate/) 폴더에서 개별 추적.
> **2026-08-15 방침 반영**: STC는 발주처 제출 최종 산출물 → Aislo import/export가 필수 요구사항(사용자 확정). 이 문서(§1~4)는 실무 관측 요약이고, **호환 포맷 명세·검증 근거는 [original/원가계산/STmate/](../원가계산/STmate/)로 이관·확장**한다. 이 문서는 실무 관측 진입점으로 유지.
## 검증 결과
@@ -133,3 +148,4 @@ status: draft
| V2 | PRJLST 원가 3값 = xlsx 공사원가계산서 D5ㆍD9(직노)ㆍD12와 대조 — 재료비ㆍ경비 일치, LCOST1=직접노무비 일치 (스크립트 재조회) |
| V4 | BOQ11 트리: 1.1 준비공 소계(E2) 3,376,748 = 하위 3항목 합산 (361,494+1,119,272+1,895,982) — **일치** |
| V5 | 미확정 사항은 ⚠ㆍ[해석] 마커 표기 (TAMT2 의미, BOQ 이중화, RATE 라벨, GRASS) |
| V6 (2026-08-15) | 7개 STC 전수 엔트리·스키마·레코드 상태 자동 비교 — BDQTY·SYSINFO 난독화 전수 재현, SYSINFO 키스트림 파일 간 100% 동일 확인 ([STC_교차비교](../원가계산/STmate/STC_교차비교.md)) |
@@ -53,15 +53,31 @@ status: draft
| 유형 | 탭 | 판정 |
|---|---|---|
| STmate 메타 | `〓 INITIAL 〓` (veryHidden) — 봉화ㆍ영월ㆍ울진공통ㆍ영덕ㆍ소광 내역 | 프로그램 내부, 무가치 |
| ~~STmate 메타~~ | `〓 INITIAL 〓` (veryHidden) — 봉화ㆍ영월ㆍ울진공통ㆍ영덕ㆍ소광 내역 | **재분류 (2026-08-15)** — 아래 별도 항 참조. "무가치" 철회 |
| 오솔길 계산탭 | 사거리입력/출력/변환ㆍ노선측량 보정(veryHidden) | 오솔길 내부 중간계산, 무가치 |
| estx 내역 중간 | 거창 BUDGE/daega/junggi/noyim/unitpric/구역화물(hidden) | 견적 중간과정 — xlsx 최종본에 반영됨, 별도 가치 없음 |
| 매크로 웜ㆍ복구 | `XL4Poppy`ㆍ`Recovered_Sheet*`ㆍ`xxxxxxxx`ㆍ`VXXXXXXX`(veryHidden) — 영덕 | XL4Poppy 감염 잔재, 무가치 (열람 시 매크로 차단) |
| 문서 구성탭 | 울진2공구 겉표지ㆍ목차ㆍ설계설명서 등 17탭(hidden), 소광 개요 목차ㆍ간지 | 설계서 편집 흔적 — 설계설명서 등 내용은 이미 분석 |
| 오탐(vis=1) | 울진1 거리이정표, 공통 공정표, 영덕 사방댐 시트 등 | 실제 visible — 필터 오탐, 이미 분석 대상 |
## E. INITIAL 시트 재분류 (2026-08-15 — STC 입출력 요구사항 반영)
> D의 "STmate 메타·무가치" 판정을 철회한다. STC가 발주처 제출 최종물이고 Aislo import/export가 필수 요구사항(사용자 확정)이 되면서, xlsx↔STmate 왕복(재등록) 메타데이터의 성격이 바뀌었다.
`〓 INITIAL 〓` (veryHidden) = xlsx에 STmate가 심어둔 **재등록 교환 메타** — STmate 코드ㆍ행 위치ㆍ수량ㆍ분류 등. 가치를 목적별로 구분:
| 용도 | 가치 |
|---|---|
| xlsx → STmate 재등록(왕복 변환) | **핵심 참조** — export 후 STmate가 되읽는 경로 |
| STC ↔ xlsx 항목 매핑 (행↔코드) | **유력한 보조 근거** |
| STC 바이너리 생성 규칙 | 간접 근거 (xlsx 메타 ≠ STC 내부 구조 직접 명세) |
| 계산 공식의 법적·기술 근거 | 아님 (여전히 무관) |
> [해석] "계산 로직 근거로는 낮은 가치, STmate 교환·매핑 분석에는 유가치". 단 INITIAL은 **xlsx의 재등록 메타**이지 **STC DBF 구조의 직접 명세가 아니다** — STC 생성 규칙은 STC 파일 자체 분석([STmate 포맷 명세](../원가계산/STmate/))이 1차 근거. 구조 분석 착수 시 [XLSX_INITIAL_구조](../원가계산/STmate/XLSX_INITIAL_구조.md)에 정리.
## 결론 (사용자 질문 답)
- **활용 가능**: 소광 07-구조도 숨김 31탭 = **임도 구조물 원단위 전집** (해당 공사지 미사용분 포함). 종합비교 04ㆍ미결 No.9 반영 후보. ★최대 성과.
- **주의**: 오솔길 배수관규격표는 3공사지 동일값 → 템플릿 예시 의심, 장수 서술 재검토.
- **무시**: INITIALㆍXL4PoppyㆍRecoveredㆍ문서편집 흔적.
- **재분류**: INITIAL = STC/xlsx 입출력 매핑 참조 (구 "무가치" 철회 — §E).
- **무시**: XL4PoppyㆍRecoveredㆍ문서편집 흔적.
@@ -0,0 +1,66 @@
---
title: SIxMemo 구조 — COSTN.SMT·PRJLST.SMT·QCODE.SMT
type: 포맷명세
분석일: 2026-08-15
status: draft
---
관련: [_meta](_meta.md) · [STC_필드사전_핵심](STC_필드사전_핵심.md)
> SMT = STmate 커스텀 메모 파일. DBF의 M(memo) 필드 본문 저장. **표준 FPT 아님** — 표준 dbfread 파서 실패 → raw 처리 필요.
## 1. 헤더
```
[0x00] uint32 첫 레코드 길이(?) 예 COSTN.SMT=650(0x028A)
[0x04] uint32 블록 크기 = 0x40(64)
[0x08] "SIxMemo\0" 시그니처
[0x10] 0x00 패딩 → 첫 메모 블록까지
```
- 파일: COSTN.SMT(단가 산출메모, ~41KB) · PRJLST.SMT(프로젝트 메모) · QCODE.SMT(수량 메모, 대부분 미사용).
- DBF의 M필드(COSTN.IFORM·PRJLST.PRJ_MEMO)가 이 파일의 메모 인덱스를 가리킴. ⚠ **인덱스 = 10바이트 바이너리** — 표준 FPT(4바이트 블록번호)와 다름, 규칙 미확정.
## 2. 본문 = 일위대가·단가 산출 전개 (실측)
COSTN.SMT 메모 = 호표별 산출 텍스트. 예 (크로울러드릴 발파 일위대가):
```
'1. 재료비'
'폭약' 0.31'Kg' * &M00034M& =
'뇌 관(3.5m)' 0.04'개' * &M00018M& =
'잡재료비(바라선,전색재료등)(재료비의 5%)' &M00580M& * 5'%' =
'2. 노무비'
'화약취급공 :' &L00010L& * 0.004'인' =
'보통인부 :' &L00017L& * 0.006'인' =
'3.크로울러드릴(탑승유압식)(110kw)'
'Q = 0.014 Hr/㎥'
'노 무 비 :'&X00055L& * 0.014 :=A01
'재 료 비 :'&X00055M& * 0.014 :=A02
'경 비 :'&X00055S& * 0.014 :=A03
```
### 텍스트 문법 (관측)
| 요소 | 의미 |
|---|---|
| `&M00034M&` | 자원 코드 마커 — 접미 M(재료)·L(노무)·S(경비). 단가 삽입 위치 |
| `0.31'Kg'` | **품(원단위)** + 단위 |
| `5'%'` | **할증률** (잡재료비 = 재료비의 5%) |
| `:=A01` | 중간결과 라벨 (A01·A02… 참조) |
| `(=)` `(==)` | 소계·합계 구분선 |
| `'Q = 0.014 Hr/㎥'` | 기계 시간당 작업량 (품셈 Q식) |
> [해석] **품(원단위)과 할증(%)이 이 텍스트에 기록됨** — Aislo의 pum[공종][자원]·surcharge_mat 소스 (미결 No.10 연계). 단, 이는 STC 저장 시점의 값 = 과거 데이터. 현행 품은 산림·건설품셈. 회수 대상 = 전개 **구조**(자원마커·품·할증·중간라벨 문법).
## 3. import/export 관점
- **import**: 메모 텍스트는 UTF/cp949 판독 가능 → 일위대가 전개 파싱 가능. 인덱스(10바이트) 규칙만 확정하면 DBF행↔메모 연결 완성.
- **export**: 신규 STC 생성 시 이 텍스트를 STmate 문법으로 재생성 + 인덱스 구성 필요 → **리버싱 잔여 최대 항목**. 레코드 경계·인덱스 바이너리 규칙 미확정.
- 우선순위: import(원문 보존) → 재출력(원본 메모 유지) → 신규생성(문법 재현). 신규생성이 가장 어려움.
## 4. 미해명 (⚠)
- 10바이트 메모 인덱스 구조 (블록번호·길이·플래그 배분)
- 레코드 경계 규칙 (0x40 정렬?)
- QCODE.SMT 용도 (대부분 빈 것)
@@ -0,0 +1,64 @@
---
title: STC 교차비교 — 보유 7개 전수 자동 분석
type: 포맷명세
분석일: 2026-08-15
status: draft
소스: _scripts/stc_cross_compare.py
---
관련: [_meta](_meta.md) · [STC_컨테이너_구조](STC_컨테이너_구조.md) · [STC_난독화_상태](STC_난독화_상태.md)
> 재현: `python _scripts/stc_cross_compare.py` (리포지토리 임의 위치). 결과를 아래로 정리. 원본 개정·추가 시 재실행.
## 1. 레코드·필드 매트릭스
| 테이블 | 기번3 | 봉화 | 영월 | 계류보전변경 | 소광당초 | 소광다공구 | 소광변경1차 |
|---|---|---|---|---|---|---|---|
| PRJLST | 1건/48 | 1/48 | 1/47 | 1/55 | 1/48 | **2**/55 | 1/55 |
| BOQ11 | 261/83 | 141/83 | 225/83 | 148/94 | 90/83 | 90/94 | 115/94 |
| BOQ12 | — | — | — | — | — | **89/94** | — |
| COSTN | 186/101 | 248/101 | 224/**99** | 115/101 | 168/101 | 209/101 | 211/101 |
| KONGJONG | 1/3 | 1/3 | 7/3 | 7/3 | 1/3 | 1/3 | 1/3 |
| BDQTY(난독화) | 667/37 | 845/37 | 883/37 | 395/38 | 540/38 | 813/38 | 747/38 |
| SYSINFO(난독화) | 8/5 | 8/5 | 8/5 | 8/5 | 8/5 | 8/5 | 8/5 |
| BOQ(빈) | 0/83 | 0/83 | 0/83 | 0/94 | 0/83 | 0/94 | 0/94 |
| COSTA(빈) | 0/53 | 0/53 | 0/53 | 0/53 | 0/53 | 0/53 | 0/53 |
| QCODE(빈) | 0/15 | 0/15 | 0/15 | 0/15 | 0/15 | 0/15 | 0/15 |
| GRASS(빈) | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 |
| RATE | 41/10 | 동일 전파일 | | | | | |
| RATE_M | 25/10 | 동일 | | | | | |
| RATE_R | 50/35 | 동일 | | | | | |
| RATE_RA | 50/20 | 동일 | | | | | |
| SET_REP | 75~92/28 | 파일별 건수차, 스키마 동일 | | | | | |
## 2. 스키마 변형 요약
| 테이블 | 변형 | 그룹 |
|---|---|---|
| BOQ11 | 83필드 (임도: 기번3·봉화·영월·소광당초) / 94필드 (사방·다공구: 계류보전·소광다공구·소광변경1차) | rlen도 1214↔1222 등 상이 |
| COSTN | 101필드 (6파일) / 99필드 (영월 1파일) | |
| PRJLST | 48(기번3·봉화·소광당초) / 47(영월) / 55(사방·다공구 3파일) | |
| BDQTY | 37(기번3·봉화·영월) / 38(2025년 4파일) | 연도·버전차 |
| KONGJONG | 건수 1(단일공종)~7(다공종), 스키마 동일 | |
| RATE계열·SYSINFO·GRASS·QCODE·COSTA | **전 파일 스키마 동일** | 내장 기준표·시스템 테이블 |
> [구현] 83↔94 = 임도↔사방 확장(사방 전용 필드). 같은 83필드에서도 rlen이 다름 → **위치 파싱 금지, 필드명 매핑**. import는 필드명 화이트리스트로 알려진 필드 해석 + 미지원 필드 원문 보존.
## 3. 신규 발견
1. **BOQ12.DBF** — 소광 신설(다공구) STC에만 존재(21엔트리). 2공구를 BOQ11/BOQ12로 병렬 저장. 다공구 사업은 BOQ1n 확장 → export 시 공구 수만큼 BOQ 테이블 생성 규칙 필요.
2. **소광 STC 3종** = 당초 신설 / 다공구 신설 / 변경 1차. **당초↔변경 차분분석 소스** (한 항목만 바뀐 전후 비교로 필드 의미 확정 — tmp 보고서가 요구한 비교 세트).
3. **PRJLST 다공구본 = 2 레코드** (공구별 1행). 단일공구는 1건.
## 4. 난독화 재현
- BDQTY·SYSINFO는 **7파일 전부** 플래그 0x45(`E`) + 고엔트로피 → 난독화가 포맷 고유 특성임을 전수 확정.
- SYSINFO 키스트림 파일 간 100% 동일. 상세 = [STC_난독화_상태 §2](STC_난독화_상태.md).
## 5. 후속 (미해명 — 이 폴더에서 추적)
- BOQ vs BOQ11 이중화 규칙 (BOQ 항상 빈 이유), BOQ12 분할 트리거
- ENDCODE 산식 (제목 블록 종료 포인터), SORTCODE 부여 규칙
- PRJLST 차수 슬롯 ~1..4 저장 규칙 → [STC_필드사전_보조](STC_필드사전_보조.md)
- SIxMemo 인덱스 → [SIxMemo_구조](SIxMemo_구조.md)
- BDQTY·SYSINFO 복호 → [STC_난독화_상태](STC_난독화_상태.md)
@@ -0,0 +1,68 @@
---
title: STC 난독화 상태 — BDQTY·SYSINFO
type: 포맷명세
분석일: 2026-08-15
status: draft
목적: "빈 테이블 오판" 정정 + 난독화 증거 수준을 단계별로 구분 (과장·과소 방지)
---
관련: [_meta](_meta.md) · [STC_컨테이너_구조](STC_컨테이너_구조.md) · [STC_필드사전_보조](STC_필드사전_보조.md) · [STC_교차비교](STC_교차비교.md)
> **핵심 원칙**: "표준 DBF 파서가 못 읽음" ≠ "빈 테이블" ≠ "불필요한 테이블" ≠ "암호화 확정". 각 명제의 증거 수준을 분리 기재한다. 외부 파서가 못 읽는다는 사실은 STmate 내부에서 안 쓴다는 뜻이 아니다.
## 1. 관측 사실 (7개 STC 전수)
| 테이블 | 레코드 수(파일별) | 삭제플래그 | 본문 비ASCII 비율 | 정상 판독 여부 |
|---|---|---|---|---|
| BDQTY | 395~883 (기번3=667) | **전 레코드 `E`(0x45)** | 0.63~0.66 | ✕ (표준 파서 누락) |
| SYSINFO | 전 파일 **8건 고정** | 전 레코드 `E` | 0.70~0.71 | ✕ |
- 정상 DBF 레코드 플래그 = 0x20(활성) / 0x2A(삭제). BDQTY·SYSINFO는 **0x45(`E`)** — 표준 파서가 이를 미인식하여 "레코드 없음"으로 처리 → 종전 "빈 것" 오판의 원인.
- 헤더의 레코드 수 필드는 정상. 즉 **레코드는 실재**한다.
- 대조: 같은 STC의 COSTN·PRJLST 등은 플래그 0x20 + 비ASCII 0.005~0.08 (정상 텍스트). 난독화는 **BDQTY·SYSINFO 2종에 한정**.
## 2. XOR 부분 검증 (가설·재현)
> 방법: 레코드들의 같은 열(오프셋)에서 **최빈 바이트**를 구하면, 평문에 공백(0x20)이 많은 열은 최빈 바이트 = "공백의 암호문"으로 추정된다. 이 추정 키스트림으로 역변환 시 공백 영역이 일괄 복원되는지 확인.
| 검증 | 결과 |
|---|---|
| 기번3 BDQTY 공백영역 복원 | 667 레코드 전체에서 위치 고정 공백 복원 재현 |
| **SYSINFO 키스트림 파일 간 동일성** | 7개 파일 전부 **100% 동일** (100/100 바이트) |
| BDQTY 키스트림 파일 간 동일성 | 83~100% (공백 많은 열 100%, 값 열은 레코드 내용차로 최빈값 흔들림) |
- SYSINFO 키스트림이 **7파일 100% 동일** → 개별 파일 우연이 아닌 **STmate 포맷 고유의 고정 변환**. 강한 증거.
- 단, "최빈=공백 암호문" 가정 하의 유도이므로 **순수 XOR인지·키 유도 방법은 미확정**. 전 필드가 올바른 평문(BDCODE·QTY 등 알려진 필드값)으로 복원되고 다수 파일에서 재현돼야 알고리즘 확정.
## 3. 증거 수준 상태표 (과장·과소 방지)
| 명제 | 상태 |
|---|---|
| 레코드 존재 (헤더 기준) | ✅ 확인 |
| 표준 DBF 파서에서 누락 | ✅ 확인 (플래그 0x45) |
| 비표준 변환 또는 난독화 적용 | ✅ 강한 증거 (고엔트로피 + 플래그) |
| 고정 키스트림 방식 | ✅ 부분 검증 (SYSINFO 7파일 100% 재현) |
| 순수 XOR·키 유도법 | ⚠ 미확정 |
| STmate 내부 사용 여부 | ⚠ 미확정 |
| 빈 테이블로 export 가능 여부 | ⚠ 미확정 — **실기 검증 필수** |
| 유효 생성(복호·재난독화) 규칙 | ⚠ 미확정 |
## 4. export 관점 — 빈 테이블 대체 가능성은 실험으로만
> [해석] "제출본조차 이 두 테이블이 난독화 상태로 유통된다"는 관측은 있으나, 이는 **STmate가 쓰지 않는다는 증거가 아니다**. 오히려 안 쓰는 데이터를 굳이 난독화할 이유가 없다는 점에서 내부 사용 가설을 약하게 지지한다. 가능한 역할(전부 미확정): 전용 인코딩 수량정보, 파일 무결성·버전 확인, 수량산출 연결, 재등록 내부 상태, 열기 시 필수 검사 레코드, 재계산 시 참조 데이터.
**실기 검증 설계** (STmate 환경 확보 후 — [03_미결](../../../03_미결_및_확인사항.md) 선행):
1. 원본 그대로 열기 (기준)
2. BDQTY만 비운 파일 열기
3. SYSINFO만 비운 파일 열기
4. 둘 다 비운 파일 열기
5. 각 변형본을 열고 → 내역 조회 → 수정 → 재계산 → 저장 → 재개방
6. 재저장 STC 구조를 원본과 비교 (STmate가 두 테이블을 재생성하는지)
- 통과 기준: 단순 "열림"이 아니라 **열기·조회·수정·재계산·저장·재개방 전 과정** 정상.
- 결과 → [STC_왕복검증](STC_왕복검증.md) 기록.
## 5. BDQTY 로직 회수 관점 (후순위)
BDQTY 복호의 가치 = 과거 수량값 회수가 아니라 **구조 규명**: BDCODE(일위대가 호표 연결)·QTY1~4(차수별 수량)·X_HAL1~3(할증)·BIMOK(비목)·X_QTY1~3(할증 전 수량)의 전개·연결 방식. 필드 목록 = [STC_필드사전_보조 §BDQTY](STC_필드사전_보조.md). Aislo 자체 수량 파이프라인(F1~F3)이 있으므로 **STC 입출력 구현 착수 전 선행 과제**로 두되, 계산 로직 신규 확보로서의 우선순위는 낮음.
@@ -0,0 +1,43 @@
---
title: STC 왕복검증 — STmate 실기 테스트 결과 (기록처)
type: 포맷명세
분석일: 2026-08-15
status: draft
목적: 생성·변형 STC를 실제 STmate에서 열고 사용할 수 있는지 판정한 결과를 기록
---
관련: [_meta](_meta.md) · [STC_난독화_상태 §4](STC_난독화_상태.md) · [STC_컨테이너_구조](STC_컨테이너_구조.md)
> **현재 미착수** — STmate 실기 환경 미확보([03_미결](../../../03_미결_및_확인사항.md) 선행). 이 문서는 환경 확보 후 채운다. 미확정을 확정처럼 기록하지 않기 위해 **자리만** 둔다.
## 1. 전제 (미결 — 선행 조건)
- STmate 실기 환경 확보 (보유 / 체험판 / 설계사무소 협조)
- STC 포맷 역공학·변형실험·신규생성 라이선스 확인
- 둘 다 [03_미결_및_확인사항](../../../03_미결_및_확인사항.md) 등록 — 사용자 결정 대기
## 2. 검증 매트릭스 (설계 — 실행 전)
> 통과 기준 = 단순 "열림"이 아니라 **열기 → 조회 → 수정 → 재계산 → 저장 → 재개방** 전 과정 정상.
| # | 대상 파일 | 검증 항목 | 결과 |
|---|---|---|---|
| R1 | 원본 그대로 | 열기(기준선) | (대기) |
| R2 | 무변경 read→write 왕복 | 재저장본 = 원본 구조 동일 | (대기) |
| R3 | 알려진 필드 1개 변경 (수량 등) | 변경 반영·재계산 정합 | (대기) |
| R4 | BDQTY만 제거 | 열기·조회·수정·재계산·저장·재개방 | (대기) |
| R5 | SYSINFO만 제거 | 〃 | (대기) |
| R6 | BDQTY+SYSINFO 제거 | 〃 (빈 export 가능 여부 판정) | (대기) |
| R7 | 기존 STC 기반 재출력 (Aislo 수정분 반영) | STmate 정상 사용 | (대기) |
| R8 | Aislo 데이터만으로 신규 STC 생성 | STmate 정상 사용 | (대기) |
| R9 | xlsx 재등록 (INITIAL 경로) | STmate 되읽기 | (대기) |
## 3. import/export 난이도 분리 (설계 지침)
| 트랙 | 접근 | 난이도 |
|---|---|---|
| import | 알려진 필드 해석 + 미지원 필드·전용 바이너리 **원문 보존** | 낮음 (빠른 구현) |
| export ① 기존 STC 수정·재출력 | 원본을 템플릿 삼아 알려진 필드만 갱신, 나머지 보존 | 중 (현실적 중간단계) |
| export ② 신규 STC 생성 | 복사할 원본 없음 → 포맷 생성 규칙 대부분 필요 (BDQTY·SIxMemo·ENDCODE·차수) | 높음 |
> [구현] 초기 목표 = import(무손실) + export①(템플릿 재출력). 발주처가 신규 프로젝트 STC 제출을 요구하면 export②까지. R4~R6 실기 결과가 "빈 테이블 허용" 여부를 결정하고, 그에 따라 export② 난이도가 갈린다.
@@ -0,0 +1,78 @@
---
title: STC 컨테이너 구조 — 엔트리·버전·스키마 판별
type: 포맷명세
분석일: 2026-08-15
status: draft
---
관련: [_meta](_meta.md) · [STC_필드사전_핵심](STC_필드사전_핵심.md) · [STC_필드사전_보조](STC_필드사전_보조.md) · [STC_난독화_상태](STC_난독화_상태.md) · [STC_교차비교](STC_교차비교.md)
## 1. 컨테이너 = ZIP
- 매직 `PK\x03\x04`. 표준 zip. 무압축~deflate 혼재.
- 확장자만 `.STC`. `zipfile`로 그대로 해제.
- 파일명 케이스 혼재: `Start8x.Ini`(대부분) vs `Start8x.ini`(계류보전) — **대소문자 무시 매칭 필요**.
## 2. 엔트리 구성 (표준 20 + 다공구 시 21)
| 종류 | 엔트리 | 판독 | 역할 |
|---|---|---|---|
| DBF | PRJLST | 정상 | 프로젝트 마스터·총액 (핵심) |
| DBF | BOQ11 | 정상 | 설계내역서 본체 트리 (핵심) |
| DBF | BOQ12 | 정상 | **다공구 2공구 내역** — 소광 신설본에만 존재 |
| DBF | COSTN | 정상 | 단가 마스터 (핵심) |
| DBF | KONGJONG | 정상 | 공종 구분 (1~7건) |
| DBF | RATE / RATE_M / RATE_R / RATE_RA | 정상 | 제비율 내장 기준표 (전 파일 스키마·건수 동일) |
| DBF | SET_REP | 정상 | 출력 보고서 양식 정의 |
| DBF | BOQ | **빈(레코드 0)** | BOQ11 미러 구조 — 작업본/발행본 이중화 추정 ⚠ |
| DBF | COSTA | 빈(레코드 0) | COSTN 대안 단가 구조 |
| DBF | QCODE | 빈(레코드 0) | 수량코드 슬롯 |
| DBF | GRASS | 빈(레코드 0) | BDCODE 1필드 — 용도 미상 ⚠ |
| DBF | **BDQTY** | **난독화**(레코드 실재) | 수량·할증 세부 전개 → [난독화 상태](STC_난독화_상태.md) |
| DBF | **SYSINFO** | **난독화**(레코드 8) | 시스템 이력 → 〃 |
| SMT | COSTN.SMT / PRJLST.SMT / QCODE.SMT | 커스텀 | SIxMemo 메모 본문 → [SIxMemo_구조](SIxMemo_구조.md) |
| Ini | Start8x.Ini | 텍스트 | 프로그램 설정·코드 카운터·단가출처 정의 |
| MEM | STini.MEM | 텍스트 | Start8x.Ini와 **내용 동일** (백업/미러) |
> [해석] "빈 테이블 4종"과 "난독화 2종"은 다르다. BOQ·COSTA·QCODE·GRASS는 헤더 레코드수 0 = 진짜 빈 테이블. BDQTY·SYSINFO는 레코드 실재하나 표준 파서가 비표준 플래그로 건너뜀. export 시 전자는 빈 상태 재현 가능, 후자는 실기 검증 필요.
## 3. DBF 버전 바이트 (헤더 첫 바이트)
| 바이트 | 테이블 | 성격 |
|---|---|---|
| 0x03 | BOQ·BOQ11·BOQ12·KONGJONG·RATE계열·SET_REP·GRASS | dBase III |
| 0x06 | BDQTY·SYSINFO·COSTA | 비표준 — **난독화 2종이 이 바이트**(COSTA는 빈) |
| 0xE5 | COSTN·PRJLST·QCODE | Visual FoxPro 계열 (memo 필드 M 보유) |
- 인코딩: cp949 (한글). 필드명은 ASCII.
## 4. 스키마 버전 판별 — 위치 파싱 금지
같은 테이블도 필드 수·레코드 길이가 사업·버전별로 다르다.
| 테이블 | 필드 수 변형 | 비고 |
|---|---|---|
| BOQ11 | 83(임도) / 94(사방·다공구) | rlen 1214↔1222(83필드 내에서도 상이) |
| COSTN | 99 / 101 | 영월만 99 |
| PRJLST | 47 / 48 / 55 | 55=사방·다공구 |
| BDQTY | 37 / 38 | 버전차 |
| KONGJONG | 1~7건 | 공구·공종 분할 수 |
> [구현] **필드명 기반 동적 매핑 필수, 위치(오프셋) 기반 절대 금지.** 파서는 DBF 헤더 필드 디스크립터를 읽어 이름→오프셋 맵을 만들고, 알려진 필드만 해석, **미지원 필드는 원문 바이트 보존**(무손실 import). 상세 비교 = [STC_교차비교](STC_교차비교.md).
## 5. Start8x.Ini 주요 키
```
[START]
JMOK1~6 = 조달가격/물가정보/물가자료/적산정보/견적단가/적용 단가 # 단가출처 6종
JMK10~50 = 조달/물정/물자/적산/견적 # 약칭
BCDNM=1208 DCDNM=2263 LCDNM=34 MCDNM=954 SCDNM=170 XCDNM=105 # 코드 카운터(다음 발번)
JUKNM=6 # 채택 출처 기본(6=적용단가)
CUTNM=0 # 단수처리 옵션 추정 ⚠ — 단수처리_규칙 작성 시 대조 대상
[PRINT]
BMK_STR1~4 = 재료비/노무비/경비/합계 # 비목 표시명
```
- JMOK1~6 = COSTN의 AMT_11~AMT_61 6슬롯과 대응 → [적용단가 채택 규칙](../../../technical_info/01_임도/05_원가정보/자재단가_적용.md).
- 코드 카운터(BCDNM 등) = export 시 신규 코드 발번 기준. B(일위대가)=1208번까지 사용 의미.
- `CUTNM=0` = 단수처리(cut) 관련 옵션일 가능성 — 값 의미 미확정, 여러 파일 CUTNM값 대조 필요.
@@ -0,0 +1,89 @@
---
title: STC 필드사전 (보조) — BDQTY·RATE계열·SET_REP·KONGJONG·차수 슬롯·빈 테이블
type: 포맷명세
분석일: 2026-08-15
status: draft
소스: 기번3 STC 실측 (BDQTY는 헤더 필드만 — 본문 난독화)
---
관련: [_meta](_meta.md) · [STC_필드사전_핵심](STC_필드사전_핵심.md) · [STC_난독화_상태](STC_난독화_상태.md) · [STC_교차비교](STC_교차비교.md)
## 1. BDQTY — 수량·할증 세부 전개 (37필드 / 38, 난독화)
> 헤더 필드는 판독되나 **레코드 본문 난독화** → 값 미판독([난독화 상태](STC_난독화_상태.md)). 필드명 기준 구조만 정리.
| 필드 | 추정 의미 |
|---|---|
| BDCODE(C6) | 일위대가 호표 연결키 (BOQ11.BDCODE·COSTN 대응) |
| CODE(C6) | 자원 코드 (M/L/S/E) |
| NAME1(C50) / SIZE1(C60) / UNIT1(C10) | 명칭·규격·단위 |
| QTY1~QTY4 (N20) | 차수별 수량 |
| BIMOK(C1) | 비목 (재/노/경 배분) |
| DEVD1~4(L1) | 차수별 편집·전개 플래그 ⚠ |
| X_CODE(C6) | 확장·연결 코드 ⚠ |
| EXIST1(N1) | 존재 플래그 |
| RATE(N20) / PRPT(N2) / TPRPT(N2) | 비율·소수자릿수 |
| PRIN(N1) / PROUT(N1) | 반입·반출 플래그 |
| KWNGP(N1) | 관급 플래그 |
| EQCODE(C11) | 중기 코드 |
| X_QTY1~X_QTY3 (N6) | **할증 전 수량** 추정 ⚠ |
| X_HAL1~X_HAL3 (N6) | **할증률** 슬롯 |
| X_RATE / S_RATE (L1) | 할증·비율 적용 여부 |
| SBMOK(C1) | 세부 비목 |
| NAME2 / SIZE2 | 보조 명칭·규격 |
> [해석] QTY(수량)·X_HAL(할증)·BIMOK(비목)·BDCODE(일위대가 연결) 구조 = STmate의 수량 전개·할증 연결 로직 소스. 복호 시 회수 대상은 값이 아니라 이 **연결·전개 규칙**. Aislo F1~F3 파이프라인 보유로 후순위.
## 2. RATE 계열 — 제비율 내장 기준표 (전 파일 스키마·건수 동일)
> STmate 프로그램 내장 기준표. 값 자체는 STC 저장 시점(2024·2025)의 조달청 제비율 — **과거 데이터**. 현행 요율은 [제비율](../../../technical_info/01_임도/05_원가정보/제비율.md).
| 테이블 | 크기 | 내용 (실측 관측) |
|---|---|---|
| RATE (41×10) | 간접노무비(규모×기간 행렬)·기타경비·보험료·안전관리비 계수 행 + 타 업종 행렬 |
| RATE_M (25×10) | 이윤·일반관리비 관련 (행0: 15.0/14.5/15.5/15.0) |
| RATE_R (50×35) | 요율 세부 (고용보험 등급별 등) |
| RATE_RA (50×20) | 요율 확장 |
- ⚠ 행 라벨이 DBF에 없음 → 열·행 의미는 STmate 화면 대조로 완전 확정. export 시 이 내장표를 복사 삽입.
## 3. SET_REP — 출력 보고서 양식 정의 (28필드, 75~92건)
| 필드 | 의미 |
|---|---|
| INDX(C5) | 보고서 코드 (0101 재료비목록표 등) |
| NAME(C24) / HEADTITLE(C40) | 보고서명·표제 |
| FLDCNT(N2) | 컬럼 수 |
| FLD_N1~FLD_N11 (C25) | 컬럼명 |
| FLD_W1~FLD_W11 (N2) | 컬럼 폭 |
| EN_HEAD(L1) / PG_ADD(C15) | 머리행·페이지 옵션 |
- export 시 STmate 출력 양식 재현용. 산출물 표(A1~A16, [원가_출력변수_사전](../../../technical_info/01_임도/05_원가정보/원가_출력변수_사전.md))와 대응.
## 4. KONGJONG — 공종 구분 (3필드, 1~7건)
| 필드 | 의미 |
|---|---|
| CODE(C8) / NAME(C30) / SIZE(C30) | 공종 코드·명칭·규격 |
- 단일공종 1건, 다공종(영월·계류보전) 7건. 공구·공종 분할 트리 상위 그룹.
## 5. 차수 슬롯 (당초·변경설계 저장 규칙)
- PRJLST·BOQ11·COSTN·BDQTY 모두 접미 **1/2/3/4** = 차수 슬롯 (1=당초, 2~4=변경 1~3차).
- EXIST1~4 = 차수별 존재 플래그. 변경설계 STC(계류보전·소광변경1차)에서 2차 슬롯 사용 여부 = **차분분석 대상** ⚠.
- 소광 3종(당초·다공구·변경1차)으로 당초↔변경 저장 방식 실증 가능.
- ACOST1~4(PRJLST) = 관급자재대 차수 슬롯 추정.
> [구현] export가 변경설계를 지원하려면 차수 슬롯 규칙 확정 필수. 실무 변경설계 제출 빈번 → 미지원 시 반쪽 호환. 우선 당초(1차)만 read/write 왕복 검증 후 차수 확장.
## 6. 빈 테이블 4종 (레코드 0 — 진짜 빈)
| 테이블 | 필드 | 추정 역할 |
|---|---|---|
| BOQ | 83/94 | BOQ11 미러 (작업본/발행본 이중화?) ⚠ |
| COSTA | 53 | COSTN 대안 단가 (RATE_L/M/S·차수단가) |
| QCODE | 15 | 수량코드 (QTY0~9 슬롯) |
| GRASS | 1 (BDCODE) | 용도 미상 ⚠ |
- 7파일 전부 레코드 0. export 시 빈 헤더로 재현 가능성 높으나 실기 확인.
@@ -0,0 +1,114 @@
---
title: STC 필드사전 (핵심) — PRJLST·BOQ11·COSTN
type: 포맷명세
분석일: 2026-08-15
status: draft
소스: 기번3 STC 실측 (필드명·타입·길이). 값 예시는 기번3 관측(과거 데이터 — 참조).
---
관련: [_meta](_meta.md) · [STC_필드사전_보조](STC_필드사전_보조.md) · [STC_컨테이너_구조](STC_컨테이너_구조.md)
> 표기: `필드명(타입+길이)`. 타입 C=문자 N=숫자 L=논리 M=메모. import 핵심 3테이블. 미기재 필드는 [보조](STC_필드사전_보조.md) 또는 미해명(⚠). **접미 1/2/3/4 = 차수(당초·1차변경·2차·3차) 슬롯** — 이 파일들은 1차만 사용.
## 1. PRJLST — 프로젝트 마스터 (48필드, 1건 / 다공구 2건)
| 필드 | 의미 (기번3 실측) |
|---|---|
| PNUM(C30) / PNAME(C100) | 표시명 "남부청(3.50km)" / 공사명 "2024년 간선임도사업(기번3)" |
| PCODE(N3) / CCODE(N3) | 발주처·지역 코드 추정 ⚠ |
| PCLASS(N2) / SCLASS(N1) | 공사 분류 ⚠ |
| MCOST1 / LCOST1 / SCOST1 (N14) | 재료비 194,234,417 / 노무비 346,800,554 / 경비 169,324,165 — **xlsx 공사원가계산서 일치** |
| TCOST1(N14) | 재+노+산출경비 710,359,136 |
| COSTW1(N14) | 총공사비 1,201,879,000 |
| ACOST1(N14) | 관급자재대 추정 ⚠ |
| ~2,3,4 접미 | 차수 슬롯 (변경설계) — 기번3은 1차만 |
| CHAMT(L1) / CHGNM / CHNNM | 변경 관련 플래그 ⚠ |
| FINL_LV(N2) | 내역 트리 최종 레벨 (=4) |
| OVR_6LV(L1) | 6레벨 초과 여부 |
| PRJ_MEMO(M10) | PRJLST.SMT 참조 (SIxMemo) |
| K_ORD / KXXNM / QXXNM / FXXNM | 코드·수량·양식 표시 옵션 ⚠ |
| ACTTYP(N1) / ACTOPT(N1) | 사업유형·옵션 ⚠ |
> [해석] 사방·다공구본은 55필드(7필드 확장). 확장 필드 의미는 사방 STC(계류보전)·다공구본(소광) 대조로 확정 필요 ⚠.
## 2. BOQ11 — 설계내역서 본체 (83필드 / 사방·다공구 94, 261건) ★핵심
내역서 **트리 + 항목 행**을 한 테이블에 직렬화.
### 2-1. 식별·수량·금액 (차수 1)
| 필드 | 의미 |
|---|---|
| ITNUM(C18) | 항번 "1.1" |
| NAME1(C50) / SIZE1(C60) / UNIT1(C10) | 명칭·규격·단위 |
| QTY1(N18) | 수량 |
| TAMT1 / LAMT1 / MAMT1 / SAMT1 (N15) | **단가** (합계·노무·재료·경비 분해) |
| T_AMT1 / L_AMT1 / M_AMT1 / S_AMT1 (N14) | **금액** (=단가×수량, 분해 동일) |
| EXIST1(N1) | 차수1 존재 플래그 |
| BDCODE1(C6) | **행 유형 / 일위대가 참조 키** (§2-3) |
| ~2,3,4 접미 (QTY2·TAMT2·… BDCODE2 등) | 차수 2·3·4 슬롯 |
### 2-2. 트리·정렬·기타
| 필드 | 의미 |
|---|---|
| SORTCODE(N13) | 정렬키 (256 간격 — 중간삽입 여유) |
| ENDCODE(N13) | 제목 블록 종료 위치 포인터 ⚠ 산식 미확정 |
| BQCODE(C16) | 단가 적용월 등 ⚠ |
| RL / RM / RS (N7) | 노무·재료·경비 비율 추정 ⚠ |
| RATE(N12) / PRPT(N2) / PRIN(N1) / PROUT(N1) | 비율·소수자릿수·반입출 플래그 ⚠ |
| KWNGP(N1) | **관급자재 플래그** |
| EQCODE(C11) | 중기·장비 코드 |
| UC(L1) | 단가 확정 여부 ⚠ |
| CODEA(C15) / AAMT1~4 / A_AMT1~4 | 관급 관련 금액 슬롯 추정 ⚠ |
| NAME2 / SIZE2 / UNIT2 | 보조 명칭·규격·단위 |
| QTY1_B / QTY1_N / QTY1_O / QTY1_H (N18) | 수량 세분(기본·순·구·할증?) ⚠ |
| ITM_TYP(N3) / RCU / FRM_NH / BL_AMT | 항목유형·플래그 ⚠ |
### 2-3. BDCODE 행 유형 코드 (실측)
| 값 | 의미 |
|---|---|
| `T5` | 총괄 제목 |
| `T4` | 공구 제목 |
| `T3` | 공사 제목 |
| `T2` | 공종 제목 |
| `B#####` | 일위대가 참조 항목 (COSTN·일위대가 연결키) |
| `E2` | 소계(계) 행 |
- 실측 트리: `총괄(T5) → 1공구 L=1.20km(T4) → 1.토공사(T3) → 1.1 준비공(T2) → 규준틀설치(B00194)… → 계(E2)`.
- 검증(V4): 1.1 준비공 소계(E2) 3,376,748 = 하위 3항목 합산 일치.
## 3. COSTN — 단가 마스터 (101필드 / 영월 99, 186건) ★핵심
### 3-1. 식별·단가
| 필드 | 의미 |
|---|---|
| CODE(C6) | M/L/S/E 코드 (재료·노무·경비·중기) |
| NAME(C50) / SIZE(C60) / UNIT(C10) | 품명·규격·단위 |
| BQCODE(C16) | 단가 적용월 "2024.01" |
| EQCODE(C11) | 중기 코드 |
| TAMT1 / LAMT1 / MAMT1 / SAMT1 (N15) | 적용 단가 (합·노·재·경) |
| TAMT2~4 등 | 차수·보조 단가 (TAMT2 의미 ⚠) |
| NAGCD(N8) | 노임·품 코드 추정 ⚠ |
| IFORM(M10) | 산출 메모 (COSTN.SMT SIxMemo) |
### 3-2. 출처별 단가 슬롯 (적용단가 채택 구조) ★
6출처 × 4차수 = AMT_slots. 슬롯 번호 `AMT_<출처><차수>`:
| 필드군 | 의미 |
|---|---|
| AMT_11~AMT_61 (N14) | 차수1의 **6출처 단가** (1=조달 2=물정 3=물자 4=적산 5=견적 6=적용) |
| PG_11~PG_51 (C12) | 각 출처의 **물가지 페이지·식별정보** (출처6=적용은 페이지 없음) |
| AMT_12~62, PG_12~52 | 차수2 슬롯 / AMT_13~, AMT_14~ | 차수3·4 |
| PG_J(C12) | 채택 출처 관련 ⚠ |
| **PG_1 / PG_2 / PG_3 / PG_4 (N1)** | 차수별 **채택 출처 인덱스** (예: 5=견적단가 채택) |
| **AMT_1 / AMT_2 / AMT_3 / AMT_4 (N1)** | 차수별 채택 관련 플래그 ⚠ |
| K_S(L1) / GUN(C1) / GUN2(C1) | 관급·구분 플래그 ⚠ |
| JDCODE(C17) / WQCODE(C15) / CONTCODE(C6) | 조달·수량·연결 코드 ⚠ |
| ADJ_M / ADJ_L / ADJ_S (N12) / S_ADJ(L1) | 단가 보정값·보정 여부 ⚠ |
| EDT_DATE(C16) | 편집일 |
> [구현] **적용단가 채택 로직**: 6슬롯(AMT_11~61)에 출처별 단가, PG_1이 채택 출처 인덱스. 그러나 **저장 구조 해석일 뿐 선택 알고리즘(자동 최저가? 우선순위? 수동선택?)은 미확정** — 규칙 후보 도출은 COSTN 통계 + 자재단가대비표 대조 + 전후 비교로 검증. 상세 = [자재단가_적용](../../../technical_info/01_임도/05_원가정보/자재단가_적용.md) 채택 규칙 절. 출처 정의 = Start8x.Ini JMOK1~6.
@@ -0,0 +1,36 @@
---
title: XLSX INITIAL 시트 구조 — STmate 재등록 교환 메타
type: 포맷명세
분석일: 2026-08-15
status: draft
---
관련: [_meta](_meta.md) · [숨김탭분석 §E](../../실무문서/_숨김탭분석.md)
> `〓 INITIAL 〓` = STmate가 xlsx 산출 시 심는 **veryHidden 재등록 메타 시트**. 문서 속성 "STmate w24.04로 작성". VBA로만 해제되는 상태(veryHidden). xlsx를 STmate로 되읽어 재등록할 때 사용.
## 1. 가치 (목적별 — 2026-08-15 재분류)
> `_숨김탭분석.md`의 "무가치·무시" 판정 철회. STC import/export 필수 요구사항 반영.
| 용도 | 가치 |
|---|---|
| xlsx → STmate 재등록 (왕복 변환) | **핵심 참조** |
| STC ↔ xlsx 항목 매핑 (내역 행 ↔ STmate 코드) | **유력한 보조 근거** |
| STC 바이너리 생성 규칙 | 간접 근거 (xlsx 메타 ≠ STC DBF 직접 명세) |
| 계산 공식의 법적·기술 근거 | 아님 |
> [해석] INITIAL은 **xlsx의 재등록 메타**이지 **STC 내부 구조 명세가 아니다**. STC 생성 규칙의 1차 근거는 STC 파일 자체([STC_필드사전_핵심](STC_필드사전_핵심.md) 등). INITIAL은 "xlsx 셀 ↔ STmate 코드" 매핑을 보여주므로, Aislo가 xlsx로 내보낸 뒤 STmate 재등록을 노릴 때의 경로 참조로 유효.
## 2. 관측 (기번3 등 — 요약)
- 숨겨진 INITIAL 시트에 STmate 코드·행 위치·수량·분류·재등록용 메타데이터 포함(숨김탭분석 관측).
- xlsx는 단순 보고서가 아니라 **STmate 재등록 고려 교환 포맷** 성격.
## 3. 후속 (미착수 ⚠)
- INITIAL 셀 레이아웃 전수 추출 (행↔코드 매핑 규칙, 어떤 메타가 재등록에 필수인지)
- STC BOQ11.ITNUM/BDCODE ↔ INITIAL 매핑 ↔ xlsx 셀 3자 대응 검증
- STmate 재등록 실기 테스트 (xlsx 수정 후 되읽기) → [STC_왕복검증](STC_왕복검증.md)
> 착수 시점: STC import 명세 안정화 후. 현재는 재분류·용도 정의까지만.
@@ -0,0 +1,50 @@
---
title: STmate STC 포맷 명세 — 문서 지도·대상 파일
type: 포맷명세
분석일: 2026-08-15
status: draft
목적: STC(STmate 프로젝트 파일) import/export 구현을 위한 컨테이너·DBF 명세·검증 근거 축적
---
> **왜 이 폴더인가**: STC는 설계자가 발주처에 제출하는 **최종 산출물**이며, Aislo도 STC **불러오기(import)·내보내기(export)가 필수 요구사항**이다(사용자 확정 2026-08-15). 따라서 STC 포맷 해석은 연구성 작업이 아니라 **납품 호환성 확보를 위한 구현 선행 명세**다.
> 계산 로직(밑수·절사·채택 규칙)은 `technical_info/01_임도/05_원가정보/`가 담당하고, 이 폴더는 **포맷 구조·필드 사전·검증 근거**만 담는다. 두 트랙은 분리한다.
> 실무 관측 진입점 = [original/실무문서/_STC포맷분석.md](../../실무문서/_STC포맷분석.md) (이 폴더로 명세 이관, 그 문서는 요약·링크로 유지).
## 문서 지도
| 문서 | 내용 |
|---|---|
| [STC_컨테이너_구조](STC_컨테이너_구조.md) | zip 엔트리 구성, 필수/가변 엔트리, DBF 버전 바이트, 스키마 버전 판별 |
| [STC_필드사전_핵심](STC_필드사전_핵심.md) | PRJLSTㆍBOQ11ㆍCOSTN — import 구현의 핵심 3테이블 |
| [STC_필드사전_보조](STC_필드사전_보조.md) | BDQTYㆍRATE계열ㆍSET_REPㆍKONGJONGㆍ빈 테이블ㆍ차수 슬롯 |
| [STC_난독화_상태](STC_난독화_상태.md) | BDQTYㆍSYSINFO — 난독화 7단계 상태표, XOR 부분 검증 |
| [STC_교차비교](STC_교차비교.md) | 보유 7개 STC 전수 자동 비교 결과 (엔트리·스키마·레코드·난독화 재현) |
| [SIxMemo_구조](SIxMemo_구조.md) | COSTN.SMTㆍPRJLST.SMTㆍQCODE.SMT 커스텀 메모 포맷 |
| [XLSX_INITIAL_구조](XLSX_INITIAL_구조.md) | xlsx veryHidden INITIAL 시트 — STmate 재등록·행↔코드 매핑 |
| [STC_왕복검증](STC_왕복검증.md) | STmate 실기 테스트 결과 기록처 (실기 환경 확보 후 작성) |
| [_scripts/stc_cross_compare.py](_scripts/stc_cross_compare.py) | 교차비교 자동화 스크립트 (재현용) |
## 대상 파일 (보유 STC 7건)
> 자동 수집. sha256[:16] = 무결성 스냅샷. 원본 개정·재수집 시 갱신.
| 라벨 | 사업 | 크기 | sha256[:16] | 특징 |
|---|---|---|---|---|
| 기번3 | 2024 간선임도 울진대흥 (공통) | 48,956B | 4e405a2043b6b8c1 | 기준 샘플, 1공구, xlsx 병존 |
| 봉화 | 2024 산불진화임도 기번41 현동 | 52,477B | 2eaa38ec2e185b2b | 1공종 |
| 영월 | 2024 임도시설2차 기번6 | 58,421B | 6c52808f61b291a2 | 7공종(본선·지선), COSTN 99필드 |
| 계류보전(변경) | 2025 계류보전 기번1 영덕 (변경) | 38,613B | cdc9b052ff3c1cb1 | **사방** 94필드, 변경설계 |
| 소광 당초 | 2025 산불진화임도 울진소광 | 39,964B | 85bc63eaab9e3670 | 당초 신설 |
| 소광 신설(다공구) | 〃 기번8 산29외 | 51,541B | 46e8c3a660935c9e | **BOQ12 존재**(2공구), PRJLST 2건 |
| 소광 변경1차 | 〃 변경 1차분 | 49,680B | 50a8cd2285e9bdaa | 변경설계 |
- **차분분석 소스**: 소광 3종(당초·다공구·변경1차) = 당초↔변경 대비. 계류보전 = 사방 스키마 대조.
- 원본 경로는 각 `original/실무문서/<사업>/…/*.STC`.
## 작업 트랙·미결 연계
- import/export **기능 구현**은 개발 영역 → `docs/raw/PLAN.md` 협의 (이 폴더 아님).
- **선행 미결**(→ [03_미결_및_확인사항](../../../03_미결_및_확인사항.md)):
- STmate 실기 환경 확보 (열기·수정·재계산·저장·재개방 검증 전제)
- STC 포맷 역공학·변형실험·신규생성 라이선스 확인
- **권장 구현 순서**: ① 포맷 명세 정정·확장(이 폴더) → ② 필수/가변 엔트리 분류 → ③ 무변경 read/write 왕복 → ④ 최소 변형 실험 → ⑤ BDQTY·SYSINFO 제거·보존·변형 실기 → ⑥ SIxMemo·ENDCODE 해석 → ⑦ 기존 STC 기반 재출력 → ⑧ 신규 STC 생성 → ⑨ STmate 실기 전체 검증.
@@ -0,0 +1,98 @@
# 내역 xlsx에서 단수처리(ROUND 계열) 수식을 항목명과 함께 추출
# 목적: 단계별 절사·반올림 규칙을 6개 공사지 교차로 확인 (값 아닌 로직)
import zipfile
import re
import sys
import os
sys.stdout.reconfigure(encoding="utf-8")
FILES = {
"기번3": "2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료/2024년 간선임도사업(기번3).xlsx",
"봉화": "04.산불 봉화/01. 내역서/2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.xlsx",
"영월": "05. 동부청 영월/내역서/2024년 임도시설사업2차(산불진화_기번6)-영월.xlsx",
"계류보전": "20250527 2025년 계류보전사업(기번1-영덕.병곡.영리.산214-1)(변경)/01.내역자료/2025년 계류보전사업(기번1 영덕.병곡.영.산214)(변경).xlsx",
"소광": "25년 산불진화임도(기번8)(울진소광)/2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).xlsx",
}
ROOT = os.path.normpath(
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..")
)
BASE = os.path.join(ROOT, "original", "실무문서")
RE_SI = re.compile(r"<si>(.*?)</si>", re.S)
RE_T = re.compile(r"<t[^>]*>(.*?)</t>", re.S)
RE_SHEET = re.compile(r'<sheet name="([^"]+)"[^>]*r:id="(rId\d+)"')
RE_REL = re.compile(r'Id="(rId\d+)"[^>]*Target="([^"]+)"')
RE_CELL = re.compile(
r'<c r="([A-Z]+)(\d+)"([^>]*)>(?:<f[^>]*>(.*?)</f>)?(?:<v>(.*?)</v>)?</c>', re.S
)
RE_ROUND = re.compile(r"ROUND(DOWN|UP)?|TRUNC|\bINT\(", re.I)
def shared_strings(z):
try:
ss = z.read("xl/sharedStrings.xml").decode("utf-8")
except KeyError:
return []
out = []
for si in RE_SI.findall(ss):
out.append("".join(RE_T.findall(si)))
return out
def sheet_map(z):
wb = z.read("xl/workbook.xml").decode("utf-8")
rels = dict(RE_REL.findall(z.read("xl/_rels/workbook.xml.rels").decode("utf-8")))
out = []
for name, rid in RE_SHEET.findall(wb):
tgt = rels.get(rid)
if tgt:
out.append((name, "xl/" + tgt.lstrip("/").replace("worksheets/", "worksheets/") if not tgt.startswith("xl/") else tgt))
return out
def main():
target_sheets = re.compile(r"원가계산|설계내역|총괄|내역서|집계|관급")
for label, rel in FILES.items():
p = os.path.join(BASE, rel)
if not os.path.exists(p):
print(f"## {label}: 파일 없음 {rel}\n")
continue
z = zipfile.ZipFile(p)
ss = shared_strings(z)
print(f"## {label}")
for name, path in sheet_map(z):
if not target_sheets.search(name):
continue
try:
x = z.read(path).decode("utf-8")
except KeyError:
continue
# 행 텍스트(항목명) 맵: 각 행의 문자열 셀 모으기
rows = {}
hits = []
for col, row, attr, f, v in RE_CELL.findall(x):
row = int(row)
is_str = 't="s"' in attr
if is_str and v.isdigit():
txt = ss[int(v)] if int(v) < len(ss) else ""
if txt:
rows.setdefault(row, []).append(txt)
if f and RE_ROUND.search(f):
hits.append((row, col, f, v))
if hits:
print(f" ### [{name}] ROUND수식 {len(hits)}건")
seen_pat = {}
for row, col, f, v in hits:
label_txt = " ".join(rows.get(row, []))[:40]
# 패턴 정규화 (셀참조 → REF)
pat = re.sub(r"[A-Z]+\d+", "REF", f)
seen_pat.setdefault(pat, []).append((row, col, label_txt, v))
for pat, items in sorted(seen_pat.items(), key=lambda kv: -len(kv[1])):
r0, c0, lt0, v0 = items[0]
print(f" - {pat} ×{len(items)} 예:{c0}{r0} '{lt0}' v={v0}")
print()
if __name__ == "__main__":
main()
@@ -0,0 +1,114 @@
# STC 교차비교: 보유 STC 전체의 엔트리·DBF 스키마·레코드 상태·난독화 재현 여부 수집
# 사용: python stc_cross_compare.py (리포지토리 루트에서 실행)
# 출력: stdout (문서 작성용 원자료)
import zipfile
import struct
import hashlib
import glob
import os
import sys
from collections import Counter
ROOT = os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")
KNOW = os.path.normpath(os.path.join(ROOT, "knowledge"))
# 이 스크립트는 resources/knowledge/original/원가계산/STmate/_scripts/ 에 위치
# → knowledge 루트 = ../../../..
KNOW = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", ".."))
sys.stdout.reconfigure(encoding="utf-8")
def dbf_header(data: bytes):
"""DBF 헤더 파싱: (버전바이트, 레코드수, 헤더길이, 레코드길이, 필드목록)"""
ver = data[0]
nrec = struct.unpack("<I", data[4:8])[0]
hlen = struct.unpack("<H", data[8:10])[0]
rlen = struct.unpack("<H", data[10:12])[0]
fields = []
off = 32
while off + 32 <= len(data) and data[off] != 0x0D:
raw = data[off : off + 32]
name = raw[0:11].split(b"\x00")[0].decode("ascii", errors="replace")
ftype = chr(raw[11])
flen = raw[16]
fdec = raw[17]
fields.append((name, ftype, flen, fdec))
off += 32
return ver, nrec, hlen, rlen, fields
def rec_flags(data: bytes, nrec: int, hlen: int, rlen: int):
"""레코드 플래그 바이트 분포"""
c = Counter()
for i in range(nrec):
b = data[hlen + i * rlen : hlen + i * rlen + 1]
c[b] += 1
return c
def entropy_sample(data: bytes, nrec: int, hlen: int, rlen: int, n=5):
"""앞 n개 레코드 본문의 비ASCII 비율 (난독화 지표)"""
if nrec == 0:
return None
tot = 0
non = 0
for i in range(min(n, nrec)):
body = data[hlen + i * rlen + 1 : hlen + (i + 1) * rlen]
tot += len(body)
non += sum(1 for b in body if not (0x20 <= b <= 0x7E or b in (0x00,)))
return round(non / tot, 3) if tot else None
def main():
pattern = os.path.join(KNOW, "original", "실무문서", "**", "*.STC")
paths = sorted(glob.glob(pattern, recursive=True))
print(f"# STC 파일 {len(paths)}건\n")
all_schemas = {} # (파일라벨, DBF명) -> 필드시그니처
for p in paths:
label = os.path.relpath(p, os.path.join(KNOW, "original", "실무문서"))
with open(p, "rb") as f:
raw = f.read()
sha = hashlib.sha256(raw).hexdigest()[:16]
z = zipfile.ZipFile(p)
names = z.namelist()
print(f"## {label}")
print(f"- size={len(raw):,}B sha256[:16]={sha}")
print(f"- 엔트리 {len(names)}개: {sorted(names)}")
for name in sorted(names):
if not name.upper().endswith(".DBF"):
continue
d = z.read(name)
ver, nrec, hlen, rlen, fields = dbf_header(d)
flags = rec_flags(d, nrec, hlen, rlen)
ent = entropy_sample(d, nrec, hlen, rlen)
flag_s = ", ".join(
f"{repr(k)}x{v}" for k, v in sorted(flags.items(), key=lambda x: -x[1])
)
sig = tuple(fields)
all_schemas[(label, name)] = sig
print(
f" - {name}: ver=0x{ver:02X} rec={nrec} rlen={rlen} "
f"fields={len(fields)} flags[{flag_s}] nonascii={ent}"
)
# Ini 존재 여부
ini = [n for n in names if n.lower().endswith((".ini", ".mem", ".smt"))]
print(f" - 비DBF: {ini}")
print()
# 스키마 시그니처 그룹핑 (동일 DBF명 기준)
print("# DBF별 스키마 변형 요약")
by_dbf = {}
for (label, name), sig in all_schemas.items():
by_dbf.setdefault(name, {}).setdefault(sig, []).append(label)
for name in sorted(by_dbf):
variants = by_dbf[name]
print(f"## {name}: 변형 {len(variants)}종")
for i, (sig, labels) in enumerate(variants.items(), 1):
print(f" - 변형{i} ({len(sig)}필드): {len(labels)}개 파일")
for lb in labels:
print(f" · {lb}")
if __name__ == "__main__":
main()