From 254d275d217a13ed37ab4874eced1be9b4247901 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 5 Nov 2026 19:56:16 +0900 Subject: [PATCH] =?UTF-8?q?feat(794-2a2):=20M01=20=EB=B3=B4=EA=B0=95=20?= =?UTF-8?q?=E2=80=94=20=EC=9E=90=EC=9E=AC=20=EC=9A=B4=EB=B0=98=20GF=20?= =?UTF-8?q?=EB=91=98=20=C2=B7=20=EA=B8=B0=EA=B3=84=20=EC=B1=84=EC=A7=91=20?= =?UTF-8?q?=EC=9E=90=EC=B2=B4=20GX=20=C2=B7=20=EB=8D=A4=ED=94=84=EC=9A=B4?= =?UTF-8?q?=EB=B0=98=20GF000284=20=EC=A0=81=EC=9E=AC=EA=B8=B0=EA=B3=84=20?= =?UTF-8?q?=C2=B7=20=EB=8D=A4=ED=94=84=20=EA=B3=A0=EB=A5=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GF000335 시멘트운반 · GF000336 철근운반(산림 10-1 · 10-2) — 운임 = 자재 구역화물(톤급 · 거리 줄) ÷ 1.1 ÷ 적재 톤 · 하차 품 = 입력(기본값 없음 · 옛 판 값은 설명 참고만) - GX000004 깬잡석 · 야면석 · 고임돌 · 파쇄석 채집(기계) — 굴착기 1회 N개 · 건설 공통 8-2-3(GC001147) · 상차 · 소운반 덤프 8-2-8(GC001155) · 실무 봉화#3 Q 12.38 · 9.44 재현 - GF000284 입력 끝에 적재기계(굴착기 0.2~1.0 · 로더 타이어 1.34 · 1.72) · 덤프(15 · 8 · 24ton) — 첫째 = 원문 · 기존 셈 그대로(54 조합 전후 같음) - 빌더 build_로직_M01보강_794_2a2.py = M01 [저장] 길 · 지킴 시험 test_794_2a2_m01_add.py Co-Authored-By: Claude Opus 5.5 --- resources/master_data/_키대장.json | 5 +- .../scripts/build_로직_M01보강_794_2a2.py | 436 ++++++++++++++++++ .../로직_산림품셈_10장_자재․장비_운반.json | 251 +++++++++- resources/master_data/로직_자체.json | 74 ++- resources/tester/test_794_2a2_m01_add.py | 226 +++++++++ 5 files changed, 980 insertions(+), 12 deletions(-) create mode 100644 resources/master_data/scripts/build_로직_M01보강_794_2a2.py create mode 100644 resources/tester/test_794_2a2_m01_add.py diff --git a/resources/master_data/_키대장.json b/resources/master_data/_키대장.json index 998e88569..02b91c080 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": 528, "GC": 1194, "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": 337, "CC": 528, "GC": 1194, "QC": 2060, "GX": 5, "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"}, @@ -2873,9 +2873,12 @@ "GF000332": {"원문번호": "8-3 그루터기 훈증", "파일": "로직_산림품셈_08장_방제.json"}, "GF000333": {"원문번호": "8-3 그루터기 박피", "파일": "로직_산림품셈_08장_방제.json"}, "GF000334": {"원문번호": "8-3 원목 박피", "파일": "로직_산림품셈_08장_방제.json"}, + "GF000335": {"원문번호": "10-1", "파일": "로직_산림품셈_10장_자재․장비_운반.json"}, + "GF000336": {"원문번호": "10-2", "파일": "로직_산림품셈_10장_자재․장비_운반.json"}, "GX000001": {"원문번호": "13-4-1 돌쌓기 메쌓기(인력)", "파일": "로직_자체.json"}, "GX000002": {"원문번호": "12-34-4 암거이음받침 채움재(T=20㎜)", "파일": "로직_자체.json"}, "GX000003": {"원문번호": "13-4-1 돌쌓기 메쌓기(인력) (2)", "파일": "로직_자체.json"}, + "GX000004": {"원문번호": "13-2-4 깬잡석 · 야면석 · 고임돌 · 파쇄석 채집(기계)", "파일": "로직_자체.json"}, "LB000001": {"원문번호": "1001", "파일": "인력.json"}, "LB000002": {"원문번호": "1002", "파일": "인력.json"}, "LB000003": {"원문번호": "1003", "파일": "인력.json"}, diff --git a/resources/master_data/scripts/build_로직_M01보강_794_2a2.py b/resources/master_data/scripts/build_로직_M01보강_794_2a2.py new file mode 100644 index 000000000..2904d8df0 --- /dev/null +++ b/resources/master_data/scripts/build_로직_M01보강_794_2a2.py @@ -0,0 +1,436 @@ +# -*- coding: utf-8 -*- +"""M01 보강(794-2a2 · 요구 653 · 분석 652-794-2a 2 장 번 1 · 3 · 9) — 로직 셋 더함 · GF000284 넓힘. + +돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_로직_M01보강_794_2a2.py +- 저장 = M01 화면 [저장] 길(`M01_MasterData_Store.save` — 대장 키 · 검사 · 글 모양 유지) · 한 번 돌림(있으면 멈춤). +- 1 자재 운반 — 산림 10-1 시멘트운반 · 10-2 철근운반 GF 둘: 운임 = 자재 구역화물(톤급 · 거리 이내) ÷ 적재 톤 · + 하차 = 보통인부 품 입력(원문 「인도조건에 따라」 · 품 수 없음 · 옛 판 값은 설명에 참고로만). +- 9 기계 채집 — 자체 GX 한 줄: 굴착기 1 회 N 개(q = N ÷ ㎡당 개수) · 건설 공통 8-2-3 작업량(GC001147) · + 상차 · 소운반 덤프 8-2-8(GC001155) · 근거 = 산림 13-2-4 주② · 13-2-3 주⑪. +- 3 덤프 조합 — GF000284 입력 끝에 적재기계 · 덤프 고르기(첫째 = 원문 굴착기 0.7㎥ · 15ton = 기존 셈 그대로). +""" + +from __future__ import annotations + +import json +import sys +from decimal import Decimal as D +from pathlib import Path + +HERE = Path(__file__).resolve().parent +MASTER = HERE.parent +sys.path.insert(0, str(MASTER.parents[1])) +from M01_MasterData import M01_MasterData_Store as st # noqa: E402 + +F10 = "로직_산림품셈_10장_자재․장비_운반.json" +FX = "로직_자체.json" +REGIONS = [ + "전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", + "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주", +] # fmt: skip +ROCK = ["암석작업(연암・보통암・경암)", "해당 없음", "전석섞인토사"] +NAMES = ( + "시멘트운반(구역화물 운임 + 하차비)", + "철근운반(구역화물 운임 + 하차비)", + "깬잡석 · 야면석 · 고임돌 · 파쇄석 채집(기계)", +) + + +# ── 1 자재 운반 ───────────────────────────────────────────────────────── +def _freight() -> tuple[list[str], list[str]]: + """구역화물 줄 규격 「<톤급>, <거리>」 → (톤급 목록 · 거리 목록) — 2.0톤이하는 뺌 · 원문 10.5톤 첫째.""" + rows = json.loads((MASTER / "재료_자재품목.json").read_text(encoding="utf-8"))["줄"] + tons, kms = [], [] + for r in rows: + if r.get("이름") != "구역화물" or ", " not in str(r.get("규격")): + continue + ton, km = r["규격"].split(", ") + tons += [ton] if ton not in tons and not ton.endswith("이하") else [] + kms += [km] if km not in kms else [] + tons.remove("10.5톤") + return ["10.5톤", *tons], kms + + +def _chain(name: str, pairs: list[tuple[str, D]], last: D) -> str: + """고른 글 → 수 — 만약(name == 'a', 1, 만약(…, last)).""" + text = str(last) + for label, value in reversed(pairs): + text = f"만약({name} == '{label}', {value}, {text})" + return text + + +def haul(no: str, what: str, unit: str, per: str, old: str) -> dict: + tons, kms = _freight() + t_pairs = [(t, D(t.removesuffix("톤"))) for t in tons[1:]] + sack = no == "10-1" + mid = [ + {"이름": "적재량", "식": _chain("톤급", t_pairs, D("10.5")), "단위": "ton"}, + ] + if sack: + mid.append( + {"이름": "포수", "식": "1000 / 40", "단위": "포/ton", "출처": "산림품셈 10-1 주"} + ) + div = "1.1 * 적재량 * 포수" if sack else "1.1 * 적재량" + return { + "원문번호": no, + "이름": f"{what}(구역화물 운임 + 하차비)", + "결과단위": unit, + "출처": f"산림품셈 {no}", + "소유": "공용", + "비고": ( + f"운반비 = 구역화물 운임(자재 「구역화물」 톤급 · 거리 이내 줄 · 원/회) ÷ 적재 톤{per}" + " · 해석: 적재 톤 = 톤급 · ÷ 1.1 = 운임의 부가세 뺌(실무 · 원문 없음)" + " · 하차비 = 보통인부 하차 품(입력 · 원문 「1회 · 인도조건에 따라」 · 품 수 없음)" + f" · 옛 판 참고(현행 원문 없음): {old}" + ), + "시험입력": {"톤급": "10.5톤", "거리": "20km", "하차품": D("0.0489")}, + "입력": [ + { + "이름": "톤급", + "설명": "구역화물 차 톤급 — 원문 10.5ton", + "고르기": tons, + "출처": f"산림품셈 {no}", + }, + { + "이름": "거리", + "설명": "운반 거리 구간(이내) — 구역화물 운임표 거리", + "고르기": kms, + "출처": f"산림품셈 {no}", + }, + { + "이름": "하차품", + "설명": f"하차 보통인부 품(ton당) — 인도조건에 따라 · 옛 판 참고 {old}", + "단위": "인/ton", + "출처": f"산림품셈 {no}", + "비고": "기본값 없음 — 설계자 입력", + }, + ], + "중간": mid, + "호표": [ + { + "종류": "재료", + "요소": { + "구분": "운송·용역", + "상세구분": "화물운임", + "검색어": "구역화물 {톤급}, {거리}", + }, + "이름": "구역화물 운임", + "단위": "회", + "수량": f"1 / ({div})", + "비목": "경비", + "출처": f"산림품셈 {no}", + }, + { + "종류": "인력", + "요소": "LB000002", + "이름": "보통인부(하차)", + "단위": "인", + "수량": "하차품 / 포수" if sack else "하차품", + "비목": "노무비", + "출처": f"산림품셈 {no}", + }, + ], + "덧줄": [], + "끝수": None, + } + + +# ── 9 기계 채집 ───────────────────────────────────────────────────────── +K_ROCK = "버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우" +DIGGERS = ["0201-0070", "0201-0020", "0201-0040", "0201-0060", "0201-0080", "0201-0100"] +DUMPS = [("0602-0025", D("2.5")), ("0602-0045", D("4.5")), ("0602-0060", D(6)), ("0602-0080", D(8))] + + +def gather() -> dict: + q147 = ( + "로직(GC001147, 버킷용량=q, 규격구분=규격구분, 선회각도=선회각도, " + f"버킷조건='{K_ROCK}', 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암', " + "E보정='해당 없음', 협소현장='해당 없음', f=1, E입력=0, 싸이클입력=0)" + ) + q155 = ( + "로직(GC001155, 적재용량=덤프T, 단위중량=쌓기무게, 체적변화율=1, f=1, 운반거리=운반거리, " + "도로상태='교차가 힘든 산간지도로 및 제방 등의 도로', 적하토질='모래, 역, 호박돌', " + "적하조건=적하조건, 적재장소='적재장소가 좁아서 목적장소에 진입하는데 불편을 느낄 때', " + "덮개='해당 없음', 세륜='안 함', 적재방법='적재기계', 적재싸이클=cm, 적재버킷=q, " + "버킷계수=K, 적재효율=E, 인력종류='석재류', 적재시간입력=0, E입력=0)" + ) + dig = "로직(GC000267, 기계=굴착기, 지역=지역, 보정작업=보정작업)" + return { + "원문번호": "13-2-4", + "구분": "자체", + "상세구분": "13장 구조물", + "이름": NAMES[2], + "결과단위": "원/㎡", + "출처": "자체", + "소유": "공용", + "비고": ( + "깬잡석 · 야면석 · 고임돌 · 파쇄석 기계 채집 — 원문 품 없음: 산림 13-2-4 주② 「기계화 시공이 가능한" + " 지역은 상기 품을 적용하지 않는다」 · 13-2-3 주⑪ 「건설 8-2-3 굴착기 참조」 · 실무 = 8-2-3 Q 식 +" + " 1회 개수 · 해석: q = 1회 개수 ÷ ㎡당 개수(㎡ · f = 1) · K = 암·파쇄암 · E = 파쇄암 표(흐트러진)" + " · 상차 = 채집과 같은 Q · 소운반 = 건설 공통 8-2-8(적재기계 = 같은 굴착기 · 산간지도로 · 좁은 적재장소)" + ), + "시험입력": { + "버킷개수": D(4), + "쌓기개수": D(16), + "굴착기": "0201-0070", + "선회각도": D(90), + "등급": "보통", + "보정작업": ROCK[0], + "상차": "포함", + "덤프": "0602-0025", + "운반거리": D("0.2"), + "쌓기무게": D("0.575"), + "적하조건": "불량", + "지역": "전국평균", + }, # fmt: skip + "입력": [ + {"이름": "버킷개수", "설명": "굴착기가 한 번에 담는 돌 개수", "단위": "개"}, + { + "이름": "쌓기개수", + "설명": "쌓기 1㎡에 드는 돌 개수(돌 뒷길이에 따름)", + "단위": "개/㎡", + }, + { + "이름": "굴착기", + "설명": "채집 · 상차 굴착기(무한궤도)", + "고르기": DIGGERS, + "출처": "건설품셈 공통 8-2-3", + }, + { + "이름": "선회각도", + "설명": "선회 각도", + "단위": "도", + "고르기": [D(90), D(135), D(45), D(180)], + "출처": "건설품셈 공통 8-2-3 3", + }, + { + "이름": "등급", + "설명": "작업 조건 등급(파쇄암 작업효율)", + "고르기": ["보통", "불량"], + "출처": "건설품셈 공통 8-2-3 2", + }, + { + "이름": "보정작업", + "설명": "암석 작업이면 기계손료 가산", + "고르기": ROCK, + "출처": "건설품셈 공통 8-1-7 1", + }, + {"이름": "상차", "설명": "채집한 돌을 덤프에 싣는지", "고르기": ["포함", "안 함"]}, + { + "이름": "덤프", + "설명": "소운반 덤프트럭", + "고르기": [d for d, _ in DUMPS], + "출처": "건설품셈 공통 8-2-8", + }, + { + "이름": "운반거리", + "설명": "소운반 편도 거리 — 0 이면 소운반 없음", + "단위": "㎞", + "출처": "건설품셈 공통 8-2-8", + }, + {"이름": "쌓기무게", "설명": "쌓기 1㎡ 돌 무게", "단위": "t/㎡"}, + { + "이름": "적하조건", + "설명": "덤프 적하 작업조건", + "고르기": ["불량", "보통", "양호"], + "출처": "건설품셈 공통 8-2-8 4", + }, + { + "이름": "지역", + "설명": "기계 연료값을 고를 지역", + "고르기": REGIONS, + "출처": "건설품셈 공통 8-1-7 5", + }, + ], + "중간": [ + {"이름": "q", "식": "반올림(버킷개수 / 쌓기개수, 2)", "단위": "㎡"}, + { + "이름": "규격구분", + "식": "만약(굴착기 == '0201-0020' 또는 굴착기 == '0201-0040', '0.12∼0.4', " + "만약(굴착기 == '0201-0100', '1.0∼1.2', '0.6∼0.8'))", + }, + {"이름": "K", "식": f"찾기(CC000042, 현장조건='{K_ROCK}').K"}, + { + "이름": "E", + "식": "찾기(CC000043, 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암').E", + }, + {"이름": "cm", "식": "찾기(CC000045, 규격=규격구분, 각도=선회각도).시간", "단위": "초"}, + {"이름": "Q", "식": q147, "단위": "㎡/hr", "출처": "건설품셈 공통 8-2-3"}, + {"이름": "덤프T", "식": _chain("덤프", DUMPS[1:], DUMPS[0][1]), "단위": "ton"}, + {"이름": "Q2", "식": q155, "단위": "㎡/hr", "출처": "건설품셈 공통 8-2-8"}, + ], + "호표": [ + {"종류": "로직", "요소": dig, "이름": "굴착기(채집)", "단위": "시간", "수량": "1 / Q"}, + { + "종류": "로직", + "요소": dig, + "이름": "굴착기(상차)", + "단위": "시간", + "수량": "만약(상차 == '포함', 1 / Q, 0)", + }, + { + "종류": "로직", + "요소": "로직(GC000269, 기계=덤프, 지역=지역, 보정작업=보정작업)", + "이름": "덤프트럭(소운반)", + "단위": "시간", + "수량": "만약(운반거리 > 0, 1 / Q2, 0)", + }, + ], + "덧줄": [], + "끝수": None, + } + + +# ── 3 덤프 조합 ───────────────────────────────────────────────────────── +LOADERS = ["0302-0134", "0302-0172"] +BUCKET = [("0201-0020", "0.2"), ("0201-0040", "0.4"), ("0201-0060", "0.6"), ("0201-0080", "0.8"), + ("0201-0100", "1.0"), ("0302-0134", "1.34"), ("0302-0172", "1.72")] # fmt: skip +LOOSE = "흐트러진 산적상태에서 적재하는 것으로 위 상태보다 약간 삽날이 들어가기 어려운 토질로서 버킷에 가득 채울 수 있는 것, 즉 점토, 역질토" +LUMP = "버킷에 넣기 어렵고 허술하며 불규칙한 공극이 생긴 것, 예를 들면 발파 또는 리퍼로 깎은 암괴, 호박돌, 역 등" + + +def dump(row: dict) -> dict: + """GF000284 — 입력 끝에 적재기계 · 덤프 · 0.7 · 20 · 0.85 · 15 수를 고른 값으로(기본 = 그 수).""" + old = {m["이름"]: m for m in row["중간"]} + assert "q0" not in old, "이미 넓힘" + k_loader = f"만약(암 == 0, 찾기(CC000048, 현장조건='{LOOSE}').계수, 찾기(CC000049, 현장조건='{LUMP}').계수)" + loader_cm = ( + "찾기(CC000051, 기종='타이어식').m * 찾기(CC000052, 항목='편도주행거리(표준)').값 + " + "찾기(CC000047, 기종='타이어식', 작업방법='산적상태에서 담을 때', 현장조건='용이한 경우').t1 + " + "찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값" + ) + swap = { + "Q1": ("3600 * 0.7 * K * (1 / L) * E0 / 찾기(QF000336, 구분='㎝(초)').값", "3600 * q0 * K * (1 / L) * E0 / Cm"), + "q": ("15 / 감마 * L", "덤프T / 감마 * L"), + "n": ("q / (0.7 * K)", "q / (q0 * K)"), + "Cmt": ("20 * n / (60 * 0.85)", "Cms * n / (60 * Es)"), + } # fmt: skip + mid = [] + for m in row["중간"]: + m = dict(m) + if m["이름"] in swap: + a, b = swap[m["이름"]] + assert m["식"].count(a) == 1, m["이름"] + m["식"] = m["식"].replace(a, b) + if m["이름"] == "K": + m["식"] = f"만약(로더 == 1, {k_loader}, {m['식']})" + m["출처"] += " · 건설품셈 공통 8-2-5 2" + if m["이름"] == "E0": + m["식"] = ( + "만약(로더 == 1, 찾기(CC000050, 현장조건='흐트러진 상태', 등급='보통', " + f"토질명=만약(암 == 0, '모래, 사질토', '파쇄암')).E, {m['식']})" + ) + m["출처"] += " · 건설품셈 공통 8-2-5 3" + if m["이름"] == "Q1": + for x in [ + {"이름": "로더cm", "식": loader_cm, "단위": "초", "출처": "건설품셈 공통 8-2-5 1"}, + { + "이름": "Cm", + "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', " + "찾기(QF000336, 구분='㎝(초)').값, 찾기(CC000045, 규격=규격구분, 각도=180).시간))", + "단위": "초", + "출처": "산림품셈 10-12-1 주⑤ · 건설품셈 공통 8-2-3 3", + }, + { + "이름": "Cms", + "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', 20, " + "찾기(CC000045, 규격=규격구분, 각도=135).시간))", + "단위": "초", + "출처": "산림품셈 10-12-1 2. 운반 · 건설품셈 공통 8-2-3 3", + }, + { + "이름": "Es", + "식": "만약(로더 == 1, E0, 0.85)", + "출처": "산림품셈 10-12-1 2. 운반", + }, + { + "이름": "덤프T", + "식": "만약(덤프 == '0602-0080', 8, 만약(덤프 == '0602-0240', 24, 15))", + "단위": "ton", + "출처": "산림품셈 10-12-1 주① · 건설품셈 공통 8-2-8", + }, + ]: + mid.append(x) + mid.append(m) + if m["이름"] == "암": + mid += [ + {"이름": "로더", "식": "만약(적재기계 == '0302-0134' 또는 적재기계 == '0302-0172', 1, 0)"}, + {"이름": "q0", "식": _chain("적재기계", [(a, D(b)) for a, b in BUCKET], D("0.7")), "단위": "㎥"}, + {"이름": "규격구분", "식": "만약(q0 <= 0.4, '0.12∼0.4', 만약(q0 <= 0.8, '0.6∼0.8', '1.0∼1.2'))"}, + ] # fmt: skip + lines = [dict(x) for x in row["호표"]] + a0 = "로직(GC000267, 기계='0201-0070', 지역=지역, 보정작업=보정작업)" + a1 = "로직(GC000269, 기계='0602-0150', 지역=지역, 보정작업=보정작업)" + assert lines[0]["요소"] == a0 and lines[1]["요소"] == a1 + lines[0]["요소"] = ( + "로직(GC000267, 기계=적재기계, 지역=지역, 보정작업=만약(로더 == 1, '해당 없음', 보정작업))" + ) + lines[1]["요소"] = "로직(GC000269, 기계=덤프, 지역=지역, 보정작업=보정작업)" + more = [ + { + "이름": "적재기계", + "설명": "덤프에 싣는 기계 — 원문 굴착기 0.7㎥", + "고르기": ["0201-0070", *DIGGERS[1:], *LOADERS], + "출처": "산림품셈 10-12-1 주① · 건설품셈 공통 8-2-3 · 8-2-5", + }, + { + "이름": "덤프", + "설명": "덤프트럭 톤급 — 원문 15ton", + "고르기": ["0602-0150", "0602-0080", "0602-0240"], + "출처": "산림품셈 10-12-1 주① · 건설품셈 공통 8-2-8", + }, + ] + memo = ( + " · 적재기계 · 덤프 고르기(794-2a2) — 첫째 = 원문(굴착기 0.7㎥ · 15ton · 셈 같음) · 다른 굴착기 =" + " 8-2-3 싸이클 표(적재 180° · 운반 적재 135° — 0.7㎥ 이면 22 · 20초와 같음) · K · E0 · Es 는 산림 값 ·" + " 해석: 로더(타이어) cm = m·ℓ + t1 + t2(8-2-5 · 산적 · 용이 · 표준 8m) · K = 산적 흐트러진 · 암은" + " 자연·기타 넣기 어려움 · E = 흐트러진 보통 · Es = E · 손료 보정 없음(8-1-7 1 = 무한궤도만)" + ) + return { + **row, + "비고": row["비고"] + memo, + "입력": row["입력"] + more, + "중간": mid, + "호표": lines, + } + + +def main() -> None: + files, _ = st.loaded() + have = { + r.get("이름") for d in files.values() if d.get("그룹") == "로직" for r in d.get("줄", []) + } + if any(n in have for n in NAMES): + raise SystemExit("이미 넣음 — 멈춤") + gf = next(r for r in files[F10]["줄"] if r["키"] == "GF000284") + cement = haul( + "10-1", + "시멘트운반", + "원/포", + " ÷ 포수(40kg 포대 · 원문 주)", + "적상 0.18 · 적하 0.13 인/ton", + ) + rebar = haul("10-2", "철근운반", "원/ton", "", "철근 하차 11분/ton(2인 1조)") + batch = [ + { + "file": F10, + "version": st.read(F10)[1], + "changes": [ + {"op": "edit", "key": "GF000284", "row": dump(gf)}, + {"op": "add", "row": cement}, + {"op": "add", "row": rebar}, + ], + }, + {"file": FX, "version": st.read(FX)[1], "changes": [{"op": "add", "row": gather()}]}, + ] + print(st.save(batch)) + files, _ = st.loaded() + for d in (files[F10], files[FX]): + for r in d["줄"]: + if r.get("이름") in NAMES: + print(r["키"], r["이름"]) + + +if __name__ == "__main__": + sys.stdout.reconfigure(encoding="utf-8") + main() diff --git a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json index b6157e558..53336ccd6 100644 --- a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json +++ b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json @@ -820,7 +820,7 @@ "결과단위": "원/㎥", "출처": "산림품셈 10-12-1", "소유": "공용", - "비고": "적재 Q1 = 3600·q0·K·f·E0/Cm · 운반 q = T/γt × L · n = q/(q0·K) · t1 = Cms·n/(60·Es) · t2 = (L/5 + L/6)×60 · Cmt = t1+…+t5 · Q = 60·q·f·E/Cmt(원문 식) · 해석: 원문 식 줄의 수 q0 0.7 · T 15ton · E 0.9 · Cms 20초 · Es 0.85 는 표가 없어 식에 수로 적음 · 해석: K·E0 는 주⑤ 표 「임도」 값(토사 0.9·0.60 · 파쇄암 0.55·0.35) — 식 줄의 E0 0.75 와 다름(사람 확인 목록) · f = 1/L(재료별 토량환산계수)", + "비고": "적재 Q1 = 3600·q0·K·f·E0/Cm · 운반 q = T/γt × L · n = q/(q0·K) · t1 = Cms·n/(60·Es) · t2 = (L/5 + L/6)×60 · Cmt = t1+…+t5 · Q = 60·q·f·E/Cmt(원문 식) · 해석: 원문 식 줄의 수 q0 0.7 · T 15ton · E 0.9 · Cms 20초 · Es 0.85 는 표가 없어 식에 수로 적음 · 해석: K·E0 는 주⑤ 표 「임도」 값(토사 0.9·0.60 · 파쇄암 0.55·0.35) — 식 줄의 E0 0.75 와 다름(사람 확인 목록) · f = 1/L(재료별 토량환산계수) · 적재기계 · 덤프 고르기(794-2a2) — 첫째 = 원문(굴착기 0.7㎥ · 15ton · 셈 같음) · 다른 굴착기 = 8-2-3 싸이클 표(적재 180° · 운반 적재 135° — 0.7㎥ 이면 22 · 20초와 같음) · K · E0 · Es 는 산림 값 · 해석: 로더(타이어) cm = m·ℓ + t1 + t2(8-2-5 · 산적 · 용이 · 표준 8m) · K = 산적 흐트러진 · 암은 자연·기타 넣기 어려움 · E = 흐트러진 보통 · Es = E · 손료 보정 없음(8-1-7 1 = 무한궤도만)", "시험입력": { "재료": "토사", "운반거리": 1.0, "지역": "전국평균", "보정작업": "해당 없음" }, "입력": [ { "이름": "재료", "설명": "나르는 흙·암 갈래", "고르기": ["토사", "암절취", "발파암"], "출처": "산림품셈 10-12-1 주②③" }, @@ -831,27 +831,69 @@ "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], "출처": "건설품셈 공통 8-1-7 5" }, - { "이름": "보정작업", "설명": "암석·전석 작업이면 기계손료 가산", "고르기": ["해당 없음", "암석작업(연암・보통암・경암)", "전석섞인토사"], "출처": "건설품셈 공통 8-1-7 1" } + { "이름": "보정작업", "설명": "암석·전석 작업이면 기계손료 가산", "고르기": ["해당 없음", "암석작업(연암・보통암・경암)", "전석섞인토사"], "출처": "건설품셈 공통 8-1-7 1" }, + { + "이름": "적재기계", + "설명": "덤프에 싣는 기계 — 원문 굴착기 0.7㎥", + "고르기": ["0201-0070", "0201-0020", "0201-0040", "0201-0060", "0201-0080", "0201-0100", "0302-0134", "0302-0172"], + "출처": "산림품셈 10-12-1 주① · 건설품셈 공통 8-2-3 · 8-2-5" + }, + { "이름": "덤프", "설명": "덤프트럭 톤급 — 원문 15ton", "고르기": ["0602-0150", "0602-0080", "0602-0240"], "출처": "산림품셈 10-12-1 주① · 건설품셈 공통 8-2-8" } ], "중간": [ { "이름": "암", "식": "만약(재료 == '토사', 0, 1)" }, - { "이름": "K", "식": "만약(암 == 0, 찾기(QF000336, 구분='K').값, 찾기(QF000336, 구분='K').괄호값)", "출처": "산림품셈 10-12-1 주⑤" }, - { "이름": "E0", "식": "만약(암 == 0, 찾기(QF000336, 구분='E0', 세부='토사').값, 찾기(QF000336, 구분='E0', 세부='파쇄암').값)", "출처": "산림품셈 10-12-1 주⑤" }, + { "이름": "로더", "식": "만약(적재기계 == '0302-0134' 또는 적재기계 == '0302-0172', 1, 0)" }, + { + "이름": "q0", + "식": "만약(적재기계 == '0201-0020', 0.2, 만약(적재기계 == '0201-0040', 0.4, 만약(적재기계 == '0201-0060', 0.6, 만약(적재기계 == '0201-0080', 0.8, 만약(적재기계 == '0201-0100', 1.0, 만약(적재기계 == '0302-0134', 1.34, 만약(적재기계 == '0302-0172', 1.72, 0.7)))))))", + "단위": "㎥" + }, + { "이름": "규격구분", "식": "만약(q0 <= 0.4, '0.12∼0.4', 만약(q0 <= 0.8, '0.6∼0.8', '1.0∼1.2'))" }, + { + "이름": "K", + "식": "만약(로더 == 1, 만약(암 == 0, 찾기(CC000048, 현장조건='흐트러진 산적상태에서 적재하는 것으로 위 상태보다 약간 삽날이 들어가기 어려운 토질로서 버킷에 가득 채울 수 있는 것, 즉 점토, 역질토').계수, 찾기(CC000049, 현장조건='버킷에 넣기 어렵고 허술하며 불규칙한 공극이 생긴 것, 예를 들면 발파 또는 리퍼로 깎은 암괴, 호박돌, 역 등').계수), 만약(암 == 0, 찾기(QF000336, 구분='K').값, 찾기(QF000336, 구분='K').괄호값))", + "출처": "산림품셈 10-12-1 주⑤ · 건설품셈 공통 8-2-5 2" + }, + { + "이름": "E0", + "식": "만약(로더 == 1, 찾기(CC000050, 현장조건='흐트러진 상태', 등급='보통', 토질명=만약(암 == 0, '모래, 사질토', '파쇄암')).E, 만약(암 == 0, 찾기(QF000336, 구분='E0', 세부='토사').값, 찾기(QF000336, 구분='E0', 세부='파쇄암').값))", + "출처": "산림품셈 10-12-1 주⑤ · 건설품셈 공통 8-2-5 3" + }, { "이름": "L", "식": "찾기(QF000339, 재료=재료).'토량환산계수(L)'", "출처": "산림품셈 10-12-1 주③" }, { "이름": "감마", "식": "만약(암 == 0, 찾기(QF000338, 재료='토사').단위중량, 찾기(QF000338, 재료='암절취 및 발파암').단위중량)", "출처": "산림품셈 10-12-1 주②" }, - { "이름": "Q1", "식": "반올림(3600 * 0.7 * K * (1 / L) * E0 / 찾기(QF000336, 구분='㎝(초)').값, 3)", "출처": "산림품셈 10-12-1 1. 적재" }, - { "이름": "q", "식": "15 / 감마 * L", "출처": "산림품셈 10-12-1 2. 운반" }, - { "이름": "n", "식": "q / (0.7 * K)", "출처": "산림품셈 10-12-1 2. 운반" }, + { + "이름": "로더cm", + "식": "찾기(CC000051, 기종='타이어식').m * 찾기(CC000052, 항목='편도주행거리(표준)').값 + 찾기(CC000047, 기종='타이어식', 작업방법='산적상태에서 담을 때', 현장조건='용이한 경우').t1 + 찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값", + "단위": "초", + "출처": "건설품셈 공통 8-2-5 1" + }, + { + "이름": "Cm", + "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', 찾기(QF000336, 구분='㎝(초)').값, 찾기(CC000045, 규격=규격구분, 각도=180).시간))", + "단위": "초", + "출처": "산림품셈 10-12-1 주⑤ · 건설품셈 공통 8-2-3 3" + }, + { + "이름": "Cms", + "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', 20, 찾기(CC000045, 규격=규격구분, 각도=135).시간))", + "단위": "초", + "출처": "산림품셈 10-12-1 2. 운반 · 건설품셈 공통 8-2-3 3" + }, + { "이름": "Es", "식": "만약(로더 == 1, E0, 0.85)", "출처": "산림품셈 10-12-1 2. 운반" }, + { "이름": "덤프T", "식": "만약(덤프 == '0602-0080', 8, 만약(덤프 == '0602-0240', 24, 15))", "단위": "ton", "출처": "산림품셈 10-12-1 주① · 건설품셈 공통 8-2-8" }, + { "이름": "Q1", "식": "반올림(3600 * q0 * K * (1 / L) * E0 / Cm, 3)", "출처": "산림품셈 10-12-1 1. 적재" }, + { "이름": "q", "식": "덤프T / 감마 * L", "출처": "산림품셈 10-12-1 2. 운반" }, + { "이름": "n", "식": "q / (q0 * K)", "출처": "산림품셈 10-12-1 2. 운반" }, { "이름": "Cmt", - "식": "20 * n / (60 * 0.85) + (운반거리 / 찾기(QF000337, 구분='t2', 세부='V1(적재)').값 + 운반거리 / 찾기(QF000337, 구분='t2', 세부='V2(공차)').값) * 60 + 찾기(QF000337, 구분='적하(t3)').값 + 찾기(QF000337, 구분='대기(t4)').값 + 찾기(QF000337, 구분='덮개(t5)').값", + "식": "Cms * n / (60 * Es) + (운반거리 / 찾기(QF000337, 구분='t2', 세부='V1(적재)').값 + 운반거리 / 찾기(QF000337, 구분='t2', 세부='V2(공차)').값) * 60 + 찾기(QF000337, 구분='적하(t3)').값 + 찾기(QF000337, 구분='대기(t4)').값 + 찾기(QF000337, 구분='덮개(t5)').값", "출처": "산림품셈 10-12-1 2. 운반" }, { "이름": "Q", "식": "반올림(60 * q * (1 / L) * 0.9 / Cmt, 3)", "출처": "산림품셈 10-12-1 2. 운반" } ], "호표": [ - { "종류": "로직", "요소": "로직(GC000267, 기계='0201-0070', 지역=지역, 보정작업=보정작업)", "이름": "굴착기(무한궤도) 0.7㎥(적재)", "단위": "시간", "수량": "1 / Q1" }, - { "종류": "로직", "요소": "로직(GC000269, 기계='0602-0150', 지역=지역, 보정작업=보정작업)", "이름": "덤프트럭 15ton(운반)", "단위": "시간", "수량": "1 / Q" } + { "종류": "로직", "요소": "로직(GC000267, 기계=적재기계, 지역=지역, 보정작업=만약(로더 == 1, '해당 없음', 보정작업))", "이름": "굴착기(무한궤도) 0.7㎥(적재)", "단위": "시간", "수량": "1 / Q1" }, + { "종류": "로직", "요소": "로직(GC000269, 기계=덤프, 지역=지역, 보정작업=보정작업)", "이름": "덤프트럭 15ton(운반)", "단위": "시간", "수량": "1 / Q" } ], "덧줄": [], "끝수": null @@ -875,6 +917,195 @@ "호표": [{ "종류": "인력", "요소": "LB:{직종}", "이름": "운반 인력", "단위": "인", "수량": "찾기(QF000341, '수평거리(m)'=수평거리).인원", "비목": "노무비" }], "덧줄": [], "끝수": null + }, + { + "키": "GF000335", + "원문번호": "10-1", + "구분": "산림품셈", + "상세구분": "10장 자재․장비 운반", + "이름": "시멘트운반(구역화물 운임 + 하차비)", + "결과단위": "원/포", + "출처": "산림품셈 10-1", + "소유": "공용", + "비고": "운반비 = 구역화물 운임(자재 「구역화물」 톤급 · 거리 이내 줄 · 원/회) ÷ 적재 톤 ÷ 포수(40kg 포대 · 원문 주) · 해석: 적재 톤 = 톤급 · ÷ 1.1 = 운임의 부가세 뺌(실무 · 원문 없음) · 하차비 = 보통인부 하차 품(입력 · 원문 「1회 · 인도조건에 따라」 · 품 수 없음) · 옛 판 참고(현행 원문 없음): 적상 0.18 · 적하 0.13 인/ton", + "시험입력": { "톤급": "10.5톤", "거리": "20km", "하차품": 0.0489 }, + "입력": [ + { + "이름": "톤급", + "설명": "구역화물 차 톤급 — 원문 10.5ton", + "고르기": [ + "10.5톤", + "2.5톤", + "3톤", + "3.5톤", + "4톤", + "4.5톤", + "5.0톤", + "5.5톤", + "6.0톤", + "6.5톤", + "7.0톤", + "7.5톤", + "8.0톤", + "8.5톤", + "9.0톤", + "9.5톤", + "10톤", + "11.0톤", + "11.5톤", + "12.0톤" + ], + "출처": "산림품셈 10-1" + }, + { + "이름": "거리", + "설명": "운반 거리 구간(이내) — 구역화물 운임표 거리", + "고르기": [ + "10km", + "20km", + "30km", + "40km", + "50km", + "60km", + "70km", + "80km", + "90km", + "100km", + "120km", + "140km", + "160km", + "180km", + "200km", + "230km", + "260km", + "290km", + "320km", + "350km", + "380km", + "410km", + "460km", + "510km" + ], + "출처": "산림품셈 10-1" + }, + { "이름": "하차품", "설명": "하차 보통인부 품(ton당) — 인도조건에 따라 · 옛 판 참고 적상 0.18 · 적하 0.13 인/ton", "단위": "인/ton", "출처": "산림품셈 10-1", "비고": "기본값 없음 — 설계자 입력" } + ], + "중간": [ + { + "이름": "적재량", + "식": "만약(톤급 == '2.5톤', 2.5, 만약(톤급 == '3톤', 3, 만약(톤급 == '3.5톤', 3.5, 만약(톤급 == '4톤', 4, 만약(톤급 == '4.5톤', 4.5, 만약(톤급 == '5.0톤', 5.0, 만약(톤급 == '5.5톤', 5.5, 만약(톤급 == '6.0톤', 6.0, 만약(톤급 == '6.5톤', 6.5, 만약(톤급 == '7.0톤', 7.0, 만약(톤급 == '7.5톤', 7.5, 만약(톤급 == '8.0톤', 8.0, 만약(톤급 == '8.5톤', 8.5, 만약(톤급 == '9.0톤', 9.0, 만약(톤급 == '9.5톤', 9.5, 만약(톤급 == '10톤', 10, 만약(톤급 == '11.0톤', 11.0, 만약(톤급 == '11.5톤', 11.5, 만약(톤급 == '12.0톤', 12.0, 10.5)))))))))))))))))))", + "단위": "ton" + }, + { "이름": "포수", "식": "1000 / 40", "단위": "포/ton", "출처": "산림품셈 10-1 주" } + ], + "호표": [ + { + "종류": "재료", + "요소": { "구분": "운송·용역", "상세구분": "화물운임", "검색어": "구역화물 {톤급}, {거리}" }, + "이름": "구역화물 운임", + "단위": "회", + "수량": "1 / (1.1 * 적재량 * 포수)", + "비목": "경비", + "출처": "산림품셈 10-1" + }, + { "종류": "인력", "요소": "LB000002", "이름": "보통인부(하차)", "단위": "인", "수량": "하차품 / 포수", "비목": "노무비", "출처": "산림품셈 10-1" } + ], + "덧줄": [], + "끝수": null + }, + { + "키": "GF000336", + "원문번호": "10-2", + "구분": "산림품셈", + "상세구분": "10장 자재․장비 운반", + "이름": "철근운반(구역화물 운임 + 하차비)", + "결과단위": "원/ton", + "출처": "산림품셈 10-2", + "소유": "공용", + "비고": "운반비 = 구역화물 운임(자재 「구역화물」 톤급 · 거리 이내 줄 · 원/회) ÷ 적재 톤 · 해석: 적재 톤 = 톤급 · ÷ 1.1 = 운임의 부가세 뺌(실무 · 원문 없음) · 하차비 = 보통인부 하차 품(입력 · 원문 「1회 · 인도조건에 따라」 · 품 수 없음) · 옛 판 참고(현행 원문 없음): 철근 하차 11분/ton(2인 1조)", + "시험입력": { "톤급": "10.5톤", "거리": "20km", "하차품": 0.0489 }, + "입력": [ + { + "이름": "톤급", + "설명": "구역화물 차 톤급 — 원문 10.5ton", + "고르기": [ + "10.5톤", + "2.5톤", + "3톤", + "3.5톤", + "4톤", + "4.5톤", + "5.0톤", + "5.5톤", + "6.0톤", + "6.5톤", + "7.0톤", + "7.5톤", + "8.0톤", + "8.5톤", + "9.0톤", + "9.5톤", + "10톤", + "11.0톤", + "11.5톤", + "12.0톤" + ], + "출처": "산림품셈 10-2" + }, + { + "이름": "거리", + "설명": "운반 거리 구간(이내) — 구역화물 운임표 거리", + "고르기": [ + "10km", + "20km", + "30km", + "40km", + "50km", + "60km", + "70km", + "80km", + "90km", + "100km", + "120km", + "140km", + "160km", + "180km", + "200km", + "230km", + "260km", + "290km", + "320km", + "350km", + "380km", + "410km", + "460km", + "510km" + ], + "출처": "산림품셈 10-2" + }, + { "이름": "하차품", "설명": "하차 보통인부 품(ton당) — 인도조건에 따라 · 옛 판 참고 철근 하차 11분/ton(2인 1조)", "단위": "인/ton", "출처": "산림품셈 10-2", "비고": "기본값 없음 — 설계자 입력" } + ], + "중간": [ + { + "이름": "적재량", + "식": "만약(톤급 == '2.5톤', 2.5, 만약(톤급 == '3톤', 3, 만약(톤급 == '3.5톤', 3.5, 만약(톤급 == '4톤', 4, 만약(톤급 == '4.5톤', 4.5, 만약(톤급 == '5.0톤', 5.0, 만약(톤급 == '5.5톤', 5.5, 만약(톤급 == '6.0톤', 6.0, 만약(톤급 == '6.5톤', 6.5, 만약(톤급 == '7.0톤', 7.0, 만약(톤급 == '7.5톤', 7.5, 만약(톤급 == '8.0톤', 8.0, 만약(톤급 == '8.5톤', 8.5, 만약(톤급 == '9.0톤', 9.0, 만약(톤급 == '9.5톤', 9.5, 만약(톤급 == '10톤', 10, 만약(톤급 == '11.0톤', 11.0, 만약(톤급 == '11.5톤', 11.5, 만약(톤급 == '12.0톤', 12.0, 10.5)))))))))))))))))))", + "단위": "ton" + } + ], + "호표": [ + { + "종류": "재료", + "요소": { "구분": "운송·용역", "상세구분": "화물운임", "검색어": "구역화물 {톤급}, {거리}" }, + "이름": "구역화물 운임", + "단위": "회", + "수량": "1 / (1.1 * 적재량)", + "비목": "경비", + "출처": "산림품셈 10-2" + }, + { "종류": "인력", "요소": "LB000002", "이름": "보통인부(하차)", "단위": "인", "수량": "하차품", "비목": "노무비", "출처": "산림품셈 10-2" } + ], + "덧줄": [], + "끝수": null } ] } diff --git a/resources/master_data/로직_자체.json b/resources/master_data/로직_자체.json index 71ce77145..faf22ce73 100644 --- a/resources/master_data/로직_자체.json +++ b/resources/master_data/로직_자체.json @@ -2,5 +2,77 @@ "그룹": "로직", "원문": "자체", "판": "2026", - "줄": [] + "줄": [ + { + "키": "GX000004", + "원문번호": "13-2-4", + "구분": "자체", + "상세구분": "13장 구조물", + "이름": "깬잡석 · 야면석 · 고임돌 · 파쇄석 채집(기계)", + "결과단위": "원/㎡", + "출처": "자체", + "소유": "공용", + "비고": "깬잡석 · 야면석 · 고임돌 · 파쇄석 기계 채집 — 원문 품 없음: 산림 13-2-4 주② 「기계화 시공이 가능한 지역은 상기 품을 적용하지 않는다」 · 13-2-3 주⑪ 「건설 8-2-3 굴착기 참조」 · 실무 = 8-2-3 Q 식 + 1회 개수 · 해석: q = 1회 개수 ÷ ㎡당 개수(㎡ · f = 1) · K = 암·파쇄암 · E = 파쇄암 표(흐트러진) · 상차 = 채집과 같은 Q · 소운반 = 건설 공통 8-2-8(적재기계 = 같은 굴착기 · 산간지도로 · 좁은 적재장소)", + "시험입력": { + "버킷개수": 4, + "쌓기개수": 16, + "굴착기": "0201-0070", + "선회각도": 90, + "등급": "보통", + "보정작업": "암석작업(연암・보통암・경암)", + "상차": "포함", + "덤프": "0602-0025", + "운반거리": 0.2, + "쌓기무게": 0.575, + "적하조건": "불량", + "지역": "전국평균" + }, + "입력": [ + { "이름": "버킷개수", "설명": "굴착기가 한 번에 담는 돌 개수", "단위": "개" }, + { "이름": "쌓기개수", "설명": "쌓기 1㎡에 드는 돌 개수(돌 뒷길이에 따름)", "단위": "개/㎡" }, + { "이름": "굴착기", "설명": "채집 · 상차 굴착기(무한궤도)", "고르기": ["0201-0070", "0201-0020", "0201-0040", "0201-0060", "0201-0080", "0201-0100"], "출처": "건설품셈 공통 8-2-3" }, + { "이름": "선회각도", "설명": "선회 각도", "단위": "도", "고르기": [90, 135, 45, 180], "출처": "건설품셈 공통 8-2-3 3" }, + { "이름": "등급", "설명": "작업 조건 등급(파쇄암 작업효율)", "고르기": ["보통", "불량"], "출처": "건설품셈 공통 8-2-3 2" }, + { "이름": "보정작업", "설명": "암석 작업이면 기계손료 가산", "고르기": ["암석작업(연암・보통암・경암)", "해당 없음", "전석섞인토사"], "출처": "건설품셈 공통 8-1-7 1" }, + { "이름": "상차", "설명": "채집한 돌을 덤프에 싣는지", "고르기": ["포함", "안 함"] }, + { "이름": "덤프", "설명": "소운반 덤프트럭", "고르기": ["0602-0025", "0602-0045", "0602-0060", "0602-0080"], "출처": "건설품셈 공통 8-2-8" }, + { "이름": "운반거리", "설명": "소운반 편도 거리 — 0 이면 소운반 없음", "단위": "㎞", "출처": "건설품셈 공통 8-2-8" }, + { "이름": "쌓기무게", "설명": "쌓기 1㎡ 돌 무게", "단위": "t/㎡" }, + { "이름": "적하조건", "설명": "덤프 적하 작업조건", "고르기": ["불량", "보통", "양호"], "출처": "건설품셈 공통 8-2-8 4" }, + { + "이름": "지역", + "설명": "기계 연료값을 고를 지역", + "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], + "출처": "건설품셈 공통 8-1-7 5" + } + ], + "중간": [ + { "이름": "q", "식": "반올림(버킷개수 / 쌓기개수, 2)", "단위": "㎡" }, + { "이름": "규격구분", "식": "만약(굴착기 == '0201-0020' 또는 굴착기 == '0201-0040', '0.12∼0.4', 만약(굴착기 == '0201-0100', '1.0∼1.2', '0.6∼0.8'))" }, + { "이름": "K", "식": "찾기(CC000042, 현장조건='버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우').K" }, + { "이름": "E", "식": "찾기(CC000043, 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암').E" }, + { "이름": "cm", "식": "찾기(CC000045, 규격=규격구분, 각도=선회각도).시간", "단위": "초" }, + { + "이름": "Q", + "식": "로직(GC001147, 버킷용량=q, 규격구분=규격구분, 선회각도=선회각도, 버킷조건='버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우', 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암', E보정='해당 없음', 협소현장='해당 없음', f=1, E입력=0, 싸이클입력=0)", + "단위": "㎡/hr", + "출처": "건설품셈 공통 8-2-3" + }, + { "이름": "덤프T", "식": "만약(덤프 == '0602-0045', 4.5, 만약(덤프 == '0602-0060', 6, 만약(덤프 == '0602-0080', 8, 2.5)))", "단위": "ton" }, + { + "이름": "Q2", + "식": "로직(GC001155, 적재용량=덤프T, 단위중량=쌓기무게, 체적변화율=1, f=1, 운반거리=운반거리, 도로상태='교차가 힘든 산간지도로 및 제방 등의 도로', 적하토질='모래, 역, 호박돌', 적하조건=적하조건, 적재장소='적재장소가 좁아서 목적장소에 진입하는데 불편을 느낄 때', 덮개='해당 없음', 세륜='안 함', 적재방법='적재기계', 적재싸이클=cm, 적재버킷=q, 버킷계수=K, 적재효율=E, 인력종류='석재류', 적재시간입력=0, E입력=0)", + "단위": "㎡/hr", + "출처": "건설품셈 공통 8-2-8" + } + ], + "호표": [ + { "종류": "로직", "요소": "로직(GC000267, 기계=굴착기, 지역=지역, 보정작업=보정작업)", "이름": "굴착기(채집)", "단위": "시간", "수량": "1 / Q" }, + { "종류": "로직", "요소": "로직(GC000267, 기계=굴착기, 지역=지역, 보정작업=보정작업)", "이름": "굴착기(상차)", "단위": "시간", "수량": "만약(상차 == '포함', 1 / Q, 0)" }, + { "종류": "로직", "요소": "로직(GC000269, 기계=덤프, 지역=지역, 보정작업=보정작업)", "이름": "덤프트럭(소운반)", "단위": "시간", "수량": "만약(운반거리 > 0, 1 / Q2, 0)" } + ], + "덧줄": [], + "끝수": null + } + ] } diff --git a/resources/tester/test_794_2a2_m01_add.py b/resources/tester/test_794_2a2_m01_add.py new file mode 100644 index 000000000..bcfed710a --- /dev/null +++ b/resources/tester/test_794_2a2_m01_add.py @@ -0,0 +1,226 @@ +"""794-2a2 M01 보강 지킴 — 자재 운반 GF 둘(산림 10-1 · 10-2) · 기계 채집 자체 GX · 덤프 조합 GF000284 넓힘. + +① 새 줄 셋 엔진 셈 — 운임 = 자재 구역화물(톤급 · 거리 줄 하나) ÷ 1.1 ÷ 적재 톤 · 하차 품 = 입력(기본값 없음) · + 채집 = 실무 봉화#3 식(q = 4/16 · Q 12.38 · 소운반 Q 9.44) · 견본 자동값으로 멈추지 않음. +② GF000284 — 고르기 첫째(굴착기 0.7㎥ · 15ton) = 넓히기 전 식(원문 수 0.7 · 15 · 20 · 0.85 · 22초) 그대로 · + 다른 고름 = 건설 공통 8-2-3 싸이클 · 8-2-5 로더 cm(거창#27 1.8 × 8 + 6 + 14 = 34.4) · 덤프 T. +③ 근거 제안(793-3a) — 794-2a 「없음」 블록 견본이 새 줄을 기초로 잡음(철근 · 깬잡석 · 파쇄석). +마스터 = 지금 M01(읽기만) · 엑셀 = `helper_m02_hopyo_golden.FILES`(없으면 ③ skip). +""" + +from __future__ import annotations + +import importlib +import itertools +from decimal import Decimal as D +from functools import lru_cache + +import helper_m02_hopyo_golden as golden +import pytest + +from M01_MasterData import M01_MasterData_Store as m01 # scripts 길을 둠 + +ma = importlib.import_module("master_auto") +mcut = importlib.import_module("master_cut") +mf = importlib.import_module("master_formula") + +CEMENT, REBAR, GATHER, DUMP = "GF000335", "GF000336", "GX000004", "GF000284" + + +@lru_cache(maxsize=1) +def whole(): + return m01.loaded()[1] + + +def run(key: str, given: dict) -> dict: + w = whole() + auto = {k: v for k, v in ma.values(w, w.logic(key)).items() if v is not None} + return mcut.engine(w, key, mf.run(w, key, {**auto, **given})) + + +def near(a: D, b: D) -> bool: + """줄 단가 = 하위 로직 계(끝수 셈이 원 미만을 끊음).""" + return abs(a - b) < 1 + + +def line(got: dict, name: str) -> dict: + return next(x for x in got["줄"] if x["이름"] == name) + + +# ── ① 새 줄 ──────────────────────────────────────────────────────────── +def test_새_줄_셋_자리(): + w = whole() + for key, no, unit in ( + (CEMENT, "10-1", "원/포"), + (REBAR, "10-2", "원/ton"), + (GATHER, "13-2-4", "원/㎡"), + ): + row = w.logic(key) + assert row["원문번호"] == no and row["결과단위"] == unit and row["소유"] == "공용" + got = run(key, {}) # 견본 자동값 — 멈추지 않음 + assert got["계"] >= 0 + + +def test_철근운반_실무_북부청임도36(): + got = run(REBAR, {"톤급": "10.5톤", "거리": "20km", "하차품": D(2) * 11 / 450}) + fr = line(got, "구역화물 운임") + assert fr["품목"]["규격"] == "10.5톤, 20km" and fr["비목"].keys() == {"경비"} + assert fr["수량"] == 1 / (D("1.1") * D("10.5")) + assert fr["금액"] == (fr["단가"] / D("1.1") / D("10.5")).quantize( + D("0.1"), "ROUND_DOWN" + ) # 줄 0.1 원 버림 + labor = line(got, "보통인부(하차)") + assert labor["수량"] == D(2) * 11 / 450 and labor["비목"].keys() == {"노무비"} + # 엑셀 「구역화물(10.5톤,20km) 111,240 ÷ 1.1 ÷ 10.5 = 9,631.1」 — 같은 단가면 같은 금액 + if fr["단가"] == 111240: + assert round(fr["금액"], 1) == D("9631.1") + + +def test_시멘트운반_포당(): + got = run(CEMENT, {"톤급": "5.0톤", "거리": "20km", "하차품": D("0.31")}) + fr = line(got, "구역화물 운임") + assert fr["품목"]["규격"] == "5.0톤, 20km" + assert fr["수량"] == 1 / (D("1.1") * D("5.0") * 25) # 40kg 포대 · 25 포/ton(원문 주) + assert line(got, "보통인부(하차)")["수량"] == D("0.31") / 25 + + +def test_구역화물_톤급_거리_줄_하나씩(): + w = whole() + spec = {s["이름"]: s["고르기"] for s in w.logic(REBAR)["입력"] if "고르기" in s} + assert spec["톤급"][0] == "10.5톤" and len(spec["톤급"]) == 20 and len(spec["거리"]) == 24 + for ton, km in itertools.product(spec["톤급"], spec["거리"]): + got = run(REBAR, {"톤급": ton, "거리": km, "하차품": D(0)}) + fr = line(got, "구역화물 운임") + assert fr["품목"]["규격"] == f"{ton}, {km}", (ton, km) + assert fr["수량"] == 1 / (D("1.1") * D(ton.removesuffix("톤"))) + + +def test_하차품_기본값_없음(): + w = whole() + for key in (CEMENT, REBAR): + assert ma.defaults(w, w.logic(key))["하차품"] is None + assert line(run(key, {}), "보통인부(하차)")["금액"] == 0 + + +def test_기계채집_실무_봉화3(): + w = whole() + got = run(GATHER, w.logic(GATHER)["시험입력"]) + mid = got["중간"] + assert mid["q"] == D("0.25") and mid["K"] == D("0.55") and mid["E"] == D("0.45") + assert mid["cm"] == 18 and mid["Q"] == D("12.375") # 엑셀 Q = 12.38 + assert round(mid["Q2"], 2) == D("9.44") # 엑셀 소운반 Q = 9.44(덤프 2.5톤 · L 0.2) + names = [x["이름"] for x in got["줄"]] + assert names == ["굴착기(채집)", "굴착기(상차)", "덤프트럭(소운반)"] + assert got["줄"][0]["수량"] == got["줄"][1]["수량"] == 1 / mid["Q"] + off = run(GATHER, {**w.logic(GATHER)["시험입력"], "상차": "안 함", "운반거리": D(0)}) + assert [x["수량"] for x in off["줄"][1:]] == [0, 0] + + +# ── ② GF000284 넓힘 ─────────────────────────────────────────────────── +OLD = { # 넓히기 전 식 — 원문 수 + "Q1": "반올림(3600 * 0.7 * K * (1 / L) * E0 / 찾기(QF000336, 구분='㎝(초)').값, 3)", + "q": "15 / 감마 * L", + "n": "q / (0.7 * K)", + "t1": "20 * n / (60 * 0.85)", +} + + +def _old(mid: dict, name: str) -> D: + return mf.evaluate(mf.parse(OLD[name]), dict(mid), whole(), 0) + + +@pytest.mark.parametrize("재료", ["토사", "암절취", "발파암"]) +def test_덤프운반_기본_고름_옛_식_그대로(재료): + w = whole() + row = w.logic(DUMP) + assert [s["이름"] for s in row["입력"][:4]] == ["재료", "운반거리", "지역", "보정작업"] + assert ( + ma.defaults(w, row)["적재기계"] == "0201-0070" + and ma.defaults(w, row)["덤프"] == "0602-0150" + ) + for dist, rock in itertools.product( + (D("0.3"), D("23.877")), ("해당 없음", "암석작업(연암・보통암・경암)") + ): + got = run(DUMP, {"재료": 재료, "운반거리": dist, "지역": "전국평균", "보정작업": rock}) + mid = got["중간"] + assert ( + mid["Q1"] == _old(mid, "Q1") + and mid["q"] == _old(mid, "q") + and mid["n"] == _old(mid, "n") + ) + assert mid["Cms"] * mid["n"] / (60 * mid["Es"]) == _old(mid, "t1") + dig, truck = got["줄"] + assert [dig["이름"], truck["이름"]] == [ + "굴착기(무한궤도) 0.7㎥(적재)", + "덤프트럭 15ton(운반)", + ] + base = {"지역": "전국평균", "보정작업": rock} + assert near(dig["단가"], mf.run(w, "GC000267", {"기계": "0201-0070", **base})["계"]) + assert near(truck["단가"], mf.run(w, "GC000269", {"기계": "0602-0150", **base})["계"]) + + +def test_덤프운반_넓힌_고름(): + g = {"재료": "토사", "운반거리": D(1), "지역": "전국평균", "보정작업": "해당 없음"} + small = run(DUMP, {**g, "적재기계": "0201-0020"})["중간"] + assert (small["q0"], small["Cm"], small["Cms"]) == ( + D("0.2"), + 20, + 18, + ) # 8-2-3 0.12∼0.4 · 180° · 135° + big = run(DUMP, {**g, "적재기계": "0201-0100"})["중간"] + assert (big["q0"], big["Cm"], big["Cms"]) == (D("1.0"), 23, 21) + loader = run(DUMP, {**g, "적재기계": "0302-0134"}) + mid = loader["중간"] + assert ( + mid["q0"] == D("1.34") and mid["Cms"] == D("34.4") == mid["Cm"] + ) # 거창#27 1.8 × 8 + 6 + 14 + assert mid["K"] == 1 and mid["Es"] == mid["E0"] == D("0.6") # 거창#27 K 1 · Es 0.6 + rock = run(DUMP, {**g, "보정작업": "암석작업(연암・보통암・경암)", "적재기계": "0302-0134"}) + plain = {"기계": "0302-0134", "지역": "전국평균", "보정작업": "해당 없음"} + assert near( + rock["줄"][0]["단가"], mf.run(whole(), "GC000267", plain)["계"] + ) # 타이어 로더 = 손료 보정 없음 + for dump, ton in (("0602-0080", 8), ("0602-0240", 24)): + mid = run(DUMP, {**g, "덤프": dump})["중간"] + assert mid["덤프T"] == ton and mid["q"] == ton / mid["감마"] * mid["L"] + + +# ── ③ 근거 제안 ─────────────────────────────────────────────────────── +SAMPLES = { # 블록: (기초, 단) + "북부청임도#36": (REBAR, {"확정", "근접"}), # 철근운반 [산림사업표준품셈 10-2] + "봉화#117": ( + CEMENT, + {"확정", "근접", "사람"}, + ), # 시멘트운반 — 제목 단독(톤급 「10.5ton」 못 읽음 = 794-2c) + "봉화#3": (GATHER, {"확정", "근접"}), # 깬잡석채집 + "울진소광#1448": (GATHER, {"확정", "근접"}), # 파쇄석 채집 +} + + +@lru_cache(maxsize=8) +def _book(tag: str): + from M02_MasterTemplete import M02_San_Basis_Store as store + from M02_MasterTemplete import M02_San_Book as san_book + + path = golden.FILES[tag] + if not path.exists(): + return None + book = golden.read(tag) + if book is None: + return None + sheets = san_book.sheets(path.name, path.read_bytes()) if book["꼴"] == "STmate" else {} + return {**book, "숨은식": store.hidden(book, sheets)} + + +@pytest.mark.parametrize("block", sorted(SAMPLES)) +def test_근거제안_없음에서_새_기초(block): + from M02_MasterTemplete import M02_San_Basis_Store as store + + tag, no = block.split("#") + book = _book(tag) + if book is None: + pytest.skip("실무 엑셀 없음") + got = store.one(book, int(no)) + key, tiers = SAMPLES[block] + assert got["단"] in tiers, got["단"] + assert key in [c["기초"] for c in got["근거후보"]]