From 465574a18972df2c425d3582350940ac7113cf9b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 20 Sep 2026 20:09:01 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(master=5Fdata):=20=EB=B2=A4=EC=B9=98?= =?UTF-8?q?=ED=94=8C=EB=A5=A8=EA=B4=80=20=C2=B7=20=EC=86=8C=ED=98=95?= =?UTF-8?q?=EA=B3=A0=EC=95=95=EB=B8=94=EB=A1=9D=20=C2=B7=20=EB=AC=B4?= =?UTF-8?q?=EA=B2=8C=EC=88=98=EB=A1=9C=EA=B4=80=20=EC=A1=B0=EB=8B=AC?= =?UTF-8?q?=EC=B2=AD=20=ED=92=88=EB=AA=85=EC=9C=BC=EB=A1=9C=20=EC=A7=9D=20?= =?UTF-8?q?=E2=80=94=20=EA=B4=80=EA=B8=89=20=EB=AA=BB=20=EC=B1=84=EC=9B=80?= =?UTF-8?q?=20495=20=E2=86=92=20355?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 벤치플륨관 = 조달청 「벤치플룸관」 (「플룸」 조회 2,640줄 받음) · 모델 · 가로×세로 · 종별로 짝 - 소형고압블록 = 보차도용콘크리트블록 · 형 · 두께 · 색으로 짝 · 무게수로관 = 모델명 · 치수 글자 일치 - 「전지역(제주 제외)」 줄과 「대구경북」 식 붙여 쓴 두 도 지역 읽음 - 설계 문서 대조표 (채움 4,197 · 맞음 3,461) · 시험 16건 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ --- .../관급/2026-09-20/MASCntrct_플룸.json.gz | Bin 0 -> 56443 bytes .../자재단가/관급/2026-09-20/_meta.md | 5 +- .../master_data/ref/_설계_관급단가_갱신.md | 33 +- .../master_data/scripts/update_관급단가.py | 1 + .../scripts/update_관급단가_기타.py | 61 ++- resources/master_data/재료_자재품목.json | 422 +++++++++--------- resources/tester/test_masterdata_procure.py | 55 ++- 7 files changed, 337 insertions(+), 240 deletions(-) create mode 100644 resources/knowledge/original/원가계산/자재단가/관급/2026-09-20/MASCntrct_플룸.json.gz 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 0000000000000000000000000000000000000000..168abbcc8584507304d53e293949d081e7abebeb GIT binary patch literal 56443 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 z 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 z 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 L=5=S)va}%pxUbVm literal 0 HcmV?d00001 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"(? Date: Sun, 20 Sep 2026 20:12:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(M01):=20=EA=B1=B0=EB=A6=84=20=EC=BB=A8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=84=88=20=ED=8E=BC=EC=B9=98=EB=A9=B4=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=C2=B7=20=EB=8B=AB=EC=95=84=EB=8F=84=20?= =?UTF-8?q?=EA=B1=B0=EB=A6=84=C2=B7=EC=B0=BE=EA=B8=B0=C2=B7=EC=AA=BD=20?= =?UTF-8?q?=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인력·기계·소요량·계수·로직 펼치면 전체(또는 닫기 전 거름)가 바로 열림 - 닫을 때는 화면 그대로 · 컨테이너마다 찾기 글·쪽을 sessionStorage 에 따로 기억 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT --- .../M01_MasterData_UI_Logic_Page.ts | 1 + M01_MasterData/M01_MasterData_UI_Page.ts | 10 +++- M01_MasterData/M01_MasterData_UI_Rows.ts | 5 +- M01_MasterData/M01_MasterData_UI_Side.ts | 59 ++++++++++++++++++- M01_MasterData/M01_MasterData_UI_Tables.ts | 5 +- 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Page.ts b/M01_MasterData/M01_MasterData_UI_Logic_Page.ts index a6d98903..92589788 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Page.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Page.ts @@ -237,6 +237,7 @@ export async function mountM01Logic(host: HTMLElement, side: SideHandle): Promis total: list.total(), subLabel: L("M01_LaborSub"), detailLabel: L("M01_LaborDetail"), + restore: true, onPick: (sub, detail) => { list.setPick(sub, detail); if (opened) show(null); diff --git a/M01_MasterData/M01_MasterData_UI_Page.ts b/M01_MasterData/M01_MasterData_UI_Page.ts index 08799466..79e47e04 100644 --- a/M01_MasterData/M01_MasterData_UI_Page.ts +++ b/M01_MasterData/M01_MasterData_UI_Page.ts @@ -23,7 +23,7 @@ import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import { saveFiles } from "./M01_MasterData_Api_Fetch"; import { discard, isStale, onDraftChange, payload, totalCount } from "./M01_MasterData_Draft"; import { renderRows } from "./M01_MasterData_UI_Rows"; -import { buildSide, fileLabel, type Pick } from "./M01_MasterData_UI_Side"; +import { buildSide, fileLabel, loadView, saveView, type Pick } from "./M01_MasterData_UI_Side"; import { renderTables } from "./M01_MasterData_UI_Tables"; import "./M01_MasterData_UI_Style.css"; @@ -85,7 +85,11 @@ function buildPage(): HTMLElement { const openFile = (pick: Pick): void => { dispose(); showMode(false); - current = pick; + if (pick.view) { + query = pick.view.q; + search.value = query; + } else saveView(pick.group, { q: query, page: 1 }); + current = { ...pick, view: undefined }; const groupTitle = pick.group === pick.label ? [pick.group] : [pick.group, pick.label]; title.textContent = groupTitle.join(" · "); const view = TABLE_GROUPS.includes(pick.group) ? renderTables : renderRows; @@ -114,7 +118,7 @@ function buildPage(): HTMLElement { const pick = current; try { const again = (await side.refresh(pick.group)).find((f) => f.file === pick.file.file); - if (again) openFile({ ...pick, file: again }); + if (again) openFile({ ...pick, file: again, view: loadView(pick.group) }); } catch (error) { showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error"); } diff --git a/M01_MasterData/M01_MasterData_UI_Rows.ts b/M01_MasterData/M01_MasterData_UI_Rows.ts index 439f8f64..07f5b564 100644 --- a/M01_MasterData/M01_MasterData_UI_Rows.ts +++ b/M01_MasterData/M01_MasterData_UI_Rows.ts @@ -6,7 +6,7 @@ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { t as L } from "@ui/ui_template_locale"; import { openPickModal } from "./M01_MasterData_UI_Pick"; -import type { Pick } from "./M01_MasterData_UI_Side"; +import { saveView, type Pick } from "./M01_MasterData_UI_Side"; import { fetchRows, type Row, type RowsPage } from "./M01_MasterData_Api_Fetch"; import { addRow, @@ -92,7 +92,7 @@ const remember = (ref: string | null, name?: string): void => { export function renderRows(host: HTMLElement, pick: Pick, q: string): () => void { const { sub, detail, state } = pick; const file = pick.file.file; - let page = 1; + let page = pick.view?.page ?? 1; let data: RowsPage | null = null; let unlinked = false; @@ -235,6 +235,7 @@ export function renderRows(host: HTMLElement, pick: Pick, q: string): () => void try { data = await fetchRows(file, page, SIZE, q, unlinked, sub, detail, state); for (const [key, name] of Object.entries(data.refs ?? {})) names.set(key, name); + saveView(pick.group, { page: data.page }); paint(); } catch (error) { showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error"); diff --git a/M01_MasterData/M01_MasterData_UI_Side.ts b/M01_MasterData/M01_MasterData_UI_Side.ts index ebf58894..e9ec3b65 100644 --- a/M01_MasterData/M01_MasterData_UI_Side.ts +++ b/M01_MasterData/M01_MasterData_UI_Side.ts @@ -11,6 +11,30 @@ import { t as L } from "@ui/ui_template_locale"; import { fetchFiles, fetchSubsAll, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch"; import { renderTree, type MakeRow } from "./M01_MasterData_UI_Tree"; +/** 컨테이너마다 기억하는 화면 상태 — 찾기 글 · 쪽 (거름은 `m01.filter.*`) */ +export interface View { + q: string; + page: number; +} + +const viewKey = (group: string): string => `m01.view.${group}`; + +export function loadView(group: string): View { + try { + return { q: "", page: 1, ...JSON.parse(sessionStorage.getItem(viewKey(group)) ?? "{}") }; + } catch { + return { q: "", page: 1 }; + } +} + +export function saveView(group: string, patch: Partial): void { + try { + sessionStorage.setItem(viewKey(group), JSON.stringify({ ...loadView(group), ...patch })); + } catch { + /* 기억 못 해도 화면은 됨 */ + } +} + /** 요소 화면에서 열 것 — 파일 · 하위 거름(구분·세부분류) · 상세구분 · 제목 */ export interface Pick { group: string; @@ -20,6 +44,8 @@ export interface Pick { /** 인력 상태 거름 · 비면 전체 */ state?: string; label: string; + /** 다시 펼칠 때만 — 닫기 전 찾기 글 · 쪽 */ + view?: View; } export interface SideHandle { @@ -49,6 +75,8 @@ export interface FilterArgs { states?: string[]; stateLabel?: string; onPick: (sub: string, detail: string, label: string, state: string) => void; + /** 만들자마자 고른 거름으로 한 번 부름 */ + restore?: boolean; } const MATERIAL_NAMES: Record = { 유가전력: "유가·전력" }; @@ -73,6 +101,9 @@ const GROUP_TITLE = { type Group = keyof typeof GROUP_TITLE; const GROUPS = Object.keys(GROUP_TITLE) as Group[]; const LEAF: Group[] = ["환율", "요율"]; +/** 거름이 있는 컨테이너 — 펼치면 「전체」(또는 닫기 전 거름) 이 바로 열림 */ +const FILTERED: Group[] = ["인력", "기계", "소요량", "계수"]; +const LOGIC_ID = "로직|"; export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): SideHandle { const lists = new Map(); @@ -81,6 +112,8 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si // 하위 거름은 서버가 준 목록(파일 머리에 등록된 구분·세부분류) — 새 조사가 늘어도 화면은 그대로 const kinds = new Map(); const states = new Map(); + const runs = new Map void>(); + let restoring = false; const setActive = (id: string | null): void => { for (const [key, button] of buttons) button.classList.toggle("is-active", key === id); @@ -169,6 +202,7 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si s, ); }; + runs.set(a.id, run); sub.select.addEventListener("change", () => { if (detail) { const details = a.subs.find((k) => k.name === sub.select.value)?.details ?? []; @@ -189,6 +223,7 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si sub.select.dispatchEvent(new Event("change")); }, ); + if (a.restore) run(); return el("div", { children: [ all, @@ -210,7 +245,15 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si const body = bodies.get(group); if (!body) return; const open = (file: FileInfo, sub: string, label: string, detail = "", state = ""): void => - onOpen({ group, file, sub, detail, state, label }); + onOpen({ + group, + file, + sub, + detail, + state, + label, + view: restoring ? loadView(group) : undefined, + }); const one = files[0]; const dropped: Partial> = { 인력: { label: L("M01_LaborSub"), detail: L("M01_LaborDetail") }, @@ -310,13 +353,25 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si onOpen({ group, file: one, sub: "", detail: "", label: L(GROUP_TITLE[group]) }); }); } + if (FILTERED.includes(group)) { + root.querySelector(".ui-collapsible__title")?.addEventListener("click", () => { + if (!root.classList.contains("is-collapsed")) return; // 닫을 때는 그대로 둠 + restoring = true; + try { + runs.get(`${group}|`)?.(); + } finally { + restoring = false; + } + }); + } return root; }); const logicHost = el("div", { className: "m01-side__items" }); const logic = section(L("M01_GroupLogic"), logicHost); logic.querySelector(".ui-collapsible__title")?.addEventListener("click", () => { - setActive(null); + if (!logic.classList.contains("is-collapsed")) return; // 닫을 때는 그대로 둠 + setActive(buttons.has(LOGIC_ID) ? LOGIC_ID : null); onLogic(); }); diff --git a/M01_MasterData/M01_MasterData_UI_Tables.ts b/M01_MasterData/M01_MasterData_UI_Tables.ts index 2e675a0a..394899a4 100644 --- a/M01_MasterData/M01_MasterData_UI_Tables.ts +++ b/M01_MasterData/M01_MasterData_UI_Tables.ts @@ -7,7 +7,7 @@ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { t as L } from "@ui/ui_template_locale"; import { fetchTable, fetchTables, type Row, type TableHead } from "./M01_MasterData_Api_Fetch"; -import type { Pick } from "./M01_MasterData_UI_Side"; +import { saveView, type Pick } from "./M01_MasterData_UI_Side"; import { onDraftChange, peek, @@ -31,7 +31,7 @@ const LIST_COLS = ["키", "원문번호", "구분", "상세구분", "이름", " /** 돌려받은 함수 = 이 화면을 걷을 때 부를 해제. */ export function renderTables(host: HTMLElement, pick: Pick, q: string): () => void { let stop = (): void => {}; - const list = (page = 1): void => { + const list = (page = pick.view?.page ?? 1): void => { stop(); stop = (): void => {}; void showList(host, pick, q, page, (file, key) => { @@ -68,6 +68,7 @@ async function showList( showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error"); return; } + saveView(pick.group, { page }); const rows = got.tables.map((t) => { const tr = el("tr", { className: "m01-master__table-row", From 819c5019ecf764d29008f9ab0cdb32805918b5a5 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 20 Sep 2026 20:27:44 +0900 Subject: [PATCH 3/3] =?UTF-8?q?knowledge(=EB=A7=88=EC=8A=A4=ED=84=B0):=20?= =?UTF-8?q?=EB=AF=B8=ED=99=95=EB=B3=B4=209=20=EC=A7=81=EC=A2=85=EC=9D=84?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A7=80=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EC=A0=95=EC=9C=BC=EB=A1=9C=20=C2=B7=20=EA=B5=AC=EB=B6=84=20?= =?UTF-8?q?=EB=AF=B8=ED=99=95=EB=B3=B4=20=EB=BA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인력.json — 절단공·사진제판공·사진식자공·측량사·측량기사·시공측량기사·측량조수·시공측량조수·특수운전공 상태 「추정」 · 값은 준용 직종 노임 · 출처는 근거 문서 · 비고 한마디 - 구분 「미확보」 등록부·빌더 짝짓기 없앰 · check_master BOOKS 에 공간정보법 - 근거 md 에 「사용자 지정 추정」 절 - 로직 일괄 시험 계산 통과 1,109 → 1,119 · 멈춤 245 → 235 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj --- .../원가계산/노임단가_직종대응_근거.md | 19 ++++++++ resources/master_data/_틀.md | 8 ++-- .../master_data/scripts/build_인력_노임.py | 48 ++----------------- resources/master_data/scripts/check_master.py | 2 +- resources/master_data/scripts/master_keys.py | 1 - resources/master_data/인력.json | 43 ++++------------- resources/tester/test_m01_api.py | 2 +- resources/tester/test_masterdata_check.py | 14 +++--- 8 files changed, 46 insertions(+), 91 deletions(-) diff --git a/resources/knowledge/original/원가계산/노임단가_직종대응_근거.md b/resources/knowledge/original/원가계산/노임단가_직종대응_근거.md index ae48fd88..43ad510e 100644 --- a/resources/knowledge/original/원가계산/노임단가_직종대응_근거.md +++ b/resources/knowledge/original/원가계산/노임단가_직종대응_근거.md @@ -126,3 +126,22 @@ - 측량기사·측량사 — 대응표에 이미 다른 경로로 확인돼 있어 안티그래비티 주장과 상충. 기존 대응표를 따름. - 특수운전공 — "기계설비운전원"이란 이름 자체 없음. - 절단공의 "제관공(플랜트)" 절반 — 2010 통합표는 절단공을 철골공行으로만 명시. + +## 사용자 지정 추정 (2026-09-20 · 미확보 9 직종) + +> 원문이 이름을 맞대지 않아 확정 못 한 9 직종 — 사용자가 준용 직종을 지정(대부분 폐지되었거나 쓰임이 드문 직종). 마스터 `인력.json` 상태 「추정」 · 값은 준용 직종 노임 그대로 · 구분은 건설업. + +| 옛 직종 | 준용(키) | 출처(마스터) | 비고 | 까닭 | +|---|---|---|---|---| +| 절단공 | 철판공(LB000010) | 건설노임 통합표 2010.1 | 철근공·철공·철판공·철골공 중 추정 | 2010 통합표가 네 갈래로 나눔 — 그 가운데 하나를 사용자가 고름 | +| 사진제판공 | 전자조판원(LB000161) | 제조노임 직종해설 29호 | 폐지 직종 | 작업범위(제판·조판)가 29호와 가까움 | +| 사진식자공 | 전자조판원(LB000161) | 제조노임 직종해설 29호 | 폐지 직종 | 위와 같음 | +| 측량사 | 측량중급기술자(LB000321) | 공간정보법 시행령 별표5 | 구 측량사 자격 추정 | 옛 측량사 자격을 측량 기술계 중급으로 봄 | +| 측량기사 | 측량초급기술자(LB000322) | 공간정보법 시행령 별표5 | — | 별표5 1호 기술자 기준 | +| 시공측량기사 | 측량초급기술자(LB000322) | 공간정보법 시행령 별표5 | 2009 폐지 직종 | 위와 같음 | +| 측량조수 | 보통인부(LB000002) | 건설노임 직종 조정 2009 | 측부 폐지 · 보통인부 추정 | 옛 측부가 사라져 보조 노무로 봄 | +| 시공측량조수 | 보통인부(LB000002) | 건설노임 직종 조정 2009 | 측부 폐지 · 보통인부 추정 | 위와 같음 | +| 특수운전공 | 일반기계운전사(LB000050) | 건설품셈 기계설비 13-06 | 공표 이력 없음 | 품셈 노무 항목만 실존 · 공표 노임코드 없음 | + +- 마스터 구분 「미확보」 는 비어 등록부에서 뺌 — 빌더의 미확보 짝짓기도 없앰. +- 틀리면 사용자가 `인력.json` 준용 칸을 고침 — 값은 빌더가 준용 직종에서 다시 채움. diff --git a/resources/master_data/_틀.md b/resources/master_data/_틀.md index c9323756..8d0b6199 100644 --- a/resources/master_data/_틀.md +++ b/resources/master_data/_틀.md @@ -23,7 +23,7 @@ - 장 파일 — 장 번호는 두 자리 · 장 제목은 원문 본문 폴더 이름 그대로 · 부문은 이름에 넣지 않음. - 장 파일 머리 — `부문`(건설품셈만 · 공통 · 토목 · 건축 · 기계설비 · 유지관리) · `차례`(그룹 안 목록 차례: 산림 1~14장 → 건설 공통 → 토목 → 건축 → 기계설비 → 유지관리). -- 원문 이름 = 산림품셈 · 건설품셈 · 건설노임 · 제조노임 · 엔지니어링노임 · 측량노임 · 건설사업관리노임 · SW노임 · 산림노임 · 준용대상 · 유가전력 · 자재품목 · 한국은행환율 · 조달청제비율 · 품셈재료 · 자체 +- 원문 이름 = 산림품셈 · 건설품셈 · 건설노임 · 제조노임 · 엔지니어링노임 · 측량노임 · 건설사업관리노임 · SW노임 · 산림노임 · 유가전력 · 자재품목 · 한국은행환율 · 조달청제비율 · 품셈재료 · 자체 ## 3. 키 @@ -89,16 +89,16 @@ ``` - 인력은 **열 한 벌** — 키 · 원문번호 · 구분 · 상세구분 · 이름 · 규격 · 단위 · 값 · 출처 · 일시간 · 상태 · 옛이름 · 비고 · 준용 · 모든 줄이 모든 칸(없으면 `null` · 빈 목록). -- 인력 `구분` = 건설업 · 제조업 · 엔지니어링 · 측량 · 건설사업관리 · SW · 산림 · 미확보 · 구분별 원문·기관·판·공표일·원천·상세구분·비고는 머리 `구분` 묶음. +- 인력 `구분` = 건설업 · 제조업 · 엔지니어링 · 측량 · 건설사업관리 · SW · 산림 · 구분별 원문·기관·판·공표일·원천·상세구분·비고는 머리 `구분` 묶음. - 새 조사 = 머리 `구분` 에 한 항목 더하고 줄을 붙이면 끝 · 키는 LB 일련번호 이어 받음. - `상세구분` = 제조업 업종 · 엔지니어링 부문 · 측량 기술계/기능계 분야 — 없으면 `null` · 목록은 머리에. -- `상태` = 공표 · 산정(미공표 직종의 원문 4-라 값을 `값` 에 넣음 · 근거는 `비고` 한 줄) · 미공표(원문에 값 없음) · 미확보(공표 노임에 같은 직종 없음) · 추정(원문이 이름을 맞대지 않아 작업 범위로 짐작한 대응 — `구분` 은 대응 직종 것 · `준용` 에 그 직종 키 · `값`·`단위`·`일시간` 은 빌더가 준용 직종에서 채움 · 근거 문서는 `출처` 짧게). +- `상태` = 공표 · 산정(미공표 직종의 원문 4-라 값을 `값` 에 넣음 · 근거는 `비고` 한 줄) · 미공표(원문에 값 없음) · 미확보(공표 노임에 같은 직종 없음) · 추정(원문이 이름을 맞대지 않아 작업 범위로 짐작한 대응 — `준용` 에 대응 직종 키 · `구분` 은 관리자가 정함 · `값`·`단위`·`일시간` 은 빌더가 준용 직종에서 채움 · 근거 문서는 `출처` 짧게). - `일시간` = 1일 노임 기준 시간 — 8 · 잠수부만 6(원문 4-가). - 기계 `세부분류` = 건설품셈 · 산림품셈 · 세부분류별 원문·판·주는 머리 `세부분류` 묶음. - 값이 여럿인 요소(기계)는 `값` 을 묶음으로 — `{ "가격": …, "상각비계수": …, "정비비계수": …, "관리비계수": …, "손료계수": …, "주연료": …, "주연료종류": …, "잡재료율": …, "조종원": …, "조종원직종": … }` · `손료계수` = 원문 「계」. - 묶음 요소는 `단위` · `출처` 도 같은 모양의 묶음. - 원문에 없는 값을 지어 넣지 않음. 원문 「-」 = 칸을 비움(`null`). -- `준용` = 모든 줄의 공통 칸 — 관리자가 고르는 다른 요소 키 · 준용 값을 미리 채우지 않음. 공표 노임에 이름이 온전히 같은 직종이 없으면 구분 「미확보」. +- `준용` = 모든 줄의 공통 칸 — 관리자가 고르는 다른 요소 키 · 준용 값을 미리 채우지 않음. 공표 노임에 이름이 온전히 같은 직종이 없으면 상태 「추정」 · 준용에 대응 직종 키. - `옛이름` = 품셈이 쓰는 옛 직종명 목록(읽기 전용) · 이름 바뀜·통합 근거가 선 것은 준용이 아니라 현행 줄에 바로 적음 · 이력은 그 줄 `비고` · 근거는 `노임단가_직종대응_근거.md`. - 엔진 차례 = 준용이 있으면 그 요소 값 · 없으면 제 값 · 호표 출처에 「준용: <이름>」 · 상태 「추정」 은 제 값을 쓰고 호표 출처에 「추정(준용: <이름>)」. - 원문 이름 대신 번호로 적은 값(`조종원직종` = 노임 원문번호 「1048」)은 원문 이름을 `비고` 칸에. diff --git a/resources/master_data/scripts/build_인력_노임.py b/resources/master_data/scripts/build_인력_노임.py index cb31f1df..ecbfe3f6 100644 --- a/resources/master_data/scripts/build_인력_노임.py +++ b/resources/master_data/scripts/build_인력_노임.py @@ -1,11 +1,10 @@ -"""노임 공표 원문 md → 인력 요소 파일 다섯 · 미확보 짝짓기 · 건설노임 미공표 산정. +"""노임 공표 원문 md → 인력 요소 파일 다섯 · 건설노임 미공표 산정 · 추정 줄 되채움. 원문 = resources/knowledge/original/원가계산/노임단가_* (우리 md · ESTX 값 쓰지 않음). - 모든 줄이 같은 열 한 벌(`_틀.md` 인력) · 제조업 업종·엔지니어링 부문·측량 분야는 「상세구분」. - 인력_엔지니어링노임 · 측량노임 · 건설사업관리노임 · SW노임 · 산림노임 — md 표 그대로 · 줄 수·값을 md 와 기계 대조. 옛이름·비고·준용은 다시 지어도 그대로 이어받음(`carry`). -- 인력_준용대상(구분 「미확보」) — 새 노임 직종과 이름이 온전히 같은 줄은 옮김(로직 참조도). - 건설노임 원문 「통합·명칭변경 직종」 목록의 옛 직종은 현행 줄 `옛이름` 으로 옮겼음. +- 상태 「추정」 줄(옛·폐지 직종) — 관리자가 고른 준용 직종에서 값·단위·일시간을 되채움. - 인력_건설노임 미공표 — 원문 4-라 산정값을 「값」 에 넣고 상태 「산정」 · 근거는 비고 한 줄. 이음 = 원문 회차의 전체직종 평균을 그대로 이음(미공표직종_추적.md 4절) · 산정값은 같은 md 5절과 대조. - 인력_제조노임 미공표 — 조사월마다 전체 평균 증가율을 곱하며 원 미만 버림(건설 4-라 방식 준용 · 제조 원문에 규정 없음). @@ -260,7 +259,7 @@ def check_md(src: Path, rows: list[dict], table_head: str | None = None) -> None print(f" {src.parent.name[5:]}: {len(rows)} 줄 · 값 합 {sum(got.elements()):,} · md 대조 같음") -# ── 미확보 짝짓기 ──────────────────────────────────────────────────── +# ── 관리자 칸 잇기 ──────────────────────────────────────────────────── def carry(rows: list[dict], kind: str, old: list[dict]) -> None: """관리자·근거가 채운 칸(옛이름 · 비고 · 준용)은 다시 지어도 그대로 — 이름으로 맞댐.""" had = {r["이름"]: r for r in old if r.get("구분") == kind} @@ -300,36 +299,9 @@ def main(): } assert {b: len(v[2]) for b, v in books.items()} == expect - # 이름 → ref (새 다섯 + 건설·제조 공표) wage = mk.old_view("인력_건설노임.json") make = mk.old_view("인력_제조노임.json") - pool = {r["이름"]: f"인력:{b}:{r['열쇠']}" for b, (_, _, rows) in books.items() for r in rows} - old_pool = {r["이름"]: f"인력:{d['원문']}:{r['열쇠']}" for d in (wage, make) for r in d["줄"]} - now = json.loads((MD / "인력.json").read_text(encoding="utf-8"))["줄"] - applied = { # 현행 줄이 이미 품은 옛 직종명(`_틀.md` 인력 값 · 옛이름) - name for r in now for name in (r.get("옛이름") or []) - } - { # 갈린 직종(절단공)은 옛이름이면서 미확보로도 남음 — 지우지 않음 - r["이름"] for r in now if r.get("구분") == "미확보" - } - alias_path = MD / "인력_준용대상.json" - alias = mk.old_view(alias_path.name) - moved, kept = {}, [] - for r in alias["줄"]: - name = r["이름"] - if name in pool: - moved[f"인력:준용대상:{r['열쇠']}"] = pool[name] - continue - assert name not in old_pool, name - if name in applied: # 이미 현행 줄 「옛이름」 으로 옮긴 직종 — 미확보로 다시 만들지 않음 - continue - kept.append(r) - alias["비고"] = [ - "값을 확인 못 한 직종(공표 노임에 이름이 온전히 같은 직종 없음) — 이름·단위만" - " · 비고 = 이 직종을 요구한 품셈 절 · 비슷한 이름도 비고에" - ] - head = {k: v for k, v in alias.items() if k != "줄"} - dump_master(alias_path, head, kept) for book, (src, head, rows) in books.items(): carry(rows, mk.LABOR[book], now) @@ -337,20 +309,6 @@ def main(): rows += [r for r in mk.old_view(f"인력_{book}.json")["줄"] if r.get("상태") == "추정"] dump_master(MD / f"인력_{book}.json", head, rows) - # 로직 참조 옮김 — 긴 열쇠부터 - n_ref = 0 - for path in sorted(MD.glob("로직_*.json")): - text = path.read_text(encoding="utf-8") - new = text - for old in sorted(moved, key=len, reverse=True): - n_ref += new.count(old) - new = new.replace(old, moved[old]) - if new != text: - path.write_bytes(new.encode("utf-8")) - print( - f"미확보 {len(alias['줄'])} → 옮김 {len(moved)} {sorted(moved.values())} · 남김 {len(kept)} · 로직 참조 {n_ref} 곳" - ) - estimate(wage) estimate_make(make) print("새 틀로 받음", mk.adopt()) diff --git a/resources/master_data/scripts/check_master.py b/resources/master_data/scripts/check_master.py index c75282fb..34002383 100644 --- a/resources/master_data/scripts/check_master.py +++ b/resources/master_data/scripts/check_master.py @@ -40,7 +40,6 @@ BOOKS = ( "건설사업관리노임", "SW노임", "산림노임", - "준용대상", "유가전력", "자재품목", "한국은행환율", @@ -48,6 +47,7 @@ BOOKS = ( "품셈재료", "질의회신", "엔산법", + "공간정보법", "자체", ) DIVISIONS = { diff --git a/resources/master_data/scripts/master_keys.py b/resources/master_data/scripts/master_keys.py index df736f7a..98a86dd1 100644 --- a/resources/master_data/scripts/master_keys.py +++ b/resources/master_data/scripts/master_keys.py @@ -131,7 +131,6 @@ LABOR = { "건설사업관리노임": "건설사업관리", "SW노임": "SW", "산림노임": "산림", - "준용대상": "미확보", } MERGED = { "인력": ("인력.json", "구분", LABOR), diff --git a/resources/master_data/인력.json b/resources/master_data/인력.json index 08231091..69713e23 100644 --- a/resources/master_data/인력.json +++ b/resources/master_data/인력.json @@ -2,7 +2,7 @@ "그룹": "인력", "비고": [ "열 한 벌 = 키 · 원문번호 · 구분 · 상세구분 · 이름 · 규격 · 단위 · 값 · 출처 · 일시간 · 상태 · 옛이름 · 비고 · 준용 — 모든 줄이 모든 칸을 가짐(없으면 null · 빈 목록)", - "상태 = 공표 · 산정(원문 4-라 산정값을 값에 넣음 · 근거는 비고) · 미공표(원문에 값 없음) · 미확보(공표 노임에 같은 직종 없음) · 추정(원문이 이름을 맞대지 않아 작업 범위로 짐작 · 구분·값·단위·일시간은 준용 직종 것 · 준용에 그 직종 키 · 근거 문서는 출처)", + "상태 = 공표 · 산정(원문 4-라 산정값을 값에 넣음 · 근거는 비고) · 미공표(원문에 값 없음) · 미확보(공표 노임에 같은 직종 없음) · 추정(원문이 이름을 맞대지 않아 작업 범위로 짐작 · 값·단위·일시간은 준용 직종 것 · 구분은 관리자가 정함 · 준용에 그 직종 키 · 근거 문서는 출처)", "준용 = 관리자가 고르는 다른 직종 키 — 있으면 엔진이 그 직종 값을 씀(값이 있어도 앞섬) · 없으면 값 · 호표 출처에 「준용: <이름>」 · 상태 「추정」 은 제 값을 쓰고 「추정(준용: <이름>)」", "새 조사 = 머리 「구분」 에 한 항목(원문 · 기관 · 판 · 공표일 · 원천 · 상세구분) 더하고 줄을 붙이면 끝 · 키는 LB 일련번호 이어 받음", "옛이름 = 품셈이 쓰는 옛 직종명 목록(읽기 전용) · 근거 resources/knowledge/original/원가계산/노임단가_직종대응_근거.md" @@ -225,14 +225,6 @@ ], "상세구분": [], "비고": ["산림청 2026년 나무의사·수목치료기술자 노임단가 알림(수목진료 표준품셈 예정가격 작성용) · 2025년 소비자물가상승률 2.1% 반영(원문) · 통계 승인 노임 아님", "판 = 2026년 적용(원문에 적용 시작일 없음 · 1월 1일로 적음)"] - }, - "미확보": { - "원문": "준용대상", - "기관": null, - "판": "2026-09-19", - "원천": [], - "상세구분": [], - "비고": ["값을 확인 못 한 직종(공표 노임에 이름이 온전히 같은 직종 없음) — 이름·단위만 · 비고 = 이 직종을 요구한 품셈 절 · 비슷한 이름도 비고에"] } }, "줄": [ @@ -596,38 +588,23 @@ { "키": "LB000358", "원문번호": "정보보안전문가", "구분": "SW", "상세구분": null, "이름": "정보보안전문가", "규격": "", "단위": "원/일", "값": 507887, "출처": "SW노임 17", "일시간": 8, "상태": "공표", "옛이름": [], "비고": null, "준용": null }, { "키": "LB000359", "원문번호": "나무의사", "구분": "산림", "상세구분": null, "이름": "나무의사", "규격": "", "단위": "원/일", "값": 326359, "출처": "산림노임 나무의사", "일시간": 8, "상태": "공표", "옛이름": [], "비고": null, "준용": null }, { "키": "LB000360", "원문번호": "수목치료기술자", "구분": "산림", "상세구분": null, "이름": "수목치료기술자", "규격": "", "단위": "원/일", "값": 238447, "출처": "산림노임 수목치료기술자", "일시간": 8, "상태": "공표", "옛이름": [], "비고": null, "준용": null }, - { "키": "LB000362", "원문번호": "절단공", "구분": "미확보", "상세구분": null, "이름": "절단공", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 기계설비 13-6-11 · 건설품셈 기계설비 13-6-13 · 건설품셈 기계설비 13-6-17", "준용": null }, + { "키": "LB000362", "원문번호": "절단공", "구분": "건설업", "상세구분": null, "이름": "절단공", "규격": "", "단위": "원/일", "값": 221481, "출처": "건설노임 통합표 2010.1", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "철근공·철공·철판공·철골공 중 추정", "준용": "LB000010" }, { "키": "LB000373", "원문번호": "정보처리기사", "구분": "엔지니어링", "상세구분": "정보통신", "이름": "정보처리기사", "규격": "", "단위": "원/일", "값": 249574, "출처": "엔산법 시행령 별표2", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000292" }, - { "키": "LB000384", "원문번호": "사진제판공", "구분": "미확보", "상세구분": null, "이름": "사진제판공", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 토목 9-5-6-2가 · 건설품셈 토목 9-5-7-2", "준용": null }, - { "키": "LB000385", "원문번호": "사진식자공", "구분": "미확보", "상세구분": null, "이름": "사진식자공", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 토목 9-5-6-2가 · 건설품셈 토목 9-5-7-2", "준용": null }, + { "키": "LB000384", "원문번호": "사진제판공", "구분": "건설업", "상세구분": null, "이름": "사진제판공", "규격": "", "단위": "원/일", "값": 106954, "출처": "제조노임 직종해설 29호", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "폐지 직종", "준용": "LB000161" }, + { "키": "LB000385", "원문번호": "사진식자공", "구분": "건설업", "상세구분": null, "이름": "사진식자공", "규격": "", "단위": "원/일", "값": 106954, "출처": "제조노임 직종해설 29호", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "폐지 직종", "준용": "LB000161" }, { "키": "LB000386", "원문번호": "옵셋인쇄공", "구분": "제조업", "상세구분": "출판․인쇄", "이름": "옵셋인쇄공", "규격": "", "단위": "원/일", "값": 121205, "출처": "제조노임 직종해설 31호", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000163" }, - { - "키": "LB000390", - "원문번호": "측량사", - "구분": "미확보", - "상세구분": null, - "이름": "측량사", - "규격": "", - "단위": "원/일", - "값": null, - "출처": "준용대상", - "일시간": 8, - "상태": "미확보", - "옛이름": [], - "비고": "건설품셈 기계설비 13-5-2 · 건설품셈 기계설비 13-5-8 · 건설품셈 기계설비 13-6-1 · 건설품셈 기계설비 13-6-2 · 건설품셈 기계설비 13-6-3 · 건설품셈 기계설비 13-6-4 · 건설품셈 기계설비 13-6-6 · 건설품셈 기계설비 13-6-7 · 건설품셈 기계설비 13-6-8 · 건설품셈 기계설비 13-6-10 · 건설품셈 기계설비 13-6-12 · 건설품셈 기계설비 13-6-16 · 건설품셈 기계설비 13-7-1 · 건설품셈 기계설비 13-7-2 · 건설품셈 기계설비 13-7-4 · 건설품셈 기계설비 13-9-2 · 건설품셈 기계설비 13-10-2 · 건설품셈 기계설비 13-10-3 · 건설품셈 기계설비 13-11-3", - "준용": null - }, + { "키": "LB000390", "원문번호": "측량사", "구분": "건설업", "상세구분": null, "이름": "측량사", "규격": "", "단위": "원/일", "값": 251596, "출처": "공간정보법 시행령 별표5", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "구 측량사 자격 추정", "준용": "LB000321" }, { "키": "LB000391", "원문번호": "제관공", "구분": "건설업", "상세구분": null, "이름": "제관공", "규격": "", "단위": "원/일", "값": 266029, "출처": "건설품셈 기계설비 13-06", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000057" }, { "키": "LB000392", "원문번호": "공작기계공", "구분": "제조업", "상세구분": "공작기계 조작", "이름": "공작기계공", "규격": "", "단위": "원/일", "값": 123489, "출처": "제조노임 직종해설 70·85호", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "밀링원일 수 있음", "준용": "LB000202" }, { "키": "LB000394", "원문번호": "마킹공", "구분": "건설업", "상세구분": null, "이름": "마킹공", "규격": "", "단위": "원/일", "값": 266029, "출처": "건설품셈 기계설비 13-06", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000057" }, { "키": "LB000395", "원문번호": "산소절단공", "구분": "건설업", "상세구분": null, "이름": "산소절단공", "규격": "", "단위": "원/일", "값": 266029, "출처": "건설품셈 기계설비 13-06", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000057" }, - { "키": "LB000396", "원문번호": "측량기사", "구분": "미확보", "상세구분": null, "이름": "측량기사", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 기계설비 13-6-8", "준용": null }, + { "키": "LB000396", "원문번호": "측량기사", "구분": "건설업", "상세구분": null, "이름": "측량기사", "규격": "", "단위": "원/일", "값": 215609, "출처": "공간정보법 시행령 별표5", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000322" }, { "키": "LB000397", "원문번호": "시험사1급", "구분": "건설업", "상세구분": null, "이름": "시험사1급", "규격": "", "단위": "원/일", "값": 199588, "출처": "건설노임 명칭변경표 2010.1", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000068" }, - { "키": "LB000398", "원문번호": "특수운전공", "구분": "미확보", "상세구분": null, "이름": "특수운전공", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 기계설비 13-6-8 · 건설품셈 기계설비 13-6-9", "준용": null }, + { "키": "LB000398", "원문번호": "특수운전공", "구분": "건설업", "상세구분": null, "이름": "특수운전공", "규격": "", "단위": "원/일", "값": 173995, "출처": "건설품셈 기계설비 13-06", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "공표 이력 없음", "준용": "LB000050" }, { "키": "LB000400", "원문번호": "제도공", "구분": "제조업", "상세구분": "CAD설계", "이름": "제도공", "규격": "", "단위": "원/일", "값": 160065, "출처": "제조노임 직종해설 1호", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000133" }, { "키": "LB000401", "원문번호": "현도공", "구분": "건설업", "상세구분": null, "이름": "현도공", "규격": "", "단위": "원/일", "값": 266029, "출처": "건설품셈 기계설비 13-06", "일시간": 8, "상태": "추정", "옛이름": [], "비고": null, "준용": "LB000057" }, - { "키": "LB000402", "원문번호": "측량조수", "구분": "미확보", "상세구분": null, "이름": "측량조수", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 기계설비 13-6-12 · 건설품셈 기계설비 13-6-16", "준용": null }, - { "키": "LB000403", "원문번호": "시공측량기사", "구분": "미확보", "상세구분": null, "이름": "시공측량기사", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 기계설비 13-6-14", "준용": null }, - { "키": "LB000404", "원문번호": "시공측량조수", "구분": "미확보", "상세구분": null, "이름": "시공측량조수", "규격": "", "단위": "원/일", "값": null, "출처": "준용대상", "일시간": 8, "상태": "미확보", "옛이름": [], "비고": "건설품셈 기계설비 13-6-14", "준용": null } + { "키": "LB000402", "원문번호": "측량조수", "구분": "건설업", "상세구분": null, "이름": "측량조수", "규격": "", "단위": "원/일", "값": 172068, "출처": "건설노임 직종 조정 2009", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "측부 폐지 · 보통인부 추정", "준용": "LB000002" }, + { "키": "LB000403", "원문번호": "시공측량기사", "구분": "건설업", "상세구분": null, "이름": "시공측량기사", "규격": "", "단위": "원/일", "값": 215609, "출처": "공간정보법 시행령 별표5", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "2009 폐지 직종", "준용": "LB000322" }, + { "키": "LB000404", "원문번호": "시공측량조수", "구분": "건설업", "상세구분": null, "이름": "시공측량조수", "규격": "", "단위": "원/일", "값": 172068, "출처": "건설노임 직종 조정 2009", "일시간": 8, "상태": "추정", "옛이름": [], "비고": "측부 폐지 · 보통인부 추정", "준용": "LB000002" } ] } diff --git a/resources/tester/test_m01_api.py b/resources/tester/test_m01_api.py index e41f8511..f97ecd2a 100644 --- a/resources/tester/test_m01_api.py +++ b/resources/tester/test_m01_api.py @@ -328,7 +328,7 @@ def test_구분_목록(client: TestClient) -> None: """구분 목록 API — 파일 머리에 등록된 갈래 그대로(화면이 목록을 안 품음).""" got = _get(client, "/api/m01/subs", file=LABOR) names = [s["name"] for s in got["subs"]] - assert got["slot"] == "구분" and names[0] == "건설업" and names[-1] == "미확보" + assert got["slot"] == "구분" and names[0] == "건설업" and names[-1] == "산림" survey = next(s for s in got["subs"] if s["name"] == "측량") assert survey["book"] == "측량노임" and "기능계 도화" in survey["details"] machine = _get(client, "/api/m01/subs", file="기계.json") diff --git a/resources/tester/test_masterdata_check.py b/resources/tester/test_masterdata_check.py index 58df726d..ac9f56fa 100644 --- a/resources/tester/test_masterdata_check.py +++ b/resources/tester/test_masterdata_check.py @@ -612,9 +612,9 @@ def test_미공표_준용(): got = mf.run(mf.Master(files), "GX999999", {}) assert got["줄"][0]["출처"] == "준용: 보통인부" assert got["노무비"] == 2 * Decimal(172068) - lines[0]["요소"] = "LB:절단공" # 공표 노임에 없는 직종(미확보) — 준용을 골라야 값이 섬 + lines[0]["요소"] = "LB:절단공" # 값도 준용도 없으면 멈춤 — 준용을 골라야 값이 섬 alias = mf.Master(files).get("LB:절단공") - assert alias["값"] is None and alias["준용"] is None and alias["구분"] == "미확보" + alias["상태"], alias["값"], alias["준용"] = "미확보", None, None with pytest.raises(mf.FormulaError): mf.run(mf.Master(files), "GX999999", {}) alias["준용"] = common @@ -633,6 +633,9 @@ def test_추정은_준용_값에_표시(whole): assert row["값"] == target["값"] and row["단위"] == target["단위"] assert row["출처"] == "건설품셈 기계설비 13-06" assert whole.value("LB:제관공") == (target["값"], "추정(준용: 플랜트제관공)") + cut = whole.get("LB:절단공") # 사용자 지정 추정(옛 미확보 9 직종) + assert cut["상태"] == "추정" and cut["출처"] == "건설노임 통합표 2010.1" + assert whole.value("LB:절단공") == (whole.get("LB:1010")["값"], "추정(준용: 철판공)") def test_노임_갈래_파일_원문_대조(): @@ -642,18 +645,17 @@ def test_노임_갈래_파일_원문_대조(): rows = data["줄"] count = {b: sum(r["구분"] == b for r in rows) for b in data["구분"]} assert count == { - "건설업": 137, # 추정 5 = 제관공·마킹공·현도공·산소절단공·시험사1급 + "건설업": 146, # 추정 14 = 제관공류 5 + 사용자 지정 9(옛 미확보) "제조업": 132, # 추정 3 = 제도공·공작기계공·옵셋인쇄공 "엔지니어링": 57, # 추정 1 = 정보처리기사 "측량": 20, "건설사업관리": 4, "SW": 17, "산림": 2, - "미확보": 9, # 원문이 대응을 정하지 않은 직종만 남김 } assert {tuple(r) for r in rows} == {cm.LABOR_COLS} - # 「미공표」 는 남지 않음 — 건설·제조 미공표 직종은 모두 「산정」(원 미만 버림) - assert {r["상태"] for r in rows} == set(cm.LABOR_STATUS) - {"미공표"} + # 「미공표」·「미확보」 는 남지 않음 — 미공표는 「산정」(원 미만 버림) · 미확보는 「추정」 + assert {r["상태"] for r in rows} == set(cm.LABOR_STATUS) - {"미공표", "미확보"} whole = mf.Master(files) assert whole.value("LB:기술사(건설)")[0] == 467217 assert whole.value("LB:측량중급기술자")[0] == 251596