diff --git a/resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/MASCntrct_플룸.json.gz b/resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/MASCntrct_플룸.json.gz new file mode 100644 index 00000000..168abbcc Binary files /dev/null and b/resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/MASCntrct_플룸.json.gz differ diff --git a/resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/_meta.md b/resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/_meta.md index 32c34047..72d20b6d 100644 --- a/resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/_meta.md +++ b/resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/_meta.md @@ -46,5 +46,6 @@ | MASCntrct_비료.json.gz | getMASCntrctPrdctInfoList | 비료 | 101 | | MASCntrct_압륜.json.gz | getMASCntrctPrdctInfoList | 압륜 | 142 | | ThptyUcntrct_부직포.json.gz | getThptyUcntrctPrdctInfoList | 부직포 | 8 | -| MASCntrct_경질폴리염화비닐관.json.gz | getMASCntrctPrdctInfoList | 경질폴리염화비닐관 | 949 (totalCount 949) | -| ThptyUcntrct_경질폴리염화비닐관.json.gz | getThptyUcntrctPrdctInfoList | 경질폴리염화비닐관 | 330 (totalCount 330) | +| MASCntrct_경질폴리염화비닐관.json.gz | getMASCntrctPrdctInfoList | 경질폴리염화비닐관 | 949 | +| ThptyUcntrct_경질폴리염화비닐관.json.gz | getThptyUcntrctPrdctInfoList | 경질폴리염화비닐관 | 330 | +| MASCntrct_플룸.json.gz | getMASCntrctPrdctInfoList | 플룸 | 2,640 (totalCount 2,640) | diff --git a/resources/master_data/ref/_설계_관급단가_갱신.md b/resources/master_data/ref/_설계_관급단가_갱신.md index b6fcd6db..cbe87c80 100644 --- a/resources/master_data/ref/_설계_관급단가_갱신.md +++ b/resources/master_data/ref/_설계_관급단가_갱신.md @@ -6,7 +6,7 @@ - 지난번 「시설공통자재 6,999줄」(`PriceInfoService`)에는 관급 자재가 없음 — 잘못 고른 API. - **값 비교 기준 = 다산소프트 값 ±5% 안이면 「맞음」**(받는 시점이 달라 생기는 차이). - **제조사별로 값이 갈리면 같은 규격의 가장 낮은 값**을 관급 열에 넣고 비고에 그 제조사(「관급: 조달청 다수공급자계약 <제조사> 최저 <날짜> · …」) — 줄·열은 안 늘림. 보류 394 → 0. -- 정본 `재료_자재품목.json` 관급 열: 옛 4,552줄(다산소프트 관급 값이 있던 줄) 중 **채움 4,057(89%) · null 495**(맞음 3,442 · 5% 넘게 다름 615 · 못 채움 495) + **조달청에서 새로 들인 줄 380**(모두 채움) = 4,932줄 중 **채움 4,437 · null 495**. 다산 값은 남기지 않음. 못 채운 495줄은 비고에 까닭(「조달청 종합쇼핑몰에 없음」 · 「짝 못 지음」). +- 정본 `재료_자재품목.json` 관급 열: 옛 4,552줄(다산소프트 관급 값이 있던 줄) 중 **채움 4,197(92%) · null 355**(맞음 3,461 · 5% 넘게 다름 736 · 못 채움 355) + **조달청에서 새로 들인 줄 380**(모두 채움) = 4,932줄 중 **채움 4,577 · null 355**. 다산 값은 남기지 않음. 못 채운 355줄은 비고에 까닭(「조달청 종합쇼핑몰에 없음」 · 「짝 못 지음」). - 다시 돌리기 — `./venv/Scripts/python.exe resources/master_data/scripts/update_관급단가.py update` ## 조달청 API (공공데이터포털 15129471 `at/ShoppingMallPrdctInfoService`) @@ -21,7 +21,7 @@ - 조건 = `inqryDiv=2` + `prdctClsfcNoNm=<글자>` — **부분 글자 조회**(분류 통째가 아님). 「철근」은 암거블록·배수로가 섞여 옴 → 「봉강」(철근콘크리트용봉강) · H형강은 「H빔」 · 「형강」은 파형강관이 걸림. - 응답 칸(쓰는 것) — `prdctSpecNm`(품명·규격) · `cntrctPrceAmt`(단가·VAT 포함) · `prdctSplyRgnNm`(공급지역) · `prdctDlvryCndtnNm`(인도조건) · `prdctMakrNm`(「조합공통품목」 = 조합 공통 관급단가) · `cntrctCorpNm`(계약 조합·업체) · `cntrctBgnDate`·`cntrctEndDate`(계약 기간) · `prdctUnit`. - 갱신 주기 — 계약 기간 단위(레미콘 조합공통 2026-06-01~, 아스콘 2026-08-01~ 등) · 일별 조회 · 쪽당 999 · 인증키 일일 호출 한도 안. -- 이번에 찾음(2차) — 시멘트 · 골재 · 조경석 · 주철관 · 이음관 · 측구수로관 · 볼라드 · 맨홀 · 용접철망 · 돌망태 · 매트. 조달청에 같은 품명이 없어 못 이음 — 벤치플륨 · 무게수로관 · HI-VP(다산 이름 그대로는 분류 없음). +- 이번에 찾음(2차) — 시멘트 · 골재 · 조경석 · 주철관 · 이음관 · 측구수로관 · 볼라드 · 맨홀 · 용접철망 · 돌망태 · 매트. 이름이 달라 처음엔 못 이음 → 4차에 조달청 표기로 이음 — 벤치플륨(= 벤치플룸관) · 소형고압블록 · 무게수로관. 아직 없음 — HI-VP · KP 접합부속 · VR관. - 원본 보관 — `resources/knowledge/original/원가계산/자재단가/관급/<받은 날>/*.json.gz` + `_meta.md`(받은 칸만 남김 — 첨부·이미지 뺌). ## 짝짓기 방법 @@ -39,7 +39,7 @@ - 지역 — 지명 낱말로 쪼개 약칭을 풀고(경기 → 경기도) 「시·군·구」·「전지역(구 나열)」·조합 이름을 뺀 뒤, 같은 묶음 → 겹침 0.5 이상 하나뿐일 때만 짝(동점은 못 채움). 이름에 조합이 붙은 줄(제주…레미콘(협))은 그 조합 줄만. - 후보가 여럿이면 — 값이 하나면 그 값 · **제조사별로 값이 갈리면 가장 낮은 값 + 비고에 그 제조사**(확정 · `pick()` 한 곳). -## 다산소프트 값과 맞대기 (±5% 안 = 맞음 — 다산 관급 값이 있던 4,552줄 기준 · 2026-09-20 · 3차 반영) +## 다산소프트 값과 맞대기 (±5% 안 = 맞음 — 다산 관급 값이 있던 4,552줄 기준 · 2026-09-20 · 4차 반영) | 갈래 | 전체 | 짝 서고 채움 | 맞음(±5%) | 5% 넘게 다름 | 보류 | 못 채움 | |---|---|---|---|---|---|---| @@ -52,12 +52,14 @@ | 조경석 | 48 | 48 | **48** | 0 | 0 | 0 | | 파형강관이음관 | 108 | 102 | 87 | 15 | 0 | 6 | | 주철관 · 부속 | 116 | 116 | 34 | 82 | 0 | 0 | -| 측구수로관 | 23 | 17 | 8 | 9 | 0 | 6 | +| 측구수로관 · 무게수로관 | 38 | 38 | 13 | 25 | 0 | 0 | +| 벤치플륨관 | 111 | 106 | 12 | 94 | 0 | 5 | | 시멘트 · 골재 · 볼라드 · 맨홀 · 용접철망 · 돌망태 · 매트 | 56 | 36 | 24 | 12 | 0 | 20 | -| 그 밖(벤치플륨 · 소형고압블록 · 폴리에틸렌 · 교량받침 · 합성목재 · 무게수로관 등) | 297 | 12 | 12 | 0 | 0 | 285 | -| **합계** | **4,552** | **4,057** | **3,442** | **615** | **0** | **495** | +| 그 밖(소형고압블록 · 폴리에틸렌 · 교량받침 · 합성목재 · HI-VP · KP 접합부속 등) | 171 | 25 | 14 | 11 | 0 | 146 | +| **합계** | **4,552** | **4,197** | **3,461** | **736** | **0** | **355** | -- 3차 전(2차 끝) → 후: 채움 3,752 → 4,057 · 맞음 3,232 → 3,442 · 다름 520 → 615 · 못 채움 800 → 495. +- 4차 전(3차 끝) → 후: 채움 4,057 → 4,197 · 맞음 3,442 → 3,461 · 다름 615 → 736 · 못 채움 495 → 355. (2차 끝 3,752 · 800) +- 벤치플륨관 다름 94 — 조달청 최저가 다산 값의 0.85~1.00(중앙 0.92) · 규격·종별 같음 → 제조사 최저·기준일 차이. - 「5% 넘게 다름」 = 제조사별 최저값이 다산이 고른 제조사·조건 값보다 낮은 경우가 대부분(주철관 0.60~1.00 · 이음관 0.45~1.00 · 흄관·파형강관) + 아스콘 기준일 차이(조달청 계약 2026-08-01~ · 다산 「조달 26.7」) + 파형강관 PE 피복줄(조달청 최저 = 다산 값의 0.7~0.8). - 레미콘 다름 45 — 광주·전남 25-30-180(다산 117,800 · 조달청 118,710) · 제주 세 조합(다산 119,130 · 조달청 125,940~126,000) — 지역·규격 같음 → 기준일 차이 또는 다산 원문 값. @@ -68,14 +70,17 @@ - 파형강관 — 다산 규격 「Φ300×t1.6mm, 1RS, PE내면피복」 의 「×t」 · 「1RS」(괄호 없음) · 피복 글자를 읽음. 먼저 「직관(nRS)」 꼴(PE 피복 줄은 피복 글자가 맞아야), 없으면 종류·피복 글자가 맞는 줄(+112). 「-거양」은 조달청 제조사가 아니라 무시. - 교량받침(부흥시스템) 9 · 합성목재(본우드) 3 · 도로용혼합골재(충남) 6 — 이름 괄호의 제조사를 조달청 제조사와 맞대고, 「-관급」 꼬리·「×t25」·「1,000」·「solid」 표기 차이를 맞춤. - 경질폴리염화비닐관(MAS · 3자단가) 받아 봄 — 다산 HI-VP(삼중벽)는 조달청에 단층 HIVG 뿐이라 짝 안 지음. -- 못 채움 495의 까닭은 비고 「관급: 조달청 값 못 채움 — …」 에 적음: +- 벤치플륨관 · 소형고압블록 · 무게수로관 (4차) — 조달청에서 그 이름으로 찾음: 벤치플륨관 = 「철근콘크리트벤치플룸관」(품명 표기 「플룸」 · 분류 「배수로」 조회에는 안 나와 「플룸」 로 따로 받음 · MAS 2,640줄) → 모델(500C) · 가로×세로 · 종별(3종)로 짝 · 「140kg」 은 조달청 표기에 없어 뺌(+106). 소형고압블록 = 「보차도용콘크리트블록 … 소형고압블록」 → 형(I=200×100 · U=220×110) · 두께 · 색(색 안 적힌 줄 = 회색) · 「잡초방지」 줄 뺌(+13). 무게수로관 = 「철근콘크리트용배수로관 … M-400C」 모델명 · 가로×세로 글자 일치(+15 · 같은 물건인지는 사람이 확인). +- 「전지역(제주,도서 제외)」 줄은 제주 아닌 지역 묶음에도 씀(그 밖 갈래 짝짓기 한정) · 「대구경북」 · 「광주전남」 처럼 붙여 쓴 두 도는 두 글자씩 끊어 읽음. +- 소형고압블록 색은 조달청에 회색·컬러·제주 색줄뿐 — 적색 · 녹색 · 백색 등 나머지 색은 「규격 없음」. +- 못 채움 355의 까닭은 비고 「관급: 조달청 값 못 채움 — …」 에 적음: | 까닭 | 줄 수 | 갈래 | |---|---|---| -| 조달청 종합쇼핑몰에 없음 — 품명 없음 | 247 | 벤치플륨 111 · 소형고압블록 62 · 삼중벽 HI-VP 23 · 무게수로관(뚜껑 포함) 23 · KP 접합부속 17 · VR관 11 | -| 조달청 종합쇼핑몰에 없음 — 그 규격 없음 | 81 | H형강 30(치수) · 아스콘 10 · 돌망태·개비온 14 · 보행매트 6 · 이음관 6 · 합성목재 6 · 생태블록 3 · 파형강관 4 · 섬유 1 · 레미콘 1 | -| 조달청 종합쇼핑몰에 그 지역 묶음의 줄 없음 | 73 | 아스콘 57(강원 · 세종충남 · 전북) · 레미콘 여수 읍면 16 | -| 짝 못 지음 | 94 | 아스콘 「~구 제외」 60 · PE직관·PE이중벽 28(조달청은 압력·강성 등급별) · 측구수로관 6 | +| 조달청 종합쇼핑몰에 없음 — 품명 없음 | 51 | 삼중벽 HI-VP 23 · KP 접합부속 17 · VR관 11 | +| 조달청 종합쇼핑몰에 없음 — 그 규격 없음 | 123 | H형강 30(치수) · 소형고압블록 37(색) · 아스콘 10 · 돌망태·개비온 14 · 보행매트 6 · 이음관 6 · 합성목재 6 · 생태블록 3 · 파형강관 4 · 벤치플륨관 5 · 섬유 1 · 레미콘 1 | +| 조달청 종합쇼핑몰에 그 지역 묶음의 줄 없음 | 85 | 아스콘 57(강원 · 세종충남 · 전북) · 레미콘 여수 읍면 16 · 소형고압블록 12 | +| 짝 못 지음 | 96 | 아스콘 「~구 제외」 60 · PE직관·PE이중벽 28(조달청은 압력·강성 등급별) · 무게수로관·벤치플륨관 뚜껑 8(조달청 (부품) 치수 표기가 달라 안 맞음) | ## 짝짓기 규칙 넓힘 (2차) @@ -112,7 +117,7 @@ ## 남은 일 -- 못 채움 495 — 위 까닭 표. 사람 확인이 필요한 것 = 벤치플륨(수로관 유사품과 같은 물건인지) · 소형고압블록(보차도용콘크리트블록 t60·t80 으로 볼지). +- 못 채움 355 — 위 까닭 표. 사람 확인이 필요한 것 = 무게수로관(조달청 「M-…」 이형수로관과 같은 물건인지) · 소형고압블록 회색(색 안 적힌 줄을 회색으로 본 것). - 새로 더 받는 분류는 다산 줄 채움이 끝난 뒤 — 받아 둔 분류(블록 · 교량받침 · 폴리에틸렌 · 합성목재 · 그레이팅 · 낙석 · 시트 · 비료 · 매트 · 경질폴리염화비닐관)는 다산 규격과 표기가 달라 짝 규칙을 더 만들거나 새 줄로 들임. - 품셈재료 → 자재품목 연결(343 줄 미연결)은 화면에서 사람이 이음. -- 짝짓기 시험 = `resources/tester/test_masterdata_procure.py`(견본 응답 13건). +- 짝짓기 시험 = `resources/tester/test_masterdata_procure.py`(견본 응답 16건). diff --git a/resources/master_data/scripts/update_관급단가.py b/resources/master_data/scripts/update_관급단가.py index b518d9e1..b9c9adfc 100644 --- a/resources/master_data/scripts/update_관급단가.py +++ b/resources/master_data/scripts/update_관급단가.py @@ -78,6 +78,7 @@ JOBS = [ # 분류명은 부분 글자 조회 — 「철근」은 암거블록 (THPTY, "부직포"), (MAS, "경질폴리염화비닐관"), (THPTY, "경질폴리염화비닐관"), + (MAS, "플룸"), # 벤치플룸관 — 분류 「배수로」 조회에는 안 나옴 ] # 보관하는 칸 — 나머지(첨부·원산지·이미지·긴 설명)는 버림(원본이 수백 MB) KEEP = ( diff --git a/resources/master_data/scripts/update_관급단가_기타.py b/resources/master_data/scripts/update_관급단가_기타.py index 322775a0..3d69bf41 100644 --- a/resources/master_data/scripts/update_관급단가_기타.py +++ b/resources/master_data/scripts/update_관급단가_기타.py @@ -18,7 +18,8 @@ FAMILIES = ( ("조경석", r"^조경석", ["MASCntrct_조경석"]), ("골재", r"^도로용혼합골재", ["MASCntrct_골재"]), ("시멘트", r"^시멘트", ["MASCntrct_시멘트"]), - ("수로관", r"^측구수로관", ["MASCntrct_배수로", "ThptyUcntrct_배수로"]), + ("수로관", r"^(측구수로관|무게수로관-)", ["MASCntrct_배수로", "ThptyUcntrct_배수로"]), + ("벤치플룸", r"^벤치플륨관(?!뚜껑)", ["MASCntrct_플룸"]), # 조달청 표기 「벤치플룸」 ( "블록", r"^(소형고압블록|생태옹벽블록|생태어소블록)", @@ -41,16 +42,11 @@ FAMILIES = ( # 조달청에 그 품목·규격이 없거나 다산 규격으로는 물건을 가릴 수 없는 것 — 「관급」 못 채움의 까닭 글 ABSENT = ( ( - r"^벤치플륨", - "조달청 종합쇼핑몰에 없음 — 벤치플륨 품명 없음(수로관 유사품이 같은 물건인지 확인 못 함)", + r"^(벤치플륨관뚜껑|무게수로관뚜껑)", + "짝 못 지음 — 조달청 뚜껑은 (부품) 치수 표기가 달라 다산 규격과 안 맞음", ), - (r"^무게수로관", "조달청 종합쇼핑몰에 없음 — 무게수로관 품명 없음"), (r"^HI-VP", "조달청 종합쇼핑몰에 없음 — 삼중벽 품명 없음(단층 HIVG 만 있음)"), (r"^KP 접합부속", "조달청 종합쇼핑몰에 없음 — 접합부속 세트 품명 없음"), - ( - r"^소형고압블록", - "조달청 종합쇼핑몰에 없음 — 소형고압블록 품명 없음(보차도용콘크리트블록은 형·색 표기가 달라 안 지음)", - ), ( r"^(PE직관\(수도용\)|PE이중벽)", "짝 못 지음 — 조달청은 압력·강성 등급별 값인데 다산 규격에 등급 없음", @@ -110,9 +106,32 @@ def tail_of(name: str) -> str | None: return "제주" if ", 제주" in name else None +SHAPE = { + "I": "200x100", + "U": "220x110", +} # 소형고압블록 형 → 가로×세로 (조달청 「I형」 200×100 · 「U형」 220×110 줄에서) +COLORS = ("적색", "녹색", "회색", "갈색", "고동색", "백색", "청색", "황색", "흑색", "컬러") + + +def _small_block(spec: str, color: str) -> bool: + """소형고압블록 줄인지(잡초방지 뺌) · 색이 맞는지 — 회색은 색 안 적힌 줄도(기본 색).""" + if "소형고압블록" not in spec or "잡초방지" in spec: + return False + if color in spec: + return True + return color == "회색" and not any(c in spec for c in COLORS) + + def find(row: dict, raw: dict, places, pick, PROVINCE=frozenset()): """(줄, 방식) 또는 (None, 까닭) — `places` · `pick` 은 본 스크립트 것을 받음.""" n, spec = row["이름"], row["규격"] + base = places + + def places(text): # 「대구경북」 · 「광주전남」 — 붙여 쓴 두 도는 2글자씩 끊어 읽음 + w = base(text) + cut = frozenset(y for x in w for i in range(0, len(x), 2) for y in base(x[i : i + 2])) + return cut if w and not w & PROVINCE and cut <= PROVINCE else w + fam = next(((k, f) for k, pat, f in FAMILIES if re.search(pat, n)), None) if not fam: return None, "이 갈래는 아직 짝짓기 없음" @@ -126,13 +145,32 @@ def find(row: dict, raw: dict, places, pick, PROVINCE=frozenset()): spec, reg = re.sub(r"\([^)]*\)", "", spec), None paren = re.search(r"\(([가-힣]+)", n) if fam[0] in ("교량받침", "합성목재") else None need = pieces(spec) - if fam[0] == "주철관": # 이름의 종별(2종 · 3종)도 맞아야 + keep = lambda r: True # noqa: E731 + if fam[0] in ("주철관", "벤치플룸"): # 이름의 종별(2종 · 3종)도 맞아야 need += [p for p in pieces(n) if re.search(r"\d", p)] + if fam[0] == "벤치플룸": # 「140kg」 은 조달청 표기에 없음 · 뚜껑(부품) 줄은 뺌 + need = [p for p in need if not p.endswith("kg")] + keep = lambda r: "(부품)" not in r["prdctSpecNm"] # noqa: E731 + if fam[0] == "수로관" and (m := re.match(r"M-(\w+)\((\d+)\*(\d+)", spec)): + need = [ + f"m-{m[1].lower()}", + f"{m[2]}x{m[3]}", + ] # 모델 · 가로×세로 — 무개 「M-400C」 는 조달청 모델명과 같음 + keep = lambda r: re.search(r"(?