feat(M01): PLAN 7-4b 뒤 절반 ④⑤ — 재료_품셈재료.json 을 old/ 로 옮김(MP 직접참조 다 걷음)

되돌렸던 옮기기를 마무리. 앞서 놓친 자리 — 호표 「요소」 칸만 훑어서 덧줄·수량
식 안에 그대로 박힌 MP 참조 16곳(집수정 뚜껑·철망태·쉬즈관·IV전선·백열등·
바퀴숫돌 넷·버너·노즐·테르밋용재·몰드·골무·퓨즈·용접봉)을 못 봤음 — 자재품목에
새 줄 일곱(값 미확보) · 기존 줄 셋(백열등·망태석·용접봉, 가장 맞는 것으로 이음)에
연결하고 식 안의 MP 키 문자열을 그 자재품목 키로 바로 바꿔치기(품셈재료 간접 없이).

재료_품셈재료.json 을 resources/master_data/old/ 로 옮김(지우지 않음) — check_master.load()
가 `*.json` 만 훑어 자동으로 빠짐(코드 안 고쳐도 검사·엔진 다 끊김) ·
build_품셈재료_고르기.py·build_로직_재료_고르기.py 는 다시 안 돌림(한 번 쓰고 마침).

시험 — 옛길(품셈재료 간접)이 죽지 않았는지 자체 마스터로 확인하는 시험 셋을
고쳐 남김(실화면 대신 합성 데이터) · 이제 못 보는 화면 기능(품셈재료 목록·
연결 안 된 줄 거르기) 시험 둘은 뺌(기능 자체가 오늘 사라짐) · 자재품목 분류
기대표에 새로 이은 것들 더함(스페이셔와 같은 이유로 이름 다른 셋 더 뺌).

_틀.md — 재료_품셈재료 절을 「old/ 로 옮김·자료로만 둠」으로 고치고, 7장에
지역·계약종별 고르기 조건 칸을 추가.

