feat(master_data): 재료_시중물가 — 다산소프트 EST 2026-07 자재 23,980줄(개발용 · 배포 전 판권 구매)

- 값 묶음 채택·가격정보·물가자료·유통물가·물가정보·거래가격·견적가 · 면수 묶음 · 0 은 null
- 원문 이름 「시중물가」 — _틀.md 2장 · check_master BOOKS
- 옮기는 스크립트 build_재료_시중물가.py — 원본 대조(줄 수 · 출처별 합 · 표본 50줄) 포함

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
2026-09-19 22:28:54 +09:00
co-authored by Claude Opus 5
parent 11c9f562fc
commit 14e3261bbb
4 changed files with 24107 additions and 1 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
- 이름 = `그룹_원문[_장].json` — 날짜 없음 · 마스터는 항상 최신. 예: `인력_건설노임.json` · `소요량_산림품셈_13장.json` · `로직_산림품셈_13장.json`
- 그룹 = 인력 · 재료 · 기계 · 소요량 · 계수 · 환율 · 요율 · 로직
- 원문 이름 = 산림품셈 · 건설품셈 · 건설노임 · 제조노임 · 나라장터자재 · 오피넷유가 · 한국은행환율 · 조달청제비율 · 자체
- 원문 이름 = 산림품셈 · 건설품셈 · 건설노임 · 제조노임 · 나라장터자재 · 오피넷유가 · 시중물가 · 한국은행환율 · 조달청제비율 · 자체
- UTF-8 · LF · 들여쓰기 2칸 · 칸 이름은 한글.
## 3. 출처
@@ -0,0 +1,108 @@
"""상용 적산 자료(ESTX 2026-07) 자재 → 재료_시중물가.json — 개발용 · 배포 전 판권 구매 뒤 갱신.
ESTX 자재 표 = StuffcDat(칸 뜻: 다산소프트/ESTX_값사전_자재노무기계.md).
TC = 채택 · Cost1..6 = 출처 여섯 · Page1..6 = 면수.
0 · 미저장 = 값 없음(null). 이름·규격은 원문 그대로(띄어쓰기·오자 포함). 코드 없는 빈 줄은 뺌.
ESTX 에 대·중분류 칸이 없어 분류는 두지 않음.
쓰기: ./venv/Scripts/python.exe resources/master_data/scripts/build_재료_시중물가.py
쓴 뒤 원본과 대조.
"""
import hashlib
import json
import math
import random
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from _migrate_base import dump_master # noqa: E402
from compare_estx import ESTX, ROOT, SLOTS, Estx # noqa: E402
OUT = ROOT / "resources/master_data/재료_시중물가.json"
def num(v):
if not v:
return None
return int(v) if float(v).is_integer() else v
def build(rows: list[dict]) -> list[dict]:
out = []
for r in sorted(rows, key=lambda r: r["Index"]):
code = r.get("Code")
if not code:
continue
price = {"채택": num(r.get("TC"))}
pages = {}
for i, slot in enumerate(SLOTS, 1):
price[slot] = num(r.get(f"Cost{i}"))
pages[slot] = r.get(f"Page{i}") or None
out.append(
{
"열쇠": code,
"이름": r.get("Name", ""),
"규격": r.get("Size", ""),
"단위": r.get("Unite", ""),
"": price,
"면수": pages,
"출처": f"시중물가 {code}",
}
)
return out
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")}
assert len(got) == len(data[""]) == len(raw), (len(got), len(raw))
print(
f"{len(got)} = 원본 코드 있는 줄 {len(raw)} (코드 없는 빈 줄 {len(rows) - len(raw)} 뺌)"
)
for i, slot in enumerate(["채택", *SLOTS]):
col = "TC" if i == 0 else f"Cost{i}"
a = math.fsum(r.get(col) or 0 for r in raw.values())
b = math.fsum(r[""][slot] or 0 for r in got.values())
n = sum(1 for r in got.values() if r[""][slot] is not None)
assert abs(a - b) < 1e-3, (slot, a, b)
print(f" {slot}: 값 {n}줄 · 합 {b:,.1f} = 원본 {a:,.1f}")
random.seed(20260719)
for code in random.sample(sorted(raw), 50):
r, g = raw[code], got[code]
assert (g["이름"], g["규격"], g["단위"]) == (
r.get("Name", ""),
r.get("Size", ""),
r.get("Unite", ""),
)
assert (g[""]["채택"] or 0) == r.get("TC", 0)
for i, slot in enumerate(SLOTS, 1):
assert (g[""][slot] or 0) == (r.get(f"Cost{i}") or 0), (code, slot)
assert g["면수"][slot] == (r.get(f"Page{i}") or None), (code, slot)
print(" 표본 50줄 이름·규격·단위·값 7·면수 6 같음")
def main():
rows = Estx().table("StuffcDat")
head = {
"그룹": "재료",
"원문": "시중물가",
"": "2026-07",
"원천": [
{
"경로": ESTX.relative_to(ROOT).as_posix(),
"해시": hashlib.sha256(ESTX.read_bytes()).hexdigest(),
"역할": "primary",
}
],
"비고": ["다산소프트 EST 2026-07 자재 · 개발용 · 배포 전 판권 구매"],
}
dump_master(OUT, head, build(rows))
check(rows)
if __name__ == "__main__":
main()
@@ -36,6 +36,7 @@ BOOKS = (
"제조노임",
"나라장터자재",
"오피넷유가",
"시중물가",
"한국은행환율",
"조달청제비율",
"자체",
File diff suppressed because it is too large Load Diff