knowledge(경쟁사): 일위대가 레시피 사전 350개 — 누락 감사 포함

목적: 값이 아니라 조합을 확보하는 것. 표준일위대가 배포본은 암호화라 못
읽지만 실무에서 실제 쓴 조합은 출력 엑셀에 평문으로 남음.

누락 감사 — 호표 526개를 하나씩 셈
- 일위대가표 170개 전부 추출, 누락 0
- 중기사용료 143개 전부 추출, 누락 0
- 단가산출근거 213개 중 187 정상 + 빈호표 5 + 못잡음 21
- 못 잡은 21개는 전부 정체 확인함. 운반 계산(거리·속도 인자만 있음)과
  기계+인력 비율 조합이라 「자원 조합」이 아닌 것. 값을 못 읽은 게 아님
- 빈 호표 5개는 무대처리·흙쌓기 등 금액 0원 항목
⇒ 설명되지 않는 누락 0건

확보한 조합 350개 (중복 제거)
- 일위대가 132 · 단가산출 136 · 중기 82, 구성행 1,729 (조합당 평균 4.9)
- Q식을 가진 조합 107개
- 150/350 이 다른 호표를 참조 — 2단 이상으로 쌓임
-  규격이 바뀌면 구성은 같고 수량만 비례 (기슭막이 H=2.0 vs 2.5).
  한 조합 + 규격별 수량표로 접을 수 있음
- 건설표준품셈이 23개 조합에 근거로 적혀 있어 현행 갱신 시 대조점이 됨

⚠ 수량은 그 시점 품셈값이므로 조합은 그대로 쓰되 수량은 현행 품셈으로
다시 맞춰야 함.

