diff --git a/resources/master_data/_틀.md b/resources/master_data/_틀.md index 6b1fd884..07b916dd 100644 --- a/resources/master_data/_틀.md +++ b/resources/master_data/_틀.md @@ -220,9 +220,10 @@ } ``` -- 조건 칸 = `구분`(반드시) · `상세구분` · `규격` · `대표` · 그 밖의 칸은 두지 않음. `종류` 는 「재료」만. +- 조건 칸 = `구분`(반드시) · `상세구분` · `이름` · `규격` · `대표` · 그 밖의 칸은 두지 않음. `종류` 는 「재료」만. - 구분·상세구분 = 자재품목 줄의 같은 이름 칸과 똑같이 적음(묶음 목록은 `ref/_묶음_자재품목.md`). - 규격 조건 = 원문이 규격을 밝힌 것만 좁힘 — 낱말이 **모두** 든 줄이 후보(「D300」 · 「D300 B종」) · 대소문자·빈칸·`φ` 는 안 가림. 원문이 규격을 안 밝히면 칸을 두지 않음. +- 이름 조건 = 구분·상세구분·규격이 같아도 품명이 갈리는 자재품목을 가를 때만 둠(예: 파형강관 커플링밴드 vs 접속티 — 둘 다 관경만 규격에 있어 규격 조건으로는 안 갈림). 규격과 같은 방식(낱말이 **모두** 이름에 든 줄) — 대소문자·빈칸은 안 가림. - 대표 줄 = 관리자가 고른 자재품목 키 — 시험 계산이 그 줄의 값을 씀. 조건 밖 키는 검사가 잡음. - 후보는 **호표 줄 단위와 같은 자재품목 줄만** — 단위가 다르면 다른 물건(우드칩 ㎥ · 원목후로링 ㎡). 조건 안 줄이 모두 다른 단위면 `check_master 단위` 가 알림. - 조건 값에 `{입력}` 을 넣을 수 있음 — `"규격": "{관경}"` 은 계산 때 입력값으로 채워짐(검사에서는 그 칸을 뺌). diff --git a/resources/master_data/scripts/master_material.py b/resources/master_data/scripts/master_material.py index 8f30ab58..987d8822 100644 --- a/resources/master_data/scripts/master_material.py +++ b/resources/master_data/scripts/master_material.py @@ -16,7 +16,7 @@ LINKED = "MP" # 품셈재료 테이블ID MARKET = "MT" # 자재품목 테이블ID PRICED = "MO" # 유가전력 테이블ID — 값 한 칸 SLOTS = ("물가자료", "유통물가", "물가정보", "거래가격", "관급") # 자재품목 값 열 -PICK_KEYS = ("구분", "상세구분", "규격", "대표") # 재료 고르기 조건 칸 (`_틀.md` 7장) +PICK_KEYS = ("구분", "상세구분", "이름", "규격", "대표") # 재료 고르기 조건 칸 (`_틀.md` 7장) REGION = "자재지역" # 로직 입력 — 「지역」 은 유가 고르기가 씀 TARIFF = "전력 계약종별" # 로직 입력 — 품셈·계약예규에 계약종별 규정 없음 _UNITS = {"ea": "개", "ton": "톤", "t": "톤", "l": "ℓ", "리터": "ℓ", "set": "조", "세트": "조"} @@ -131,16 +131,18 @@ def by_class(master: mf.Master) -> dict[str, dict]: def candidates( master: mf.Master, cond: dict, env: dict | None = None, unit: str | None = None ) -> list[str]: - """고르기 조건 안 자재품목 키 — 구분 · 상세구분 · 규격 낱말이 모두 든 줄 · 파일 차례대로. + """고르기 조건 안 자재품목 키 — 구분 · 상세구분 · 이름 · 규격 낱말이 모두 든 줄 · 파일 차례대로. `unit` 을 주면 그 단위(호표 줄 단위)와 같은 줄만 — 단위가 다르면 다른 물건. 입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄만(그런 줄이 없으면 조건 안 전부).""" want = [plain_spec(w) for w in str(cond.get("규격") or "").split()] + want_name = [plain(w) for w in str(cond.get("이름") or "").split()] detail = cond.get("상세구분") need = plain_unit(unit) if unit else "" hits = [ key for key, row in by_class(master).get(cond.get("구분"), {}).items() if (not detail or row.get("상세구분") == detail) + and all(w in plain(row.get("이름")) for w in want_name) and all(w in plain_spec(row.get("규격")) for w in want) and (not need or plain_unit(row.get("단위")) == need) ] diff --git a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json index 66329ad6..292385a2 100644 --- a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json +++ b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json @@ -2484,7 +2484,12 @@ }, { "종류": "재료", - "요소": "MP:커플링밴드 ∅{관경}㎜", + "요소": { + "구분": "관·이음관", + "상세구분": "파형강관", + "이름": "파형강관 커플링밴드(관급)", + "규격": "Φ{관경}mm" + }, "이름": "커플링밴드", "단위": "EA", "수량": "만약(커플링밴드 == '필요', 1 / 6, 0)", diff --git a/resources/master_data/재료_품셈재료.json b/resources/master_data/재료_품셈재료.json index b406bdd8..0ac79155 100644 --- a/resources/master_data/재료_품셈재료.json +++ b/resources/master_data/재료_품셈재료.json @@ -35,17 +35,17 @@ { "키": "MP000024", "원문번호": "고무링 ∅800㎜", "이름": "고무링(VR관)", "규격": "∅800㎜", "단위": "개", "요구절": ["산림품셈 12-11-1"], "연결": "MT009721", "고르기": null, "비고": "연결: VR관고무링 D800 이름·규격 같음" }, { "키": "MP000025", "원문번호": "흄관 ∅800㎜", "이름": "흄관", "규격": "∅800㎜", "단위": "m", "요구절": ["산림품셈 12-11-2-1"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "흄관", "규격": "Φ800mm", "대표": "MT014981"}, "비고": "A·B형 갈림 · 값은 「본」 — m 아님" }, { "키": "MP000026", "원문번호": "파형강관 ∅800㎜", "이름": "파형강관", "규격": "∅800㎜", "단위": "m", "요구절": ["산림품셈 12-11-3"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "파형강관"}, "비고": "두께 1.6·2.0·2.7T 갈림 — 원문 없음" }, - { "키": "MP000027", "원문번호": "커플링밴드 ∅800㎜", "이름": "커플링밴드(파형강관)", "규격": "∅800㎜", "단위": "EA", "요구절": ["산림품셈 12-11-3 주①"], "연결": "MT015309", "고르기": null, "비고": "연결: 파형강관 커플링밴드 Φ800 관급 줄" }, + { "키": "MP000027", "원문번호": "커플링밴드 ∅800㎜", "이름": "커플링밴드(파형강관)", "규격": "∅800㎜", "단위": "EA", "요구절": ["산림품셈 12-11-3 주①"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "파형강관", "이름": "파형강관 커플링밴드(관급)", "규격": "Φ800mm", "대표": "MT015309"}, "비고": "일감35 — 규격(관경)만으론 접속티와 안 갈려 이름 조건을 더함" }, { "키": "MP000028", "원문번호": "VR관 ∅1000㎜", "이름": "VR관(소켓식)", "규격": "∅1000㎜", "단위": "m", "요구절": ["산림품셈 12-11-1"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "PVC관"}, "비고": "VR관 값은 「본」 — m 환산 안 함" }, { "키": "MP000029", "원문번호": "고무링 ∅1000㎜", "이름": "고무링(VR관)", "규격": "∅1000㎜", "단위": "개", "요구절": ["산림품셈 12-11-1"], "연결": "MT009723", "고르기": null, "비고": "연결: VR관고무링 D1000 이름·규격 같음" }, { "키": "MP000030", "원문번호": "흄관 ∅1000㎜", "이름": "흄관", "규격": "∅1000㎜", "단위": "m", "요구절": ["산림품셈 12-11-2-1"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "흄관", "규격": "Φ1000mm", "대표": "MT014983"}, "비고": "A·B형 갈림 · 값은 「본」 — m 아님" }, { "키": "MP000031", "원문번호": "파형강관 ∅1000㎜", "이름": "파형강관", "규격": "∅1000㎜", "단위": "m", "요구절": ["산림품셈 12-11-3"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "파형강관"}, "비고": "두께 1.6·2.0·2.7T 갈림 — 원문 없음" }, - { "키": "MP000032", "원문번호": "커플링밴드 ∅1000㎜", "이름": "커플링밴드(파형강관)", "규격": "∅1000㎜", "단위": "EA", "요구절": ["산림품셈 12-11-3 주①"], "연결": "MT015311", "고르기": null, "비고": "연결: 파형강관 커플링밴드 Φ1000 관급 줄" }, + { "키": "MP000032", "원문번호": "커플링밴드 ∅1000㎜", "이름": "커플링밴드(파형강관)", "규격": "∅1000㎜", "단위": "EA", "요구절": ["산림품셈 12-11-3 주①"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "파형강관", "이름": "파형강관 커플링밴드(관급)", "규격": "Φ1000mm", "대표": "MT015311"}, "비고": "일감35 — 규격(관경)만으론 접속티와 안 갈려 이름 조건을 더함" }, { "키": "MP000033", "원문번호": "VR관 ∅1200㎜", "이름": "VR관(소켓식)", "규격": "∅1200㎜", "단위": "m", "요구절": ["산림품셈 12-11-1"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "PVC관"}, "비고": "VR관 값은 「본」 — m 환산 안 함" }, { "키": "MP000034", "원문번호": "고무링 ∅1200㎜", "이름": "고무링(VR관)", "규격": "∅1200㎜", "단위": "개", "요구절": ["산림품셈 12-11-1"], "연결": "MT009725", "고르기": null, "비고": "연결: VR관고무링 D1200 이름·규격 같음" }, { "키": "MP000035", "원문번호": "흄관 ∅1200㎜", "이름": "흄관", "규격": "∅1200㎜", "단위": "m", "요구절": ["산림품셈 12-11-2-1"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "흄관", "규격": "Φ1200mm", "대표": "MT014985"}, "비고": "A·B형 갈림 · 값은 「본」 — m 아님" }, { "키": "MP000036", "원문번호": "파형강관 ∅1200㎜", "이름": "파형강관", "규격": "∅1200㎜", "단위": "m", "요구절": ["산림품셈 12-11-3"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "파형강관"}, "비고": "두께 1.6·2.0·2.7T 갈림 — 원문 없음" }, - { "키": "MP000037", "원문번호": "커플링밴드 ∅1200㎜", "이름": "커플링밴드(파형강관)", "규격": "∅1200㎜", "단위": "EA", "요구절": ["산림품셈 12-11-3 주①"], "연결": "MT015313", "고르기": null, "비고": "연결: 파형강관 커플링밴드 Φ1200 관급 줄" }, + { "키": "MP000037", "원문번호": "커플링밴드 ∅1200㎜", "이름": "커플링밴드(파형강관)", "규격": "∅1200㎜", "단위": "EA", "요구절": ["산림품셈 12-11-3 주①"], "연결": null, "고르기": {"구분": "관·이음관", "상세구분": "파형강관", "이름": "파형강관 커플링밴드(관급)", "규격": "Φ1200mm", "대표": "MT015313"}, "비고": "일감35 — 규격(관경)만으론 접속티와 안 갈려 이름 조건을 더함" }, { "키": "MP000038", "원문번호": "지수활제", "이름": "지수활제", "규격": "", "단위": "g", "요구절": ["산림품셈 12-11-1"], "연결": null, "고르기": null, "비고": "자재품목에 같은 품명 없음 · 고르기 없음(자재품목 묶음에 짝이 없음 — 관리자가 화면에서 이음)" }, { "키": "MP000039", "원문번호": "모르타르 1:2", "이름": "접합몰탈", "규격": "1:2", "단위": "㎥", "요구절": ["산림품셈 12-11-2-1", "건설품셈 건축 2-1-4", "건설품셈 건축 3-2-2"], "연결": null, "고르기": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:2"}, "비고": "자재품목에 같은 품명 없음" }, { "키": "MP000040", "원문번호": "스틸그레이팅", "이름": "집수정 뚜껑", "규격": "스틸그레이팅", "단위": "개", "요구절": ["산림품셈 12-15"], "연결": null, "고르기": null, "비고": "자재품목에 같은 품명 없음 · 고르기 없음(자재품목 그 묶음에 같은 단위·이름 줄이 없음)" }, diff --git a/resources/tester/test_masterdata_check.py b/resources/tester/test_masterdata_check.py index d8b8b4ef..06a2dea0 100644 --- a/resources/tester/test_masterdata_check.py +++ b/resources/tester/test_masterdata_check.py @@ -824,6 +824,40 @@ def test_재료_고르기_조건이_후보를_좁힘(): assert mm.candidates(whole, {"구분": "없는구분"}) == [] +def test_재료_고르기_이름_조건이_같은_규격_다른_품명을_가름(): + """일감35 — 규격(관경)만 같고 품명이 다른 줄(커플링밴드 vs 접속티)이 이름 칸으로 갈림.""" + files = { + "재료_자재품목.json": { + "줄": [ + { + "키": "MT900010", + "구분": "관·이음관", + "상세구분": "파형강관", + "이름": "파형강관 커플링밴드(관급)", + "규격": "Φ800mm", + "단위": "개", + "물가자료": None, + "관급": Decimal(19030), + }, + { + "키": "MT900011", + "구분": "관·이음관", + "상세구분": "파형강관", + "이름": "파형강관이음관(관급)-거양", + "규격": "접속티, Φ800mm, 아연도금", + "단위": "개", + "물가자료": None, + "관급": Decimal(216770), + }, + ] + } + } + whole = mf.Master(files) + cond = {"구분": "관·이음관", "상세구분": "파형강관", "규격": "Φ800mm"} + assert sorted(mm.candidates(whole, cond)) == ["MT900010", "MT900011"] # 이름 없으면 둘 다 걸림 + assert mm.candidates(whole, {**cond, "이름": "파형강관 커플링밴드(관급)"}) == ["MT900010"] + + def test_재료_고르기_시험계산은_대표_줄_없으면_값_있는_첫_줄(): whole = _pipes() cond = {"구분": "관·수로", "상세구분": "흄관", "규격": "D300"} @@ -917,8 +951,8 @@ def test_품셈재료_고르기_칸_검사(): def test_고르기_조건_글자_열쇠(): """화면 단가 미리보기가 조건 줄을 찾는 이름 — 빈 칸은 빈 글자.""" - assert mm.cond_text({"구분": "관·수로", "상세구분": "흄관"}) == "관·수로|흄관||" - assert mm.cond_text({"구분": "레미콘", "대표": "MT900002"}) == "레미콘|||MT900002" + assert mm.cond_text({"구분": "관·수로", "상세구분": "흄관"}) == "관·수로|흄관|||" + assert mm.cond_text({"구분": "레미콘", "대표": "MT900002"}) == "레미콘||||MT900002" def test_품셈재료_고르기_칸이_실파일에_있음(): @@ -1068,7 +1102,8 @@ def test_고르기_조건의_모르는_변수를_잡음(whole): def test_변수_참조_줄은_고르기_조건으로_바뀜(whole): - """일감24 — `MT:{철선}` 꼴 옛 참조 19줄을 고르기 조건으로 옮김(남은 2줄은 관·단위 사정).""" + """일감24 — `MT:{철선}` 꼴 옛 참조 19줄을 고르기 조건으로 옮김(남은 2줄 가운데 + 커플링밴드는 일감35 에서 「이름」 조건으로 옮겨 VR관 1줄만 남음(관·단위 사정).""" 남음 = [ (str(row["키"]), item["요소"]) for data in cm.load().values() @@ -1079,7 +1114,4 @@ def test_변수_참조_줄은_고르기_조건으로_바뀜(whole): and item["요소"][:2] in ("MP", "MT") and "{" in item["요소"] ] - assert sorted(남음) == [ - ("GF000173", "MP:VR관 ∅{관경}㎜"), - ("GF000176", "MP:커플링밴드 ∅{관경}㎜"), - ] + assert sorted(남음) == [("GF000173", "MP:VR관 ∅{관경}㎜")]