feat(830-1): 재료 DB 를 EST 그대로로 되돌림 — 조달청 줄 6,619 · 조달청 관급 값 걷음 · 표 양식 그대로

- 재료_자재품목 30,711 → 24,092 줄 = EST(ESTX 2026-07) 23,973 + 품셈재료에서 옮긴 줄 119
- 관급 칸 = EST 원문 값(이름에 관급 든 줄만 · 4,552) — 조달청 값 2,009 · 빈 값 112 → EST 값 · 이름 관급 아닌 줄 219 에 얹은 값 → 비움
- 비고 「관급: 조달청 …」 4,771 → EST 빌더 글(「조달 26.7」 등) · VR관 1본 길이 글(7-4b) · 분류 「이름 없음」 글은 지킴
- EST 줄 키 · 이름 · 규격 · 단위 · 물가지 넷 · 면수 · 구분 · 상세구분 바뀐 것 0 · 열 한 벌 그대로
- 키대장 MT 키 = 표 줄(조달청 키 6,619 뺌 · 「폐기」 에 안 남김 · 「다음」 30719 그대로)
- 빌더 = 정본 한 길: build_재료_자재품목 이 ESTX 밖 줄 이어 받음 · VR관 덧말 · adopt 뒤 다시 씀(구분 칸 두 번 쓰기 피함) · 분류를 adopt 뒤로(키 줄을 읽음) · 대장 정리
- compare_estx ESTX 경로 = 옮겨진 자리(경쟁사 프로그램/EST Plus 2/다산소프트 · 해시 같음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-14 15:42:36 +09:00
co-authored by Claude Opus 5.5
parent b4618925ae
commit 0719bb7eba
5 changed files with 4843 additions and 18038 deletions
File diff suppressed because it is too large Load Diff
@@ -2,7 +2,7 @@
`scripts/build_자재품목_분류.py` 가 만든 표 — 손으로 고치지 않음.
- 구분 24 · 상세구분 100 · 미분류 25 줄 (전체 30711 줄)
- 구분 24 · 상세구분 91 · 미분류 25 줄 (전체 24092 줄)
## 규칙
@@ -14,13 +14,13 @@
## 구분별 상세구분 (줄 수)
### 관·이음관 (7152)
### 관·이음관 (5399)
PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 461 · 파형강관 417 · 유리섬유·FRP관 398 · 흄관 222 · 동관·스텐관 133 · 단열이중관 77
PE관 1154 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 461 · 파형강관 417 · 유리섬유·FRP관 398 · 흄관 222 · 동관·스텐관 133 · 단열이중관 77
### 식생·종자 (6128)
### 식생·종자 (3456)
수목 5532 · 수목지주·보호 181 · 초본·야생화 138 · 종자 105 · 잔디·지피 97 · 농약·약제 42 · 비료·토양개량 26 · 비료 7
수목 2933 · 초본·야생화 138 · 수목지주·보호 115 · 종자 105 · 잔디·지피 97 · 농약·약제 42 · 비료·토양개량 26
### 레미콘 (3203)
@@ -30,13 +30,9 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
형강 717 · 철근 497 · 강판 262 · 구조용강관 132 · 철선 93 · 철망·와이어메쉬 53
### 아스콘·포장 (1705)
### 아스콘·포장 (1695)
아스콘 1479 · 포장블록 173 · 특수포장 30 · 아스팔트 13 · 투수포장재 10
### 안전시설 (1552)
표지판·지주 934 · 가드레일 458 · 낙석방지·휀스 74 · 낙석방지책 63 · 차선·방호 23
아스콘 1479 · 포장블록 173 · 특수포장 30 · 아스팔트 13
### 볼트·앵커·철물 (1002)
@@ -50,10 +46,6 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
밸브 575 · 수도계량기·유량계 101 · 스프링클러·호스 51 · 펌프 34
### 매트·토목섬유 (678)
돌망태 372 · 부직포·토목섬유 113 · 식생매트 64 · 돌망태·사방 63 · 매트 54 · 부직포 8 · 잔디보호매트 4
### 운송·용역 (677)
화물운임 552 · 시험·검사 62 · 폐기물 61 · 상하차 2
@@ -74,10 +66,6 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
도료 258 · 접착·혼화 174 · 방수·실링 140
### 석재 (535)
경계석 410 · 조경석·자연석 125
### 콘크리트제품 (494)
말뚝·파일 223 · 경계석·블록 172 · PC구조물 99
@@ -86,9 +74,9 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
천장·바닥·마감재 250 · 문·창·유리 123 · 타일·기와 66 · 위생기구 15
### 목재 (431)
### 안전시설 (329)
각재·판재 168 · 목재덱 161 · 합판 78 · 판재 24
표지판·지주 122 · 가드레일 110 · 낙석방지·휀스 74 · 차선·방호 23
### 잡재료 (317)
@@ -98,6 +86,18 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
단열재 284
### 목재 (246)
각재·판재 168 · 합판 78
### 매트·토목섬유 (230)
부직포·토목섬유 113 · 돌망태·사방 63 · 매트 54
### 석재 (207)
조경석·자연석 125 · 경계석 82
### 교량·구조부속 (133)
신축이음·받침 75 · 보강재·정착 58
@@ -7,6 +7,9 @@ Cost2..5 = 물가지 넷 · Page2..5 = 면수. TC(채택) · Cost6(견적가)는
그 밖의 줄에 남은 Cost1 은 출처가 조달인지 물가지인지 원문이 안 가름 — 값 열에 넣지 않고 비고 「가격정보 ○○」 로만.
0 · 미저장 = 값 없음(null). 이름·규격은 원문 그대로(띄어쓰기·오자 포함). 코드 없는 빈 줄은 뺌.
ESTX 에 대·중분류 칸이 없어 구분·상세구분은 build_자재품목_분류.py 가 품명 글자 규칙으로 채움(빌더 끝에서 부름).
조달청 줄 · 조달청 관급 값은 넣지 않음(요구 822 — EST 그대로) · update_관급단가*.py 는 지금 정본에 적용 안 함.
ESTX 밖 줄 = 품셈재료에서 옮긴 줄(출처 「품셈재료 …」 · PLAN 7-4b) — 지금 정본에서 그대로 이어 받음.
대장 MT 키 = 이 표의 줄만 · 지운 줄의 키는 대장에서 뺌(「다음」 번호는 그대로 — 재사용 없음).
쓰기: ./venv/Scripts/python.exe resources/master_data/scripts/build_재료_자재품목.py
쓴 뒤 원본과 대조.
@@ -37,6 +40,10 @@ DROP = {
"M0005250064": "전력",
"M0005250065": "전력(주택용저압)",
}
CARRY = "품셈재료" # 출처 머리 — ESTX 밖 줄(7-4b)
# 7-4b 덧말 — VR관 1본 길이(D300~D1200 · 이름 「VR관」 · 「VR관(관급)」)
VR_NOTE = "1본 = 2.5m(표준 유효길이 D300~D1200 공통 — PLAN 7-4b 확인 · 출처: https://kr-con.co.kr/product/index.asp?pIdx=product3)"
VR_SIZES = {f"D={d}mm" for d in (300, 400, 450, 500, 600, 700, 800, 900, 1000, 1100, 1200)}
def num(v):
@@ -63,6 +70,9 @@ def build(rows: list[dict]) -> list[dict]:
base, page = num(r.get("Cost1")), r.get("Page1") or None
public = "관급" in r.get("Name", "") and base is not None
면수 = {slot: r.get(f"Page{i}") or None for i, slot in enumerate(MARKET, 2)}
비고 = note(base, page, page if public else None)
if r.get("Name") in ("VR관", "VR관(관급)") and r.get("Size") in VR_SIZES:
비고 = " · ".join(x for x in (비고, VR_NOTE) if x)
out.append(
{
"열쇠": code,
@@ -74,19 +84,44 @@ def build(rows: list[dict]) -> list[dict]:
**{slot: num(r.get(f"Cost{i}")) for i, slot in enumerate(MARKET, 2)},
"관급": base if public else None,
"출처": f"자재품목 {code}",
"비고": note(base, page, page if public else None),
"비고": 비고,
"면수": 면수 if any(면수.values()) else None,
}
)
return out
def carried() -> list[dict]:
"""지금 정본의 ESTX 밖 줄(출처 「품셈재료 …」) — 옛 모양(열쇠 = 대장 원문번호)으로 되돌려 adopt 가 같은 키로 받게."""
if not OUT.is_file():
return []
book = mk.load_book()["키"]
rows = json.loads(OUT.read_text(encoding="utf-8"))["줄"]
return [
{"열쇠": book[r["키"]]["원문번호"], **{k: v for k, v in r.items() if k != "키"}}
for r in rows
if str(r.get("출처") or "").startswith(CARRY)
]
def prune() -> int:
"""대장의 이 파일 MT 키 가운데 표에 없는 키를 뺌 — 「다음」 · 「폐기」 는 그대로."""
keep = {r["키"] for r in json.loads(OUT.read_text(encoding="utf-8"))["줄"]}
book = mk.load_book()
gone = [k for k, v in book["키"].items() if v["파일"] == OUT.name and k not in keep]
for k in gone:
del book["키"][k]
mk.save_book(book)
return len(gone)
def check(rows: list[dict]) -> None:
"""원본 대조 — 줄 수 · 값 열마다 합 · 관급·가격정보 가름 · 표본 쉰 줄."""
data = json.loads(OUT.read_text(encoding="utf-8"))
got = {r["열쇠"]: r for r in data["줄"]}
raw = {r["Code"]: r for r in rows if r.get("Code") and r["Code"] not in DROP}
assert len(got) == len(data["줄"]) == len(raw), (len(got), len(raw))
got = {k: r for k, r in got.items() if not str(r["출처"]).startswith(CARRY)}
assert len(got) == len(raw), (len(got), len(raw))
print(
f"줄 {len(got)} = 원본 코드 있는 줄 {len(raw)}"
f" (코드 없는 빈 줄 {sum(1 for r in rows if not r.get('Code'))} · 유류 {len(DROP)} 뺌)"
@@ -138,18 +173,25 @@ def main():
"유류·전력은 뺌 — 유가·전기요금은 재료_유가전력(유류는 값이 비고 글뿐 · 중유는 받지 않음)",
"원문번호(다산소프트 품번) 칸 없음 — 대조는 _키대장.json 에만",
"관급 = 원문 「가격정보」 칸의 조달청 관급 단가(철근 · 레미콘 · 아스콘 · H형강 등) · 기간은 비고 「조달 26.7」",
"조달청에만 있는 품목 380 줄은 관급 열만 채움 — 출처 「자재품목 조달청 …」 · 원문번호는 _키대장.json 에만",
"조달청 줄 · 조달청 관급 값은 넣지 않음 — EST 그대로(요구 822)",
"품셈재료에서 옮긴 줄(출처 「품셈재료 …」 · 값 없음)은 ESTX 밖 — 지금 정본에서 이어 받음",
"원문 「가격정보」 칸에 시중 값이 남은 줄은 값 열에 넣지 않고 비고 「가격정보 ○○」 로만 둠 — 출처가 조달인지 물가지인지 원문이 안 가름",
"구분 · 상세구분 = 품명 글자 규칙으로 채움(scripts/build_자재품목_분류.py · 묶음 목록 ref/_묶음_자재품목.md) · 조달청 줄은 조달청 품명을 따름 · 못 가린 줄은 「미분류」",
"면수 = 물가지 값의 원문 면수 · 화면에는 접어 둠",
],
}
dump_master(OUT, head, build(rows))
extra = carried()
dump_master(OUT, head, build(rows) + extra)
check(rows)
print("ESTX 밖 줄 이어 받음", len(extra))
print("새 틀로 받음", mk.adopt()) # 열쇠 → 키 — 분류가 「키」 줄을 읽으므로 먼저
# adopt 의 keep_text 가 구분 · 상세구분을 한 줄에 두 번 씀 — 읽어서(뒤 값) 다시 씀
data = json.loads(OUT.read_text(encoding="utf-8"))
dump_master(OUT, {k: v for k, v in data.items() if k != "줄"}, data["줄"])
import build_자재품목_분류 as cls # noqa: E402
print("구분·상세구분 채움", len(cls.apply()), "줄")
print("새 틀로 받음", mk.adopt()) # 열쇠 → 키
print("대장에서 뺀 키", prune())
if __name__ == "__main__":
@@ -20,7 +20,7 @@ import check_master as cm # noqa: E402
import master_formula as mf # noqa: E402
ROOT = Path(__file__).resolve().parents[3]
ESTX = ROOT / "resources/knowledge/original/원가계산/다산소프트/2026-07-main.estx"
ESTX = ROOT / "resources/knowledge/original/경쟁사 프로그램/EST Plus 2/다산소프트/2026-07-main.estx"
# ── ESTX 읽기 ─────────────────────────────────────────────────────────
File diff suppressed because it is too large Load Diff