30_원자료/일위대가_레시피.json (265.8 KB) 와 감사 기록 저장.
문서 31 신규.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA
This commit is contained in:
2026-09-13 15:12:35 +09:00
co-authored by Claude Opus 5
parent 07aa13da34
commit 68634e5f63
6 changed files with 14182 additions and 0 deletions
@@ -37,6 +37,7 @@
| 28 변환·수량산출·식언어 | **식 변수의 대상/저장 비목** · 수량산출서 셀별명 · 조달청 변환 · 직종 106종 | (저장소 파일) |
| 29 암호화자료의 뼈대 | **값 없이 구조만** — 스키마 평문, 카탈로그 규모, 분류 4~5단, 단위 59~130종 | (저장소 파일) |
| 30 조달청 호환규정 XSD | ⭐ **국가 표준 납품 규격 전문** — 문서 뼈대·제경비 적용산식·기계경비 산출 | (저장소 파일) |
| 31 일위대가 레시피 사전 | ⭐ **조합 350개 전수** — 누락 감사 포함. 값 없이 조합만 확보 | (저장소 파일) |
> **정본은 저장소 파일이다** — `resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/`.
> 위 Orca 링크는 보기용이며, 문서를 지식DB 자리로 옮기면서 주소가 한 번 바뀌었다.
@@ -137,6 +138,9 @@
| 식을 데이터로 저장 | 우리 계약 | **규격이 `적용산식`·`적용산식설명`을 요구** | 30번 §3 |
| 노임 환산 갈래 | 화면 문자열 | **`H0`~`H5` 규격 코드** (일반·터널·장대터널·준설선 3종) | 30번 §4 |
| 손료 산출 | 한 갈래로 봄 | ⚠ **주간/야간/2교대/3교대 네 갈래** — 뽑은 25개는 `E0` 뿐 | 30번 §4 |
| **일위대가 조합** | 표준본 암호화라 못 얻는 줄 앎 | ⭐ **실무 조합 350개 확보** — 일위대가 132·단가산출 136·중기 82 | 31번 |
| 조합 누락 | — | **일위대가표·중기사용료 0건.** 설명 안 되는 누락 0건 | 31번 §2 |
| 규격별 조합 | — | **구성은 같고 수량만 비례** — 한 조합 + 규격별 수량표로 접힘 | 31번 §3.3 |
분석일: 2026-09-13
대상: `C:\경영정보(주)`
@@ -0,0 +1,150 @@
# 일위대가 레시피 사전 — 어떤 조합인가, 빠진 것은 없는가
작성일: 2026-09-13
목적: **값이 아니라 조합**을 확보하는 것. 표준 일위대가 배포본(`표준일위대가_*.STBSC`)은 암호화라 못 읽지만, **실무에서 실제 쓴 조합**은 출력 엑셀에 평문으로 남는다.
산출물: `30_원자료/일위대가_레시피.json` (265.8 KB)
재현 스크립트: `40_스크립트/recipe_extract.py`
## 1. 왜 이것이 값어치가 있나
```text
STmate 표준일위대가(암호화) = 조합 + 값(반기마다 바뀜)
우리가 확보한 것 = 조합 350개 ← 임도·사방 실무에서 실제 쓴 것
+ 현행 표준품셈 = 값
──────────────────────────────────────────────
= Aislo 표준일위대가
```
**값은 반기마다 바뀌므로 가질 이유가 없고, 조합은 잘 바뀌지 않는다.** 게다가 이 350개는 범용 카탈로그가 아니라 **임도·사방 설계서 6건에서 실제로 쓰인 것**이라 우리 쪽에 더 맞는다.
⚠ 수량(`0.08`·`2.09`)은 **그 시점 품셈값**이다. **조합은 그대로 쓰되 수량은 현행 품셈으로 다시 맞춰야 한다.**
## 2. 누락 감사 — 호표 526개를 하나씩 셌다
| 시트 | 호표 | 정상 추출 | 중복 | 제목 못 잡음 | 빈 호표 | 못 잡음 |
|---|---:|---:|---:|---:|---:|---:|
| 일위대가표 | 170 | **170** | 38 | 0 | 0 | **0** |
| 중기사용료 | 143 | **143** | 61 | 0 | 0 | **0** |
| 단가산출근거 | 213 | 187 | 51 | 0 | 5 | **21** |
| **합계** | **526** | **500** | 150 | 0 | 5 | **21** |
**일위대가표·중기사용료는 한 개도 안 빠졌다.** 「중복」은 같은 조합이 여러 프로젝트에 나온 것으로, 중복을 걷어내면 **서로 다른 조합 350개**다.
### 2.1 못 잡은 21개의 정체 — 전부 확인함
| 갈래 | 예 | 왜 |
|---|---|---|
| **운반 계산** | `중기운반(현동) L=36.1km` · `기계운반` · `시멘트운반 L=62.5km` | 구성이 자원 목록이 아니라 **거리·속도 인자**(`L1`·`L2`·`V1`~`V4`)로만 이뤄짐 |
| **기계+인력 조합 터파기** | `측구터파기(토사) 기계100%` · `구조물터파기 기계90%+인력10%` · `구조물 되메우기` | 기계분과 인력분을 비율로 섞는 꼴이라 한 줄 패턴으로 안 잡힘 |
**둘 다 값을 못 읽은 것이 아니라 「자원 조합」이 아닌 것이다.** 운반 인자와 기계·인력 비율은 23번 §4(운반 사이클)·26번 §1(`Cm` 54꼴)에서 이미 따로 다뤘다.
### 2.2 빈 호표 5개
`무대처리` · `흙 쌓 기` · `사토 무대처리` 등 **합계가 0원인 항목**이다. 내역에는 수량만 잡고 금액이 0인 공종으로, 조합이 없는 것이 정상이다.
**설명되지 않는 누락은 0건이다.**
## 3. 확보한 조합 350개
| 종류 | 개수 | 무엇 |
|---|---:|---|
| **일위대가** | **132** | 자원·하위 호표를 조합해 공종 단가를 만듦 |
| **단가산출** | **136** | 기계 `Q`식으로 시간당 사용료를 작업량으로 나눔 |
| **중기** | **82** | 손료 + 운전원 + 연료 + 잡품 |
| 구성행 | 1,729 | 조합당 평균 **4.9개** |
| `Q`식을 가진 것 | **107** | |
### 3.1 구성 개수 분포
```text
1개 11 · 2개 14 · 3개 17 · 4개 90 · 5개 29 · 6개 14 · 7개 15
8개 10 · 9개 5 · 10개 3 · 11개 3 · 12개 1 · 13개 1 · 17개 1
```
**4개짜리가 90개로 최다** — 중기 호표(손료·운전원·연료·잡품)가 그 꼴이다.
### 3.2 ⭐ 150 / 350 이 다른 호표를 참조한다
조합이 **2단 이상으로 쌓인다.** 17번 §5의 위상순서 계산이 여기서도 확인된다.
```text
기슭막이(깬잡석,찰쌓기 L3=45m) H=2.0m / M ← 9개 구성
깬잡석채집 L=45cm 내외 M2 2.09
깬잡석찰쌓기 L3=45cm내외 M2 2.09 ← 다른 호표
고임돌채집 기계(0.7㎥,100%) M3 0.31
막자갈채집 기계(0.7㎥,100%) M3 0.48
기초다짐 및 뒤채움 소형 장비사용 시공 M3 0.48
PVC파이프 VG1(50m/m) M 0.94
유로폼 설치 및 해체 간단 m2 0.85
레미콘타설(장비) 무근 m3 0.31
콘크리트믹서사용 0.45 m3 m3 0.42
└ 깬잡석찰쌓기 L3=45cm내외 / M2 ← 3개 구성
석공 인 0.08
보통인부 인 0.04
굴삭기(타이어) 0.6㎥,(부착용집게) 시간 0.30
```
### 3.3 ⭐ 규격이 바뀌면 수량만 비례한다
```text
기슭막이 … H=2.0m 깬잡석채집 2.09 · 고임돌 0.31 · 막자갈 0.48 · PVC 0.94
기슭막이 … H=2.5m 깬잡석채집 2.61 · 고임돌 0.39 · 막자갈 0.79 · PVC 1.28
```
**구성 목록은 같고 수량만 높이에 따라 달라진다.** 규격별로 조합을 새로 만들 필요 없이 **한 조합 + 규격별 수량표**로 둘 수 있다는 뜻이다.
### 3.4 가장 많이 쓰인 구성 자원
| 자원 | 단위 | 등장 조합 수 | | 자원 | 단위 | 등장 |
|---|---|---:|---|---|---|---:|
| 잡품 | % | **70** | | 화물차운전사 | 인 | 15 |
| 경유 | L | **68** | | 굴삭기(무한궤도) | 천원 | 13 |
| 건설기계운전사 | 인 | **52** | | 면목 | M | 13 |
| 보통인부 | 인 | **43** | | 콘크리트비빔 | m3 | 12 |
| 유로폼 설치 및 해체 | m2 | 24 | | 철근가공 및 조립 | ton | 11 |
| 석공 | 인 | 23 | | 덤프트럭 | 천원 | 11 |
| **건설표준품셈** | — | **23** | | 레미콘 | M3 | 11 |
| 고임돌채집 | m3 | 21 | | 기초잡석채집및운반부설 | M3 | 11 |
| 레미콘타설(장비) | m3 | 19 | | 막자갈채집 | m3 | 11 |
**`건설표준품셈`이 23개 조합에 근거로 적혀 있다.** 어느 품셈 조항에서 왔는지가 텍스트로 남는다(`▣ 토공의 비탈규준틀[개소]2-6-1` 같은 꼴). 현행 품셈으로 갱신할 때 **대조점이 된다.**
`잡품`이 70개 조합에 나오는 것은 중기 호표마다 붙기 때문이고, **수량×단가가 아니라 연료비에 비율을 곱하는 가산 행**이다(17번 §3).
## 4. 산출물 구조
`30_원자료/일위대가_레시피.json`
```json
{
"종류": "일위대가",
"명칭": "깬잡석찰쌓기",
"규격": "L3=45cm내외",
"단위": "M2",
"Q식": null,
"품셈근거": null,
"구성": [
{"명칭": "석공", "규격": "", "단위": "인", "수량": 0.08},
{"명칭": "보통인부", "규격": "", "단위": "인", "수량": 0.04},
{"명칭": "굴삭기(타이어)", "규격": "0.6㎥,(부착용집게)", "단위": "시간", "수량": 0.3}
]
}
```
단가산출 계열은 `Q식`(예: `3600*q*K*f*E/Cm`)과 `품셈근거`가 함께 담긴다.
## 5. 이대로 쓰려면 해야 할 것
1. **수량을 현행 표준품셈으로 갱신** — 조합은 그대로, 수량만. `품셈근거` 문자열이 대조점이다.
2. **자원 이름을 우리 코드에 잇기**`30_원자료/stmate_code_name_list.csv`(894개)와 짝지어 쓴다.
3. **못 잡은 21개 보완** — 운반 계산과 기계·인력 비율 조합은 **별도 꼴**로 다뤄야 한다(26번 §1의 `Cm` 식, 23번 §4의 운반 사이클 참조).
4. **규격별 수량표로 접기** — §3.3처럼 같은 조합이 규격만 다른 것이 많다.
## 6. 한 줄 정리
| 물음 | 답 |
|---|---|
| 어떤 조합인가 | **350개** — 일위대가 132 · 단가산출 136 · 중기 82. 구성행 1,729 |
| 빠진 것은 없나 | **일위대가표·중기사용료 0건 누락.** 단가산출 21개는 「자원 조합이 아닌 것」(운반 인자·기계인력 비율)이고 5개는 금액 0인 빈 호표. **설명 안 되는 누락 0건** |
@@ -0,0 +1,26 @@
### 누락 감사 — 출력 엑셀 6개
종류 호표 정상 중복 제목없음 빈호표 구성없음
일위대가 170 170 38 0 0 0
중기 143 143 61 0 0 0
단가산출 213 187 51 0 5 21
호표 526개 중 500개 처리 (95.1%) · 못 잡은 것 26개
2024년 산불진화임도 실시설계( 단가산출근거 호표14 측구터파기(토사) 기계100%(굴삭기 0.4 줄67
2024년 산불진화임도 실시설계( 단가산출근거 호표21 구조물터파기(육상토사0~1m) 기계90%+인 줄66
2024년 산불진화임도 실시설계( 단가산출근거 호표23 구조물터파기(육상토사0~1m) 기계100%( 줄32
2024년 산불진화임도 실시설계( 단가산출근거 호표24 구조물 되메우기 기계100%(굴삭기 0.7M 줄32
2024년 산불진화임도 실시설계( 단가산출근거 호표59 중기운반(현동) L=36.1km 줄68
2024년 산불진화임도 실시설계( 단가산출근거 호표60 기계운반(현동) L=36.1km 줄33
2024년 산불진화임도 실시설계( 단가산출근거 호표61 시멘트운반(현동) L=62.5km 줄66
2024년 산불진화임도 실시설계( 단가산출근거 호표64 철근운반(현동) L=62.5km 줄31
2024년 간선임도사업(기번3) 단가산출근거 호표21 중기운반(울진군 울진읍 대흥리) L=19.0 줄68
2024년 간선임도사업(기번3) 단가산출근거 호표24 시멘트운반(울진군 울진읍 대흥리) L=19. 줄66
### 서로 다른 조합 350개
단가산출 136개
일위대가 132개
중기 82개
구성행 합계 1729 (조합당 평균 4.9)
Q식을 가진 조합 107개
→ 일위대가_레시피.json 265.8 KB
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
"""일위대가 레시피 사전 — 전수 추출 + 누락 감사.
시트 셋(일위대가표·중기사용료·단가산출근거)의 호표를 하나도 빠뜨리지 않는다."""
import sys, io, re, pathlib, warnings, collections, json
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
warnings.filterwarnings("ignore"); import openpyxl
def _repo():
p = pathlib.Path(__file__).resolve()
for q in p.parents:
if (q / "resources" / "knowledge").is_dir(): return q
return pathlib.Path(r"D:\aislo-wt\sub")
REPO = _repo(); PRAC = REPO / "resources" / "knowledge" / "original" / "실무문서"
HOPYO = re.compile(r"^제?\s*\d+\s*호표$")
RE_COMP = re.compile(r"(노\s*무\s*비|재\s*료\s*비|경\s*비)\s*:\s*([\d,]+(?:\.\d+)?)\s*/\s*Q")
RE_QF = re.compile(r"\bQ\s*=\s*([^=\n']{3,70})")
RE_DIRECT = re.compile(r"^\s*([^:*=]{2,40}?)\s*:\s*([\d,]+(?:\.\d+)?)\s*\*\s*([\d,]+(?:\.\d+)?)\s*([^\s/=]{0,8})")
RE_PUM = re.compile(r"[▣▶●]\s*(.{3,60})")
RE_REV = re.compile(r"^\s*([\d,]+(?:\.\d+)?)\s*\*\s*([\d,]+(?:\.\d+)?)\s*=")
SKIP = {"합계", "", "소계", "총계", "총합계"}
def parse_grid(ws):
"""일위대가표·중기사용료 — A열 제목, 구성이 자원행."""
rows = [list(r) for r in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=14, values_only=True)]
out = []; cur = None
for r in rows:
a = (str(r[0]) if r[0] is not None else "").strip()
k = a.replace(" ", "")
if HOPYO.match(k):
cur = {"no": len(out)+1, "title": None, "items": [], "q": None, "raw": 0}; out.append(cur); continue
if cur is None: continue
if k in SKIP: continue
cur["raw"] += 1
if cur["title"] is None and a and (r[3] not in (None, "")) and (r[2] in (None, "")):
cur["title"] = (a, str(r[1] or "").strip(), str(r[3] or "").strip()); continue
if a and (r[2] not in (None, "")):
cur["items"].append((a, str(r[1] or "").strip(), str(r[3] or "").strip(), r[2]))
return out
def parse_san(ws):
"""단가산출근거 — B열 제목('명칭 규격 / 단위'), 구성은 산식(중기 시간당단가 / Q)."""
rows = [list(r) for r in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=8, values_only=True)]
out = []; cur = None
for r in rows:
b = (str(r[1]) if r[1] is not None else "").strip()
k = b.replace(" ", "")
if HOPYO.match(k):
amt = sum(x for x in r[2:6] if isinstance(x,(int,float)))
cur = {"no": len(out)+1, "title": None, "items": [], "q": None, "pum": None, "raw": 0, "amt": amt}; out.append(cur); continue
if cur is None: continue
if k in SKIP: continue
cur["raw"] += 1
if cur["title"] is None and b and "/" in b:
nm, _, un = b.rpartition("/")
nm = nm.strip(); un = un.strip(); sz = ""
if " " in nm:
p1, p2 = nm.split(" ", 1); nm, sz = p1.strip(), p2.strip()
cur["title"] = (nm, sz, un); continue
m = RE_QF.search(b)
if m and cur["q"] is None: cur["q"] = re.sub(r"\s+", "", m.group(1))
hit = False
for mm in RE_COMP.finditer(b):
cur["items"].append((mm.group(1).replace(" ", ""), "시간당 성분단가", "", mm.group(2))); hit = True
if not hit:
md = RE_DIRECT.match(b)
if md:
cur["items"].append((md.group(1).strip(), "", md.group(4).strip(), md.group(3)))
hit = True
if not hit:
mr = RE_REV.match(b)
if mr:
cur["items"].append(((str(r[0]) if r[0] is not None else "").strip() or "자원", "", "", mr.group(1)))
hit = True
if not hit and cur.get("pum") is None:
mp = RE_PUM.search(b)
if mp: cur["pum"] = mp.group(1).strip()
return out
SHEETS = {"일위대가표": "일위대가", "중기사용료": "중기", "단가산출근거": "단가산출"}
rec = {}; audit = collections.Counter(); miss = []; files = 0
for p in sorted(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
files += 1
for sh, kind in SHEETS.items():
if sh not in wb.sheetnames: continue
bs = parse_san(wb[sh]) if sh == "단가산출근거" else parse_grid(wb[sh])
audit[(kind, "호표")] += len(bs)
for b in bs:
if not b["title"]:
audit[(kind, "제목없음")] += 1; miss.append((p.stem[:18], sh, b["no"], "제목 못 잡음", b["raw"])); continue
if not b["items"]:
if not b.get("amt"): audit[(kind, "빈호표")] += 1
else: audit[(kind, "구성없음")] += 1; miss.append((p.stem[:18], sh, b["no"], b["title"][0][:24], b["raw"]))
continue
audit[(kind, "정상")] += 1
key = (kind,) + b["title"]
if key in rec: audit[(kind, "중복")] += 1
else: rec[key] = (b["items"], b["q"], b.get("pum"))
print("### 누락 감사 — 출력 엑셀 %d" % files)
print("%-10s %7s %7s %7s %8s %8s %8s" % ("종류","호표","정상","중복","제목없음","빈호표","구성없음"))
for kind in ("일위대가", "중기", "단가산출"):
print("%-10s %7d %7d %7d %8d %8d %8d" % (kind, audit[(kind,"호표")], audit[(kind,"정상")],
audit[(kind,"중복")], audit[(kind,"제목없음")], audit[(kind,"빈호표")], audit[(kind,"구성없음")]))
tot = sum(audit[(k,"호표")] for k in SHEETS.values()); ok = sum(audit[(k,"정상")] for k in SHEETS.values())
print("\n 호표 %d개 중 %d개 처리 (%.1f%%) · 못 잡은 것 %d" % (tot, ok, 100*ok/tot, tot-ok))
for m in miss[:10]: print(" %-18s %-12s 호표%-4d %-26s%d" % m)
print("\n### 서로 다른 조합 %d" % len(rec))
for a, b2 in sorted(collections.Counter(x[0] for x in rec).items()): print(" %-10s %3d" % (a, b2))
tot2 = sum(len(v[0]) for v in rec.values())
print(" 구성행 합계 %d (조합당 평균 %.1f)" % (tot2, tot2/max(len(rec),1)))
nq = sum(1 for v in rec.values() if v[1])
print(" Q식을 가진 조합 %d" % nq)
out = REPO/"resources"/"knowledge"/"original"/"경쟁사 프로그램"/"STmate 분석"/"30_원자료"/"일위대가_레시피.json"
J = [{"종류":k[0], "명칭":k[1], "규격":k[2], "단위":k[3], "Q식":v[1], "품셈근거":(v[2] if len(v)>2 else None),
"구성":[{"명칭":a, "규격":c, "단위":u, "수량":(float(q) if isinstance(q,(int,float)) else str(q))}
for a,c,u,q in v[0]]} for k,v in sorted(rec.items())]
out.write_text(json.dumps(J, ensure_ascii=False, indent=1), encoding="utf-8")
print("\n%s %.1f KB" % (out.name, out.stat().st_size/1024))
@@ -69,6 +69,7 @@
| 28 | **변환·수량산출·식언어** — 식 변수 대상/저장, 조달청 변환, 직종 106종 |
| 29 | **암호화자료의 뼈대** — 값 없이 구조만, 카탈로그 규모와 분류 깊이 |
| 30 | ⭐ **조달청 호환규정 XSD** — 국가 표준 납품 규격 전문 |
| 31 | ⭐ **일위대가 레시피 사전** — 조합 350개, 누락 감사 |
## 확정된 굵직한 것