From cdaec36778690900499454796eab0cc80907ce2b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 14 Nov 2026 15:49:37 +0900 Subject: [PATCH] =?UTF-8?q?test(830-1):=20=EC=9E=AC=EB=A3=8C=20EST=20?= =?UTF-8?q?=EA=B7=B8=EB=8C=80=EB=A1=9C=20=EC=A7=80=ED=82=B4=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20=C2=B7=20=EA=B4=80=EA=B8=89=20=EA=B8=B0=EB=8C=80?= =?UTF-8?q?=EA=B0=92=20EST=20=EA=B0=92=20=C2=B7=20=EC=A1=B0=EB=8B=AC?= =?UTF-8?q?=EC=B2=AD=20=EA=B0=B1=EC=8B=A0=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=E3=80=8C=EC=A7=80=EA=B8=88=20=EC=A0=95=EB=B3=B8?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9=20=EC=95=88=20=ED=95=A8=E3=80=8D?= =?UTF-8?q?=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새 지킴 시험 test_830_1_material_est_state: ESTX 를 빌더 build() 로 지은 기준 표와 정본 대조(키 · 이름 · 규격 · 단위 · 값 다섯 · 면수 · 출처) · 조달청 줄 0 · ESTX 밖 줄 = 품셈재료 119 · 키대장 = 표 · 열 한 벌 - test_masterdata_check 자재품목 열 한 벌: H 형강(관급) 관급 기대값 조달청 1,301,590 → EST 1,228,830 · 비고 「조달 26.」 · 조달청 제조사별 최저가 줄 확인(MT001637)은 뺌(조달청 값이 정본에 없음) - update_관급단가*.py 5 · ref/_설계_관급단가_갱신.md · _갱신절차.md 관급 절 머리에 「지금 정본에는 적용 안 함 — 요구 822」 (걷을지는 사용자 결정 뒤) Co-Authored-By: Claude Opus 5.5 --- resources/master_data/_갱신절차.md | 2 + .../master_data/ref/_설계_관급단가_갱신.md | 2 + .../master_data/scripts/update_관급단가.py | 2 + .../scripts/update_관급단가_기타.py | 2 + .../scripts/update_관급단가_느슨.py | 2 + .../scripts/update_관급단가_보강.py | 2 + .../scripts/update_관급단가_합침.py | 2 + .../tester/test_830_1_material_est_state.py | 86 +++++++++++++++++++ resources/tester/test_masterdata_check.py | 9 +- 9 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 resources/tester/test_830_1_material_est_state.py diff --git a/resources/master_data/_갱신절차.md b/resources/master_data/_갱신절차.md index e84ccc74b..91e951e74 100644 --- a/resources/master_data/_갱신절차.md +++ b/resources/master_data/_갱신절차.md @@ -38,6 +38,8 @@ ## 관급 (자재품목 「관급」 열) +- 지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로). + | 항목 | 값 | | --- | --- | | 출처 | 조달청 종합쇼핑몰 OpenAPI(15129471) · 원본 `원가계산/자재단가/관급/<받은 날>/*.json.gz` · 설계 `ref/_설계_관급단가_갱신.md` | diff --git a/resources/master_data/ref/_설계_관급단가_갱신.md b/resources/master_data/ref/_설계_관급단가_갱신.md index 91ea16db0..fe871aeec 100644 --- a/resources/master_data/ref/_설계_관급단가_갱신.md +++ b/resources/master_data/ref/_설계_관급단가_갱신.md @@ -1,5 +1,7 @@ # 설계 — 관급 단가 갱신 (조달청 → 자재품목 「관급」 열) +> 지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로). + ## 결론 - **관급 단가는 조달청 종합쇼핑몰 API 3종에서 「분류명 부분 글자」로 받을 수 있음** — 레미콘·아스콘은 「조합공통품목」 줄이 지역별 관급 단가(다산소프트 관급 열과 같은 것). 인증키 = 기존 공공데이터포털 일반 인증키 하나(활용신청 끝남). diff --git a/resources/master_data/scripts/update_관급단가.py b/resources/master_data/scripts/update_관급단가.py index bb041a1d8..a8e6717b5 100644 --- a/resources/master_data/scripts/update_관급단가.py +++ b/resources/master_data/scripts/update_관급단가.py @@ -1,5 +1,7 @@ """관급 단가 갱신 — 조달청 종합쇼핑몰 API 에서 받아 자재품목(재료_자재품목) 「관급」 열을 채움. +지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로). + 다시 돌리는 법 (한 줄) — 받기 + 정본 관급 열 갱신 + 바뀐 줄 목록: ./venv/Scripts/python.exe resources/master_data/scripts/update_관급단가.py update diff --git a/resources/master_data/scripts/update_관급단가_기타.py b/resources/master_data/scripts/update_관급단가_기타.py index 0ca402ae9..33a228b9a 100644 --- a/resources/master_data/scripts/update_관급단가_기타.py +++ b/resources/master_data/scripts/update_관급단가_기타.py @@ -1,5 +1,7 @@ """관급 단가 — 「그 밖」 갈래 짝짓기(주철관 · 조경석 · 골재 · 시멘트 · 이음관 …) — `update_관급단가.py` 가 부름. +지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로). + 짝 = 자재품목 규격의 숫자 조각(치수·종별)이 조달청 품명·규격에 모두 있고, 글자 조각(제품·산지)도 모두 있을 때. 이름 쪽 글자는 점수만(같은 값이면 더 많이 맞는 줄 먼저). 지역이 이름에 있으면 그 지역 줄만. 여럿이면 가장 낮은 값(호출한 쪽 `pick`). """ diff --git a/resources/master_data/scripts/update_관급단가_느슨.py b/resources/master_data/scripts/update_관급단가_느슨.py index 299e09161..57301a344 100644 --- a/resources/master_data/scripts/update_관급단가_느슨.py +++ b/resources/master_data/scripts/update_관급단가_느슨.py @@ -1,5 +1,7 @@ """관급 단가 — 느슨한 두 번째 짝짓기(`update_관급단가.py` 가 엄격한 짝이 안 서면 부름). +지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로). + 엄격한 짝짓기가 못 세운 줄만 다시 봄 — 같은 갈래 안에서 규격 글자를 느슨하게 맞춰 후보를 찾고, **값이 잡히면 다산소프트 옛 값과 ±5% 안일 때만** 채움(밖이면 안 채움 · 까닭에 조달청 값). · 후보가 같은 규격 여럿이면 최저가(제조사 비고) — 최저가가 ±5% 밖이고 후보가 등급·형이 달라 diff --git a/resources/master_data/scripts/update_관급단가_보강.py b/resources/master_data/scripts/update_관급단가_보강.py index eb23f6e0c..0045846f1 100644 --- a/resources/master_data/scripts/update_관급단가_보강.py +++ b/resources/master_data/scripts/update_관급단가_보강.py @@ -1,5 +1,7 @@ """관급 보강 — 다산소프트에 없는 자재를 조달청 종합쇼핑몰에서 더 받아 후보 파일로 모음. +지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로). + ./venv/Scripts/python.exe resources/master_data/scripts/update_관급단가_보강.py fetch 받기 → 지식DB 관급/<날짜>/보강/ ./venv/Scripts/python.exe resources/master_data/scripts/update_관급단가_보강.py build 후보 → ref/_관급_보강_후보.json diff --git a/resources/master_data/scripts/update_관급단가_합침.py b/resources/master_data/scripts/update_관급단가_합침.py index 41e9915fa..70b7448cb 100644 --- a/resources/master_data/scripts/update_관급단가_합침.py +++ b/resources/master_data/scripts/update_관급단가_합침.py @@ -1,5 +1,7 @@ """관급 보강 합치기 — `ref/_관급_보강_후보.json`(조달청 후보) → 자재품목 (`update_관급단가.py apply` 가 부름). +지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로). + (1) 다산소프트에 같은 품명이 있는 것 — 그 줄의 치수와 같은 조달청 후보의 최저가를 관급 열에 채움(치수가 안 맞으면 비움) (2) 다산소프트에 없는 품명 가운데 NEW 목록의 갈래만 — 새 줄(관급만 · 물가지 네 열 null) 나머지 후보(그레이팅 · 맨홀 · 수로 · 거푸집 · 가설재 · 철선류 …)는 더하지 않음. diff --git a/resources/tester/test_830_1_material_est_state.py b/resources/tester/test_830_1_material_est_state.py new file mode 100644 index 000000000..67b0dd03d --- /dev/null +++ b/resources/tester/test_830_1_material_est_state.py @@ -0,0 +1,86 @@ +"""830-1 재료 표 = EST 그대로(요구 822) — ESTX 원본을 빌더 build() 로 지은 기준 표와 정본 대조. + +조달청 줄 · 조달청 관급 값 없음 · EST 줄 키 그대로 · 키대장 = 표 · 열 한 벌 그대로. +기준과 달라도 되는 것(㉯) = 품셈재료에서 옮긴 줄(출처 「품셈재료 …」) · 분류가 단 비고 「이름 없음 …」. +""" + +import json +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "resources/master_data/scripts")) + +import build_자재품목_분류 as cls # noqa: E402 +import build_재료_자재품목 as builder # noqa: E402 +import compare_estx as ce # noqa: E402 +import master_keys as mk # noqa: E402 + +MASTER = ROOT / "resources/master_data" +COLS = ( + "키", + "구분", + "상세구분", + "이름", + "규격", + "단위", + "물가자료", + "유통물가", + "물가정보", + "거래가격", + "관급", + "출처", + "비고", + "면수", +) +VALUES = ("물가자료", "유통물가", "물가정보", "거래가격", "관급") +SAME = ("이름", "규격", "단위", *VALUES, "면수", "출처") + + +@pytest.fixture(scope="module") +def t(): + if not ce.ESTX.is_file(): + pytest.skip("ESTX 원본 없음") + book = mk.load_book() + by = {v["원문번호"]: k for k, v in book["키"].items() if k.startswith("MT")} + base = {by[r["열쇠"]]: r for r in builder.build(ce.Estx().table("StuffcDat"))} + rows = json.loads((MASTER / "재료_자재품목.json").read_text(encoding="utf-8"))["줄"] + return base, {r["키"]: r for r in rows}, rows, book + + +def test_EST_줄_키_그대로_다섯_값_같음(t): + base, cur, _, _ = t + assert len(base) == 23973 and set(base) <= set(cur) + assert [(k, c) for k in base for c in SAME if base[k][c] != cur[k][c]] == [] + note = [k for k in base if base[k]["비고"] != cur[k]["비고"]] + assert all(cur[k]["비고"] == cls.NOTE_EMPTY and not cur[k]["이름"] for k in note) + + +def test_조달청_줄_값_없음(t): + _, cur, _, _ = t + assert not [k for k, r in cur.items() if "조달청" in str(r["출처"])] + assert not [k for k, r in cur.items() if str(r["비고"] or "").startswith("관급:")] + assert all("관급" in r["이름"] for r in cur.values() if r["관급"] is not None) + + +def test_EST_밖_줄은_품셈재료만(t): + base, cur, _, _ = t + extra = [r for k, r in cur.items() if k not in base] + assert len(extra) == 119 + assert all(str(r["출처"]).startswith(builder.CARRY) for r in extra) + assert all(r[c] is None for r in extra for c in VALUES) + + +def test_키대장_표와_맞음_번호_재사용_없음(t): + _, cur, _, book = t + mt = {k for k, v in book["키"].items() if v["파일"] == "재료_자재품목.json"} + assert mt == set(cur) + assert book["다음"]["MT"] > max(int(k[2:]) for k in cur) + assert not set(cur) & set(book.get("폐기", {})) + + +def test_열_한_벌(t): + _, _, rows, _ = t + assert {tuple(r) for r in rows} == {COLS} diff --git a/resources/tester/test_masterdata_check.py b/resources/tester/test_masterdata_check.py index e70764286..f56df4954 100644 --- a/resources/tester/test_masterdata_check.py +++ b/resources/tester/test_masterdata_check.py @@ -724,19 +724,14 @@ def test_전력은_계약종별로_고름(whole): def test_자재품목_열_한_벌(): - """모든 줄이 같은 열 한 벌(원문번호 없음) · 관급은 조달 값 · 기간은 비고 · 남은 가격정보는 비고에만.""" + """모든 줄이 같은 열 한 벌(원문번호 없음) · 관급은 EST 원문 값(요구 822) · 기간은 비고 · 남은 가격정보는 비고에만.""" data = cm.load(["재료_자재품목.json"])["재료_자재품목.json"] rows = {r["키"]: r for r in data["줄"]} assert {tuple(r) for r in rows.values()} == {cm.COLS["자재품목"]} assert "원문번호" not in cm.COLS["자재품목"] # 남의 품번 — 대조는 대장에만 steel = rows["MT001658"] - assert steel["관급"] == Decimal(1301590) and steel["비고"].startswith( - "관급: 조달청 다수공급자계약" - ) + assert steel["관급"] == Decimal(1228830) and steel["비고"].startswith("조달 26.") assert all(steel[s] is None for s in ("물가자료", "유통물가", "물가정보", "거래가격")) - # 제조사별로 값이 갈린 줄 — 같은 규격 가운데 최저가와 그 제조사를 비고에 - low = rows["MT001637"] - assert low["관급"] == Decimal(1228830) and "최저" in low["비고"] left = [r for r in rows.values() if str(r["비고"] or "").startswith("가격정보")] assert len(left) == 67 and all(r["관급"] is None for r in left) # 유류 다섯 줄(값이 비고 글뿐)은 뗌 — 대장에 폐기로 남음