feat(master_data): 자재품목 구분 · 상세구분 채움 — 품명 글자 규칙 · 23 구분

- scripts/build_자재품목_분류.py: 품명 규칙(위에서 첫 일치) · 조달청 줄은 조달청 품명 표 · 다시 돌리면 같은 결과
- 재료_자재품목.json: 구분 · 상세구분 두 칸만 바뀜 (값 · 이름 · 키 그대로) · 못 가린 줄은 미분류
- ref/_묶음_자재품목.md: 구분별 상세구분 · 줄 수
- 시험: test_자재품목_분류.py 추가 · test_masterdata_check 의 「구분 비어 있음」 확인을 「채워져 있음」으로

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8
This commit is contained in:
2026-09-20 21:38:56 +09:00
co-authored by Claude Sonnet 5
parent 565833bcb8
commit c249f3be66
5 changed files with 31283 additions and 30595 deletions
@@ -0,0 +1,107 @@
# 자재품목 묶음 (구분 · 상세구분)
`scripts/build_자재품목_분류.py` 가 만든 표 — 손으로 고치지 않음.
- 구분 23 · 상세구분 96 · 미분류 1063 줄 (전체 30592 줄)
## 규칙
- 품명에서 공백을 모두 뺀 글자에 정규식을 위에서부터 대어 첫 일치를 따름 — 순서가 곧 우선순위.
- 조달청 줄(출처 「자재품목 조달청 …」)은 조달청 품명 16 개 표를 따름.
- 못 가린 줄은 구분 「미분류」 · 상세구분 없음.
- 값 · 이름 · 키는 바꾸지 않음(두 칸만).
- 다시 돌리면 같은 결과.
## 구분별 상세구분 (줄 수)
### 관·이음관 (7044)
PE관 2906 · 이음쇠·부속 1003 · 주철관 828 · 강관 705 · PVC관 461 · 파형강관 417 · 유리섬유·FRP관 398 · 동관·스텐관 132 · 흄관 117 · 단열이중관 77
### 식생·종자 (5869)
수목 5433 · 수목지주·보호 181 · 종자 105 · 잔디·지피 97 · 비료·토양개량 25 · 농약·약제 21 · 비료 7
### 레미콘 (3203)
레미콘 3203
### 철근·강재 (1857)
형강 703 · 철근 602 · 강판 262 · 구조용강관 147 · 철선 92 · 철망·와이어메쉬 51
### 아스콘·포장 (1705)
아스콘 1479 · 포장블록 173 · 특수포장 30 · 아스팔트 13 · 투수포장재 10
### 안전시설 (1505)
표지판·지주 933 · 가드레일 427 · 낙석방지책 63 · 낙석방지·휀스 59 · 차선·방호 23
### 볼트·앵커·철물 (1001)
볼트·너트 525 · 와이어로프·클램프 208 · 앵커 189 · 못 41 · 철근커플러 38
### 전기·통신 (799)
케이블·전선 312 · 판넬·차단기 163 · 전선관·트레이 134 · 조명·전등 133 · 부속·기타 57
### 밸브·수도자재 (761)
밸브 597 · 수도계량기·유량계 79 · 스프링클러·호스 51 · 펌프 34
### 운송·용역 (675)
화물운임 552 · 시험·검사 62 · 폐기물 61
### 매트·토목섬유 (641)
돌망태 372 · 부직포·토목섬유 76 · 식생매트 64 · 돌망태·사방 63 · 매트 54 · 부직포 8 · 잔디보호매트 4
### 맨홀·수로·그레이팅 (599)
수로관 283 · 그레이팅 166 · 맨홀 150
### 석재 (525)
경계석 401 · 조경석·자연석 124
### 도료·방수·접착 (522)
도료 249 · 접착·혼화 138 · 방수·실링 135
### 콘크리트제품 (484)
말뚝·파일 223 · 경계석·블록 171 · PC구조물 90
### 시멘트·골재 (483)
골재 323 · 모르타르 101 · 시멘트 59
### 기계·설비 (462)
보일러·공조 172 · 기계류 118 · 놀이·운동·시설물 109 · 측정·시험기기 53 · 소방·가스 10
### 목재 (430)
각재·판재 167 · 목재덱 161 · 합판 78 · 판재 24
### 단열·보온 (282)
단열재 282
### 건축마감 (279)
문·창·유리 123 · 천장·바닥·마감재 75 · 타일·기와 66 · 위생기구 15
### 잡재료 (142)
소모품 92 · 용접·가스 28 · 고철·기타금속 11 · 화약 9 · 용수 2
### 교량·구조부속 (133)
신축이음·받침 75 · 보강재·정착 58
### 거푸집·가설재 (128)
동바리·비계 72 · 거푸집 48 · 가설건축물 8
@@ -0,0 +1,461 @@
"""재료_자재품목.json 의 구분 · 상세구분 두 칸 채움 — 품명 글자 규칙(위에서 아래로 첫 일치) · 다시 돌리면 같은 결과.
· 조달청 줄(출처 「자재품목 조달청 …」) = 조달청 품명 표(조달청_분류) 를 따름
· 못 가린 줄 = 구분 「미분류」 · 상세구분 null
· 값 · 이름 · 키는 건드리지 않음 (줄 텍스트의 두 칸만 바꿈 · 나머지 글자 그대로)
· 같이 씀: ref/_묶음_자재품목.md (구분별 상세구분 · 줄 수) 를 새로 씀
사용: python scripts/build_자재품목_분류.py (JSON · 묶음 md 갱신)
python scripts/build_자재품목_분류.py --미분류 (못 가린 품명 앞쪽 나열)
"""
import collections
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "재료_자재품목.json"
OUT_MD = ROOT / "ref" / "_묶음_자재품목.md"
# 조달청 품명 → (구분, 상세구분)
조달청_분류 = {
"조경용수목": ("식생·종자", "수목"),
"수목보호용지지대": ("식생·종자", "수목지주·보호"),
"부숙유기질비료": ("식생·종자", "비료"),
"일반용폴리에틸렌관": ("관·이음관", "PE관"),
"수도용폴리에틸렌관": ("관·이음관", "PE관"),
"도로안전표지판지주": ("안전시설", "표지판·지주"),
"철제가드레일": ("안전시설", "가드레일"),
"낙석방지책": ("안전시설", "낙석방지책"),
"금속돌망태": ("매트·토목섬유", "돌망태"),
"식생매트": ("매트·토목섬유", "식생매트"),
"잔디보호매트": ("매트·토목섬유", "잔디보호매트"),
"토목용부직포": ("매트·토목섬유", "부직포"),
"자연석경계석": ("석재", "경계석"),
"투수골재포장재": ("아스콘·포장", "투수포장재"),
"목재덱": ("목재", "목재덱"),
"목재판재": ("목재", "판재"),
}
# (구분, 상세구분, 정규식) — 이름에서 공백을 모두 뺀 글자에 대고 위에서부터 첫 일치 · 좁은 규칙이 위
R = [
# 지반조사 도구
("기계·설비", "기계류", r"드라이브파이프|코아리프터|샘플러|신월튜브|코아바렐|리밍쉘"),
# 운송 · 시험 · 폐기물
("운송·용역", "화물운임", r"구역화물|폐기물운반|항만하역|운반비|운송비"),
("운송·용역", "폐기물", r"폐기물처리"),
("운송·용역", "시험·검사", r"시험(?!기|편)|출장비"),
(
"기계·설비",
"측정·시험기기",
r"시험기|시험편|탐상|측정기|계측기|측량기|판독기|분석기|필림|세륜기|축중기|레벨기|트랜싯",
),
# 레미콘 · 아스콘
("레미콘", "레미콘", r"레미콘|레디믹스"),
("아스콘·포장", "아스콘", r"아스콘|순환아스"),
("도료·방수·접착", "방수·실링", r"아스팔트(조인트|실란트|프라이머|루핑|펠트|콤파운드|방수)"),
("아스콘·포장", "아스팔트", r"^(블론|개질유화|유화)?아스팔트(유제)?$|록크하드"),
(
"아스콘·포장",
"포장블록",
r"잔디블록|바닥벽돌|투수콘|투수블[록럭]|고압블록|규사블록|다색(저속|블록)|점자블록|인터로킹|보도블[록럭]|차도블록",
),
# 밸브 · 수도
(
"밸브·수도자재",
"밸브",
r"밸브|제수|체크|게이트|감압|스트레이너|수전|분수전|공기변|수문|콕크",
),
("밸브·수도자재", "수도계량기·유량계", r"수도미터|계량기|유량계|미터기|보호통"),
("밸브·수도자재", "스프링클러·호스", r"스프링클러|살수|급수호스|송수호스|호스|분사|헤드|점적"),
("밸브·수도자재", "펌프", r"펌프|양수기"),
("도료·방수·접착", "방수·실링", r"백업재"),
("관·이음관", "단열이중관", r"단열이중"),
("기계·설비", "보일러·공조", r"정화조"),
# 거푸집 (문양거푸집 등 — 단열 글자보다 먼저)
(
"거푸집·가설재",
"거푸집",
r"거푸집|유로폼|알폼|코팅합판|폼타이|플랫타이|분리제|박리제|폼핀|핀폴|패널(?!스위치)|내부코너",
),
# 단열 · 보온
(
"단열·보온",
"단열재",
r"단열(?!문)|보온|샌드위치|폴리스티렌|스티로폼|암면|글라스울|압출법|페놀폼|은박지",
),
# 동바리·비계 (철물·브라켓 글자보다 먼저)
(
"거푸집·가설재",
"동바리·비계",
r"동바리|비계|낙하물|가설|가시설|버팀|흙막이|스트럿|안전발판|건설발판|발판|사다리|가림막|가세틀|바퀴\(|조립식틀|시스템동바리|스카폴드",
),
# 볼트 · 앵커 · 철물 중 관·형강과 겹치는 것을 먼저
("볼트·앵커·철물", "철근커플러", r"철근커플러|커플러|정착구"),
("볼트·앵커·철물", "앵커", r"앵커|앙카|케미칼|카트리지|인서트"),
(
"볼트·앵커·철물",
"볼트·너트",
r"볼트|볼트너트|못트|너트|와셔|스크류|스크루|리벳|스터드|턴버클|고정핀|앵커핀|나사(?!식)|철나사|직결",
),
("볼트·앵커·철물", "", r"철못|타정|콘크리트못|STS못|벨가못|꺾쇠|표시못"),
(
"볼트·앵커·철물",
"와이어로프·클램프",
r"와이어로프|P\.P로프|클램프|훅클램프|행거|철물|지지금구|연결금구|브라켓|샤클|피뢰침",
),
("전기·통신", "전선관·트레이", r"전선관"),
# 도료 · 방수 · 접착 (좁은 것)
(
"도료·방수·접착",
"방수·실링",
r"방수|실런트|실란트|실링|코킹|지수재|씰$|백업재|충진|퍼티|벤토나이트(?!매트)|방식테이프|줄눈재|물끊기",
),
(
"도료·방수·접착",
"도료",
r"페인트(?!붓)|프라이머|신너|시너|희석제|도료|코팅제|래커|에나멜|방청|광명단|징크|안료|염료|표면처리제|표면강화제|피막양생|양생제|나노큐브|하도|상도",
),
(
"도료·방수·접착",
"접착·혼화",
r"접착|본드|(?<!합성)수지(?!\))|레진|혼화|감수제|AE제|지연제|경화제|바인더|라텍스|폴리탑|메도킹|고무패킹|패킹|가스켓",
),
# 식생 · 종자
("식생·종자", "종자", r"종자|산림종묘|배합종자"),
(
"식생·종자",
"비료·토양개량",
r"비료|생육|영양제|침식안정제|토양개량|피트모스|펄라이트|유기질|인공토양|상토|배양토|퇴비",
),
("식생·종자", "농약·약제", r"살충제|살균제|제초제|농약|훈증약제|방제약|기피제"),
(
"식생·종자",
"수목지주·보호",
r"수목지주|지주대|수목보호|수간주입|수목표찰|수목영양|나무보호|수목받침|수목고정",
),
(
"식생·종자",
"잔디·지피",
r"잔디|줄떼|평떼|지피|초화|비비추|맥문동|원추리|꽃양귀비|수레국화|패랭이|국화|억새|갈대|송엽국|들꽃|야생화|구근",
),
(
"식생·종자",
"수목",
r"나무(\(|,|$)|나무$|소나무|곰솔|해송|주목|금송|낙우송|메타세쿼이아|측백|향나무|철쭉|산홍|산수유|목련|단풍|벚|동백|무궁화|회양목|배롱|은행|느티|팽나무|회화|서어|물푸레|이팝|층층|칠엽수|계수|산딸|먼나무|모과|감나무|대추|마가목|복자기|산사|살구|사철|쥐똥|꽝꽝|남천|피라칸|조팝|개나리|장미|수국|라일락|수수꽃다리|박태기|낙상홍|화살|병꽃|불두화|히어리|야광|다정큼|당종려|종려|소철|대나무|조릿대|등나무|능소화|담쟁이|송악|마삭|인동|덩굴|아이비|칠자화|왕버들|수양|능수|진달래|백송|과꽃|옥매화|꽃양배추|화백|버들|유카|율마|허브|채송|봉선|일일초|안개초|꽃무릇|영춘화|양매자|식물재배화단|철쭉류|사스레|황매|명자|백량금|협죽도|만리화|말발도리|골담초|아로니아|오죽|올괴불|황근|풍년화|태산목|용버들|옥향|담팔수|무화과|나한송|구상나무|편백|눈향|눈주목|비자|가시|후박|녹나무|후피향|아왜|굴거리|팔손이|목서|치자|서향|광나무|보리수|버드|갯버들|포플러|사시|아까시|싸리|자귀|주엽|쉬나무|개오동|때죽|쪽동백|고로쇠|당단풍|산벚|왕벚|겹벚|자엽|해당화|꽃사과|꽃복숭|모감주|황칠|노각|밤|호두|가래|음나무|두릅|오동|참죽|옻|붉나무|말채|백당|덜꿩|병아리|고욤|헛개|꾸지|뽕|닥|미루|사과|배$|돌배|앵두|자두|석류|유자|귤|감귤|매실|살구|블루엔젤|에메랄드|쏠라데리|사피니아|라벤다|아스타|제라늄|버베나|빈카|범부채|아게라|익소라|에키네시아|카네이션|실유카|채진목|숙근|웨이브페츄니아|나리|매미꽃|금낭화|땅채송화|사초|붓꽃|할미꽃|돌나물|돌장미|초롱|프리뮬라|팬지|비올라|페튜니아|메리골드|샐비어|맨드라미|코스모스|해바라기|수선|튤립|히아신스|크로커스|백합|창포|부들|연꽃|수련|물칸나|칸나|달리아|글라디올러스|아이리스|맥문|옥잠|가는잎|수크령|갈풀|띠|톱풀|꿩의비름|세덤|기린초|돌단풍|둥굴레|은방울|모란|작약|우산|양지꽃|뚝갈|벌개미취|구절초|쑥부쟁이|층꽃|꽃댕강|댕강|황금|미선|서부|미국|서양|중국|무늬|은사초|그린톱|그라스|핑크뮬리|팜파스|수생|정수식물",
),
("식생·종자", "수목", r"^H\d{1,2}(\.\d+)?[×xX*,]\s*[RBWrbw]\d"),
# 시멘트 · 골재
(
"시멘트·골재",
"시멘트",
r"^(백|초속경|초조강|무수축|포틀랜드)?시멘트(?!벽돌|블록|라이닝|몰탈|모르타르|판|기와)",
),
("시멘트·골재", "모르타르", r"모르타르|몰탈|그라우트|무수축"),
(
"시멘트·골재",
"골재",
r"골재|쇄석|^모래|석분|자갈|잡석|부순돌|마사토|호박돌|성토재|사질토|규사",
),
# 안전시설 (관·형강보다 먼저 — 휀스 · 지주)
("안전시설", "가드레일", r"가드레일|방호울타리|방호책|충격흡수|완충시설|방호벽"),
(
"안전시설",
"표지판·지주",
r"표지판|표지병|안전표지|도로표지|안내판|표찰|반사경|반사지|시선유도|경계표주|알림표지|지주(?!대)",
),
(
"안전시설",
"낙석방지·휀스",
r"낙석방지|방책|방음|볼라드|휀스|펜스|난간|라바콘|안전화|안전모|안전대|안전망|추락방지|방지턱|안전띠|바리케이",
),
# 거푸집 · 가설재
# 목재
("목재", "합판", r"합판|MDF|OSB|파티클|섬유판|텍스|석고보드"),
(
"목재",
"각재·판재",
r"각재|판재|미송|외송|방부|원목|목재|우드칩|통나무|낙엽송|구조목|멀바우|데크(?!플레이트|패널|고정)|삼나무|편백(?!나무)|경계목|면목|침목",
),
# 매트 · 토목섬유
("매트·토목섬유", "돌망태·사방", r"돌망태|개비온|망태석|사석|녹생토|녹화|사방|이불형"),
(
"매트·토목섬유",
"부직포·토목섬유",
r"부직포|지오텍스|지오그리드|지오콤|지오셀|토목섬유|차수시트|드레인보드|배수판|투수시트|섬유지|네트|오탁방지|보호망|그물",
),
("매트·토목섬유", "매트", r"매트|멀칭|코이어|방초|고화매트|벤토나이트매트"),
# 석재
("석재", "경계석", r"경계석|도로경계|측석"),
(
"석재",
"조경석·자연석",
r"조경석|자연석|호박석|^산석|경관석|판석|사고석|야면석|막돌|다듬돌|석재|화강석|대리석|현무암|사암|점판암",
),
# 콘크리트제품
(
"콘크리트제품",
"말뚝·파일",
r"콘크리트말뚝|PC말뚝|PHC|콘크리트파일|복합말뚝|강관파일|강관말뚝|쉬트파일|시트파일|강널말뚝|팽이파일|측량말뚝|전기저항용접강관파일",
),
("콘크리트제품", "PC구조물", r"조립식PC|프리캐스트|PC맨홀|PC박스|PC개량|PC우수"),
(
"콘크리트제품",
"경계석·블록",
r"콘크리트경계|보차도경계|경계블록|시멘트벽돌|시멘트블록|보강토|GS|옹벽블록|호안블록|생태호안|식생호안|블록|벽돌",
),
# 맨홀 · 수로 · 그레이팅
("맨홀·수로·그레이팅", "그레이팅", r"그레이팅|트렌치|격자"),
(
"맨홀·수로·그레이팅",
"맨홀",
r"뚜껑|맨홀|인상링|우수받이|집수정|측구뚜껑|주철뚜껑|집수구|물받이(?!후)|침투측구|받이",
),
(
"맨홀·수로·그레이팅",
"수로관",
r"수로|플륨|암거|개거|배수로|측구|벤치플|[UL]형(측구|수로|플륨|배수)",
),
# 관 — 재질별
("잡재료", "소모품", r"PVC(용접봉|코팅|시트|바닥|선|천막)"),
("관·이음관", "흄관", r""),
("관·이음관", "파형강관", r"파형강관|파형관|커플링밴드|수밀벨트"),
(
"관·이음관",
"PVC관",
r"(?<![A-Za-z])(PVC|P\.V\.C|VR|VG|XL|PB|CPVC|PRC)|내충격|염화비닐|고강성|레진관",
),
(
"관·이음관",
"PE관",
r"^PE|폴리에틸렌|HDPE|(?<![A-Za-z])PE|HI-?(PER|GP|3R|3P|VP)|이중벽|다중벽|삼중벽|유공관|하수관|우수관|융착",
),
(
"관·이음관",
"주철관",
r"주철|(?<![A-Za-z])(KP|PM|HP)|덕타일|닥타일|타이튼|메카니칼|압륜|이탈방지|접합부속",
),
(
"관·이음관",
"유리섬유·FRP관",
r"FRP(파이프|덕트|관|이음|곡관)|GFRP(관|파이프)|유리섬유(곡관|복합관|관|파이프|덕트)|섬유복합관",
),
("관·이음관", "동관·스텐관", r"동관|스텐관|스테인리스관|STS관|SUS관|STS(파이프|배관)"),
(
"관·이음관",
"강관",
r"강관|배관용|압력배관|흑관|백관|가스관|피복강관|도복장|에폭시강관|쉬즈관|비굴착|관세척|나팔관|스틸파이프|STEELPIPE|파이프",
),
(
"관·이음관",
"이음쇠·부속",
r"STS(티|캡|엘보|레듀)|이경|티이|엘보|니플|레듀[샤셔]|유니온|소켓|플랜지|곡관|편락|엔드캡|엔드플러그|이형관|이음쇠|이음관|분기관|새들|플럭|마개|선단캡|노말밴드|˚밴드|연결구|주름관|플렉시블|플렉스",
),
# 철근 · 강재
("철근·강재", "철근", r"철근"),
("철근·강재", "철망·와이어메쉬", r"와이어메[쉬시]|용접철망|^철망|크림프철망|메쉬"),
("철근·강재", "철선", r"철선|결속선|어닐링|아연도철|PC강선|강선"),
(
"철근·강재",
"형강",
r"[Hh]형강|[ㄱㄷ]형강|^[ICT]형강|C형강|^(STS)?앵글$|평강|평철|각철|4각강|환봉|봉강|철평석|강봉|H빔|[Hh]-?빔",
),
(
"철근·강재",
"강판",
r"(?<!보)강판|후판|동판|열연|냉연|철판|스틸플레이트|데크플레이트|기초플레이트|복공판|탄소주강",
),
("철근·강재", "구조용강관", r"구조용|각관|반경철관|철관"),
# 전기 · 통신
("전기·통신", "케이블·전선", r"케이블|CABLE|^전선|전선$|HFIX|HIV|접지|절연|배선(?!용)"),
("전기·통신", "전선관·트레이", r"전선관|TRAY|트레이|풀박스|분전함|박스"),
(
"전기·통신",
"판넬·차단기",
r"차단기|접촉기|릴레이|분전반|제어기|제어반|스위치|콘센트|PLC|타이머|인버터|변압기|SK판넬|TS판넬|퓨즈|전원용",
),
(
"전기·통신",
"조명·전등",
r"LED|조명|램프|등기구|투광|보안등|안정기|경광등|경고등|유도등|신호등|가로등|등주",
),
# 기계 · 설비
(
"기계·설비",
"보일러·공조",
r"보일러|공조|에어컨|환풍|송풍|히터|화장로|버너|탱크|물탱크|저류조|열교환|팬틸트|정화조",
),
(
"잡재료",
"용접·가스",
r"프로판가스|탄산가스|프레온가스|아세틸렌|^(용접|산소|알곤|아르곤)|용접봉",
),
("기계·설비", "소방·가스", r"소방|소화(?!화)|소화전|가스믹서|가스버너|경보|감지기|방화"),
(
"기계·설비",
"기계류",
r"유압잭|잭써포트|모터|전동기|기어|감속기|베어링|해머|브레이커|톱|드릴|비트|블레이드|코아|롤러|다짐|믹서|용접기|절단기|발전기|콤프레샤|컴프레샤|비트|리밍쉘|스텝|공구",
),
(
"기계·설비",
"놀이·운동·시설물",
r"놀이|운동기구|의자|벤치|파고라|퍼걸라|쓰레기통|분리수거함|음수대|조형물|정자",
),
# 건축마감
("건축마감", "타일·기와", r"타일|테라조|기와|세라믹|도기"),
(
"건축마감",
"문·창·유리",
r"도어|(?<![가-힣])문|문(?=\(|,|$)|문틀|문비|창호|(?<!섬)유리|샤시|경첩|손잡이|자물쇠|힌지|셔터|블라인드",
),
(
"건축마감",
"천장·바닥·마감재",
r"천장|천정|흡음|석고|후레싱|후레임|칸막이|바닥재|카페트|마감|몰딩|걸레받이|조이너|루버|점검구|코너비드|논슬립|계단",
),
("건축마감", "위생기구", r"대변기|세면기|소변기|양변기|욕조|싱크"),
# 교량 · 구조 부속 · 특수포장 · 기타 좁은 규칙
(
"교량·구조부속",
"신축이음·받침",
r"신축이음|교량받침|익스펜션조인트|익스팬션조인트|EJ|내진탄성받침|교량배수|받침",
),
(
"교량·구조부속",
"보강재·정착",
r"PC강연선|GFRP보강근|DB정착바|강섬유|다웰바|스페이셔|SOC-?BAR|간격재|장선연결|콘크리트근가|SRR보강재",
),
("아스콘·포장", "특수포장", r"소일콘포장|탄성포장|황토포장|미끄럼방지포장|탑쏘일|포장재"),
("거푸집·가설재", "가설건축물", r"콘테이너|컨테이너"),
("매트·토목섬유", "부직포·토목섬유", r"그리드|구갑망|메탈라스|스텐리스망|식생토낭"),
(
"전기·통신",
"부속·기타",
r"슬리브|부스바|애자|수평도체|터미날|CONNECTOR|백열|형광등|보조등|철주|콘크리트주|보행등|접속관",
),
(
"안전시설",
"차선·방호",
r"차선규제|계선주|단부레일|라운드레일|DS연결대|DS차광망|핸드레일촉지판",
),
(
"관·이음관",
"이음쇠·부속",
r"부싱|STS직관|안장티|THP|(?<![A-Za-z])STS(플러그|캡)|SUS밴드|연결관|알루미늄관|후렉시블|유공도수관|다발관|조립식배수구|에어밴트|수도꼭지",
),
("볼트·앵커·철물", "와이어로프·클램프", r"로프|가마니|클립|타카핀|착지핀|칼블럭"),
(
"도료·방수·접착",
"도료",
r"락카|왁스|바탕조정제|주입용에폭시|주입재|씰링재|구체강화제|타르페이퍼|봉함재",
),
("잡재료", "화약", r"폭약|뇌관"),
("잡재료", "용수", r"^물\(상수도"),
("잡재료", "고철·기타금속", r"고철|봉땜납"),
("잡재료", "용접·가스", r"아세톤"),
("철근·강재", "형강", r"트러스|중도리"),
("건축마감", "천장·바닥·마감재", r"T-?BAR"),
("거푸집·가설재", "동바리·비계", r"수평재|수직재"),
("도료·방수·접착", "접착·혼화", r"기포제"),
("잡재료", "소모품", r"^풀\(|보양지|소창"),
# 잡재료
("잡재료", "용접·가스", r"\(날\)|알곤용접봉"),
(
"잡재료",
"소모품",
r"장갑|걸레|비닐|페인트붓|^붓$|사포|브러[시쉬]|톱날|연마|숫돌|기름|오일|윤활|그리스|목탄|비누|현수막|끈|밧줄|포대|마대|볏짚|양생포|필름|테이프|와이어",
),
]
RX = [(a, b, re.compile(c, re.I)) for a, b, c in R]
def 분류(이름: str, 출처: str):
n = re.sub(r"\s+", "", 이름 or "")
if 출처.startswith("자재품목 조달청"):
return 조달청_분류.get(n, ("미분류", None))
if not n:
return ("미분류", None)
for a, b, rx in RX:
if rx.search(n):
return (a, b)
return ("미분류", None)
LINE = re.compile(r'("구분": )(?:null|"[^"]*")(, "상세구분": )(?:null|"[^"]*")')
NAME = re.compile(r'"이름": "((?:[^"\\]|\\.)*)"')
SRC_ = re.compile(r'"출처": "((?:[^"\\]|\\.)*)"')
NOTE_OLD = "구분 · 상세구분 = 원문에 대·중분류 칸이 없어 비움(지어내지 않음)"
NOTE_NEW = "구분 · 상세구분 = 품명 글자 규칙으로 채움(scripts/build_자재품목_분류.py · 묶음 목록 ref/_묶음_자재품목.md) · 조달청 줄은 조달청 품명을 따름 · 못 가린 줄은 「미분류」"
def q(s):
return "null" if s is None else json.dumps(s, ensure_ascii=False)
def main():
text = SRC.read_text(encoding="utf-8")
rows = json.loads(text)[""]
out, res = [], []
for ln in text.split("\n"):
m = LINE.search(ln)
if m and '"": "MT' in ln:
nm = json.loads('"' + NAME.search(ln).group(1) + '"')
sc = json.loads('"' + SRC_.search(ln).group(1) + '"')
a, b = 분류(nm, sc)
ln = LINE.sub(lambda _: f'"구분": {q(a)}, "상세구분": {q(b)}', ln, count=1)
res.append((a, b, nm))
out.append(ln.replace(NOTE_OLD, NOTE_NEW))
assert len(res) == len(rows), (len(res), len(rows))
return "\n".join(out), res
def 묶음_md(res):
by = collections.defaultdict(collections.Counter)
for a, b, _ in res:
by[a][b or ""] += 1
ks = sorted((k for k in by if k != "미분류"), key=lambda k: -sum(by[k].values()))
ndet = sum(len(by[k]) for k in ks)
L = [
"# 자재품목 묶음 (구분 · 상세구분)",
"",
"`scripts/build_자재품목_분류.py` 가 만든 표 — 손으로 고치지 않음.",
"",
f"- 구분 {len(ks)} · 상세구분 {ndet} · 미분류 {sum(by['미분류'].values())} 줄 (전체 {len(res)} 줄)",
"",
"## 규칙",
"",
"- 품명에서 공백을 모두 뺀 글자에 정규식을 위에서부터 대어 첫 일치를 따름 — 순서가 곧 우선순위.",
"- 조달청 줄(출처 「자재품목 조달청 …」)은 조달청 품명 16 개 표를 따름.",
"- 못 가린 줄은 구분 「미분류」 · 상세구분 없음.",
"- 값 · 이름 · 키는 바꾸지 않음(두 칸만).",
"- 다시 돌리면 같은 결과.",
"",
"## 구분별 상세구분 (줄 수)",
"",
]
for k in ks:
L.append(f"### {k} ({sum(by[k].values())})")
L.append("")
L.append(" · ".join(f"{d} {c}" for d, c in by[k].most_common()))
L.append("")
return "\n".join(L)
if __name__ == "__main__":
new, res = main()
if "--미분류" in sys.argv:
c = collections.Counter(re.sub(r"\s+", "", n) for a, b, n in res if a == "미분류")
print(sum(c.values()), len(c))
print(
" ".join(
f"{k}:{v}"
for k, v in c.most_common(int(sys.argv[-1]) if sys.argv[-1].isdigit() else 300)
)
)
sys.exit()
SRC.write_text(new, encoding="utf-8", newline="\n")
OUT_MD.write_text(묶음_md(res), encoding="utf-8", newline="\n")
print("구분·상세구분 채움", len(res), "")
File diff suppressed because it is too large Load Diff