Merge remote-tracking branches 'origin/main_desktop_1', 'origin/sub_desktop_1', 'origin/sub_laptop_1', 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_4' into main_laptop_1
This commit is contained in:
@@ -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), "줄")
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
(1) 틀 — 파일 이름 · 머리 칸 · 줄 칸 · 출처 모양 · 키 모양·겹침 · 대장
|
||||
(2) 본문 — 표형 요소의 출처 절을 본문 md 에서 찾아 수를 맞댐 · 결손(본문 표에 있고 요소에 없음)·허구(요소에 있고 본문 절에 없음)
|
||||
(3) 로직 — 변수가 가리키는 요소·표·칸 · 찾기 조건 이름 · 로직 입력 · 돌고 도는 참조 · 끊긴 키
|
||||
(3) 로직 — 변수가 가리키는 요소·표·칸 · 찾기 조건 이름 · 로직 입력 · 재료 고르기 조건 · 돌고 도는 참조 · 끊긴 키
|
||||
(4) 계산 — 로직 키와 입력값으로 호표 줄별 수량·단가·금액 · 비목 합 (`master_formula.py`)
|
||||
수 뽑기는 `resources/knowledge/original/_pipeline/pum_md_tool.py` 의 것을 씀.
|
||||
"""
|
||||
|
||||
@@ -260,8 +260,10 @@ class Master:
|
||||
raise FormulaError(f"없는 요소 「{ref}」")
|
||||
return row
|
||||
|
||||
def label(self, ref: str) -> str:
|
||||
"""알림 글 — 키 옆에 이름."""
|
||||
def label(self, ref) -> str:
|
||||
"""알림 글 — 키 옆에 이름 · 고르기 조건은 조건 글."""
|
||||
if isinstance(ref, dict):
|
||||
return "재료 고르기 " + " · ".join(str(v) for v in ref.values() if v)
|
||||
try:
|
||||
row = self.get(ref)
|
||||
except FormulaError:
|
||||
@@ -476,7 +478,8 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
else {item["비목"]: qty * price}
|
||||
)
|
||||
else:
|
||||
ref = fill(item["요소"], env)
|
||||
# 고르기 조건 묶음은 그대로 넘김 — `element` 가 대표 줄·첫 줄로 풂
|
||||
ref = item["요소"] if isinstance(item["요소"], dict) else fill(item["요소"], env)
|
||||
try:
|
||||
price, source = element(master, ref, env)
|
||||
except FormulaError:
|
||||
@@ -654,6 +657,11 @@ def check_logic(master: Master, row: dict) -> tuple[list[str], set]:
|
||||
item.get("종류") != "로직" and "비목" not in item
|
||||
):
|
||||
out.append(f"{where} · 비목 「{item.get('비목')}」")
|
||||
if isinstance(item["요소"], dict): # 재료 고르기 조건
|
||||
if item.get("종류") != "재료":
|
||||
out.append(f"{where} · 고르기 조건은 종류 「재료」 만")
|
||||
out += check_pick(master, where + " 요소", item["요소"])
|
||||
continue
|
||||
one(item["요소"], where + " 요소", known)
|
||||
group = mk.GROUP_OF.get(mk.id_of(str(item["요소"])))
|
||||
if item.get("종류") != "로직" and group != item.get("종류"):
|
||||
@@ -687,4 +695,14 @@ def find_loops(graph: dict[str, set]) -> list[list[str]]:
|
||||
|
||||
|
||||
# 품셈재료 단가 — 서로 부르는 모듈이라 끝에서 읽음
|
||||
from master_material import LINKED, REGION, SLOTS, TARIFF, element # noqa: E402, F401
|
||||
from master_material import ( # noqa: E402, F401
|
||||
LINKED,
|
||||
PICK_KEYS,
|
||||
REGION,
|
||||
SLOTS,
|
||||
TARIFF,
|
||||
candidates,
|
||||
check_pick,
|
||||
element,
|
||||
pick,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ LINKED = "MP" # 품셈재료 테이블ID
|
||||
MARKET = "MT" # 자재품목 테이블ID
|
||||
PRICED = "MO" # 유가전력 테이블ID — 값 한 칸
|
||||
SLOTS = ("물가자료", "유통물가", "물가정보", "거래가격", "관급") # 자재품목 값 열
|
||||
PICK_KEYS = ("구분", "상세구분", "규격", "대표") # 재료 고르기 조건 칸 (`_틀.md` 7장)
|
||||
REGION = "자재지역" # 로직 입력 — 「지역」 은 유가 고르기가 씀
|
||||
TARIFF = "전력 계약종별" # 로직 입력 — 품셈·계약예규에 계약종별 규정 없음
|
||||
_UNITS = {"ea": "개", "ton": "톤", "t": "톤", "l": "ℓ", "리터": "ℓ", "set": "조", "세트": "조"}
|
||||
@@ -104,8 +105,58 @@ def _candidates(master: mf.Master, ref: str, row: dict, env: dict) -> list[str]:
|
||||
return [key for _, key in hits]
|
||||
|
||||
|
||||
def element(master: mf.Master, ref: str, env: dict) -> tuple[object, str | None]:
|
||||
"""요소 값과 출처 — 자재품목은 제 줄, 품셈재료는 연결을 따라가 낮은 값 · 그 밖은 줄의 `값`."""
|
||||
# ── 재료 고르기 ────────────────────────────────────────────────────────
|
||||
def candidates(master: mf.Master, cond: dict, env: dict | None = None) -> list[str]:
|
||||
"""고르기 조건 안 자재품목 키 — 구분 · 상세구분 · 규격 낱말이 모두 든 줄 · 파일 차례대로.
|
||||
입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄만(그런 줄이 없으면 조건 안 전부)."""
|
||||
want = [plain_spec(w) for w in str(cond.get("규격") or "").split()]
|
||||
detail = cond.get("상세구분")
|
||||
hits = [
|
||||
key
|
||||
for key, row in master.index.get(MARKET, {}).items()
|
||||
if row.get("구분") == cond.get("구분")
|
||||
and (not detail or row.get("상세구분") == detail)
|
||||
and all(w in plain_spec(row.get("규격")) for w in want)
|
||||
]
|
||||
region = plain((env or {}).get(REGION, ""))
|
||||
near = [k for k in hits if region in plain(master.get(k).get("이름"))] if region else []
|
||||
return near or hits
|
||||
|
||||
|
||||
def pick(master: mf.Master, cond: dict, env: dict) -> str:
|
||||
"""시험 계산이 쓸 줄 — 관리자가 정한 `대표`, 없으면 조건 안 값 있는 첫 줄."""
|
||||
if cond.get("대표"):
|
||||
return str(cond["대표"])
|
||||
keys = candidates(master, cond, env)
|
||||
keys = [k for k in keys if _offers(master, k)] or keys
|
||||
if not keys:
|
||||
raise mf.FormulaError(f"재료 고르기 후보 없음 — {cond}")
|
||||
return keys[0]
|
||||
|
||||
|
||||
def check_pick(master: mf.Master, where: str, cond: dict) -> list[str]:
|
||||
"""고르기 조건 검사 — 칸 이름 · 구분·상세구분이 자재품목에 있는지 · 대표 줄 · 후보 0."""
|
||||
if set(cond) - set(PICK_KEYS) or not cond.get("구분"):
|
||||
return [f"{where} · 재료 고르기 조건 모양 「{cond}」 — {' · '.join(PICK_KEYS)}"]
|
||||
out, rows = [], master.index.get(MARKET, {})
|
||||
for slot in ("구분", "상세구분"):
|
||||
if cond.get(slot) and not any(r.get(slot) == cond[slot] for r in rows.values()):
|
||||
out.append(f"{where} · 자재품목에 없는 {slot} 「{cond[slot]}」")
|
||||
rep = cond.get("대표")
|
||||
if rep and str(rep) not in rows:
|
||||
out.append(f"{where} · 대표 줄 「{rep}」 이 자재품목에 없음")
|
||||
elif rep and str(rep) not in candidates(master, cond):
|
||||
out.append(f"{where} · 대표 줄 「{rep}」 이 고르기 조건 밖")
|
||||
if not out and not candidates(master, cond):
|
||||
out.append(f"{where} · 재료 고르기 후보 0")
|
||||
return out
|
||||
|
||||
|
||||
def element(master: mf.Master, ref, env: dict) -> tuple[object, str | None]:
|
||||
"""요소 값과 출처 — 자재품목은 제 줄, 품셈재료는 연결을 따라가 낮은 값 · 그 밖은 줄의 `값`.
|
||||
`ref` 가 고르기 조건 묶음이면 대표 줄(없으면 조건 안 첫 줄)로 풂."""
|
||||
if isinstance(ref, dict):
|
||||
ref = pick(master, ref, env)
|
||||
row = master.get(ref)
|
||||
if ref[:2] == MARKET: # 자재품목을 바로 가리키는 호표 줄
|
||||
return _lowest(master, ref)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""산림품셈 로직 복잡도 재기 — 입력 수 · 표 찾기 수 · 로직 부르기 · 덧줄 · 재료 가짓수.
|
||||
|
||||
사용: python resources/master_data/scripts/로직_복잡도_재기.py [상위 N]
|
||||
점수 = 입력 + 찾기 + 로직부르기*2 + 덧줄 + 재료 (읽기 전용 · 아무것도 안 씀)
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FIND = re.compile(r"찾기\(")
|
||||
CALL = re.compile(r"로직\(")
|
||||
|
||||
|
||||
def measure(row: dict) -> dict:
|
||||
ho = row.get("호표") or []
|
||||
text = " ".join(str(h.get("수량", "")) + str(h.get("요소", "")) for h in ho)
|
||||
text += " ".join(str(m.get("식", "")) for m in row.get("중간") or [])
|
||||
text += " ".join(str(m.get("식", "")) for m in row.get("덧줄") or [])
|
||||
m = {
|
||||
"입력": len(row.get("입력") or []),
|
||||
"찾기": len(FIND.findall(text)),
|
||||
"로직": len(CALL.findall(text)),
|
||||
"덧줄": len(row.get("덧줄") or []),
|
||||
"재료": sum(1 for h in ho if h.get("종류") == "재료"),
|
||||
}
|
||||
m["점수"] = m["입력"] + m["찾기"] + m["로직"] * 2 + m["덧줄"] + m["재료"]
|
||||
return m
|
||||
|
||||
|
||||
def main() -> None:
|
||||
top = int(sys.argv[1]) if len(sys.argv) > 1 else 25
|
||||
out = []
|
||||
for f in sorted(ROOT.glob("로직_산림품셈_*.json")):
|
||||
for row in json.loads(f.read_text(encoding="utf-8"))["줄"]:
|
||||
out.append(
|
||||
{"키": row["키"], "번호": row["원문번호"], "이름": row["이름"], **measure(row)}
|
||||
)
|
||||
out.sort(key=lambda r: -r["점수"])
|
||||
for r in out[:top]:
|
||||
print(
|
||||
r["키"],
|
||||
r["번호"],
|
||||
r["이름"],
|
||||
{k: r[k] for k in ("입력", "찾기", "로직", "덧줄", "재료", "점수")},
|
||||
)
|
||||
print("총", len(out))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user