diff --git a/resources/master_data/ref/_관리자값_미확보.md b/resources/master_data/ref/_관리자값_미확보.md index d25b6776..aff1862e 100644 --- a/resources/master_data/ref/_관리자값_미확보.md +++ b/resources/master_data/ref/_관리자값_미확보.md @@ -1,6 +1,6 @@ # 관리자가 채울 재료 값 (읽기 전용 · 자동 생성) -일괄 시험 계산(`check_master 계산전부`)이 「값 없음 — 관리자가 채울 값」 으로 멈춘 자리 — 재료 45 종 · 로직 56 개. +일괄 시험 계산(`check_master 계산전부`)이 「값 없음 — 관리자가 채울 값」 으로 멈춘 자리 — 재료 39 종 · 로직 49 개. 값을 어디서 가져올지(자재품목 줄 잇기 · 시중물가)는 관리자 몫 — 여기서는 이름만 모음. @@ -10,16 +10,13 @@ | MP000290 | Nozzle | 3 | GC000933 · GC000934 · GC000938 | | MP000293 | 세유 | 3 | GC000939 · GC000986 · GC000993 | | MP000048 | JOINT FILLER | 2 | GF000197 · GF000208 | -| MP000094 | 쉬즈관 | 2 | GC000230 · GC000237 | | MP000216 | 철구(Shot ball) | 2 | GC000375 · GC000822 | | MP000299 | 탄소봉 | 2 | GC000941 · GC000942 | | MP000359 | 중유 | 2 | GC000943 · GC000948 | -| MP000023 | VR관(소켓식) | 1 | GF000173 | | MP000052 | 스페이셔(몰탈 블록) | 1 | GF000200 | | MP000055 | 스티로폴 | 1 | GF000202 | | MP000060 | 콘크리트 표면 강화재(하드너) | 1 | GF000209 | | MP000065 | 틈메우기돌 | 1 | GF000223 | -| MP000066 | 철망태(사각형 돌망태) | 1 | GF000235 | | MP000070 | 깃발 | 1 | GF000081 | | MP000076 | 피복제 | 1 | GC000145 | | MP000082 | 에폭시 | 1 | GC000337 | @@ -34,10 +31,8 @@ | MP000176 | 표준보강메시 | 1 | GC000628 | | MP000177 | 고강도메시 | 1 | GC000629 | | MP000201 | 실리콘 | 1 | GC000752 | -| MP000202 | IV전선 | 1 | GC000409 | | MP000209 | 버너 | 1 | GC000360 | | MP000211 | 테르밋용재 | 1 | GC000361 | -| MP000215 | 용접봉 | 1 | GC000374 | | MP000218 | 철편(Grit) | 1 | GC000376 | | MP000235 | 슈 | 1 | GC000433 | | MP000239 | 베인 전용로드 | 1 | GC000435 | @@ -49,7 +44,6 @@ | MP000310 | Tar Epoxy | 1 | GC000944 | | MP000314 | drill | 1 | GC000947 | | MP000319 | Tar Epoxy | 1 | GC000950 | -| MP000357 | 카트리지 | 1 | GC000646 | | MP000363 | 아스팔트싱글 | 1 | GC000654 | ## 값 말고 다른 까닭으로 남은 멈춤 (손대지 않음) @@ -66,3 +60,18 @@ - 자재품목에 후보는 있으나 규격·지역 선택이 관리자 몫이라 못 이은 것 3건 — MP000023 VR관(소켓식, 후보 있음·단위 본→m 환산 안 함) · MP000066 철망태(사각형 돌망태, 후보 있음·단위 조→㎥ 환산 안 함) · MP000215 용접봉(강교 제작용, 후보 12줄·규격 다수). - 나머지 42건 — 자재품목 전체(30,592줄) · 재료_유가전력.json(오피넷 휘발유·경유·등유·한전 전력 56줄) 다시 훑어도 짝 없음 확인(바뀐 것 없음). 코크스·세유·중유·그리스·머신오일 계열은 해당 상세구분(잡재료/용접·가스 등) 통째로 훑어도 없음. - 오피넷 홈페이지(중유·벙커C유) 검색 — 시세표가 동적 페이지라 검색 결과에 실제 값이 안 잡힘 · 값을 지어내지 않아 그대로 둠. + +## 2026-09-22 로직이 실제로 부르는지 다시 잣대질(로직이 안 쓰는 6건 표에서 뺌) + +`check_값없는줄.py` — 로직 1,354 줄을 다시 훑어 위 표 45종이 지금도 로직 호표에서 그 MP 키를 부르는지 확인. 6건은 더 안 불러 뺌. + +| 키 | 이름 | 까닭 | +|---|---|---| +| MP000094 | 쉬즈관 | GC000230·GC000237 호표에서 재료 줄 자체가 빠짐 — 로직이 이제 재료를 안 씀 | +| MP000023 | VR관(소켓식) | GF000173 고무링이 재료 고르기 조건(관·이음관·PVC관)으로 바뀌어 이 MP 줄을 더 안 부름 | +| MP000066 | 철망태(사각형 돌망태) | GF000235 철망태·채움재가 고르기 조건(매트·토목섬유·돌망태·사방)으로 바뀜 | +| MP000202 | IV전선 | GC000409 IV전선이 고르기 조건(전기·통신·케이블·전선)으로 바뀜 | +| MP000215 | 용접봉 | GC000374 용접봉(강교 제작)이 고르기 조건(잡재료·용접·가스)으로 바뀜 | +| MP000357 | 카트리지 | GC000646 카트리지가 고르기 조건(볼트·앵커·철물·앵커, 대표 MT007751 — 그 줄은 값 있음)으로 바뀜 | + +나머지 39종·로직 49개는 그대로 관리자 몫. diff --git a/resources/master_data/ref/_분석_값없는줄.md b/resources/master_data/ref/_분석_값없는줄.md new file mode 100644 index 00000000..9396c251 --- /dev/null +++ b/resources/master_data/ref/_분석_값없는줄.md @@ -0,0 +1,53 @@ +# 값 없는 줄 분석 — 다섯 칸이 모두 빈 줄 가운데 로직이 실제로 쓰는 것 + +읽기 전용 분석 · 값 새로 안 채움 · 뽑은 법 `scripts/check_값없는줄.py`(로직 1,354 줄을 훑어 실제 참조를 셈). + +문제 삼는 자리 = **다섯 칸(물가자료·유통물가·물가정보·거래가격·관급)이 하나도 없는 줄** 뿐 — 다섯 칸 가운데 일부만 있으면 문제 아님. + +## ① 재료_자재품목.json 다섯 칸 모두 빈 줄 + +MT 30,592 줄 가운데 **345 줄**이 다섯 칸 모두 없음. + +## ② 그 가운데 로직이 실제로 가리키는 줄 — 진짜 구멍 + +호표 요소가 그 키를 바로 적거나 · 재료 고르기 조건(구분·상세구분·규격)의 후보로 걸리거나 · 대표 줄로 콕 집힌 것만 셈 — 345 줄 가운데 **31 줄**. + +## ③ 관리 파일 두 개 다시 잣대질(같은 방법으로 재확인) + +- `ref/_관리자값_미확보.md`(품셈재료 연결 안 된 45종) — 재검증 결과 **6종**이 이제 로직이 안 씀(고르기 조건으로 바뀌었거나 그 로직 자체가 재료 줄을 뺌) → 표에서 빼고 「로직이 안 씀」 절로 옮김. 남은 **39종·로직 49개**는 그대로 관리자 몫. +- `ref/_없는_재료.md`(자재품목에 짝 없는 품셈재료 111줄) — 재검증 결과 **111줄 모두 지금도 로직이 부름** → 뺄 줄 없음. + +두 파일에 각각 「2026-09-22 로직이 실제로 부르는지 다시 잣대질」 절로 적어 둠. + +## ④ 끝 수로 · 재료 이름 목록(20개까지) + +- 다섯 칸 모두 빈 줄 — **345** +- 그 가운데 로직이 쓰는 줄(진짜 구멍) — **31** +- 이름 목록(20개, 나머지 11개는 스크립트 재실행으로 확인): + +| 키 | 이름 | 구분·상세구분 | +|---|---|---| +| MT000009 | 치 즐(0.4㎥) | 기계·설비·기계류 | +| MT000011 | 치 즐(0.7㎥) | 기계·설비·기계류 | +| MT000519 | 시스템동바리(멍애제) | 거푸집·가설재·동바리·비계 | +| MT000521 | 수직재 | 거푸집·가설재·동바리·비계 | +| MT000522 | 수직재 | 거푸집·가설재·동바리·비계 | +| MT000523 | 수평재 | 거푸집·가설재·동바리·비계 | +| MT000524 | 수평재 | 거푸집·가설재·동바리·비계 | +| MT000527 | 안전발판 | 거푸집·가설재·동바리·비계 | +| MT000528 | 받침철물 | 볼트·앵커·철물·와이어로프·클램프 | +| MT000529 | 비계버팀대 | 거푸집·가설재·동바리·비계 | +| MT000531 | 수평재 | 거푸집·가설재·동바리·비계 | +| MT000533 | 안전발판 | 거푸집·가설재·동바리·비계 | +| MT003480 | 필러 | 시멘트·골재·모르타르 | +| MT003850 | 자연자갈, 대전, 도착도 | 시멘트·골재·골재 | +| MT003860 | 쇄석골재, 가평, 도착도 | 시멘트·골재·골재 | +| MT004059 | 혼합골재, 가평, 도착도 | 시멘트·골재·골재 | +| MT005950 | 원타임Ⅰ | 도료·방수·접착·접착·혼화 | +| MT007609 | 낙방사다리 | 거푸집·가설재·동바리·비계 | +| MT007751 | 카트리지 | 볼트·앵커·철물·앵커 | +| MT007754 | 기포제 | 도료·방수·접착·접착·혼화 | + +거푸집·가설재(동바리·비계) 묶음이 절반 가까이 — 시스템동바리 계열 자재품목 줄에 값이 통째로 안 채워진 자리로 보임. 나머지는 토목자재 낱개. + +값 확정은 관리자 몫 — 여기서는 찾아서 셈만 함. diff --git a/resources/master_data/ref/_없는_재료.md b/resources/master_data/ref/_없는_재료.md index 70ca645e..bea7123c 100644 --- a/resources/master_data/ref/_없는_재료.md +++ b/resources/master_data/ref/_없는_재료.md @@ -128,3 +128,7 @@ - 자재품목에 후보는 있으나 규격·지역 선택이 관리자 몫이라 못 이은 것 5건 — MP000078 색소(후보 1줄 MT006740) · MP000082 에폭시(포켓식 후보 4줄) · MP000209 버너(후보 1줄 MT009352) · MP000214 퓨즈(후보 5줄) · MP000285 땜납(후보 2줄 MT001251·MT001252, 원문 규격 50:50과는 안 맞음). - 나머지 106건 — 자재품목 전체(30,592줄) 다시 훑어도(이름 겹침만·규격 대조 포함) 짝 없음 확인 · `_검증_재료_공공출처.md` 의 2022 하반기 조달청 시설자재 대조(에폭시·보호막·실리콘·노즐·아스팔트싱글 이름 겹침)도 지금 자재품목에는 없음(그 옛 자료는 새 마스터에 아직 안 들어옴). - 오피넷·한전 — 중유·코크스·세유·머신오일 계열은 재료_유가전력.json(오피넷 휘발유·경유·등유, 한전 전력 56줄) 어디에도 없음 · 오피넷 홈페이지 검색은 동적 페이지라 실제 값을 못 얻어 지어내지 않고 그대로 둠. + +## 2026-09-22 로직이 실제로 부르는지 다시 잣대질 + +`check_값없는줄.py` — 로직 1,354 줄을 다시 훑어 위 표 111줄이 지금도 로직 호표에서 그 MP 키를 부르는지 확인. 111줄 모두 지금도 부름 — 뺄 줄 없음. diff --git a/resources/master_data/scripts/check_값없는줄.py b/resources/master_data/scripts/check_값없는줄.py new file mode 100644 index 00000000..6182c96b --- /dev/null +++ b/resources/master_data/scripts/check_값없는줄.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +"""재료_자재품목.json 다섯 값 칸이 모두 빈 줄 가운데 로직이 실제로 가리키는 줄 — 읽기 전용 분석. + + · 「빈 줄」 = 물가자료·유통물가·물가정보·거래가격·관급 다섯 칸이 모두 없음 + · 「로직이 가리킴」 = 로직 호표 요소가 그 키를 바로 적음 · 재료 고르기 조건(구분·상세구분·규격)의 + 후보 줄 · 대표 줄로 콕 집음 · 품셈재료 연결(문자열 키 · 지역 조건)을 따라간 줄 + · 지역 조건(모래·자갈 등)은 실제 프로젝트 자재지역을 모르므로 지역을 안 가리고 이름·단위만 맞춤 + (후보를 넓게 잡아 「안 쓰임」 판정이 헛짚지 않게) + · 값은 채우지 않음 · 마스터 파일을 고치지 않음 + +돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/check_값없는줄.py +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +MASTER = HERE.parent +sys.path.insert(0, str(HERE)) + +import check_master as cm # noqa: E402 +import master_material as mm # noqa: E402 + +PRICE = ("물가자료", "유통물가", "물가정보", "거래가격", "관급") + +# 두 ref 표의 키 목록 — 로직이 지금도 부르는지 다시 잣대질할 때 씀 (`_관리자값_미확보.md`·`_없는_재료.md`) +ADMIN_KEYS = ( + "MP000253 MP000290 MP000293 MP000048 MP000094 MP000216 MP000299 MP000359 " + "MP000023 MP000052 MP000055 MP000060 MP000065 MP000066 MP000070 MP000076 MP000082 " + "MP000095 MP000113 MP000129 MP000134 MP000144 MP000150 MP000169 MP000172 MP000176 " + "MP000177 MP000201 MP000202 MP000209 MP000211 MP000215 MP000218 MP000235 MP000239 " + "MP000241 MP000242 MP000250 MP000269 MP000294 MP000310 MP000314 MP000319 MP000357 " + "MP000363" +).split() +NO_ITEM_KEYS = ( + "MP000012 MP000038 MP000048 MP000052 MP000055 MP000056 MP000057 MP000060 " + "MP000065 MP000070 MP000076 MP000077 MP000078 MP000082 MP000095 MP000096 MP000101 " + "MP000113 MP000129 MP000134 MP000139 MP000144 MP000148 MP000150 MP000169 MP000172 " + "MP000176 MP000177 MP000201 MP000209 MP000210 MP000211 MP000212 MP000213 MP000214 " + "MP000216 MP000218 MP000220 MP000221 MP000223 MP000235 MP000239 MP000240 MP000241 " + "MP000242 MP000250 MP000253 MP000254 MP000255 MP000257 MP000258 MP000259 MP000260 " + "MP000261 MP000262 MP000265 MP000269 MP000271 MP000272 MP000273 MP000274 MP000275 " + "MP000276 MP000280 MP000281 MP000285 MP000286 MP000287 MP000288 MP000290 MP000293 " + "MP000294 MP000295 MP000296 MP000297 MP000298 MP000299 MP000301 MP000302 MP000303 " + "MP000304 MP000305 MP000306 MP000308 MP000309 MP000310 MP000314 MP000315 MP000319 " + "MP000325 MP000326 MP000327 MP000328 MP000330 MP000334 MP000335 MP000336 MP000337 " + "MP000338 MP000340 MP000341 MP000342 MP000343 MP000344 MP000345 MP000349 MP000351 " + "MP000352 MP000353 MP000359 MP000363" +).split() + + +def all_empty(master) -> set[str]: + """MT 다섯 값 칸이 모두 빈 키.""" + return { + key for key, row in master.index.get(mm.MARKET, {}).items() if not mm._offers(master, key) + } + + +def region_reach(master, name, unit) -> set[str]: + """MP 연결이 지역 조건(모래·자갈 등)일 때 이름 뿌리·단위만으로 넓게 잡은 후보.""" + want, need = mm.plain(name), (mm.plain_unit(unit) if unit else "") + return { + key + for root, _, _, u, key in mm._market_index(master) + if root.endswith(want) and (not need or u == need) + } + + +def reached(master) -> tuple[set[str], set[str]]: + """(로직이 가리키는 MT 키, 로직이 지금도 문자열로 부르는 MP 키).""" + mt_hit: set[str] = set() + mp_hit: set[str] = set() + for path in sorted(MASTER.glob("로직_*.json")): + rows = json.loads(path.read_text(encoding="utf-8")).get("줄", []) + for row in rows: + for h in row.get("호표") or []: + e, unit = h.get("요소"), h.get("단위") + if isinstance(e, str) and e[:2] == mm.MARKET: + mt_hit.add(e) + elif isinstance(e, dict) and "구분" in e: + mt_hit.update(mm.candidates(master, e, None, unit)) + if e.get("대표"): + mt_hit.add(str(e["대표"])) + elif isinstance(e, str) and e[:2] == mm.LINKED: + mp_hit.add(e) + link = master.index.get(mm.LINKED, {}).get(e, {}).get("연결") + if isinstance(link, str) and link[:2] == mm.MARKET: + mt_hit.add(link) + elif isinstance(link, dict) and "계약종별" not in link: + mrow = master.index[mm.LINKED][e] + mt_hit.update(region_reach(master, link.get("이름"), mrow.get("단위"))) + return mt_hit, mp_hit + + +def main() -> int: + master = cm.master() + empty = all_empty(master) + mt_hit, mp_hit = reached(master) + gap = sorted(empty & mt_hit) + print(f"MT 줄 {len(master.index.get(mm.MARKET, {}))}") + print(f"다섯 칸 모두 빈 줄 {len(empty)} · 그 가운데 로직이 가리키는 줄 {len(gap)}") + for key in gap: + row = master.get(key) + print(f" {key} {row.get('이름')} ({row.get('구분')}·{row.get('상세구분')})") + print(f"\n로직이 지금도 MP 키를 문자열로 부르는 곳 {len(mp_hit)}") + for title, keys in ( + ("_관리자값_미확보.md 45종", ADMIN_KEYS), + ("_없는_재료.md 111줄", NO_ITEM_KEYS), + ): + unused = [k for k in keys if k not in mp_hit] + print(f"{title} 가운데 로직이 이제 안 씀 {len(unused)} — {unused}") + return 0 + + +if __name__ == "__main__": + sys.stdout.reconfigure(encoding="utf-8") + sys.exit(main())