From a8ed3390e9b2170e3afa6c27ee9970b8259631c4 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 4 Nov 2026 20:42:38 +0900 Subject: [PATCH] =?UTF-8?q?feat(782-2):=20=EA=B1=B4=EC=84=A4=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=201=EC=9E=A5=20=EB=82=98=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B8=B0=EC=B4=88=20=E2=80=94=20=EA=B3=84=EC=88=98=20=ED=91=9C?= =?UTF-8?q?=2033=20=C2=B7=20=ED=99=98=EA=B2=BD=EA=B4=80=EB=A6=AC=EB=B9=84?= =?UTF-8?q?=20=C2=B7=20=EA=B3=B5=EC=82=AC=EC=9A=A9=EC=88=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=C2=B7=20=EC=84=B1=EA=B2=A9=20=EC=B9=B8=20=C2=B7=20?= =?UTF-8?q?check=5Fmaster=20=EB=AA=A9=EC=B0=A8=20=EB=8C=80=EC=A1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 계수_건설품셈_01장_적용기준.json 덧(CC000495~527): 규칙 · 계수만 절(1-2-2 · 1-2-3 · 1-2-4 · 1-2-5 · 1-2-6 · 1-2-8 · 1-3-2 · 1-4-1 · 1-4-2 · 1-5-1~10)마다 「규칙 글」 표(원문 글 그대로) + 수 표(단위 · 소수 표준 · 금액 단위 · 체적 변화율 L · C · 체적환산계수 f · 파쇄된 돌 10∼40% · 잡재료 2∼5% · 작업반장 · 공구손료 3% · 시공단위 품 소수자리 · 품 산정 식 · 예시 · 건설폐기물 발생량 · 공사용수) · 1-2-9 소규모 적용시공량 · 다수 규격 예시 = 계수로만 - 표 칸 「성격」 — 기존 1-3 · 1-4 표 33 + 1-2-9 둘(같은 성격 묶음 · 1-2-9 ↔ 1-4-6 작업시간 제한 한 성격) · 기존 표의 다른 글자 그대로 - 로직_건설품셈_01장_적용기준.json 덧: GC001192 건설폐기물 발생량(1-5-4 · 면적 × TON/㎡ × 폐콘크리트 증가율) · GC001193 공사용수(1-5-6 · 작업량 × 단위용수 × 잡용수율) · 원문 「-」 칸 = 줄 없음(멈춤) - 1-2-9 소규모 · 1-4 품의 할증 로직 = 만들지 않음(PLAN 783-1) - 빌더 덧 build_로직_건설품셈_01장_적용기준.py 나머지(+ _적용기준_나머지_표 · _적용기준_나머지_로직) · 키 = master_keys.issue(adopt 는 로직을 받지 않음) - check_master 「목차」(check_master_toc.py) — 공통 1장 원문 절 목록 ↔ 마스터 전수 · 통째 빠진 절 · 로직 없는 품·표·식 절 · 1-2-9 · 1-4-2 = 783-1 로 미룸 표지 - 지킴 시험 test_782_2_common_ch1.py · test_782_1_carry_127.py 로직 줄 목록 = 앞 셋만 봄(같은 파일에 1-5 로직이 붙어 낡음) - check_master 전후 같음(+ 목차 0) · 기존 로직 1513 셈 값 전후 같음 · 계산전부 1515 Co-Authored-By: Claude Opus 5.5 --- resources/master_data/_키대장.json | 37 +- .../scripts/_적용기준_나머지_로직.py | 218 +++++ .../scripts/_적용기준_나머지_표.py | 553 +++++++++++++ .../build_로직_건설품셈_01장_적용기준.py | 8 +- resources/master_data/scripts/check_master.py | 6 +- .../master_data/scripts/check_master_toc.py | 89 +++ .../계수_건설품셈_01장_적용기준.json | 744 ++++++++++++++++++ .../로직_건설품셈_01장_적용기준.json | 47 ++ resources/tester/test_782_1_carry_127.py | 2 +- resources/tester/test_782_2_common_ch1.py | 143 ++++ 10 files changed, 1843 insertions(+), 4 deletions(-) create mode 100644 resources/master_data/scripts/_적용기준_나머지_로직.py create mode 100644 resources/master_data/scripts/_적용기준_나머지_표.py create mode 100644 resources/master_data/scripts/check_master_toc.py create mode 100644 resources/tester/test_782_2_common_ch1.py diff --git a/resources/master_data/_키대장.json b/resources/master_data/_키대장.json index b16577b6c..998e88569 100644 --- a/resources/master_data/_키대장.json +++ b/resources/master_data/_키대장.json @@ -1,5 +1,5 @@ { - "다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 335, "CC": 495, "GC": 1192, "QC": 2060, "GX": 4, "UA": 38}, + "다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 335, "CC": 528, "GC": 1194, "QC": 2060, "GX": 4, "UA": 38}, "폐기": {"MT000001": "자재품목 휘발유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000002": "자재품목 경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000003": "자재품목 선박용경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000004": "자재품목 중유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000005": "자재품목 등유 — 값이 비고 글뿐 · 유가 테이블로 감", "LB000399": "이름 「〃」 — 직종이 아니라 되풀이 기호 · 소요량 13-6-9 에서 윗줄 직종으로 바로잡음", "MN": "재료_나라장터자재 — old 로 옮김 · 로직은 자재품목·품셈재료로 갈아 끼움", "LB000372": "미확보 고급기능사 — 측량노임 항공사진고급기능사 옛이름으로 옮김", "LB000393": "미확보 리베팅공 — 건설노임 철골공 옛이름으로 옮김(2010 통합표 연번10)", "LB000405": "미확보 Belt Conveyor 설치공 — 품셈 13-10-1 주② 배분으로 다섯 직종에 나눔", "MT000006": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "MT000007": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "LB000368": "미확보 기술사 — 기술사(건설) 옛이름으로 옮김", "LB000369": "미확보 특급기술자 — 특급기술자(건설) 옛이름으로 옮김", "LB000370": "미확보 고급기술자 — 고급기술자(건설) 옛이름으로 옮김", "LB000361": "미확보 중급기술자 — 중급기술자(건설) 옛이름으로 옮김", "LB000364": "미확보 초급기술자 — 초급기술자(건설) 옛이름으로 옮김", "LB000365": "미확보 고급숙련기술자 — 고급숙련기술자(건설) 옛이름으로 옮김", "LB000366": "미확보 중급숙련기술자 — 중급숙련기술자(건설) 옛이름으로 옮김", "LB000367": "미확보 초급숙련기술자 — 초급숙련기술자(건설) 옛이름으로 옮김", "LB000389": "미확보 기계기사 — 초급기술자(기계·설비) 옛이름으로 옮김", "LB000388": "미확보 기계산업기사 — 초급숙련기술자(기계·설비) 옛이름으로 옮김", "MO000055": {"원문번호": "주택용저압:기본_200이하", "파일": "재료_유가전력.json"}, "MO000056": {"원문번호": "주택용저압:기본_201~400", "파일": "재료_유가전력.json"}, "MO000057": {"원문번호": "주택용저압:기본_400초과", "파일": "재료_유가전력.json"}, "MO000059": {"원문번호": "주택용저압:전력량_다음200", "파일": "재료_유가전력.json"}, "MO000060": {"원문번호": "주택용저압:전력량_400초과", "파일": "재료_유가전력.json"}, "MO000061": {"원문번호": "주택용저압:월간최저요금", "파일": "재료_유가전력.json"}, "MO000062": {"원문번호": "일반용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000063": {"원문번호": "일반용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000064": {"원문번호": "일반용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000065": {"원문번호": "일반용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000066": {"원문번호": "산업용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000067": {"원문번호": "산업용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000068": {"원문번호": "산업용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000069": {"원문번호": "산업용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000070": {"원문번호": "임시전력갑:기본요금적용", "파일": "재료_유가전력.json"}, "MO000071": {"원문번호": "임시전력갑:전력량요금적용", "파일": "재료_유가전력.json"}, "MO000072": {"원문번호": "임시전력갑:월간최저요금", "파일": "재료_유가전력.json"}}, "키": { "CC000001": {"원문번호": "공통 1-3-1 콘크리트 및 포장용 재료", "파일": "계수_건설품셈_01장_적용기준.json"}, @@ -496,6 +496,39 @@ "CC000492": {"원문번호": "공통 8-2-33 E", "파일": "계수_건설품셈_08장_건설기계.json"}, "CC000493": {"원문번호": "공통 8-2-33 배사관", "파일": "계수_건설품셈_08장_건설기계.json"}, "CC000494": {"원문번호": "공통 8-2-33 상수", "파일": "계수_건설품셈_08장_건설기계.json"}, + "CC000495": {"원문번호": "공통 1-2-2 설계서의 단위 및 소수의 표준", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000496": {"원문번호": "공통 1-2-2 금액의 단위표준", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000497": {"원문번호": "공통 1-2-3 체적의 변화율", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000498": {"원문번호": "공통 1-2-3 체적환산계수", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000499": {"원문번호": "공통 1-2-3 파쇄된 돌 발생량", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000500": {"원문번호": "공통 1-2-3 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000501": {"원문번호": "공통 1-2-4 잡재료 및 소모재료", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000502": {"원문번호": "공통 1-2-4 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000503": {"원문번호": "공통 1-2-5 작업반장", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000504": {"원문번호": "공통 1-2-5 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000505": {"원문번호": "공통 1-2-6 공구손료", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000506": {"원문번호": "공통 1-2-6 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000507": {"원문번호": "공통 1-2-8 시공단위 품 소수자리", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000508": {"원문번호": "공통 1-2-8 시공단위 품 산정 식", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000509": {"원문번호": "공통 1-2-8 시공단위 품 표기 예시", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000510": {"원문번호": "공통 1-2-8 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000511": {"원문번호": "공통 1-2-9 소규모 적용시공량", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000512": {"원문번호": "공통 1-2-9 다수 규격 예시", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000513": {"원문번호": "공통 1-3-2 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000514": {"원문번호": "공통 1-4-1 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000515": {"원문번호": "공통 1-4-2 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000516": {"원문번호": "공통 1-5-1 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000517": {"원문번호": "공통 1-5-2 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000518": {"원문번호": "공통 1-5-3 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000519": {"원문번호": "공통 1-5-4 건설폐기물 발생량", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000520": {"원문번호": "공통 1-5-4 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000521": {"원문번호": "공통 1-5-5 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000522": {"원문번호": "공통 1-5-6 공사용수", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000523": {"원문번호": "공통 1-5-6 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000524": {"원문번호": "공통 1-5-7 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000525": {"원문번호": "공통 1-5-8 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000526": {"원문번호": "공통 1-5-9 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000527": {"원문번호": "공통 1-5-10 규칙 글", "파일": "계수_건설품셈_01장_적용기준.json"}, "CF000001": {"원문번호": "1-1-3 일일 작업시간", "파일": "계수_산림품셈_01장_적용기준.json"}, "CF000002": {"원문번호": "1-2-1 소수 처리", "파일": "계수_산림품셈_01장_적용기준.json"}, "CF000003": {"원문번호": "1-2-1 총축적 계산 기준", "파일": "계수_산림품셈_01장_적용기준.json"}, @@ -2504,6 +2537,8 @@ "GC001189": {"원문번호": "공통 8-2-32 임목파쇄기", "파일": "로직_건설품셈_08장_건설기계.json"}, "GC001190": {"원문번호": "공통 8-2-33 골재채취선", "파일": "로직_건설품셈_08장_건설기계.json"}, "GC001191": {"원문번호": "기계설비 13-2-7 아세틸렌량의 환산", "파일": "로직_건설품셈_13장_플랜트설비공사.json"}, + "GC001192": {"원문번호": "공통 1-5-4 건설폐기물 발생량", "파일": "로직_건설품셈_01장_적용기준.json"}, + "GC001193": {"원문번호": "공통 1-5-6 공사용수", "파일": "로직_건설품셈_01장_적용기준.json"}, "GF000001": {"원문번호": "3-1 경계표시", "파일": "로직_산림품셈_03장_작업장_관리.json"}, "GF000002": {"원문번호": "3-3 작업로 설치", "파일": "로직_산림품셈_03장_작업장_관리.json"}, "GF000003": {"원문번호": "3-4-2 임산물 운반로 신설 작업량", "파일": "로직_산림품셈_03장_작업장_관리.json"}, diff --git a/resources/master_data/scripts/_적용기준_나머지_로직.py b/resources/master_data/scripts/_적용기준_나머지_로직.py new file mode 100644 index 000000000..5bb13e5b4 --- /dev/null +++ b/resources/master_data/scripts/_적용기준_나머지_로직.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +"""건설품셈 공통 1장 나머지(782-2) — 1-5-4 · 1-5-6 로직 둘 + 계수 표 넣기 · 기존 할증 표 「성격」 칸. + +`build_로직_건설품셈_01장_적용기준.py 나머지` 가 부름 · 한 번 돌림(이미 넣었으면 멈춤). +- 계수 표 = `_적용기준_나머지_표.all_tables()` · 절 차례 자리에 끼움(처음으로 절 번호가 더 큰 기존 표 앞 · 없으면 끝). +- 로직 = 로직_건설품셈_01장_적용기준.json 끝에 덧 · 키 = master_keys.issue(adopt 는 로직을 받지 않음). +- 1-2-9 소규모 · 1-4 할증 로직 = 만들지 않음(PLAN 783-1). +""" + +from __future__ import annotations + +import json +import re +from decimal import Decimal + +import _적용기준_나머지_표 as T +import build_로직_건설품셈_01장_적용기준 as base +import build_로직_건설품셈_08장_건설기계 as b08 +import master_keys as mk + +MASTER = base.MASTER +COEF, LOGIC, HEAD = base.COEF, base.LOGIC, base.HEAD +SRC4, SRC6 = "건설품셈 공통 1-5-4", "건설품셈 공통 1-5-6" + +# 기존 1-3 · 1-4 표 → 성격(같은 성격 묶음 · 1-4-2 이중 적용 불가) — 기본 = 같은 표 · 딸린 표(정의 · 한도)는 본 표와 같음 +NATURE = { + **{f"CC{n:06d}": "재료 할증" for n in range(1, 9)}, + "CC000009": "작업지연 — 현장조건", + "CC000010": "작업지연 — 열차의 운행빈도", + "CC000011": "작업지연 — 건물 층수", + "CC000012": "작업지연 — 건물 층수", + **{f"CC{n:06d}": "지세/지형 — 지세" for n in range(13, 20)}, + "CC000020": "지세/지형 — 도심지", + "CC000021": "위험 — 고소작업", + "CC000022": "위험 — 교량상 작업", + **{f"CC{n:06d}": "위험 — 터널내 작업" for n in range(23, 26)}, + "CC000026": "위험 — 유해 작업", + "CC000027": "위험 — 유해 작업", + "CC000028": T.SMALL, + "CC000029": "작업제한 — 폭염시 휴게시간", + "CC000030": "작업환경 — 야간", + "CC000031": "작업환경 — 특수작업", + "CC000032": "작업환경 — 기타", + "CC000033": "작업환경 — 기타", +} + + +def inp(name, desc, unit=None, pick=None, src=None): + return base.inp(name, desc, unit, pick, src) + + +def logics(K: dict) -> list[dict]: + waste, water = K["건설폐기물 발생량"], K["공사용수"] + w_rows = T.t_1_5_4()[0]["줄"] + builds = list(dict.fromkeys(r["구조"] for r in w_rows)) + kinds = [r["구분"] for r in T.t_1_5_6()[0]["줄"] if r["구분"] != "잡용수"] + env = { + "열쇠": "공통 1-5-4 건설폐기물 발생량", + **HEAD, + "이름": "건설폐기물 발생량 — 건설 공통 1-5-4", + "결과단위": "TON", + "출처": f"{SRC4} 2", + "소유": "공용", + "비고": "발생량 = 면적 × 단위면적당 발생량(TON/㎡) × (1 + 증가율 ÷ 100) · 증가율 = 폐콘크리트류만(주③ 파일 시공 15% · 20% 이내 · 주④ 세륜 시설 개소당 3% 이내 — 설계자가 넣음 · 0 = 증가 없음) · 원문 칸 「-」 = 줄 없음(멈춤) · 건축물 해체는 설계도서 산출 우선(원문 2) · 주⑤ 현장 여건 조정", + "시험입력": { + "공사": "신축", + "구조": "철근콘크리트조", + "폐기물": "폐콘크리트류", + "면적": 1000, + "증가율": 0, + }, + "입력": [ + inp("공사", "신축 · 해체", pick=["신축", "해체"], src=f"{SRC4} 2"), + inp("구조", "건물 구조(주거용 = 단독주택 · 아파트)", pick=builds, src=f"{SRC4} 2"), + inp("폐기물", "건설폐기물 종류", pick=list(T.WASTE), src=f"{SRC4} 2"), + inp("면적", "단위면적 산출 면적", "㎡", src=f"{SRC4} 2"), + inp( + "증가율", + "폐콘크리트 수량 증가(%) — 주③ 파일 15 · 20% 이내 · 주④ 세륜 개소당 3% 이내 · 0 = 없음", + "%", + src=f"{SRC4} 2 주③ ④", + ), + ], + "중간": [ + { + "이름": "원단위", + "식": f"찾기({waste}, 공사=공사, 구조=구조, 폐기물=폐기물).발생량", + "단위": "TON/㎡", + }, + { + "이름": "증가", + "식": "만약(폐기물 == '폐콘크리트류', 증가율 / 100, 0)", + }, + ], + "결과": {"식": "면적 * 원단위 * (1 + 증가)", "출처": f"{SRC4} 2"}, + "끝수": None, + } + use = { + "열쇠": "공통 1-5-6 공사용수", + **HEAD, + "이름": "공사용수 — 건설 공통 1-5-6", + "결과단위": "㎥", + "출처": f"{SRC6} 3", + "소유": "공용", + "비고": "용수량 = 작업량 × 단위당 용수(㎥) × (1 + 잡용수율 ÷ 100) · 양생 물 포함([주]) · 해석: 잡용수 = 원문 「사용량비의 40∼50%」 — 잡용수율 0 = 더하지 않음(설계자가 40∼50 넣음)", + "시험입력": {"구분": "콘크리트혼합 및 양생", "작업량": 100, "잡용수율": 0}, + "입력": [ + inp("구분", "용수 구분", pick=kinds, src=f"{SRC6} 3"), + inp("작업량", "구분의 단위 분모 수량(㎡ · ㎥ · 1,000매 · 표면적 ㎡)", src=f"{SRC6} 3"), + inp( + "잡용수율", + "잡용수(%) — 원문 사용량비의 40∼50% · 0 = 더하지 않음", + "%", + src=f"{SRC6} 3", + ), + ], + "중간": [ + {"이름": "단위용수", "식": f"찾기({water}, 구분=구분).수량", "단위": "㎥"}, + ], + "결과": {"식": "작업량 * 단위용수 * (1 + 잡용수율 / 100)", "출처": f"{SRC6} 3"}, + "끝수": None, + } + return [env, use] + + +def emit(t: dict) -> str: + """기존 표 글 모양 + 「성격」 칸(용도 다음 줄).""" + text = base.emit(t) + if "성격" in t: + text = re.sub( + r'(\n "용도": [^\n]*\n)', + lambda m: m[1] + f' "성격": {json.dumps(t["성격"], ensure_ascii=False)},\n', + text, + count=1, + ) + return text + + +def _sec(no: str) -> tuple[int, ...]: + return b08._sec(no) + + +def insert_tables(text: str, made: list[dict]) -> str: + """새 표 → 처음으로 절 번호가 더 큰 기존 표 앞(없으면 끝).""" + starts = [ + (m.start(), m[1]) + for m in re.finditer(r' \{\n "키": "CC\d+", "원문번호": "([^"]+)"', text) + ] + pieces, last, left = [], 0, list(made) + for at, no in starts: + due = [t for t in left if _sec(t["원문번호"]) < _sec(no)] + if due: + pieces += [text[last:at], "".join(emit(t) + ",\n" for t in due)] + left, last = [t for t in left if t not in due], at + tail = text.rindex("\n ]\n}") + pieces.append(text[last:tail]) + if left: + pieces.append(",\n" + ",\n".join(emit(t) for t in left)) + pieces.append(text[tail:]) + return "".join(pieces) + + +def add_nature(text: str) -> str: + """기존 1-3 · 1-4 표에 「성격」 한 줄 — 다른 글자는 그대로.""" + for key, nature in NATURE.items(): + pat = re.compile( + rf'( "키": "{key}",[^\n]*\n(?: (?!"용도")[^\n]*\n)*? "용도": [^\n]*\n)' + ) + text, n = pat.subn( + lambda m: m[1] + f' "성격": {json.dumps(nature, ensure_ascii=False)},\n', + text, + count=1, + ) + assert n == 1, key + return text + + +def run() -> None: + coef_path = MASTER / COEF + text = coef_path.read_text(encoding="utf-8") + if '"원문번호": "1-5-4"' in text: + raise SystemExit("이미 넣음 — 멈춤") + old = json.loads(text, parse_float=Decimal, parse_int=Decimal)["표"] + book = mk.load_book() + made = T.all_tables() + K = {} + for t in made: + t["키"] = mk.issue(book, "CC", t.pop("열쇠"), COEF) + K[t["이름"]] = K[t["키"]] = t["키"] + t.update(HEAD) + K["건설폐기물 발생량"] = next( + t["키"] for t in made if t["원문번호"] == "1-5-4" and "발생량" in t["이름"] + ) + K["공사용수"] = next( + t["키"] for t in made if t["원문번호"] == "1-5-6" and "공사용수" in t["이름"] + ) + + rows = [] + for r in logics(K): + r["키"] = mk.issue(book, "GC", r.pop("열쇠"), LOGIC) + r["원문번호"] = r["출처"].split()[2] + head = ["키", "원문번호"] + rows.append({k: r[k] for k in head + [k for k in r if k not in head]}) + for t in made: + t["용도"]["로직키"] = [ + r["키"] for r in rows if t["키"] in json.dumps(r, ensure_ascii=False) + ] + + new = add_nature(insert_tables(text, made)) + data = json.loads(new, parse_float=Decimal, parse_int=Decimal)["표"] + keep = {t["키"]: {k: v for k, v in t.items() if k != "성격"} for t in data} + assert [keep[t["키"]] for t in old] == old # 기존 표 = 성격 칸만 더함 + assert len(data) == len(old) + len(made) + assert sum("성격" in t for t in data) == len(NATURE) + 2 + coef_path.write_text(new, encoding="utf-8", newline="\n") + b08.append_rows(LOGIC, rows) + mk.save_book(mk.finish(book)) + print("계수", made[0]["키"], "~", made[-1]["키"], len(made), "· 로직", [r["키"] for r in rows]) diff --git a/resources/master_data/scripts/_적용기준_나머지_표.py b/resources/master_data/scripts/_적용기준_나머지_표.py new file mode 100644 index 000000000..83fbac65a --- /dev/null +++ b/resources/master_data/scripts/_적용기준_나머지_표.py @@ -0,0 +1,553 @@ +# -*- coding: utf-8 -*- +"""건설품셈 공통 1장 나머지 — 규칙 · 계수만 절의 계수 표(782-2) · `build_로직_건설품셈_01장_적용기준.py 나머지` 가 부름. + +- 절 범위 = `### N-N-N` 머리부터 다음 같은 · 윗 단계 머리 앞까지(요구 630). +- 절마다 「규칙 글」 표 하나(원문 글 그대로 · 항 = 「N.」 · 번호 없는 머리 글 = 「머리」) + 수 · 표가 있는 절은 수 표. +- 1-2-9 소규모 · 1-4-2 중복가산 = 계수로만(로직은 PLAN 783-1) · 성격 칸 = 같은 성격 묶음(1-4-2 이중 적용 불가 판정). +- 값은 원문 글자 그대로(N) · 「-」 칸 = 값 없음(줄 · 칸을 두지 않음) · 범위 = [아래, 위]. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import build_로직_건설품셈_01장_적용기준 as base + +N, cell = base.N, base.cell +CH1 = base.BODY.parent +FILES = { + "1-2": "1-02_설계_및_수량.md", + "1-3": "1-03_재료_및_노임의_할증.md", + "1-4": "1-04_품의_할증.md", + "1-5": "1-05_기타.md", +} +SMALL = "작업시간 제한 · 소규모" # 1-2-9 ↔ 1-4-6 1 같은 성격(_제안_634 1-2) + + +def section(no: str, md: Path | None = None) -> tuple[str, list[str]]: + """(절 이름, 절 줄) — 머리 줄부터 다음 같은 · 윗 단계 머리 앞까지.""" + md = md or CH1 / FILES[no.rsplit("-", 1)[0]] + lines = md.read_text(encoding="utf-8").split("\n") + head = re.compile(rf"^(#+) {re.escape(no)} (.+)$") + a = next(i for i, x in enumerate(lines) if head.match(x)) + level = len(head.match(lines[a])[1]) + b = next( + (j for j in range(a + 1, len(lines)) if re.match(rf"^#{{1,{level}}} ", lines[j])), + len(lines), + ) + name = re.sub(r"\s*\(['‘].*$", "", head.match(lines[a])[2]).strip() + return name, lines[a:b] + + +def _plain(x: str) -> str: + x = re.sub(r"", "", x) + x = re.sub(r"^(\d+)\\\.", r"\1.", x.strip()) + return x.strip() + + +def rules(lines: list[str]) -> list[dict]: + """규칙 글 — 표 · 그림 · 주석 줄을 뺀 원문 글을 항(「N.」)마다 한 줄.""" + out, item = [], {"항": "머리", "글": []} + for x in lines[1:]: + if x.startswith(("|", "![")) or re.fullmatch(r"\s*\s*", x): + continue + m = re.match(r"^(\d+)\\\. ", x) + if m: + out.append(item) + item = {"항": m[1], "글": []} + if _plain(x): + item["글"].append(_plain(x)) + out.append(item) + return [{"항": r["항"], "규칙": "\n".join(r["글"])} for r in out if r["글"]] + + +def table(no, label, name, src, cols, rows, *, cond=None, note=(), base_="", extra=None, 대상=None): + return { + "열쇠": f"공통 {no} {label}", + "원문번호": no, + "이름": name, + "기준": base_, + "출처": f"건설품셈 공통 {no} {src}".strip(), + **(extra or {}), + "용도": {"공종": section(no)[0], "대상": 대상 or ["작업량 조건"], "로직키": []}, + "조건": cond or {}, + "값칸": cols, + "줄": rows, + "주": list(note), + } + + +def rule_table(no: str, extra: dict | None = None) -> dict: + name, lines = section(no) + return table( + no, + "규칙 글", + f"{name} — 규칙 글", + "", + {"규칙": "글"}, + rules(lines), + cond={"항": "고르기"}, + base_="원문 글 그대로", + extra={ + "비고": "계산 칸 없는 규칙 · 기준 글 — 항 = 원문 「N.」 · 머리 = 번호 없는 글", + **(extra or {}), + }, + ) + + +def notes(lines: list[str], after: int = 0) -> list[str]: + """`after` 줄 뒤 첫 「[주]」 묶음(빈 줄까지).""" + i = next(j for j in range(after, len(lines)) if lines[j].strip() == "[주]") + out = [] + for x in lines[i + 1 :]: + if not x.strip(): + break + if not x.startswith("