typecheck 통과 · 관련 시험 107 통과 · 로직 1354개 전수 대조 — 금액 달라짐 0 ·
새로 멈춤 0 · 새로 통과 2(GC000374·GF000235, 덧줄 식이 못 풀던 것이 오늘 이어져
정상적으로 풀림). PLAN.md 는 이 워크트리에 7장이 없어(gitignore·기기별 파일)
체크 못 함 — 반복된 자리(문서에 이미 적음).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-22 14:14:24 +09:00
co-authored by Claude Sonnet 5
parent 3dd00a5608
commit ada55c2b68
14 changed files with 142 additions and 65 deletions
+67 -15
View File
@@ -1,5 +1,6 @@
"""마스터 데이터 검사 도구 — 견본 셋 손 계산 · 견본 파일 검사 통과 (`resources/master_data/scripts/check_master.py`)."""
import json
import sys
from decimal import ROUND_HALF_UP, Decimal
from pathlib import Path
@@ -432,8 +433,6 @@ def test_토목_보링_심도_가산():
files["재료_자재품목.json"]["줄"].append(
{"키": "MT999999", "원문번호": "가짜", "물가자료": Decimal(1000)}
)
for row in files["재료_품셈재료.json"]["줄"]:
row["연결"] = row["연결"] or "MT999999" # 못 이은 재료 — 시험용 가짜 연결
given = {"층": "점토층", "규격": "BX", "깊이": 35, "심도가산율": 5}
got = mf.run(mf.Master(files), "GC000431", given)
qty = {x["이름"]: x["수량"] for x in got["줄"]}
@@ -695,20 +694,49 @@ def test_노임_갈래_파일_원문_대조():
def test_품셈재료_연결_낮은_값과_출처(whole):
"""연결 하나 — 값 열 다섯 가운데 낮은 값 · 출처 = 「<키>.<열>」."""
"""연결 하나 — 값 열 다섯 가운데 낮은 값 · 출처 = 「<키>.<열>」.
재료_품셈재료.json 은 PLAN 7-4b 뒤 절반이 old/ 로 옮겨 살아 있는 마스터엔 없음 —
element() 의 옛길(품셈재료 간접)이 여전히 도는지 자체 마스터로 확인."""
row = whole.get("MT004176") # 시멘트
low = min(row[s] for s in mm.SLOTS if row.get(s) is not None)
value, source = mf.element(whole, "MP:시멘트", {})
files = cm.load(["재료_자재품목.json"]) | {
"재료_품셈재료.json": {
"그룹": "재료",
"원문": "품셈재료",
"줄": [{"키": "MP900001", "이름": "시멘트", "단위": "kg", "연결": "MT004176"}],
}
}
value, source = mf.element(mf.Master(files), "MP900001", {})
assert value == low and source.split(".")[0] == row["키"]
def test_전력은_계약종별로_고름(whole):
"""전력은 자재품목이 아니라 유가전력 줄 — 계약종별은 로직 입력 · 그 안에서 낮은 값."""
"""전력은 자재품목이 아니라 유가전력 줄 — 계약종별은 로직 입력 · 그 안에서 낮은 값.
옛길(품셈재료 연결이 계약종별 조건 묶음)로도 여전히 도는지 자체 마스터로 확인."""
assert whole.index["MT"].get("MT000006") is None # 자재품목 전력 2줄 뺌
files = cm.load(["재료_유가전력.json"]) | {
"재료_품셈재료.json": {
"그룹": "재료",
"원문": "품셈재료",
"줄": [
{
"키": "MP900002",
"이름": "전력",
"단위": "㎾h",
"연결": {"이름": "전력량요금", "계약종별": "입력"},
}
],
}
}
linked = mf.Master(files)
with pytest.raises(mf.FormulaError): # 계약종별 없이는 값이 안 섬
mf.element(whole, "MP:전력", {})
value, source = mf.element(whole, "MP:전력", {mf.TARIFF: "주택용 저압"})
mf.element(linked, "MP900002", {})
value, source = mf.element(linked, "MP900002", {mf.TARIFF: "주택용 저압"})
assert value == 120 and source == "MO000058.값"
# PLAN 7-4b 뒤 절반 — 호표에 조건 묶음을 바로 박아도 같은 길(품셈재료 간접 없이)
cond = {"이름": "전력량요금", "계약종별": "입력"}
value2, source2 = mf.element(whole, cond, {mf.TARIFF: "주택용 저압"})
assert value2 == value and source2 == source
def test_자재품목_열_한_벌():
@@ -735,15 +763,31 @@ def test_자재품목_열_한_벌():
def test_품셈재료_자재지역_후보(whole):
"""지역 재료 — 입력 없음 · 그 지역 줄 없음 · 규격 여럿은 멈춤 · 하나면 낮은 값."""
"""지역 재료 — 입력 없음 · 그 지역 줄 없음 · 규격 여럿은 멈춤 · 하나면 낮은 값.
옛길(품셈재료 연결이 지역 조건 묶음)로도 여전히 도는지 자체 마스터로 확인."""
files = cm.load(["재료_자재품목.json"]) | {
"재료_품셈재료.json": {
"그룹": "재료",
"원문": "품셈재료",
"줄": [
{"키": "MP900003", "이름": "모래", "단위": "㎥", "연결": {"이름": "모래", "규격": "", "지역": "입력"}},
{"키": "MP900004", "이름": "레미콘", "단위": "㎥", "연결": {"이름": "레미콘", "규격": "", "지역": "입력"}},
],
}
}
linked = mf.Master(files)
with pytest.raises(mf.FormulaError, match="입력 「자재지역」 없음"):
mf.element(whole, "MP:모래", {})
mf.element(linked, "MP900003", {})
with pytest.raises(mf.FormulaError, match="지역 값 없음"):
mf.element(whole, "MP:모래", {"자재지역": "울진"})
mf.element(linked, "MP900003", {"자재지역": "울진"})
with pytest.raises(mf.FormulaError, match="후보 여럿"):
mf.element(whole, "MP:레미콘", {"자재지역": "울진"})
value, source = mf.element(whole, "MP:모래", {"자재지역": "수원"})
mf.element(linked, "MP900004", {"자재지역": "울진"})
value, source = mf.element(linked, "MP900003", {"자재지역": "수원"})
assert source.startswith("MT") and value > 0
# PLAN 7-4b 뒤 절반 — 호표에 조건 묶음을 바로 박아도 같은 길(품셈재료 간접 없이)
cond = {"이름": "모래", "규격": "", "지역": "입력"}
value2, source2 = mf.element(whole, cond, {"자재지역": "수원"}, "㎥")
assert value2 == value and source2 == source
# ── 재료 고르기 조건 (`_틀.md` 7장) ────────────────────────────────────
@@ -955,13 +999,21 @@ def test_고르기_조건_글자_열쇠():
assert mm.cond_text({"구분": "레미콘", "대표": "MT900002"}) == "레미콘||||MT900002||"
def test_품셈재료_고르기_칸이_실파일에_있음():
"""일괄 변환의 바탕 — 모든 품셈재료 줄에 고르기 칸 · 조건은 자재품목 묶음 안."""
whole = cm.master()
def test_품셈재료_고르기_칸이_옛_파일에_있음():
"""일괄 변환의 바탕(옛 자료) — PLAN 7-4b 뒤 절반이 재료_품셈재료.json 을 old/ 로 옮겨
이제 살아 있는 마스터엔 안 읽힘 — 옮기기 전 앞뒤 확인이 남긴 옛 자료 자체를 대고 봄."""
market = cm.load(["재료_자재품목.json"])
old_pm = json.loads(
(cm.MASTER / "old" / "재료_품셈재료.json").read_text(encoding="utf-8"),
parse_float=Decimal,
parse_int=Decimal,
)
whole = mf.Master({**market, "재료_품셈재료.json": old_pm})
rows = whole.index["MP"]
assert rows and all("고르기" in row for row in rows.values())
assert any(isinstance(row["고르기"], dict) for row in rows.values())
assert cm.check_links(whole) == []
assert cm.master().index.get("MP") is None # 살아 있는 마스터엔 이제 없음
def test_로직_일괄_시험계산():