From 187fda10550ccc8ba01c5a01dd180cf23d2f1874 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 4 Nov 2026 18:44:25 +0900 Subject: [PATCH] =?UTF-8?q?feat(782-1):=20=EA=B1=B4=EC=84=A4=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=201-2-7=20=EC=9A=B4=EB=B0=98=20=EA=B8=B0=EC=B4=88=20?= =?UTF-8?q?=E2=80=94=20=EA=B3=84=EC=88=98=20=ED=91=9C=20=EC=97=AC=EB=8D=9F?= =?UTF-8?q?=20=C2=B7=20=EC=9D=B8=EB=A0=A5=EC=9A=B4=EB=B0=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=85=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 계수_건설품셈_01장_적용기준.json 1-2-7 표 덧(CC000341~348): 소운반 거리 · 인력운반 기본공식 상수 · 지게운반 t · V · 벽돌운반 · 인력운반(기계설비) 왕복평균속도 · 준비작업시간 · 경사지 환산계수 α · 화물자동차 적재량 — 옛 표 목록이 1-2-5 밑에 붙였던 1-2-7 표 바로잡음 - 새 로직_건설품셈_01장_적용기준.json(차례 13 · 공통 2장 앞): GC001140 인력운반(지게) · GC001141 인력운반(기계설비) · GC001142 벽돌운반 - 지게 = N = V × T ÷ (120 × α × L + V × t) 또는 운반회수 직접 · 보통인부 = 무게 ÷ 1회 운반량 ÷ N · 무게 0 = 1-2-4 3 단위중량 표 - 새 빌더 build_로직_건설품셈_01장_적용기준.py · 지킴 시험 test_782_1_carry_127.py(재현 넷 0.547619 · 0.163556 · 0.654222 · 0.0132 · check_master) - 기존 표 · 로직 글자 그대로 · check_master 전후 같음 · 계산전부 1461 → 1464 멈춤 0 Co-Authored-By: Claude Opus 5.5 --- resources/master_data/_키대장.json | 13 +- .../build_로직_건설품셈_01장_적용기준.py | 619 ++++++++++++++++++ .../계수_건설품셈_01장_적용기준.json | 207 ++++++ .../로직_건설품셈_01장_적용기준.json | 236 +++++++ resources/tester/test_782_1_carry_127.py | 100 +++ 5 files changed, 1174 insertions(+), 1 deletion(-) create mode 100644 resources/master_data/scripts/build_로직_건설품셈_01장_적용기준.py create mode 100644 resources/master_data/로직_건설품셈_01장_적용기준.json create mode 100644 resources/tester/test_782_1_carry_127.py diff --git a/resources/master_data/_키대장.json b/resources/master_data/_키대장.json index 21c23f223..65bad3dfd 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": 341, "GC": 1140, "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": 349, "GC": 1143, "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"}, @@ -342,6 +342,14 @@ "CC000338": {"원문번호": "공통 1-2-4 5 발생재 공제율", "파일": "계수_건설품셈_01장_적용기준.json"}, "CC000339": {"원문번호": "공통 1-2-4 6 가 강관배관 부자재율 일반업무용 건물", "파일": "계수_건설품셈_01장_적용기준.json"}, "CC000340": {"원문번호": "공통 1-2-4 6 나 강관배관 부자재율 병원건물", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000341": {"원문번호": "공통 1-2-7 소운반 거리", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000342": {"원문번호": "공통 1-2-7 인력운반 기본공식", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000343": {"원문번호": "공통 1-2-7 지게운반", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000344": {"원문번호": "공통 1-2-7 벽돌운반", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000345": {"원문번호": "공통 1-2-7 인력운반(기계설비) 왕복평균속도", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000346": {"원문번호": "공통 1-2-7 인력운반(기계설비) 준비작업시간", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000347": {"원문번호": "공통 1-2-7 경사지 운반 환산계수", "파일": "계수_건설품셈_01장_적용기준.json"}, + "CC000348": {"원문번호": "공통 1-2-7 화물자동차 적재량", "파일": "계수_건설품셈_01장_적용기준.json"}, "CF000001": {"원문번호": "1-1-3 일일 작업시간", "파일": "계수_산림품셈_01장_적용기준.json"}, "CF000002": {"원문번호": "1-2-1 소수 처리", "파일": "계수_산림품셈_01장_적용기준.json"}, "CF000003": {"원문번호": "1-2-1 총축적 계산 기준", "파일": "계수_산림품셈_01장_적용기준.json"}, @@ -2298,6 +2306,9 @@ "GC001137": {"원문번호": "유지관리 4-3-3 배관누수 검사", "파일": "로직_건설품셈_04장_기계설비.json"}, "GC001138": {"원문번호": "공통 7-4-2 석재판 붙임 앵커 긴결공법", "파일": "로직_건설품셈_07장_돌공사.json"}, "GC001139": {"원문번호": "공통 7-4-3 석재판 붙임 강제트러스 지지공법", "파일": "로직_건설품셈_07장_돌공사.json"}, + "GC001140": {"원문번호": "공통 1-2-7 인력운반(지게)", "파일": "로직_건설품셈_01장_적용기준.json"}, + "GC001141": {"원문번호": "공통 1-2-7 인력운반(기계설비)", "파일": "로직_건설품셈_01장_적용기준.json"}, + "GC001142": {"원문번호": "공통 1-2-7 벽돌운반", "파일": "로직_건설품셈_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/build_로직_건설품셈_01장_적용기준.py b/resources/master_data/scripts/build_로직_건설품셈_01장_적용기준.py new file mode 100644 index 000000000..058e399b0 --- /dev/null +++ b/resources/master_data/scripts/build_로직_건설품셈_01장_적용기준.py @@ -0,0 +1,619 @@ +# -*- coding: utf-8 -*- +"""건설품셈 공통 1-2-7 운반 — 계수 표 여덟(계수_건설품셈_01장_적용기준.json 에 덧) + 로직 셋(새 파일). + +돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_로직_건설품셈_01장_적용기준.py +- 원문 = 건설품셈 공통 `1-02_설계_및_수량.md` 1-2-7 운반(머리 ~ 1-2-8 앞). +- 계수 표는 1-2-4 표 뒤 · 1-3-1 표 앞에 글로 끼움(기존 표 글자 그대로) · 화물자동차 적재량은 본문 md 표에서 읽음. +- 로직 파일 `로직_건설품셈_01장_적용기준.json` 은 통째로 씀 · 차례 13(같은 차례 공통 2장보다 이름이 앞). +- 키는 `_키대장.json` 다음 번호(master_keys.issue) · 이미 넣었으면 멈춤(한 번 돌림). +""" + +from __future__ import annotations + +import json +import re +import sys +from decimal import Decimal +from pathlib import Path + +HERE = Path(__file__).resolve().parent +MASTER = HERE.parent +sys.path.insert(0, str(HERE)) +import master_keys as mk # noqa: E402 + +COEF = "계수_건설품셈_01장_적용기준.json" +LOGIC = "로직_건설품셈_01장_적용기준.json" +BODY = ( + MASTER.parents[1] + / "resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준/1-02_설계_및_수량.md" +) +SRC = "건설품셈 공통 1-2-7" +HEAD = {"구분": "건설품셈 공통", "상세구분": "01장 적용기준"} +ANGLES = [6, 11, 17, 22, 27, 31, 35, 39, 42, 45] + + +class N(str): + """원문 그대로의 수 글자.""" + + +def raw(v) -> str: + if isinstance(v, N): + return str(v) + if v is None: + return "null" + if isinstance(v, list): + return "[" + ", ".join(raw(x) for x in v) + "]" + if isinstance(v, int) and not isinstance(v, bool): + return str(v) + if isinstance(v, dict): + return ( + "{" + + ", ".join(f"{json.dumps(k, ensure_ascii=False)}: {raw(x)}" for k, x in v.items()) + + "}" + ) + return json.dumps(v, ensure_ascii=False) + + +def emit(t: dict) -> str: + """기존 공통 1장 표 글 모양 — 머리 넷 한 줄 · 줄은 한 줄씩.""" + head = ", ".join(f'"{k}": {raw(t[k])}' for k in ("키", "원문번호", "구분", "상세구분")) + out = [" {", f" {head},"] + for k in ("이름", "기준", "출처", "비고", "용도", "조건", "범위규칙", "값칸"): + if k in t: + out.append(f" {json.dumps(k, ensure_ascii=False)}: {raw(t[k])},") + out.append(' "줄": [') + out.append(",\n".join(" " + raw(r) for r in t["줄"])) + out.append(" ],") + if t["주"]: + out.append(' "주": [') + out.append(",\n".join(" " + raw(x) for x in t["주"])) + out.append(" ]") + else: + out.append(' "주": []') + out.append(" }") + return "\n".join(out) + + +# ── 본문 md ────────────────────────────────────────────────────────── +def section() -> list[str]: + lines = BODY.read_text(encoding="utf-8").split("\n") + a = next(i for i, x in enumerate(lines) if x.startswith("### 1-2-7 ")) + b = next(i for i, x in enumerate(lines) if x.startswith("### 1-2-8 ")) + return lines[a:b] + + +def md_tables(lines: list[str]) -> list[list[list[str]]]: + out, i = [], 0 + while i < len(lines): + if not lines[i].startswith("|"): + i += 1 + continue + rows = [] + while i < len(lines) and lines[i].startswith("|"): + cells = [c.strip() for c in lines[i].strip().strip("|").split("|")] + if not all(re.fullmatch(r":?-+:?", c) for c in cells): + rows.append([c.replace("
", "\n") for c in cells]) + i += 1 + out.append(rows) + return out + + +_NUM = r"\d[\d,]*(?:\.\d+)?" + + +def cell(c: str): + """값 칸 → (값, 원문) · 「-」 · 빈 칸 = (None, None).""" + c = c.strip() + if c in ("", "-"): + return None, None + if re.fullmatch(_NUM, c): + return N(c.replace(",", "")), None + m = re.fullmatch(rf"({_NUM})\s*∼\s*({_NUM})", c) + if m: + return [N(m[1].replace(",", "")), N(m[2].replace(",", ""))], None + m = re.fullmatch(rf"\(({_NUM})\)", c) + if m: + return N(m[1].replace(",", "")), c + m = re.match(rf"({_NUM})\n", c) + if m: + return N(m[1].replace(",", "")), c.replace("\n", "") + raise ValueError(f"값 칸 못 읽음 {c!r}") + + +TRUCK = ("6톤 차량", "8톤 차량", "11톤 차량", "20톤 트레일러") + + +def truck_rows(table: list[list[str]]) -> list[dict]: + """화물자동차 적재량 — 「〃」 · 빈 종별 · 빈 단위는 윗줄 · 비고 「모자이크 포함」은 원문 묶음(타일 셋).""" + rows, kind, unit = [], "", "" + for r in table[2:]: + name, spec, u, *vals, note = r + kind = kind if name in ("", "〃") else name + unit = unit if u in ("", "〃") else u + if kind == "페인트" and spec == "": + spec = "(18ℓ)/통" # 원문 둘째 줄 = 18ℓ 통 괄호 값 + row = {"종별": kind, "규격": spec.replace("\n", ""), "단위": unit} + for col, v in zip(TRUCK, vals): + value, orig = cell(v) + if value is not None: + row[col] = value + if orig is not None: + row[col + "원문"] = orig + if kind in ("타일", "크링커타일"): + row["비고"] = "모자이크 포함" + elif note: + row["비고"] = note.replace("\n", "") + rows.append(row) + return rows + + +def notes(lines: list[str], start: str) -> list[str]: + """`start` 로 시작하는 줄 뒤 「[주]」 묶음(빈 줄까지).""" + i = next(i for i, x in enumerate(lines) if x.startswith(start)) + i = next(j for j in range(i, len(lines)) if lines[j].strip() == "[주]") + out = [] + for x in lines[i + 1 :]: + if not x.strip(): + break + out.append(x.strip()) + return out + + +# ── 계수 표 ────────────────────────────────────────────────────────── +def tables(lines: list[str], logic: dict) -> list[dict]: + md = md_tables(lines) + jige, brick, slope, truck = md[0], md[1], md[2], md[3] + assert jige[2][0] == "토사류" and brick[2][0] == "보통인부" and slope[0][0] == "경사도" + t = [] + + def add(label, name, src, use, cond, cols, rows, note=(), base="", extra=None): + t.append( + { + "열쇠": f"공통 1-2-7 {label}", + "이름": name, + "기준": base, + "출처": f"{SRC} {src}".strip(), + **(extra or {}), + "용도": {"공종": "운반", "대상": use, "로직키": logic.get(label, [])}, + "조건": cond, + "값칸": cols, + "줄": rows, + "주": list(note), + } + ) + + one = [x.strip() for x in lines if re.match(r"^[가-다]\. ", x)][:3] + add( + "소운반 거리", + "소운반 — 품에 포함된 거리 · 경사면 환산", + "1", + ["소운반", "환산"], + {}, + {"소운반거리": "m", "직고": "m", "수평거리": "m"}, + [{"소운반거리": N("20"), "직고": N("1"), "수평거리": N("6")}], + one, + base="소운반 규정", + ) + add( + "인력운반 기본공식", + "인력운반 기본공식 — 1일 실작업시간 · 1회 운반량", + "2", + ["인력", "작업량 조건"], + {}, + {"실작업시간": "분", "1회 운반량": "㎏"}, + [{"실작업시간": "480 - 30", "실작업시간원문": "480분-30분", "1회 운반량": N("25")}], + notes(lines, "2\\. "), + base="기본공식 상수", + extra={ + "비고": "식 Q = N × q · N = T/(60 × L × 2/V + t) = VT/(120L + Vt) — Q 1일 운반량 · N 1일 운반횟수 · q 1회 운반량 · T 1일 실작업시간(분) · L 운반거리(m) · t 적재적하 시간(분) · V 평균왕복속도(m/hr) · 1회 운반량 25㎏ = [주] 「25㎏을 1인의 비율로」 · 3 주③" + }, + ) + rows = [] + for kind, tt in (("토사류", jige[2][1]), ("석재류", jige[3][1])): + for road, v in zip(("양호", "보통", "불량"), jige[2][2:5]): + rows.append( + { + "종류": kind, + "운반로": road, + "적재적하시간": N(tt.removesuffix("분")), + "평균왕복속도": cell(v)[0], + } + ) + add( + "지게운반", + "지게운반 — 적재적하 시간 · 평균왕복속도", + "3", + ["인력", "작업량 조건"], + {"종류": "고르기", "운반로": "고르기"}, + {"적재적하시간": "분", "평균왕복속도": "m/hr"}, + rows, + notes(lines, "3\\. "), + base="기본공식 상수", + extra={ + "비고": "원문 평균왕복속도 칸은 토사류 · 석재류 두 줄에 걸친 한 칸 — 두 종류에 같은 값" + }, + ) + rows = [ + {"운반": "인력", "층수": N(str(i + 1)), "보통인부": cell(v)[0]} + for i, v in enumerate(brick[2][2:7]) + ] + assert "0.31" in brick[3][1] + rows.append({"운반": "리프트", "보통인부": N("0.31")}) + add( + "벽돌운반", + "벽돌운반 — 층별 인력운반", + "4", + ["인력"], + {"운반": "고르기", "층수": "수"}, + {"보통인부": "인"}, + rows, + notes(lines, "4\\. "), + base="1,000매당", + extra={ + "비고": "리프트 줄 = 원문 비고 「리프트를 사용할 경우 보통인부 0.31인을 적용한다」 — 층수 없음(어느 층이든)" + }, + ) + add( + "인력운반(기계설비) 왕복평균속도", + "인력운반(기계설비) — 왕복평균속도", + "5 가", + ["인력", "작업량 조건"], + {"도로상태": "고르기"}, + {"왕복평균속도": "km/hr"}, + [ + {"도로상태": "양호", "왕복평균속도": N("2")}, + {"도로상태": "보통", "왕복평균속도": N("1.5")}, + {"도로상태": "불량", "왕복평균속도": N("1")}, + {"도로상태": "물논", "왕복평균속도": N("0.5")}, + ], + ["※ 도로상태 구분은 토목부분 참조"], + base="기본공식 상수", + ) + add( + "인력운반(기계설비) 준비작업시간", + "인력운반(기계설비) — 준비작업시간 · 1회 운반량", + "5 가", + ["인력", "작업량 조건"], + {}, + {"준비작업시간": "분", "1회 운반량": "㎏"}, + [{"준비작업시간": N("2"), "1회 운반량": N("25")}], + base="기본공식 상수", + extra={ + "비고": "장대물 · 중량물 등 인력운반비 산출공식 — 운반비 = M/T × A(60 × 2 × L/V + t) · A 인력운반공 노임 · M 인력운반공 수(총운반량/1인당 1회 운반량) · L 운반거리(km) · V 왕복평균속도(km/hr) · T 1일 실작업시간 · t 준비작업시간" + }, + ) + pct, ang, alpha = slope[0][2:], slope[1][2:], slope[2][2:] + assert [int(a) for a in ang] == ANGLES + add( + "경사지 운반 환산계수", + "경사지 운반 환산계수(α)", + "5 나", + ["환산"], + {"경사도": "수", "각도": "수"}, + {"환산계수": ""}, + [{"경사도": N(p), "각도": N(a), "환산계수": N(x)} for p, a, x in zip(pct, ang, alpha)], + ["경사지 환산거리 a×L"], + base="환산계수", + extra={ + "비고": "경사도 = % · 각도 = ° · 해석: 1 나 · 3 주⑤(직고 1m = 수평 6m)와 함께 지게운반(3)에도 씀" + }, + ) + seven = [ + x.strip() + for x in lines[lines.index("7\\. 화물자동차의 적재량") :] + if re.match(r"^[가-라]\. ", x) + ] + add( + "화물자동차 적재량", + "화물자동차의 적재량", + "7 라", + ["작업량 조건", "운반"], + {"종별": "고르기", "규격": "고르기"}, + {**{c: "단위 칸" for c in TRUCK}, "비고": ""}, + truck_rows(truck), + seven, + base="차 1대당", + extra={ + "비고": "줄마다 단위 칸 · 「〃」 종별 · 단위와 빈 종별 줄은 윗줄 · 괄호 값은 원문 칸(타일 8㎜ · 페인트 18ℓ — 페인트 둘째 줄 규격 「(18ℓ)/통」은 원문 괄호를 따름) · 범위 값은 [아래, 위]" + }, + ) + return t + + +# ── 로직 ──────────────────────────────────────────────────────────── +def inp(name, desc, unit=None, pick=None, src=None): + d = {"이름": name, "설명": desc} + if unit: + d["단위"] = unit + if pick is not None: + d["고르기"] = pick + if src: + d["출처"] = src + return d + + +def weights() -> tuple[list[str], list[str]]: + data = json.loads((MASTER / COEF).read_text(encoding="utf-8")) + table = next(x for x in data["표"] if x["키"] == "CC000337") + kinds = list(dict.fromkeys(r["종별"] for r in table["줄"])) + shapes = ["해당 없음", *dict.fromkeys(r["형상"] for r in table["줄"] if "형상" in r)] + return kinds, shapes + + +def logics(key: dict) -> list[dict]: + kinds, shapes = weights() + angle = inp( + "경사각도", "운반로의 경사 각도 — 0 이면 평지(환산계수 1)", "°", [0, *ANGLES], f"{SRC} 5 나" + ) + weight = [ + inp( + "운반무게", + "운반할 자재 한 단위의 무게 — 0 이면 아래 재료의 단위중량 표 값(㎏/㎥)", + "㎏", + src="건설품셈 공통 1-2-4 3", + ), + inp( + "재료종별", + "단위중량 표에서 고를 재료(운반무게 0 일 때)", + pick=kinds, + src="건설품셈 공통 1-2-4 3", + ), + inp( + "재료형상", + "단위중량 표의 형상(운반무게 0 일 때)", + pick=shapes, + src="건설품셈 공통 1-2-4 3", + ), + ] + w_step = { + "이름": "무게", + "식": f"만약(운반무게 > 0, 운반무게, 찾기({key['CC000337']}, 종별=재료종별, 형상=재료형상).위)", + "단위": "㎏", + } + jige = { + "키": None, + "열쇠": "공통 1-2-7 인력운반(지게)", + **HEAD, + "이름": "인력운반(지게) — 건설 공통 1-2-7", + "결과단위": "원/자재 단위", + "출처": f"{SRC} 2 · 3", + "소유": "공용", + "비고": "품에 포함된 소운반 = 20m 이내(1-2-7 1 가) — 넘거나 2차 운반이면 이 로직으로 별도 계상(1 다) · 해석: 경사지 환산계수 α(5 나 표)를 지게운반에도 씀(1 나 · 3 주⑤ 직고 1m = 수평 6m) · 운반회수를 넣으면 식 대신 그 값(실무 엑셀이 1일 운반횟수를 값으로 넣은 경우) · 운반무게 0 = 재료의 단위중량 표(1-2-4 3) 위끝 · 1회 운반량 0 = 25㎏ · 실작업시간 0 = 450분", + "시험입력": { + "운반무게": 575, + "운반거리": 6, + "경사각도": 35, + "재료갈래": "석재류", + "운반로": "불량", + "회당운반량": 0, + "실작업시간": 0, + "운반회수": 42, + }, + "입력": [ + *weight, + inp( + "회당운반량", + "한 사람이 한 번에 지는 무게 — 0 이면 25㎏", + "㎏", + src=f"{SRC} 2 주 · 3 주③", + ), + inp("운반거리", "수평 운반 거리(편도)", "m", src=f"{SRC} 2"), + angle, + inp( + "재료갈래", + "적재적하 시간 갈래(석재류 = 자갈 · 부순돌 · 조약돌 등)", + pick=["토사류", "석재류"], + src=f"{SRC} 3", + ), + inp("운반로", "운반로 상태", pick=["양호", "보통", "불량"], src=f"{SRC} 3 주②"), + inp("실작업시간", "1일 실작업시간 — 0 이면 450분", "분", src=f"{SRC} 2"), + inp("운반회수", "1일 운반횟수를 직접 넣을 때 — 0 이면 식으로 셈", "회", src=f"{SRC} 2"), + ], + "중간": [ + w_step, + { + "이름": "운반량", + "식": f"만약(회당운반량 > 0, 회당운반량, 찾기({key['인력운반 기본공식']}).'1회 운반량')", + "단위": "㎏", + }, + { + "이름": "환산계수", + "식": f"만약(경사각도 > 0, 찾기({key['경사지 운반 환산계수']}, 각도=경사각도).환산계수, 1)", + }, + {"이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "m"}, + { + "이름": "작업시간", + "식": f"만약(실작업시간 > 0, 실작업시간, 찾기({key['인력운반 기본공식']}).실작업시간)", + "단위": "분", + }, + { + "이름": "적재적하시간", + "식": f"찾기({key['지게운반']}, 종류=재료갈래, 운반로=운반로).적재적하시간", + "단위": "분", + }, + { + "이름": "평균왕복속도", + "식": f"찾기({key['지게운반']}, 종류=재료갈래, 운반로=운반로).평균왕복속도", + "단위": "m/hr", + }, + { + "이름": "일운반회수", + "식": "만약(운반회수 > 0, 운반회수, 평균왕복속도 * 작업시간 / (120 * 환산거리 + 평균왕복속도 * 적재적하시간))", + "단위": "회", + }, + ], + "호표": [ + { + "종류": "인력", + "요소": "LB000002", + "이름": "보통인부", + "단위": "인", + "수량": "무게 / 운반량 / 일운반회수", + "비목": "노무비", + "출처": f"{SRC} 2", + } + ], + "덧줄": [], + "끝수": None, + } + machine = { + "키": None, + "열쇠": "공통 1-2-7 인력운반(기계설비)", + **HEAD, + "이름": "인력운반(기계설비) — 건설 공통 1-2-7", + "결과단위": "원/자재 단위", + "출처": f"{SRC} 5", + "소유": "공용", + "비고": "장대물 · 중량물 등 — 운반비 = M/T × A(60 × 2 × L/V + t) · M = 운반무게 ÷ 1회 운반량 · 해석: T = 1일 실작업시간 450분(1-2-7 2) · 경사지는 환산거리 α × L(5 나)", + "시험입력": { + "운반무게": 100, + "운반거리": 0.1, + "도로상태": "보통", + "경사각도": 0, + "회당운반량": 0, + "실작업시간": 0, + }, + "입력": [ + *weight, + inp("회당운반량", "인력운반공 1회 운반량 — 0 이면 25㎏", "㎏", src=f"{SRC} 5 가"), + inp("운반거리", "운반 거리(편도)", "km", src=f"{SRC} 5 가"), + inp( + "도로상태", + "운반로 도로상태", + pick=["양호", "보통", "불량", "물논"], + src=f"{SRC} 5 가", + ), + angle, + inp("실작업시간", "1일 실작업시간 — 0 이면 450분", "분", src=f"{SRC} 2"), + ], + "중간": [ + w_step, + { + "이름": "운반량", + "식": f"만약(회당운반량 > 0, 회당운반량, 찾기({key['인력운반(기계설비) 준비작업시간']}).'1회 운반량')", + "단위": "㎏", + }, + {"이름": "인원", "식": "무게 / 운반량", "단위": "인"}, + { + "이름": "환산계수", + "식": f"만약(경사각도 > 0, 찾기({key['경사지 운반 환산계수']}, 각도=경사각도).환산계수, 1)", + }, + {"이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "km"}, + { + "이름": "왕복평균속도", + "식": f"찾기({key['인력운반(기계설비) 왕복평균속도']}, 도로상태=도로상태).왕복평균속도", + "단위": "km/hr", + }, + { + "이름": "준비작업시간", + "식": f"찾기({key['인력운반(기계설비) 준비작업시간']}).준비작업시간", + "단위": "분", + }, + { + "이름": "작업시간", + "식": f"만약(실작업시간 > 0, 실작업시간, 찾기({key['인력운반 기본공식']}).실작업시간)", + "단위": "분", + }, + ], + "호표": [ + { + "종류": "인력", + "요소": "LB000045", + "이름": "인력운반공", + "단위": "인", + "수량": "인원 / 작업시간 * (60 * 2 * 환산거리 / 왕복평균속도 + 준비작업시간)", + "비목": "노무비", + "출처": f"{SRC} 5 가", + } + ], + "덧줄": [], + "끝수": None, + } + brick = { + "키": None, + "열쇠": "공통 1-2-7 벽돌운반", + **HEAD, + "이름": "벽돌운반 — 건설 공통 1-2-7", + "결과단위": "원/1,000매", + "출처": f"{SRC} 4", + "소유": "공용", + "비고": "기본벽돌(19×9×5.7㎝)을 인력으로 층별(층고 3.6m) 운반 · 리프트를 쓰면 층수와 관계없이 보통인부 0.31인", + "입력": [ + inp("운반", "운반 방법", pick=["인력", "리프트"], src=f"{SRC} 4"), + inp("층수", "올릴 층(리프트면 무관)", "층", [1, 2, 3, 4, 5], f"{SRC} 4"), + ], + "중간": [], + "호표": [ + { + "종류": "인력", + "요소": "LB000002", + "이름": "보통인부", + "단위": "인", + "수량": f"찾기({key['벽돌운반']}, 운반=운반, 층수=층수).보통인부", + "비목": "노무비", + "출처": f"{SRC} 4", + } + ], + "덧줄": [], + "끝수": None, + } + return [jige, machine, brick] + + +USES = { # 표 → 그 표를 부르는 로직(열쇠) + "소운반 거리": [], + "인력운반 기본공식": ["인력운반(지게)", "인력운반(기계설비)"], + "지게운반": ["인력운반(지게)"], + "벽돌운반": ["벽돌운반"], + "인력운반(기계설비) 왕복평균속도": ["인력운반(기계설비)"], + "인력운반(기계설비) 준비작업시간": ["인력운반(기계설비)"], + "경사지 운반 환산계수": ["인력운반(지게)", "인력운반(기계설비)"], + "화물자동차 적재량": [], +} +LOGIC_LABELS = ("인력운반(지게)", "인력운반(기계설비)", "벽돌운반") + + +def main() -> None: + coef_path, logic_path = MASTER / COEF, MASTER / LOGIC + text = coef_path.read_text(encoding="utf-8") + if '"원문번호": "1-2-7"' in text or logic_path.exists(): + raise SystemExit("이미 넣음 — 멈춤") + book = mk.load_book() + lkey = {x: mk.issue(book, "GC", f"공통 1-2-7 {x}", LOGIC) for x in LOGIC_LABELS} + uses = {label: [lkey[x] for x in v] for label, v in USES.items()} + lines = section() + made = tables(lines, uses) + ckey = {"CC000337": "CC000337"} + for t in made: + label = t["열쇠"].removeprefix("공통 1-2-7 ") + ckey[label] = mk.issue(book, "CC", t["열쇠"], COEF) + t["키"], t["원문번호"] = ckey[label], "1-2-7" + t.update(HEAD) + at = text.index(' {\n "키": "CC000001"') + new_text = text[:at] + "".join(emit(t) + ",\n" for t in made) + text[at:] + data = json.loads(new_text, parse_float=Decimal, parse_int=Decimal) + assert sum(1 for x in data["표"] if x["원문번호"] == "1-2-7") == len(made) + coef_path.write_text(new_text, encoding="utf-8", newline="\n") + + rows = [] + for row in logics(ckey): + label = row.pop("열쇠").removeprefix("공통 1-2-7 ") + row["키"], row["원문번호"] = lkey[label], "1-2-7" + order = ["키", "원문번호", *[k for k in row if k not in ("키", "원문번호")]] + rows.append({k: row[k] for k in order}) + head = { + "그룹": "로직", + "원문": "건설품셈", + "부문": "공통", + "차례": 13, + "판": "2026", + "줄": rows, + } + logic_path.write_text(mk._store().dump(head), encoding="utf-8", newline="\n") + mk.save_book(mk.finish(book)) + print("계수", [t["키"] for t in made], "로직", list(lkey.values())) + + +if __name__ == "__main__": + sys.stdout.reconfigure(encoding="utf-8") + main() diff --git a/resources/master_data/계수_건설품셈_01장_적용기준.json b/resources/master_data/계수_건설품셈_01장_적용기준.json index daa164f08..5bed4eb08 100644 --- a/resources/master_data/계수_건설품셈_01장_적용기준.json +++ b/resources/master_data/계수_건설품셈_01장_적용기준.json @@ -175,6 +175,213 @@ "⑥ 수직관은 2개 층마다 플랜지 또는 유니온을 적용하였다." ] }, + { + "키": "CC000341", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "소운반 — 품에 포함된 거리 · 경사면 환산", + "기준": "소운반 규정", + "출처": "건설품셈 공통 1-2-7 1", + "용도": {"공종": "운반", "대상": ["소운반", "환산"], "로직키": []}, + "조건": {}, + "값칸": {"소운반거리": "m", "직고": "m", "수평거리": "m"}, + "줄": [ + {"소운반거리": 20, "직고": 1, "수평거리": 6} + ], + "주": [ + "가. 품에서 자재의 소운반은 포함하며, 품에서 포함된 것으로 규정된 소운반 거리는 20m 이내의 거리를 의미한다.", + "나. 경사면의 소운반 거리는 직고 1m를 수평거리 6m의 비율로 본다.", + "다. 현장 내 운반거리가 소운반 범위를 초과하거나, 별도의 2차 운반이 발생될 경우 별도 계상한다." + ] + }, + { + "키": "CC000342", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "인력운반 기본공식 — 1일 실작업시간 · 1회 운반량", + "기준": "기본공식 상수", + "출처": "건설품셈 공통 1-2-7 2", + "비고": "식 Q = N × q · N = T/(60 × L × 2/V + t) = VT/(120L + Vt) — Q 1일 운반량 · N 1일 운반횟수 · q 1회 운반량 · T 1일 실작업시간(분) · L 운반거리(m) · t 적재적하 시간(분) · V 평균왕복속도(m/hr) · 1회 운반량 25㎏ = [주] 「25㎏을 1인의 비율로」 · 3 주③", + "용도": {"공종": "운반", "대상": ["인력", "작업량 조건"], "로직키": ["GC001140", "GC001141"]}, + "조건": {}, + "값칸": {"실작업시간": "분", "1회 운반량": "㎏"}, + "줄": [ + {"실작업시간": "480 - 30", "실작업시간원문": "480분-30분", "1회 운반량": 25} + ], + "주": [ + "삽으로 적재할 수 없는 자재(시멘트・목재・철근・말뚝・전주・관・큰석재 등)의 인력적사는 기본공식을 적용하되 25㎏을 1인의 비율로 계산하고 t 및 v는 자재 및 현장여건을 감안하여 계상한다." + ] + }, + { + "키": "CC000343", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "지게운반 — 적재적하 시간 · 평균왕복속도", + "기준": "기본공식 상수", + "출처": "건설품셈 공통 1-2-7 3", + "비고": "원문 평균왕복속도 칸은 토사류 · 석재류 두 줄에 걸친 한 칸 — 두 종류에 같은 값", + "용도": {"공종": "운반", "대상": ["인력", "작업량 조건"], "로직키": ["GC001140"]}, + "조건": {"종류": "고르기", "운반로": "고르기"}, + "값칸": {"적재적하시간": "분", "평균왕복속도": "m/hr"}, + "줄": [ + {"종류": "토사류", "운반로": "양호", "적재적하시간": 1.5, "평균왕복속도": 3000}, + {"종류": "토사류", "운반로": "보통", "적재적하시간": 1.5, "평균왕복속도": 2500}, + {"종류": "토사류", "운반로": "불량", "적재적하시간": 1.5, "평균왕복속도": 2000}, + {"종류": "석재류", "운반로": "양호", "적재적하시간": 2, "평균왕복속도": 3000}, + {"종류": "석재류", "운반로": "보통", "적재적하시간": 2, "평균왕복속도": 2500}, + {"종류": "석재류", "운반로": "불량", "적재적하시간": 2, "평균왕복속도": 2000} + ], + "주": [ + "① 절취는 별도 계상한다.", + "② 양호 : 운반로가 평탄하며 보행이 자유롭고 운반상 장애물이 없는 경우", + "보통 : 운반로가 평탄하지만 다소 운반에 지장이 있는 경우", + "불량 : 보행에 지장이 있는 운반로의 경우, 습지, 모래질, 자갈질, 암반 등 지장이 있는 운반로의 경우", + "③ 1회 운반량은 보통토사 25㎏으로 하고, 삽작업이 가능한 토석재를 기준으로 한다.", + "④ 석재류라 함은 자갈, 부순돌 및 조약돌 등을 말한다.", + "⑤ 고갯길인 경우에는 직고(直高) 1m를 수평거리 6m의 비율로 본다.", + "⑥ 적재운반 적하는 1인을 기준으로 한다." + ] + }, + { + "키": "CC000344", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "벽돌운반 — 층별 인력운반", + "기준": "1,000매당", + "출처": "건설품셈 공통 1-2-7 4", + "비고": "리프트 줄 = 원문 비고 「리프트를 사용할 경우 보통인부 0.31인을 적용한다」 — 층수 없음(어느 층이든)", + "용도": {"공종": "운반", "대상": ["인력"], "로직키": ["GC001142"]}, + "조건": {"운반": "고르기", "층수": "수"}, + "값칸": {"보통인부": "인"}, + "줄": [ + {"운반": "인력", "층수": 1, "보통인부": 0.44}, + {"운반": "인력", "층수": 2, "보통인부": 0.56}, + {"운반": "인력", "층수": 3, "보통인부": 0.74}, + {"운반": "인력", "층수": 4, "보통인부": 0.96}, + {"운반": "인력", "층수": 5, "보통인부": 1.19}, + {"운반": "리프트", "보통인부": 0.31} + ], + "주": [ + "본 품은 기본벽돌(19×9×5.7㎝)을 인력으로 층별(층고 3.6m) 운반하는 기준이다." + ] + }, + { + "키": "CC000345", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "인력운반(기계설비) — 왕복평균속도", + "기준": "기본공식 상수", + "출처": "건설품셈 공통 1-2-7 5 가", + "용도": {"공종": "운반", "대상": ["인력", "작업량 조건"], "로직키": ["GC001141"]}, + "조건": {"도로상태": "고르기"}, + "값칸": {"왕복평균속도": "km/hr"}, + "줄": [ + {"도로상태": "양호", "왕복평균속도": 2}, + {"도로상태": "보통", "왕복평균속도": 1.5}, + {"도로상태": "불량", "왕복평균속도": 1}, + {"도로상태": "물논", "왕복평균속도": 0.5} + ], + "주": [ + "※ 도로상태 구분은 토목부분 참조" + ] + }, + { + "키": "CC000346", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "인력운반(기계설비) — 준비작업시간 · 1회 운반량", + "기준": "기본공식 상수", + "출처": "건설품셈 공통 1-2-7 5 가", + "비고": "장대물 · 중량물 등 인력운반비 산출공식 — 운반비 = M/T × A(60 × 2 × L/V + t) · A 인력운반공 노임 · M 인력운반공 수(총운반량/1인당 1회 운반량) · L 운반거리(km) · V 왕복평균속도(km/hr) · T 1일 실작업시간 · t 준비작업시간", + "용도": {"공종": "운반", "대상": ["인력", "작업량 조건"], "로직키": ["GC001141"]}, + "조건": {}, + "값칸": {"준비작업시간": "분", "1회 운반량": "㎏"}, + "줄": [ + {"준비작업시간": 2, "1회 운반량": 25} + ], + "주": [] + }, + { + "키": "CC000347", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "경사지 운반 환산계수(α)", + "기준": "환산계수", + "출처": "건설품셈 공통 1-2-7 5 나", + "비고": "경사도 = % · 각도 = ° · 해석: 1 나 · 3 주⑤(직고 1m = 수평 6m)와 함께 지게운반(3)에도 씀", + "용도": {"공종": "운반", "대상": ["환산"], "로직키": ["GC001140", "GC001141"]}, + "조건": {"경사도": "수", "각도": "수"}, + "값칸": {"환산계수": ""}, + "줄": [ + {"경사도": 10, "각도": 6, "환산계수": 2}, + {"경사도": 20, "각도": 11, "환산계수": 3}, + {"경사도": 30, "각도": 17, "환산계수": 4}, + {"경사도": 40, "각도": 22, "환산계수": 5}, + {"경사도": 50, "각도": 27, "환산계수": 6}, + {"경사도": 60, "각도": 31, "환산계수": 7}, + {"경사도": 70, "각도": 35, "환산계수": 8}, + {"경사도": 80, "각도": 39, "환산계수": 9}, + {"경사도": 90, "각도": 42, "환산계수": 10}, + {"경사도": 100, "각도": 45, "환산계수": 11} + ], + "주": [ + "경사지 환산거리 a×L" + ] + }, + { + "키": "CC000348", "원문번호": "1-2-7", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", + "이름": "화물자동차의 적재량", + "기준": "차 1대당", + "출처": "건설품셈 공통 1-2-7 7 라", + "비고": "줄마다 단위 칸 · 「〃」 종별 · 단위와 빈 종별 줄은 윗줄 · 괄호 값은 원문 칸(타일 8㎜ · 페인트 18ℓ — 페인트 둘째 줄 규격 「(18ℓ)/통」은 원문 괄호를 따름) · 범위 값은 [아래, 위]", + "용도": {"공종": "운반", "대상": ["작업량 조건", "운반"], "로직키": []}, + "조건": {"종별": "고르기", "규격": "고르기"}, + "값칸": {"6톤 차량": "단위 칸", "8톤 차량": "단위 칸", "11톤 차량": "단위 칸", "20톤 트레일러": "단위 칸", "비고": ""}, + "줄": [ + {"종별": "목재(원목)", "규격": "길이가 긴 것은 낱개", "단위": "㎥", "6톤 차량": 7.7, "8톤 차량": 10, "11톤 차량": 13}, + {"종별": "목재(제재목)", "규격": "〃", "단위": "㎥", "6톤 차량": 9.0, "8톤 차량": 12, "11톤 차량": 16}, + {"종별": "경유・휘발유", "규격": "200ℓ", "단위": "드럼", "6톤 차량": 30, "8톤 차량": 40, "11톤 차량": 55}, + {"종별": "아스팔트", "규격": "〃", "단위": "드럼", "6톤 차량": 24, "8톤 차량": 35, "11톤 차량": 50}, + {"종별": "새끼", "규격": "12㎜, 9.4㎏", "단위": "다발", "6톤 차량": 480, "8톤 차량": 640}, + {"종별": "벽돌", "규격": "19㎝×9㎝×5.7㎝(표준형)", "단위": "개", "6톤 차량": 2930, "8톤 차량": 3900, "11톤 차량": 5300}, + {"종별": "기와", "규격": "34㎝×30㎝×1.5㎝", "단위": "매", "6톤 차량": 1860, "8톤 차량": 2480, "11톤 차량": 3400}, + {"종별": "보도블록", "규격": "30㎝×45㎝×6㎝", "단위": "개", "6톤 차량": 490, "8톤 차량": 650, "11톤 차량": 890}, + {"종별": "견치돌", "규격": "뒷길이 45㎝", "단위": "개", "6톤 차량": 100, "8톤 차량": 135, "11톤 차량": 180}, + {"종별": "블록", "규격": "두께 10㎝", "단위": "개", "6톤 차량": 650, "8톤 차량": 860, "11톤 차량": 1180}, + {"종별": "블록", "규격": "두께 15㎝", "단위": "개", "6톤 차량": 450, "8톤 차량": 600, "11톤 차량": 820}, + {"종별": "블록", "규격": "두께 20㎝", "단위": "개", "6톤 차량": 350, "8톤 차량": 460, "11톤 차량": 630}, + {"종별": "타일", "규격": "두께 6㎜", "단위": "㎡", "6톤 차량": 500, "8톤 차량": 660, "비고": "모자이크 포함"}, + {"종별": "타일", "규격": "(8㎜)", "단위": "㎡", "6톤 차량": 350, "6톤 차량원문": "(350)", "8톤 차량": 460, "8톤 차량원문": "(460)", "비고": "모자이크 포함"}, + {"종별": "크링커타일", "규격": "두께 24㎜", "단위": "㎡", "6톤 차량": 150, "8톤 차량": 200, "비고": "모자이크 포함"}, + {"종별": "합판", "규격": "12×900×1,800㎜", "단위": "매", "6톤 차량": 450, "8톤 차량": 600, "11톤 차량": 820}, + {"종별": "유리", "규격": "두께 3㎜", "단위": "㎡", "6톤 차량": 700, "8톤 차량": 930}, + {"종별": "페인트", "규격": "4ℓ(18ℓ)/통", "단위": "통", "6톤 차량": 1300, "8톤 차량": 1720, "11톤 차량": 2365}, + {"종별": "페인트", "규격": "(18ℓ)/통", "단위": "통", "6톤 차량": 300, "6톤 차량원문": "(300)", "8톤 차량": 400, "8톤 차량원문": "(400)", "11톤 차량": 550, "11톤 차량원문": "(550)"}, + {"종별": "아스타일", "규격": "3㎜×30㎝×30㎝", "단위": "매", "6톤 차량": 9600, "8톤 차량": 12800, "11톤 차량": 17600}, + {"종별": "흄관", "규격": "ø300㎜ L=2.5m", "단위": "본", "6톤 차량": 27, "8톤 차량": 36, "11톤 차량": 52}, + {"종별": "흄관", "규격": "450 〃", "단위": "본", "6톤 차량": 15, "8톤 차량": 20, "11톤 차량": 27}, + {"종별": "흄관", "규격": "600 〃", "단위": "본", "6톤 차량": 8, "8톤 차량": 12, "11톤 차량": 15}, + {"종별": "흄관", "규격": "800 〃", "단위": "본", "6톤 차량": 4, "8톤 차량": 6, "11톤 차량": 9}, + {"종별": "흄관", "규격": "900 〃", "단위": "본", "6톤 차량": 4, "8톤 차량": 5, "11톤 차량": 7}, + {"종별": "흄관", "규격": "1,000 〃", "단위": "본", "6톤 차량": 3, "8톤 차량": 4, "11톤 차량": 5, "20톤 트레일러": 10}, + {"종별": "흄관", "규격": "1,200 〃", "단위": "본", "6톤 차량": 2, "8톤 차량": 3, "11톤 차량": 4, "20톤 트레일러": 7}, + {"종별": "흄관", "규격": "1,500 〃", "단위": "본", "6톤 차량": 1, "8톤 차량": 2, "11톤 차량": 2, "20톤 트레일러": 5}, + {"종별": "콘크리트관", "규격": "ø250㎜ L=1m", "단위": "본", "6톤 차량": 60, "8톤 차량": 80, "11톤 차량": 110}, + {"종별": "콘크리트관", "규격": "300 〃", "단위": "본", "6톤 차량": 52, "8톤 차량": 70, "11톤 차량": 96}, + {"종별": "콘크리트관", "규격": "350 〃", "단위": "본", "6톤 차량": 42, "8톤 차량": 60, "11톤 차량": 82}, + {"종별": "콘크리트관", "규격": "450 〃", "단위": "본", "6톤 차량": 25, "8톤 차량": 30, "11톤 차량": 41}, + {"종별": "콘크리트관", "규격": "600 〃", "단위": "본", "6톤 차량": 16, "8톤 차량": 20, "11톤 차량": 27}, + {"종별": "콘크리트관", "규격": "900 〃", "단위": "본", "6톤 차량": 9, "8톤 차량": 12, "11톤 차량": 16}, + {"종별": "콘크리트관", "규격": "1,000∼1,500 〃", "단위": "본", "6톤 차량": [3, 6], "8톤 차량": [4, 8], "11톤 차량": [5, 10], "20톤 트레일러": 12}, + {"종별": "주철관", "규격": "ø80㎜∼150㎜L=6.0m", "단위": "본", "6톤 차량": [42, 111], "8톤 차량": [46, 123]}, + {"종별": "주철관", "규격": "200∼450 〃", "단위": "본", "6톤 차량": [9, 30], "8톤 차량": [10, 34]}, + {"종별": "주철관", "규격": "500∼600 〃", "단위": "본", "6톤 차량": 6, "8톤 차량": [6, 9]}, + {"종별": "주철관", "규격": "700∼900 〃", "단위": "본", "6톤 차량": 3, "8톤 차량": [3, 5]}, + {"종별": "주철관", "규격": "1,000〃", "단위": "본", "6톤 차량": 2, "8톤 차량": 2}, + {"종별": "도복장강관", "규격": "ø300㎜∼450㎜L=6.0m", "단위": "본", "6톤 차량": [10, 18], "8톤 차량": [14, 22]}, + {"종별": "도복장강관", "규격": "500∼ 700〃", "단위": "본", "6톤 차량": [3, 9], "8톤 차량": [6, 10]}, + {"종별": "도복장강관", "규격": "800∼1,000〃", "단위": "본", "6톤 차량": [1, 3], "8톤 차량": 3}, + {"종별": "도복장강관", "규격": "1,200∼2,100〃", "단위": "본", "6톤 차량": 1, "8톤 차량": 1}, + {"종별": "도복장강관", "규격": "2,200∼2,300〃", "단위": "본", "8톤 차량": 1}, + {"종별": "P・C파일", "규격": "ø300㎜∼440㎜L=9.0m", "단위": "본", "11톤 차량": [6, 10], "20톤 트레일러": [11, 18]}, + {"종별": "P・C파일", "규격": "450∼500 〃", "단위": "본", "11톤 차량": [4, 5], "20톤 트레일러": [8, 9]}, + {"종별": "시멘트", "규격": "40㎏", "단위": "대", "6톤 차량": 150, "8톤 차량": 200, "11톤 차량": 275, "20톤 트레일러": 637, "20톤 트레일러원문": "637(25.5톤화물차는풀카고기준)"}, + {"종별": "전주", "규격": "10m(일반용)", "단위": "본", "11톤 차량": 12, "20톤 트레일러": 23}, + {"종별": "전주", "규격": "체신주 8m", "단위": "본", "8톤 차량": 17, "11톤 차량": 23, "20톤 트레일러": 43} + ], + "주": [ + "가. 중량으로 적재할 수 있는 품종에 대하여는 중량적재 하는 것을 원칙으로 한다.", + "나. 중량적재가 곤란한 것에 대하여는 적재할 수 있는 실측치에 의한다.", + "다. 화물자동차의 적재량은 중량적재나 용량적재 그 어느 쪽의 제한 범위도 벗어나지 않도록 해야 하며 운반로의 종별(공도, 사도) 및 상태에 따라서도 달라질 수 있다.", + "라. 화물자동차의 적재량은 중량으로 적재하거나 특수한 품목을 제외하고는 일반적으로 다음의 값을 기준으로 한다." + ] + }, { "키": "CC000001", "원문번호": "1-3-1", "구분": "건설품셈 공통", "상세구분": "01장 적용기준", "이름": "재료 할증 — 콘크리트 및 포장용 재료", diff --git a/resources/master_data/로직_건설품셈_01장_적용기준.json b/resources/master_data/로직_건설품셈_01장_적용기준.json new file mode 100644 index 000000000..f9443b8ef --- /dev/null +++ b/resources/master_data/로직_건설품셈_01장_적용기준.json @@ -0,0 +1,236 @@ +{ + "그룹": "로직", + "원문": "건설품셈", + "부문": "공통", + "차례": 13, + "판": "2026", + "줄": [ + { + "키": "GC001140", + "원문번호": "1-2-7", + "구분": "건설품셈 공통", + "상세구분": "01장 적용기준", + "이름": "인력운반(지게) — 건설 공통 1-2-7", + "결과단위": "원/자재 단위", + "출처": "건설품셈 공통 1-2-7 2 · 3", + "소유": "공용", + "비고": "품에 포함된 소운반 = 20m 이내(1-2-7 1 가) — 넘거나 2차 운반이면 이 로직으로 별도 계상(1 다) · 해석: 경사지 환산계수 α(5 나 표)를 지게운반에도 씀(1 나 · 3 주⑤ 직고 1m = 수평 6m) · 운반회수를 넣으면 식 대신 그 값(실무 엑셀이 1일 운반횟수를 값으로 넣은 경우) · 운반무게 0 = 재료의 단위중량 표(1-2-4 3) 위끝 · 1회 운반량 0 = 25㎏ · 실작업시간 0 = 450분", + "시험입력": { "운반무게": 575, "운반거리": 6, "경사각도": 35, "재료갈래": "석재류", "운반로": "불량", "회당운반량": 0, "실작업시간": 0, "운반회수": 42 }, + "입력": [ + { "이름": "운반무게", "설명": "운반할 자재 한 단위의 무게 — 0 이면 아래 재료의 단위중량 표 값(㎏/㎥)", "단위": "㎏", "출처": "건설품셈 공통 1-2-4 3" }, + { + "이름": "재료종별", + "설명": "단위중량 표에서 고를 재료(운반무게 0 일 때)", + "고르기": [ + "암석", + "자갈", + "모래", + "점토", + "점질토", + "모래질흙", + "자갈섞인토사", + "자갈섞인모래", + "호박돌", + "사석", + "조약돌", + "주철", + "강, 주강, 단철", + "스테인리스", + "연철", + "놋쇠", + "구리", + "납(鉛)", + "목재", + "소나무", + "소나무(적송)", + "미송", + "시멘트", + "철근콘크리트", + "콘크리트", + "시멘트모르타르", + "역청포장", + "역청재(방수용)", + "물", + "해수", + "눈", + "고로슬래그부순돌" + ], + "출처": "건설품셈 공통 1-2-4 3" + }, + { + "이름": "재료형상", + "설명": "단위중량 표의 형상(운반무게 0 일 때)", + "고르기": [ + "해당 없음", + "화강암", + "안산암", + "사암", + "현무암", + "건조", + "습기", + "포화", + "보통의 것", + "력이 섞인 것", + "력이 섞이고 습한 것", + "STS 304", + "STS 430", + "생송재(生松材)", + "건재(乾材)", + "건재", + "분말상(粉末狀)", + "동결(凍結)", + "수분포화(水分飽和)" + ], + "출처": "건설품셈 공통 1-2-4 3" + }, + { "이름": "회당운반량", "설명": "한 사람이 한 번에 지는 무게 — 0 이면 25㎏", "단위": "㎏", "출처": "건설품셈 공통 1-2-7 2 주 · 3 주③" }, + { "이름": "운반거리", "설명": "수평 운반 거리(편도)", "단위": "m", "출처": "건설품셈 공통 1-2-7 2" }, + { "이름": "경사각도", "설명": "운반로의 경사 각도 — 0 이면 평지(환산계수 1)", "단위": "°", "고르기": [0, 6, 11, 17, 22, 27, 31, 35, 39, 42, 45], "출처": "건설품셈 공통 1-2-7 5 나" }, + { "이름": "재료갈래", "설명": "적재적하 시간 갈래(석재류 = 자갈 · 부순돌 · 조약돌 등)", "고르기": ["토사류", "석재류"], "출처": "건설품셈 공통 1-2-7 3" }, + { "이름": "운반로", "설명": "운반로 상태", "고르기": ["양호", "보통", "불량"], "출처": "건설품셈 공통 1-2-7 3 주②" }, + { "이름": "실작업시간", "설명": "1일 실작업시간 — 0 이면 450분", "단위": "분", "출처": "건설품셈 공통 1-2-7 2" }, + { "이름": "운반회수", "설명": "1일 운반횟수를 직접 넣을 때 — 0 이면 식으로 셈", "단위": "회", "출처": "건설품셈 공통 1-2-7 2" } + ], + "중간": [ + { "이름": "무게", "식": "만약(운반무게 > 0, 운반무게, 찾기(CC000337, 종별=재료종별, 형상=재료형상).위)", "단위": "㎏" }, + { "이름": "운반량", "식": "만약(회당운반량 > 0, 회당운반량, 찾기(CC000342).'1회 운반량')", "단위": "㎏" }, + { "이름": "환산계수", "식": "만약(경사각도 > 0, 찾기(CC000347, 각도=경사각도).환산계수, 1)" }, + { "이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "m" }, + { "이름": "작업시간", "식": "만약(실작업시간 > 0, 실작업시간, 찾기(CC000342).실작업시간)", "단위": "분" }, + { "이름": "적재적하시간", "식": "찾기(CC000343, 종류=재료갈래, 운반로=운반로).적재적하시간", "단위": "분" }, + { "이름": "평균왕복속도", "식": "찾기(CC000343, 종류=재료갈래, 운반로=운반로).평균왕복속도", "단위": "m/hr" }, + { "이름": "일운반회수", "식": "만약(운반회수 > 0, 운반회수, 평균왕복속도 * 작업시간 / (120 * 환산거리 + 평균왕복속도 * 적재적하시간))", "단위": "회" } + ], + "호표": [{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "무게 / 운반량 / 일운반회수", "비목": "노무비", "출처": "건설품셈 공통 1-2-7 2" }], + "덧줄": [], + "끝수": null + }, + { + "키": "GC001141", + "원문번호": "1-2-7", + "구분": "건설품셈 공통", + "상세구분": "01장 적용기준", + "이름": "인력운반(기계설비) — 건설 공통 1-2-7", + "결과단위": "원/자재 단위", + "출처": "건설품셈 공통 1-2-7 5", + "소유": "공용", + "비고": "장대물 · 중량물 등 — 운반비 = M/T × A(60 × 2 × L/V + t) · M = 운반무게 ÷ 1회 운반량 · 해석: T = 1일 실작업시간 450분(1-2-7 2) · 경사지는 환산거리 α × L(5 나)", + "시험입력": { "운반무게": 100, "운반거리": 0.1, "도로상태": "보통", "경사각도": 0, "회당운반량": 0, "실작업시간": 0 }, + "입력": [ + { "이름": "운반무게", "설명": "운반할 자재 한 단위의 무게 — 0 이면 아래 재료의 단위중량 표 값(㎏/㎥)", "단위": "㎏", "출처": "건설품셈 공통 1-2-4 3" }, + { + "이름": "재료종별", + "설명": "단위중량 표에서 고를 재료(운반무게 0 일 때)", + "고르기": [ + "암석", + "자갈", + "모래", + "점토", + "점질토", + "모래질흙", + "자갈섞인토사", + "자갈섞인모래", + "호박돌", + "사석", + "조약돌", + "주철", + "강, 주강, 단철", + "스테인리스", + "연철", + "놋쇠", + "구리", + "납(鉛)", + "목재", + "소나무", + "소나무(적송)", + "미송", + "시멘트", + "철근콘크리트", + "콘크리트", + "시멘트모르타르", + "역청포장", + "역청재(방수용)", + "물", + "해수", + "눈", + "고로슬래그부순돌" + ], + "출처": "건설품셈 공통 1-2-4 3" + }, + { + "이름": "재료형상", + "설명": "단위중량 표의 형상(운반무게 0 일 때)", + "고르기": [ + "해당 없음", + "화강암", + "안산암", + "사암", + "현무암", + "건조", + "습기", + "포화", + "보통의 것", + "력이 섞인 것", + "력이 섞이고 습한 것", + "STS 304", + "STS 430", + "생송재(生松材)", + "건재(乾材)", + "건재", + "분말상(粉末狀)", + "동결(凍結)", + "수분포화(水分飽和)" + ], + "출처": "건설품셈 공통 1-2-4 3" + }, + { "이름": "회당운반량", "설명": "인력운반공 1회 운반량 — 0 이면 25㎏", "단위": "㎏", "출처": "건설품셈 공통 1-2-7 5 가" }, + { "이름": "운반거리", "설명": "운반 거리(편도)", "단위": "km", "출처": "건설품셈 공통 1-2-7 5 가" }, + { "이름": "도로상태", "설명": "운반로 도로상태", "고르기": ["양호", "보통", "불량", "물논"], "출처": "건설품셈 공통 1-2-7 5 가" }, + { "이름": "경사각도", "설명": "운반로의 경사 각도 — 0 이면 평지(환산계수 1)", "단위": "°", "고르기": [0, 6, 11, 17, 22, 27, 31, 35, 39, 42, 45], "출처": "건설품셈 공통 1-2-7 5 나" }, + { "이름": "실작업시간", "설명": "1일 실작업시간 — 0 이면 450분", "단위": "분", "출처": "건설품셈 공통 1-2-7 2" } + ], + "중간": [ + { "이름": "무게", "식": "만약(운반무게 > 0, 운반무게, 찾기(CC000337, 종별=재료종별, 형상=재료형상).위)", "단위": "㎏" }, + { "이름": "운반량", "식": "만약(회당운반량 > 0, 회당운반량, 찾기(CC000346).'1회 운반량')", "단위": "㎏" }, + { "이름": "인원", "식": "무게 / 운반량", "단위": "인" }, + { "이름": "환산계수", "식": "만약(경사각도 > 0, 찾기(CC000347, 각도=경사각도).환산계수, 1)" }, + { "이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "km" }, + { "이름": "왕복평균속도", "식": "찾기(CC000345, 도로상태=도로상태).왕복평균속도", "단위": "km/hr" }, + { "이름": "준비작업시간", "식": "찾기(CC000346).준비작업시간", "단위": "분" }, + { "이름": "작업시간", "식": "만약(실작업시간 > 0, 실작업시간, 찾기(CC000342).실작업시간)", "단위": "분" } + ], + "호표": [ + { + "종류": "인력", + "요소": "LB000045", + "이름": "인력운반공", + "단위": "인", + "수량": "인원 / 작업시간 * (60 * 2 * 환산거리 / 왕복평균속도 + 준비작업시간)", + "비목": "노무비", + "출처": "건설품셈 공통 1-2-7 5 가" + } + ], + "덧줄": [], + "끝수": null + }, + { + "키": "GC001142", + "원문번호": "1-2-7", + "구분": "건설품셈 공통", + "상세구분": "01장 적용기준", + "이름": "벽돌운반 — 건설 공통 1-2-7", + "결과단위": "원/1,000매", + "출처": "건설품셈 공통 1-2-7 4", + "소유": "공용", + "비고": "기본벽돌(19×9×5.7㎝)을 인력으로 층별(층고 3.6m) 운반 · 리프트를 쓰면 층수와 관계없이 보통인부 0.31인", + "입력": [ + { "이름": "운반", "설명": "운반 방법", "고르기": ["인력", "리프트"], "출처": "건설품셈 공통 1-2-7 4" }, + { "이름": "층수", "설명": "올릴 층(리프트면 무관)", "단위": "층", "고르기": [1, 2, 3, 4, 5], "출처": "건설품셈 공통 1-2-7 4" } + ], + "중간": [], + "호표": [{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "찾기(CC000344, 운반=운반, 층수=층수).보통인부", "비목": "노무비", "출처": "건설품셈 공통 1-2-7 4" }], + "덧줄": [], + "끝수": null + } + ] +} diff --git a/resources/tester/test_782_1_carry_127.py b/resources/tester/test_782_1_carry_127.py new file mode 100644 index 000000000..c5da7f966 --- /dev/null +++ b/resources/tester/test_782_1_carry_127.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +"""782-1 건설 공통 1-2-7 운반 기초 — 계수 표 여덟 · 로직 셋 · 실무 소운반 재현 넷 · check_master.""" + +import sys +from decimal import Decimal +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "master_data/scripts")) + +import check_master as cm # noqa: E402 +import master_formula as mf # noqa: E402 + +COEF = "계수_건설품셈_01장_적용기준.json" +LOGIC = "로직_건설품셈_01장_적용기준.json" +JIGE = "GC001140" +BASE = { + "재료종별": "암석", + "재료형상": "화강암", + "회당운반량": 0, + "실작업시간": 0, + "경사각도": 0, + "재료갈래": "석재류", + "운반로": "불량", + "운반회수": 0, +} + + +@pytest.fixture(scope="module") +def whole(): + return cm.master() + + +def qty(whole, ref, **given) -> Decimal: + line = mf.run(whole, ref, {**BASE, **given} if ref == JIGE else given)["줄"][0] + return line["수량"].quantize(Decimal("0.000001")) + + +@pytest.mark.parametrize( + "given, want", + [ + # 북부청 임도 단산10 야면석소운반 — 엑셀이 1일 운반횟수 42 를 값으로 넣음 + ({"운반무게": 575, "운반거리": 6, "경사각도": 35, "운반회수": 42}, "0.547619"), + # 동부청 영월 D01466 야면석소운반 — L 20 · V 2,000 · t 2 + ({"운반무게": 575, "운반거리": 20}, "0.163556"), + # 동부청 영월 D00450 콘크리트소운반 — 단위중량 표 콘크리트 2,300㎏ + ( + {"운반무게": 0, "재료종별": "콘크리트", "재료형상": "해당 없음", "운반거리": 20}, + "0.654222", + ), + # 북부청 사방 단산17 떼소운반 — 33㎏ · 운반회수 100 + ({"운반무게": 33, "운반거리": 20, "운반회수": 100}, "0.013200"), + ], +) +def test_재현(whole, given, want): + assert qty(whole, JIGE, **given) == Decimal(want) + + +def test_북부청_금액(whole): + got = mf.run(whole, JIGE, {**BASE, "운반무게": 575, "운반거리": 6, "운반회수": 42}) + line = got["줄"][0] + assert line["이름"] == "보통인부" + if line["단가"] == 172068: # 2026 상반기 건설노임 — 엑셀 94,227.7 + assert round(line["금액"], 1) == Decimal("94227.7") + + +def test_경사_환산_식(whole): + # α 35° = 8 → 환산 48m · V 2,000 · t 2 · T 450 → N 92.21 → 575/25/N + got = mf.run(whole, JIGE, {**BASE, "운반무게": 575, "운반거리": 6, "경사각도": 35}) + assert got["중간"]["환산계수"] == 8 + assert got["중간"]["작업시간"] == 450 + assert got["줄"][0]["수량"].quantize(Decimal("0.000001")) == Decimal("0.249422") + + +def test_기계설비_벽돌(whole): + given = { + "운반무게": 100, + "재료종별": "암석", + "재료형상": "화강암", + "회당운반량": 0, + "실작업시간": 0, + "운반거리": Decimal("0.1"), + "경사각도": 0, + "도로상태": "보통", + } + # 4인 ÷ 450분 × (60 × 2 × 0.1 / 1.5 + 2) + assert qty(whole, "GC001141", **given) == Decimal("0.088889") + assert qty(whole, "GC001142", 운반="인력", 층수=3) == Decimal("0.74") + assert qty(whole, "GC001142", 운반="리프트", 층수=5) == Decimal("0.31") + + +def test_check_master(): + files, whole = cm.load([COEF, LOGIC]), cm.master() + found = [x for name, data in files.items() for x in cm.check_form(name, data)] + found += cm.check_body(files) + cm.check_logics(files, whole) + assert found == [] + keys = {t["키"] for t in files[COEF]["표"] if t["원문번호"] == "1-2-7"} + assert keys == {f"CC000{n}" for n in range(341, 349)} + assert [r["키"] for r in files[LOGIC]["줄"]] == ["GC001140", "GC001141", "GC001142"]