From 58bebaf0c31182e2e066db9b8d1dc579b3175e2b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 18:12:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(822-2):=20=EA=B8=B0=EA=B3=84=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EB=8B=B9=20=EC=8B=9D=20=EC=9E=90=EB=A3=8C=20?= =?UTF-8?q?=E2=80=94=20=EC=83=88=20=EC=8B=9D=20=EB=84=B7=20=C2=B7=2033=20?= =?UTF-8?q?=EB=8C=80=20=EC=98=AE=EA=B9=80=20=C2=B7=20=EC=A7=81=EC=A2=85=20?= =?UTF-8?q?13=20=EB=8C=80=20=C2=B7=20=EC=A0=84=EB=A0=A5=20=EC=B9=B8=20?= =?UTF-8?q?=C2=B7=20POWER=20TROWEL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 08장 로직: GC001196 전력 기종 · GC001197 전력 + 조종원 · GC001198 손료계수 한 칸 연료 · GC001199 직접 값 - 손료만 식(GC000273) 목록에서 연료 · 조종원 · 전력 있는 기계 뺌 → 276 · 268 · 새 식 목록(보정 식 274 그대로) - 기계표: 주연료 칸 ㎾ 글 10 대 → 전력(㎾) 숫자 칸(값원문 보존) · 원문 잡재료 「-」 = 0 · 조종원직종 13 대(요구 799) - EQ000534 이름 POWER TLOWEL → POWER TROWEL(옛이름 보존) - 가격 없는 기계(스프레어 둘 · TBM 4.5m) · 중유 믹싱플랜트 · 73.7+ 표층재생기는 목록 밖(꼴로 고름 → 까닭 글) - 수식 문서 802-2 다시(1,518 저장 · 기본 다름 0) · 기존 M01 문서 1,514 금액 전후 같음 - M01 기계 저장 손질: 보낸 단위 · 출처 칸이 있으면 그 글 Co-Authored-By: Claude Opus 5.5 --- M01_MasterData/M01_MasterData_Machine.py | 83 ++++- docs/raw/요구_1104/_목록_802-2_변환대조.xlsx | Bin 13006 -> 13302 bytes resources/master_data/_키대장.json | 6 +- .../scripts/build_기계시간당_822_2.py | 310 ++++++++++++++++++ resources/master_data/기계.json | 273 ++++++++------- .../로직_건설품셈_08장_건설기계.json | 187 +++++++++-- .../수식문서/수식_건설품셈_08장_건설기계.json | 145 +++++++- 7 files changed, 849 insertions(+), 155 deletions(-) create mode 100644 resources/master_data/scripts/build_기계시간당_822_2.py diff --git a/M01_MasterData/M01_MasterData_Machine.py b/M01_MasterData/M01_MasterData_Machine.py index 0972627af..fa6061d72 100644 --- a/M01_MasterData/M01_MasterData_Machine.py +++ b/M01_MasterData/M01_MasterData_Machine.py @@ -3,11 +3,14 @@ 계수 = `master_machine_coef`(TS 짝 `common_util/common_util_machine_coef.ts`) · 시간당 = 서버 기계경비식(`M02_Machine_Hourly.hourly` · 전국평균 · 기본 옵션) · +쓰는 계산식 = `M02_Machine_Hourly.pick`(목록 › 꼴 · 822-2) · [새 기계] 저장 = 꼴에 맞는 식 목록에 원문번호 등록 · +M01 에서 만든 기계(출처 모두 「자체」)를 지우면 그 번호 기계가 더 없을 때 목록에서 뺌(`register`) · 마스터 파일은 읽기만 — 쓰기는 `Store_Make.save`. """ from __future__ import annotations +import copy import json import re from decimal import Decimal @@ -102,6 +105,78 @@ def hourly(keys: list[str]) -> dict[str, dict]: return out +def formula(row: dict) -> dict: + """기계 줄(저장 전 고친 값도) → 쓰는 계산식 {키, 이름, 방법 목록|꼴|그 기계 식, 까닭}(822-2 상세 페이지).""" + from M02_MasterTemplete import M02_Machine_Hourly as mh # M02 가 M01 을 부름 — 늦게 + + code = str(row.get("원문번호") or "") + got = mh.pick({**row, "값": store._dec(row.get("값") or {})}) + how = "목록" if mh.listed(code) else "그 기계 식" if got and mh._own(code) else "꼴" + base = {"방법": how, "까닭": mh.why({**row, "값": store._dec(row.get("값") or {})}, got)} + return base | ({"키": got["키"], "이름": got.get("이름")} if got else {}) + + +def _family_files() -> dict[str, str]: + """기계경비식 키 → 로직 파일 이름.""" + return { + str(r.get("키")): name + for name, data in store.loaded()[0].items() + if data.get("그룹") == "로직" and "건설기계" in name + for r in data.get("줄") or [] + } + + +def register(batch: list[dict]) -> None: + """[새 기계] 저장 = 꼴 식 「기계」 목록에 그 원문번호 더함 · M01 에서 만든 기계(출처 모두 「자체」)를 + 지우면 같은 번호 기계가 더 없을 때 목록에서 뺌 — 로직 파일 고침을 같은 저장 묶음에 붙임(정본 저장 길).""" + from M02_MasterTemplete import M02_Machine_Hourly as mh # M02 가 M01 을 부름 — 늦게 + from M02_MasterTemplete import M02_MyMachine as mym + + part = next((p for p in batch if p.get("file") == FILE), None) + if part is None: + return + rows = _rows() + gone = {str(c.get("key")) for c in part.get("changes") or [] if c.get("op") == "delete"} + add: dict[str, list[str]] = {} + drop: set[str] = set() + for ch in part.get("changes") or []: + row = ch.get("row") if ch.get("op") == "add" else None + code = str((row or {}).get("원문번호") or "").strip() + if row and code and not mh.listed(code) and not mh._own(code): + key = mym.logic_key({**row, "값": store._dec(row.get("값") or {})}) + if key and code not in add.get(key, []): + add.setdefault(key, []).append(code) + old = next((r for r in rows if r.get("키") == ch.get("key")), None) + if ( + ch.get("op") == "delete" + and old + and set((old.get("출처") or {}).values()) == {OWN_SOURCE} + ): + code = str(old.get("원문번호") or "") + if code and not any( + str(r.get("원문번호")) == code and r.get("키") not in gone for r in rows + ): + drop.add(code) + if not add and not drop: + return + files = _family_files() + for name in dict.fromkeys(files.values()): + changes = [] + for logic in store.read(name)[0].get("줄") or []: + ins = logic.get("입력") or [] + pick = next((i for i in ins if i.get("이름") == "기계" and "고르기" in i), None) + if pick is None: + continue + codes = [c for c in pick["고르기"] if str(c) not in drop] + codes += [c for c in add.get(str(logic["키"]), []) if c not in codes] + if codes != pick["고르기"]: + new = copy.deepcopy(logic) + next(i for i in new["입력"] if i.get("이름") == "기계")["고르기"] = codes + changes.append({"op": "edit", "key": logic["키"], "row": new}) + if changes: + batch.append({"file": name, "version": store.read(name)[1], "changes": changes}) + + # ── 쓰는 곳 ───────────────────────────────────────────────────────── def _logic_index() -> tuple[dict[str, list[str]], dict[str, list[str]]]: """(원문번호 → 로직 「키 이름」 · 기계 키 → 로직) — 기계 고르기 · 식 안 EQ 키.""" @@ -218,5 +293,9 @@ def fix(part: dict) -> None: values.update(got) was_unit = (old or {}).get("단위") or {} was_src = (old or {}).get("출처") or {} - row["단위"] = {k: was_unit.get(k, units.get(k, "")) for k in values} - row["출처"] = {k: was_src.get(k, OWN_SOURCE) for k in values} + unit, src = ( + row.get("단위") or {}, + row.get("출처") or {}, + ) # 보낸 칸이 있으면 그 글(822-2 빌더) + row["단위"] = {k: unit.get(k) or was_unit.get(k, units.get(k, "")) for k in values} + row["출처"] = {k: src.get(k) or was_src.get(k, OWN_SOURCE) for k in values} diff --git a/docs/raw/요구_1104/_목록_802-2_변환대조.xlsx b/docs/raw/요구_1104/_목록_802-2_변환대조.xlsx index d3abd9b33f25d6ee46b63f6ce3cfa96731470cf7..ada56c1562df2ae9a570f1ce1043a2cdd8c15b9f 100644 GIT binary patch delta 8069 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 Zwz*N#{4oEsS;2y^G7%1}X`w&F{{<2MA~gU2 delta 7750 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 dict: + sign = {"손료": "C", "시간환산": "Q"} + return { + n: {"기호": sign.get(n, "S"), "아래": n.replace("전력 계약종별", "전력계약종별")} + for n in names + } + + +def _machine_in(codes: list[str]) -> dict: + return {"이름": "기계", "고르기": codes} + + +def new_rows(regions: list) -> dict[str, dict]: + """새 식 넷(키 없음 — 저장이 대장 다음 번호).""" + region = {**_REGION, "고르기": regions} + return { + P1: { + **_HEAD, + "이름": "기계 시간당 기계경비(전력 기종)", + "출처": f"{_SRC} · 8-1-6 3 가", + "부품": {"갈래": "기계경비식"}, + "소유": "공용", + "비고": "원문 운전경비 표 주연료 칸에 ㎾ 가 찍힌 전동 기종 — 전력비 = 전력(㎾) × 전력량요금 · 잡재료 = 전력비의 %(8-4 주②)", + "입력": [_machine_in(MOVE[P1]), _ELEC_IN], + "중간": [_LOSS_ONE], + "호표": [_ELEC], + "덧줄": [_MISC, _LOSS_ADD], + "끝수": _CUT, + "변수표": _vars("기계", "전력 계약종별", "손료"), + }, + P2: { + **_HEAD, + "이름": "기계 시간당 기계경비(전력 · 조종원 기종)", + "출처": f"{_SRC} · 8-1-6 3 가", + "부품": {"갈래": "기계경비식"}, + "소유": "공용", + "비고": "원문 운전경비 표 주연료 칸에 ㎾ 가 찍히고 조종원이 있는 전동 기종(점보드릴) — 전력비 + 잡재료(전력비의 %) + 조종원", + "입력": [_machine_in(MOVE[P2]), _ELEC_IN], + "중간": [_LOSS_ONE, _JOB, _HOUR], + "호표": [_ELEC, _CREW], + "덧줄": [_MISC, _LOSS_ADD], + "끝수": _CUT, + "변수표": _vars("기계", "전력 계약종별", "손료", "직종", "시간환산"), + }, + N3: { + **_HEAD, + "이름": "기계 시간당 기계경비(조종원 없는 기종 · 손료계수 한 칸)", + "출처": _SRC, + "부품": {"갈래": "기계경비식"}, + "소유": "공용", + "비고": "원문 손료 표가 시간당 손료계수 하나만 준 기종(8-3-8 기타기계 등) 가운데 주연료가 있고 조종원이 없는 것", + "입력": [_machine_in(MOVE[N3]), region], + "중간": [_LOSS_ONE, _FUEL], + "호표": [_FUEL_LINE], + "덧줄": [_MISC, _LOSS_ADD], + "끝수": _CUT, + "변수표": _vars("기계", "지역", "손료", "연료"), + }, + DIRECT: { + **_HEAD, + "이름": "기계 시간당 기계경비(직접 값)", + "출처": "자체", + "부품": {"갈래": "기계경비식"}, + "소유": "공용", + "비고": "자체 — 구성 없는 기계 · 구성(손료 · 연료 · 조종원) 없이 시간당 노무비 · 재료비 · 경비를 바로 둔 기계(내 기계 「시간당 값 직접」) — 기계 줄 값 그대로", + "입력": [{"이름": "기계", "고르기": []}], + "중간": [], + "호표": [], + "덧줄": [ + { + "이름": "시간당 노무비", + "식": "EQ:{기계}.노무비", + "비목": "노무비", + "출처": "자체", + }, + { + "이름": "시간당 재료비", + "식": "EQ:{기계}.재료비", + "비목": "재료비", + "출처": "자체", + }, + {"이름": "시간당 경비", "식": "EQ:{기계}.경비", "비목": "경비", "출처": "자체"}, + ], # fmt: skip + "끝수": None, + "변수표": _vars("기계"), + }, + } + + +def _note(row: dict, text: str) -> None: + was = str(row.get("비고") or "") + if text not in was: + row["비고"] = f"{was} · {text}" if was else text + + +def machine_changes() -> list[dict]: + rows = {r["키"]: r for r in store.read(MACHINE)[0]["줄"]} + out = [] + for key, row in rows.items(): + new = copy.deepcopy(row) + vals, unit, src = new["값"], new.setdefault("단위", {}), new.setdefault("출처", {}) + if key in POWER and vals.get("전력") is None: + text = str(vals.get("주연료")) + text += "㎾" if unit.get("주연료") == "㎾" else "" # 모르타르 믹서 = 단위 칸에 ㎾ + got = re.search(r"([\d.]+)\s*㎾", text) + assert got and float(got.group(1)) == POWER[key], (key, text) + vals["주연료"], vals["전력"] = None, POWER[key] + unit["주연료"], unit["전력"] = "ℓ/hr", "kWh/hr" + src["전력"] = src.get("주연료") + _note(new, f"전력 = 원문 주연료 칸 「{text}」(㎾ · 전동기 부하율 규정 없음 → 그대로)") + if key == "EQ000532": + _note(new, "원문 「휘발유1.3」 갈래는 주연료2 칸 · 시간당 식은 전력") + if key in MISC_DASH and vals.get("잡재료율") is None: + vals["잡재료율"] = 0 + _note(new, "잡재료율 0 = 원문 잡재료 「-」") + if key in JOBS and not vals.get("조종원직종"): + job, why = JOBS[key] + vals["조종원직종"] = job + src["조종원직종"] = JOB_SRC.format(job) + _note(new, f"직종 = 사용자 확정(요구 799) — {why}") + if key == TYPO[0] and new.get("이름") == TYPO[1]: + items = list(new.items()) + at = [k for k, _ in items].index("이름") + 1 + items.insert(at, ("옛이름", TYPO[1])) + new = dict(items) + new["이름"] = TYPO[2] + if new.get("상세구분") == TYPO[1]: + new["상세구분"] = TYPO[2] + if new != row: + out.append({"op": "edit", "key": key, "row": new}) + return out + + +def logic_changes() -> list[dict]: + data = store.read(LOGIC)[0] + rows = {r["키"]: r for r in data["줄"]} + if any(r.get("이름") == "기계 시간당 기계경비(직접 값)" for r in rows.values()): + return [] + moved = {c for codes in MOVE.values() for c in codes} | set(DROP_ONLY) + out = [] + for key in (LOSS_ONLY, *[k for k in MOVE if k.startswith("GC")]): + new = copy.deepcopy(rows[key]) + pick = next(i for i in new["입력"] if i.get("이름") == "기계") + if key == LOSS_ONLY: + pick["고르기"] = [c for c in pick["고르기"] if str(c) not in moved] + else: + pick["고르기"] += [c for c in MOVE[key] if c not in pick["고르기"]] + out.append({"op": "edit", "key": key, "row": new}) + regions = next(i["고르기"] for i in rows["GC000268"]["입력"] if i["이름"] == "지역") + out += [{"op": "add", "row": row} for row in new_rows(regions).values()] + return out + + +def main(check: bool) -> None: + parts = [] + for file, changes in ((MACHINE, machine_changes()), (LOGIC, logic_changes())): + print(file, len(changes), [c.get("key") or c["row"]["이름"] for c in changes]) + if changes: + parts.append({"file": file, "version": store.read(file)[1], "changes": changes}) + if check or not parts: + return + # 대장 열쇠 = 원문번호 — 넷 다 「8」 이면 한 키로 겹침 → 옛 기계경비식처럼 이름 붙은 열쇠(줄은 「8」) + labels = {r["이름"]: f"공통 8 기계 시간당 경비({LABEL[t]})" for t, r in new_rows([]).items()} + plain = store.ledger_label + store.ledger_label = lambda book, tid, no, row: ( + labels.get(row.get("이름")) or plain(book, tid, no, row) + ) + got = make.save(parts, confirmed=True) + print("저장", got["added"]) + + +if __name__ == "__main__": + main("--check" in sys.argv) diff --git a/resources/master_data/기계.json b/resources/master_data/기계.json index 03b622f14..b1f595b6d 100644 --- a/resources/master_data/기계.json +++ b/resources/master_data/기계.json @@ -10907,7 +10907,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-3", @@ -10924,9 +10924,9 @@ "주연료종류": "건설품셈 공통 8-4-3", "잡재료율": "건설품셈 공통 8-4-3", "조종원": "건설품셈 공통 8-4-3", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (m×ton)" + "비고": "규격 단위: (m×ton) · 직종 = 사용자 확정(요구 799) — 건설기계관리법상 건설기계" }, { "키": "EQ000166", @@ -10968,7 +10968,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-3", @@ -10985,9 +10985,9 @@ "주연료종류": "건설품셈 공통 8-4-3", "잡재료율": "건설품셈 공통 8-4-3", "조종원": "건설품셈 공통 8-4-3", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (m×ton)" + "비고": "규격 단위: (m×ton) · 직종 = 사용자 확정(요구 799) — 건설기계관리법상 건설기계" }, { "키": "EQ000167", @@ -11029,7 +11029,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-3", @@ -11046,9 +11046,9 @@ "주연료종류": "건설품셈 공통 8-4-3", "잡재료율": "건설품셈 공통 8-4-3", "조종원": "건설품셈 공통 8-4-3", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (m×ton)" + "비고": "규격 단위: (m×ton) · 직종 = 사용자 확정(요구 799) — 건설기계관리법상 건설기계" }, { "키": "EQ000168", @@ -11090,7 +11090,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-3", @@ -11107,9 +11107,9 @@ "주연료종류": "건설품셈 공통 8-4-3", "잡재료율": "건설품셈 공통 8-4-3", "조종원": "건설품셈 공통 8-4-3", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (m×ton)" + "비고": "규격 단위: (m×ton) · 직종 = 사용자 확정(요구 799) — 건설기계관리법상 건설기계" }, { "키": "EQ000169", @@ -11151,7 +11151,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-3", @@ -11168,9 +11168,9 @@ "주연료종류": "건설품셈 공통 8-4-3", "잡재료율": "건설품셈 공통 8-4-3", "조종원": "건설품셈 공통 8-4-3", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (m×ton)" + "비고": "규격 단위: (m×ton) · 직종 = 사용자 확정(요구 799) — 건설기계관리법상 건설기계" }, { "키": "EQ000170", @@ -12251,7 +12251,7 @@ "손료계수": 2501, "주연료": 487.2, "주연료종류": "중유", - "잡재료율": null, + "잡재료율": 0, "조종원": 2, "조종원직종": "1048" }, @@ -12272,10 +12272,8 @@ "조종원": "건설품셈 공통 8-4-4", "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름", - "값원문": { - "8-4-4 주연료": "중유487.2" - } + "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름 · 잡재료율 0 = 원문 잡재료 「-」", + "값원문": { "8-4-4 주연료": "중유487.2" } }, { "키": "EQ000188", @@ -12315,7 +12313,7 @@ "손료계수": 2159, "주연료": 614.7, "주연료종류": "경유", - "잡재료율": null, + "잡재료율": 0, "조종원": 2, "조종원직종": "1048" }, @@ -12336,7 +12334,7 @@ "조종원": "건설품셈 공통 8-4-4", "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름" + "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름 · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000189", @@ -12376,7 +12374,7 @@ "손료계수": 2159, "주연료": 678.4, "주연료종류": "경유", - "잡재료율": null, + "잡재료율": 0, "조종원": 2, "조종원직종": "1048" }, @@ -12397,7 +12395,7 @@ "조종원": "건설품셈 공통 8-4-4", "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름" + "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름 · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000190", @@ -12437,7 +12435,7 @@ "손료계수": 2159, "주연료": 746.7, "주연료종류": "경유", - "잡재료율": null, + "잡재료율": 0, "조종원": 2, "조종원직종": "1048" }, @@ -12458,7 +12456,7 @@ "조종원": "건설품셈 공통 8-4-4", "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름" + "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름 · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000191", @@ -12498,7 +12496,7 @@ "손료계수": 2159, "주연료": 819.6, "주연료종류": "경유", - "잡재료율": null, + "잡재료율": 0, "조종원": 2, "조종원직종": "1048" }, @@ -12519,7 +12517,7 @@ "조종원": "건설품셈 공통 8-4-4", "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름" + "비고": "조종원직종 원문 이름: 건설기계운전사 · 규격 단위: (ton/hr) · 주연료종류: 원문 글을 가름 · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000192", @@ -16081,7 +16079,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-6", @@ -16098,9 +16096,9 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (ton/hr)(㎾)" + "비고": "규격 단위: (ton/hr)(㎾) · 직종 = 사용자 확정(요구 799) — 원문 8-1-3 5 표의 「쇄석기」" }, { "키": "EQ000250", @@ -16142,7 +16140,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-6", @@ -16159,9 +16157,9 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (ton/hr)(㎾)" + "비고": "규격 단위: (ton/hr)(㎾) · 직종 = 사용자 확정(요구 799) — 원문 8-1-3 5 표의 「쇄석기」" }, { "키": "EQ000251", @@ -16203,7 +16201,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-6", @@ -16220,9 +16218,9 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (ton/hr)(㎾)" + "비고": "규격 단위: (ton/hr)(㎾) · 직종 = 사용자 확정(요구 799) — 원문 8-1-3 5 표의 「쇄석기」" }, { "키": "EQ000252", @@ -16264,7 +16262,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1048" }, "출처": { "가격": "건설품셈 공통 8-5-6", @@ -16281,9 +16279,9 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048" }, - "비고": "규격 단위: (ton/hr)(㎾)" + "비고": "규격 단위: (ton/hr)(㎾) · 직종 = 사용자 확정(요구 799) — 원문 8-1-3 5 표의 「쇄석기」" }, { "키": "EQ000253", @@ -19689,7 +19687,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1050" }, "출처": { "가격": "건설품셈 공통 8-5-6", @@ -19706,8 +19704,9 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" - } + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1050" + }, + "비고": "직종 = 사용자 확정(요구 799) — 두 법에 없는 기계" }, { "키": "EQ000310", @@ -21858,7 +21857,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": null, @@ -21871,11 +21871,12 @@ "정비비계수": 167, "관리비계수": 328, "손료계수": 870, - "주연료": "동력330㎾", + "주연료": null, "주연료종류": null, "잡재료율": 10, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 330 }, "출처": { "가격": "건설품셈 공통 8-5", @@ -21892,11 +21893,11 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-6" }, - "값원문": { - "8-4-6 주연료": "동력330㎾" - } + "값원문": { "8-4-6 주연료": "동력330㎾" }, + "비고": "전력 = 원문 주연료 칸 「동력330㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로)" }, { "키": "EQ000346", @@ -21981,7 +21982,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 605472, @@ -21994,11 +21996,12 @@ "정비비계수": 777, "관리비계수": 738, "손료계수": 2515, - "주연료": "135㎾", + "주연료": null, "주연료종류": null, "잡재료율": 6, "조종원": 1, - "조종원직종": null + "조종원직종": "1048", + "전력": 135 }, "출처": { "가격": "건설품셈 공통 8-5-6", @@ -22015,11 +22018,11 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048", + "전력": "건설품셈 공통 8-4-6" }, - "값원문": { - "8-4-6 주연료": "135㎾" - } + "값원문": { "8-4-6 주연료": "135㎾" }, + "비고": "전력 = 원문 주연료 칸 「135㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 직종 = 사용자 확정(요구 799) — 원문 8-1-3 5 표의 「천공기」" }, { "키": "EQ000348", @@ -22044,7 +22047,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 1153045, @@ -22057,11 +22061,12 @@ "정비비계수": 777, "관리비계수": 738, "손료계수": 2515, - "주연료": "239㎾", + "주연료": null, "주연료종류": null, "잡재료율": 10, "조종원": 1, - "조종원직종": null + "조종원직종": "1048", + "전력": 239 }, "출처": { "가격": "건설품셈 공통 8-5-6", @@ -22078,11 +22083,11 @@ "주연료종류": "건설품셈 공통 8-4-6", "잡재료율": "건설품셈 공통 8-4-6", "조종원": "건설품셈 공통 8-4-6", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1048", + "전력": "건설품셈 공통 8-4-6" }, - "값원문": { - "8-4-6 주연료": "239㎾" - } + "값원문": { "8-4-6 주연료": "239㎾" }, + "비고": "전력 = 원문 주연료 칸 「239㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 직종 = 사용자 확정(요구 799) — 원문 8-1-3 5 표의 「천공기」" }, { "키": "EQ000349", @@ -26768,7 +26773,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 16828, @@ -26781,11 +26787,12 @@ "정비비계수": 2333, "관리비계수": 1169, "손료계수": 6502, - "주연료": "동력 15.1㎾", + "주연료": null, "주연료종류": null, - "잡재료율": null, + "잡재료율": 0, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 15.1 }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -26802,12 +26809,11 @@ "주연료종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-8" }, - "비고": "규격 단위: (W×L×H)", - "값원문": { - "8-4-8 주연료": "동력 15.1㎾" - } + "비고": "규격 단위: (W×L×H) · 전력 = 원문 주연료 칸 「동력 15.1㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 잡재료율 0 = 원문 잡재료 「-」", + "값원문": { "8-4-8 주연료": "동력 15.1㎾" } }, { "키": "EQ000427", @@ -26832,7 +26838,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 21722, @@ -26845,11 +26852,12 @@ "정비비계수": 2333, "관리비계수": 1169, "손료계수": 6502, - "주연료": "동력 15.1㎾", + "주연료": null, "주연료종류": null, - "잡재료율": null, + "잡재료율": 0, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 15.1 }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -26866,12 +26874,11 @@ "주연료종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-8" }, - "비고": "규격 단위: (W×L×H)", - "값원문": { - "8-4-8 주연료": "동력 15.1㎾" - } + "비고": "규격 단위: (W×L×H) · 전력 = 원문 주연료 칸 「동력 15.1㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 잡재료율 0 = 원문 잡재료 「-」", + "값원문": { "8-4-8 주연료": "동력 15.1㎾" } }, { "키": "EQ000428", @@ -27218,7 +27225,7 @@ "주연료종류": null, "잡재료율": null, "조종원": 1, - "조종원직종": null + "조종원직종": "1050" }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -27235,8 +27242,9 @@ "주연료종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" - } + "조종원직종": "건설품셈 공통 8-1-3 5 · 건설노임 1050" + }, + "비고": "직종 = 사용자 확정(요구 799) — 두 법에 없는 기계" }, { "키": "EQ000434", @@ -33047,7 +33055,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 16913, @@ -33060,11 +33069,12 @@ "정비비계수": null, "관리비계수": null, "손료계수": 4677, - "주연료": "3.73㎾", + "주연료": null, "주연료종류": null, - "잡재료율": null, + "잡재료율": 0, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 3.73 }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -33081,11 +33091,11 @@ "주연료종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-8" }, - "값원문": { - "8-4-8 주연료": "3.73㎾" - } + "값원문": { "8-4-8 주연료": "3.73㎾" }, + "비고": "전력 = 원문 주연료 칸 「3.73㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000530", @@ -33110,7 +33120,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 21887, @@ -33123,11 +33134,12 @@ "정비비계수": null, "관리비계수": null, "손료계수": 4677, - "주연료": "7.46㎾", + "주연료": null, "주연료종류": null, - "잡재료율": null, + "잡재료율": 0, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 7.46 }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -33144,11 +33156,11 @@ "주연료종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-8" }, - "값원문": { - "8-4-8 주연료": "7.46㎾" - } + "값원문": { "8-4-8 주연료": "7.46㎾" }, + "비고": "전력 = 원문 주연료 칸 「7.46㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000531", @@ -33173,7 +33185,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 40769, @@ -33186,11 +33199,12 @@ "정비비계수": null, "관리비계수": null, "손료계수": 4677, - "주연료": "37㎾", + "주연료": null, "주연료종류": null, - "잡재료율": null, + "잡재료율": 0, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 37 }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -33207,11 +33221,11 @@ "주연료종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-8" }, - "값원문": { - "8-4-8 주연료": "37㎾" - } + "값원문": { "8-4-8 주연료": "37㎾" }, + "비고": "전력 = 원문 주연료 칸 「37㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000532", @@ -33232,13 +33246,14 @@ "정비비계수": "10^-7/시간", "관리비계수": "10^-7/시간", "손료계수": "10^-7/시간", - "주연료": "㎾", + "주연료": "ℓ/hr", "주연료종류": "", "주연료2": "ℓ/hr", "주연료2종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 5696, @@ -33251,13 +33266,14 @@ "정비비계수": null, "관리비계수": null, "손료계수": 3708, - "주연료": 1.87, + "주연료": null, "주연료종류": null, "주연료2": 1.3, "주연료2종류": "휘발유", "잡재료율": 2, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 1.87 }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -33276,11 +33292,11 @@ "주연료2종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-8" }, - "값원문": { - "8-4-8 주연료": "1.87㎾ 휘발유1.3" - } + "값원문": { "8-4-8 주연료": "1.87㎾ 휘발유1.3" }, + "비고": "전력 = 원문 주연료 칸 「1.87㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 원문 「휘발유1.3」 갈래는 주연료2 칸 · 시간당 식은 전력" }, { "키": "EQ000533", @@ -33305,7 +33321,8 @@ "주연료종류": "", "잡재료율": "주연료의 %", "조종원": "인/일", - "조종원직종": "" + "조종원직종": "", + "전력": "kWh/hr" }, "값": { "가격": 38, @@ -33318,11 +33335,12 @@ "정비비계수": null, "관리비계수": null, "손료계수": 3375, - "주연료": "1.49㎾", + "주연료": null, "주연료종류": null, - "잡재료율": null, + "잡재료율": 0, "조종원": null, - "조종원직종": null + "조종원직종": null, + "전력": 1.49 }, "출처": { "가격": "건설품셈 공통 8-5-8", @@ -33339,19 +33357,20 @@ "주연료종류": "건설품셈 공통 8-4-8", "잡재료율": "건설품셈 공통 8-4-8", "조종원": "건설품셈 공통 8-4-8", - "조종원직종": "건설품셈 공통 8-1-3 5" + "조종원직종": "건설품셈 공통 8-1-3 5", + "전력": "건설품셈 공통 8-4-8" }, - "값원문": { - "8-4-8 주연료": "1.49㎾" - } + "값원문": { "8-4-8 주연료": "1.49㎾" }, + "비고": "전력 = 원문 주연료 칸 「1.49㎾」(㎾ · 전동기 부하율 규정 없음 → 그대로) · 잡재료율 0 = 원문 잡재료 「-」" }, { "키": "EQ000534", "원문번호": "7994-0050", "원문": "건설품셈", "구분": "[70]기타기계", - "상세구분": "POWER TLOWEL", - "이름": "POWER TLOWEL", + "상세구분": "POWER TROWEL", + "이름": "POWER TROWEL", + "옛이름": "POWER TLOWEL", "규격": "3.73㎾", "단위": { "가격": "천원", diff --git a/resources/master_data/로직_건설품셈_08장_건설기계.json b/resources/master_data/로직_건설품셈_08장_건설기계.json index 929d896f8..abb927060 100644 --- a/resources/master_data/로직_건설품셈_08장_건설기계.json +++ b/resources/master_data/로직_건설품셈_08장_건설기계.json @@ -284,7 +284,13 @@ "9090-1300", "9090-1400", "9090-1500", - "9090-1600" + "9090-1600", + "3108-0060", + "3108-0080", + "3108-0100", + "3108-0120", + "9010-0120", + "9010-0200" ] }, { @@ -853,11 +859,6 @@ "2104-0220", "2104-0250", "2104-0300", - "2208-5008", - "2208-5010", - "2208-5012", - "2208-5016", - "2208-5020", "2105-0002", "2105-0003", "2105-0005", @@ -921,7 +922,6 @@ "2118-0010", "2210-0145", "3430-0300", - "3450-0642", "4115-0100", "4115-0150", "4115-0200", @@ -929,10 +929,6 @@ "4505-0015", "4505-0026", "4611-0075", - "5105-0050", - "5105-0100", - "5105-0150", - "5105-0200", "5111-0040", "5111-0050", "5111-0060", @@ -989,15 +985,12 @@ "5118-0005", "5118-0006", "5118-0007", - "5119-0625", "5203-1800", "5203-2200", "5203-2700", "5203-3500", "5203-4500", "5330-0074", - "5805-0002", - "5805-0003", "5901-0006", "5901-0010", "5901-0016", @@ -1018,9 +1011,6 @@ "6550-0130", "7103-0010", "7104-0010", - "7202-0008", - "7202-0010", - "7205-0125", "7206-0020", "7206-0070", "7206-0100", @@ -1058,10 +1048,6 @@ "7930-0075", "7930-0100", "7935-0180", - "7991-0050", - "7991-0100", - "7991-0500", - "7993-0020", "8201-0100", "8202-0100", "8203-0019", @@ -1370,7 +1356,30 @@ "소유": "공용", "비고": "원문 운전경비 표에 주연료가 빈 기종(공기식 크롤러드릴 등 — 동력은 공기압축기) — 손료 + 조종원", "입력": [ - { "이름": "기계", "고르기": ["5401-0017", "4108-0060", "4108-0090", "4108-0120", "4108-0150", "4108-0180", "4108-0210", "5401-0015"] }, + { + "이름": "기계", + "고르기": [ + "5401-0017", + "4108-0060", + "4108-0090", + "4108-0120", + "4108-0150", + "4108-0180", + "4108-0210", + "5401-0015", + "2208-5008", + "2208-5010", + "2208-5012", + "2208-5016", + "2208-5020", + "5105-0050", + "5105-0100", + "5105-0150", + "5105-0200", + "5119-0625", + "7205-0125" + ] + }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -3456,6 +3465,140 @@ "조건": { "기호": "S", "아래": "조건" }, "유속": { "기호": "v", "아래": "유속" } } + }, + { + "키": "GC001196", + "원문번호": "8", + "구분": "건설품셈 공통", + "상세구분": "08장 건설기계", + "결과단위": "원/시간", + "이름": "기계 시간당 기계경비(전력 기종)", + "출처": "건설품셈 공통 8-1-5 · 8-1-6 · 8-1-6 3 가", + "부품": { "갈래": "기계경비식" }, + "소유": "공용", + "비고": "원문 운전경비 표 주연료 칸에 ㎾ 가 찍힌 전동 기종 — 전력비 = 전력(㎾) × 전력량요금 · 잡재료 = 전력비의 %(8-4 주②)", + "입력": [ + { "이름": "기계", "고르기": ["7991-0050", "7202-0008", "7202-0010", "7991-0100", "7991-0500", "7992-0001", "7993-0020"] }, + { "이름": "전력 계약종별", "고르기": ["일반용(갑)Ⅰ 저압", "주택용 저압"], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) — 기본 = 불러오기 기본값" } + ], + "중간": [{ "이름": "손료", "식": "버림(EQ:{기계}.가격 * 1000 * EQ:{기계}.손료계수 / 10000000, 0)", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4" }], + "호표": [ + { + "종류": "재료", + "요소": { "이름": "전력량요금", "계약종별": "입력" }, + "이름": "전력", + "단위": "㎾h", + "수량": "EQ:{기계}.전력", + "비목": "재료비", + "출처": "건설품셈 공통 8-1-6 3 가", + "비고": "기계 줄 전력(㎾) 그대로 — 전동기 부하율 규정 없음" + } + ], + "덧줄": [{ "이름": "잡재료비", "식": "재료비 * EQ:{기계}.잡재료율 / 100", "비목": "재료비", "출처": "건설품셈 공통 8-4 주②" }, { "이름": "기계손료", "식": "손료", "비목": "경비", "출처": "건설품셈 공통 8-1-6 2" }], + "끝수": "손료 원 미만 버림(건설품셈 공통 8-1-5 12) · 합계 원문 규정 없음", + "변수표": { "기계": { "기호": "S", "아래": "기계" }, "전력 계약종별": { "기호": "S", "아래": "전력계약종별" }, "손료": { "기호": "C", "아래": "손료" } } + }, + { + "키": "GC001197", + "원문번호": "8", + "구분": "건설품셈 공통", + "상세구분": "08장 건설기계", + "결과단위": "원/시간", + "이름": "기계 시간당 기계경비(전력 · 조종원 기종)", + "출처": "건설품셈 공통 8-1-5 · 8-1-6 · 8-1-6 3 가", + "부품": { "갈래": "기계경비식" }, + "소유": "공용", + "비고": "원문 운전경비 표 주연료 칸에 ㎾ 가 찍히고 조종원이 있는 전동 기종(점보드릴) — 전력비 + 잡재료(전력비의 %) + 조종원", + "입력": [ + { "이름": "기계", "고르기": ["5805-0002", "5805-0003"] }, + { "이름": "전력 계약종별", "고르기": ["일반용(갑)Ⅰ 저압", "주택용 저압"], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) — 기본 = 불러오기 기본값" } + ], + "중간": [ + { "이름": "손료", "식": "버림(EQ:{기계}.가격 * 1000 * EQ:{기계}.손료계수 / 10000000, 0)", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4" }, + { "이름": "직종", "식": "EQ:{기계}.조종원직종", "출처": "건설품셈 공통 8-1-3 5" }, + { "이름": "시간환산", "식": "찾기(CC000336, 구분='시간환산').분자 / 찾기(CC000336, 구분='시간환산').분모", "출처": "건설품셈 공통 1-1-3 2(일일 작업시간 8시간)" } + ], + "호표": [ + { + "종류": "재료", + "요소": { "이름": "전력량요금", "계약종별": "입력" }, + "이름": "전력", + "단위": "㎾h", + "수량": "EQ:{기계}.전력", + "비목": "재료비", + "출처": "건설품셈 공통 8-1-6 3 가", + "비고": "기계 줄 전력(㎾) 그대로 — 전동기 부하율 규정 없음" + }, + { + "종류": "인력", + "요소": "LB:{직종}", + "이름": "조종원", + "단위": "인", + "수량": "EQ:{기계}.조종원 * 시간환산", + "비목": "노무비", + "출처": "건설품셈 공통 8-1-3 6", + "비고": "하루 노임 × 1/8(1일 8시간 · 건설품셈 공통 1-1-3 2)" + } + ], + "덧줄": [{ "이름": "잡재료비", "식": "재료비 * EQ:{기계}.잡재료율 / 100", "비목": "재료비", "출처": "건설품셈 공통 8-4 주②" }, { "이름": "기계손료", "식": "손료", "비목": "경비", "출처": "건설품셈 공통 8-1-6 2" }], + "끝수": "손료 원 미만 버림(건설품셈 공통 8-1-5 12) · 합계 원문 규정 없음", + "변수표": { + "기계": { "기호": "S", "아래": "기계" }, + "전력 계약종별": { "기호": "S", "아래": "전력계약종별" }, + "손료": { "기호": "C", "아래": "손료" }, + "직종": { "기호": "S", "아래": "직종" }, + "시간환산": { "기호": "Q", "아래": "시간환산" } + } + }, + { + "키": "GC001198", + "원문번호": "8", + "구분": "건설품셈 공통", + "상세구분": "08장 건설기계", + "결과단위": "원/시간", + "이름": "기계 시간당 기계경비(조종원 없는 기종 · 손료계수 한 칸)", + "출처": "건설품셈 공통 8-1-5 · 8-1-6", + "부품": { "갈래": "기계경비식" }, + "소유": "공용", + "비고": "원문 손료 표가 시간당 손료계수 하나만 준 기종(8-3-8 기타기계 등) 가운데 주연료가 있고 조종원이 없는 것", + "입력": [ + { "이름": "기계", "고르기": ["7994-0050"] }, + { + "이름": "지역", + "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], + "출처": "건설품셈 공통 8-1-7 5" + } + ], + "중간": [ + { "이름": "손료", "식": "버림(EQ:{기계}.가격 * 1000 * EQ:{기계}.손료계수 / 10000000, 0)", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4" }, + { "이름": "연료", "식": "EQ:{기계}.주연료종류", "출처": "건설품셈 공통 8-4 주①㉰" } + ], + "호표": [{ "종류": "재료", "요소": "MO:{연료}:{지역}", "이름": "주연료", "단위": "ℓ", "수량": "EQ:{기계}.주연료", "비목": "재료비", "출처": "건설품셈 공통 8-4-8" }], + "덧줄": [{ "이름": "잡재료비", "식": "재료비 * EQ:{기계}.잡재료율 / 100", "비목": "재료비", "출처": "건설품셈 공통 8-4 주②" }, { "이름": "기계손료", "식": "손료", "비목": "경비", "출처": "건설품셈 공통 8-1-6 2" }], + "끝수": "손료 원 미만 버림(건설품셈 공통 8-1-5 12) · 합계 원문 규정 없음", + "변수표": { "기계": { "기호": "S", "아래": "기계" }, "지역": { "기호": "S", "아래": "지역" }, "손료": { "기호": "C", "아래": "손료" }, "연료": { "기호": "S", "아래": "연료" } } + }, + { + "키": "GC001199", + "원문번호": "8", + "구분": "건설품셈 공통", + "상세구분": "08장 건설기계", + "결과단위": "원/시간", + "이름": "기계 시간당 기계경비(직접 값)", + "출처": "자체", + "부품": { "갈래": "기계경비식" }, + "소유": "공용", + "비고": "자체 — 구성 없는 기계 · 구성(손료 · 연료 · 조종원) 없이 시간당 노무비 · 재료비 · 경비를 바로 둔 기계(내 기계 「시간당 값 직접」) — 기계 줄 값 그대로", + "입력": [{ "이름": "기계", "고르기": [] }], + "중간": [], + "호표": [], + "덧줄": [ + { "이름": "시간당 노무비", "식": "EQ:{기계}.노무비", "비목": "노무비", "출처": "자체" }, + { "이름": "시간당 재료비", "식": "EQ:{기계}.재료비", "비목": "재료비", "출처": "자체" }, + { "이름": "시간당 경비", "식": "EQ:{기계}.경비", "비목": "경비", "출처": "자체" } + ], + "끝수": null, + "변수표": { "기계": { "기호": "S", "아래": "기계" } } } ] } diff --git a/resources/master_data/수식문서/수식_건설품셈_08장_건설기계.json b/resources/master_data/수식문서/수식_건설품셈_08장_건설기계.json index d637e805c..59a932359 100644 --- a/resources/master_data/수식문서/수식_건설품셈_08장_건설기계.json +++ b/resources/master_data/수식문서/수식_건설품셈_08장_건설기계.json @@ -62,7 +62,7 @@ "갈래": "기계경비식" }, "변수": [ - {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["1730-0015", "7204-0055", "0602-0150", "2702-0020", "0101-0007", "0101-0010", "0101-0012", "0101-0019", "0101-0032", "2105-0005", "2104-0010", "2104-0015", "2104-0020", "2104-0025", "2104-0030", "2104-0035", "2104-0040", "2104-0045", "2104-0050", "2104-0060", "2104-0070", "2104-0080", "2104-0100", "2104-0130", "2104-0160", "2104-0200", "2104-0220", "2104-0250", "2104-0300", "4504-0032", "4504-0036", "4504-0041", "4504-0043", "4504-0047", "4504-0052", "4205-0010", "4205-0017", "4205-0020", "4205-0030", "4205-0040", "4205-0045", "2105-0002", "2105-0003", "2105-0010", "2105-0015", "2105-0018", "2502-0025", "0602-0025", "2502-0050", "2101-0010", "2101-0015", "2101-0020", "2101-0025", "2101-0030", "2101-0035", "2101-0040", "2101-0050", "2101-0070", "2101-0080", "2101-0100", "2101-0150", "2101-0220", "2101-0280", "2101-0300", "7204-0018", "7204-0160", "5205-0210", "5405-0110", "7505-0450", "5205-0255", "7505-0150", "5205-0103", "6701-0147", "6701-0184", "5205-0170", "7505-0350", "0302-0134", "7505-0100", "7505-0050", "5205-0071", "7505-0025", "0502-0036", "1306-0100", "1506-0032", "0121-0004", "1305-0007", "7204-0038", "7204-0065", "0302-0057", "5701-0020", "0302-0095", "3201-0003", "1106-0012", "1406-0015", "1206-0008", "3601-0102", "1306-0025", "0211-0018", "0602-0045", "0602-0060", "0602-0105", "1630-0080", "3901-0300", "7505-0250", "2502-0035", "1306-0120", "1106-0010", "3201-0001", "1406-0008", "3601-0202", "3601-0402", "3611-0142", "2502-0020", "2502-0075", "5205-0035", "2702-0030", "2702-0060", "0211-0060", "0211-0080", "0211-0100", "0221-0040", "0221-0070", "0302-0025", "0302-0172", "0302-0229", "0302-0287", "0302-0350", "0302-0500", "0406-0054", "0406-0115", "0406-0161", "0406-0206", "0503-0036", "0602-0200", "0602-0240", "0602-0320", "1106-0015", "1206-0010", "1206-0014", "1209-0001", "1209-0002", "1209-0004", "1209-0006", "1209-0007", "1209-0008", "1209-0013", "1306-0044", "1306-0060", "1406-0025", "1506-0011", "1506-0012", "1506-0015", "1506-0019", "1506-0025", "1506-0030", "1506-0037", "2602-0015", "2602-0025", "2602-0035", "2602-0045", "2702-0040", "3530-0015", "3530-0036", "3601-0204", "3601-0302", "3701-0200", "3805-0120", "4504-0021", "4504-0028", "5405-0150", "5701-0010", "7106-0035", "7505-0125", "7505-0200", "7505-0500", "7505-0700", "9010-0003", "9010-0006", "9010-0010", "9010-0012", "9010-0020", "9010-0022", "9010-0033", "9010-0040", "9010-0044", "9010-0060", "9010-0080", "9020-0010", "9020-0015", "9020-0016", "9020-0022", "9020-0035", "9020-0050", "9020-0072", "9020-0160", "9020-0180", "9020-0200", "9050-0075", "9050-0150", "9050-0450", "9050-0750", "9050-0850", "9090-0800", "9090-1000", "9090-1200", "9090-1300", "9090-1400", "9090-1500", "9090-1600"], "값": "1730-0015"}}, + {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["1730-0015", "7204-0055", "0602-0150", "2702-0020", "0101-0007", "0101-0010", "0101-0012", "0101-0019", "0101-0032", "2105-0005", "2104-0010", "2104-0015", "2104-0020", "2104-0025", "2104-0030", "2104-0035", "2104-0040", "2104-0045", "2104-0050", "2104-0060", "2104-0070", "2104-0080", "2104-0100", "2104-0130", "2104-0160", "2104-0200", "2104-0220", "2104-0250", "2104-0300", "4504-0032", "4504-0036", "4504-0041", "4504-0043", "4504-0047", "4504-0052", "4205-0010", "4205-0017", "4205-0020", "4205-0030", "4205-0040", "4205-0045", "2105-0002", "2105-0003", "2105-0010", "2105-0015", "2105-0018", "2502-0025", "0602-0025", "2502-0050", "2101-0010", "2101-0015", "2101-0020", "2101-0025", "2101-0030", "2101-0035", "2101-0040", "2101-0050", "2101-0070", "2101-0080", "2101-0100", "2101-0150", "2101-0220", "2101-0280", "2101-0300", "7204-0018", "7204-0160", "5205-0210", "5405-0110", "7505-0450", "5205-0255", "7505-0150", "5205-0103", "6701-0147", "6701-0184", "5205-0170", "7505-0350", "0302-0134", "7505-0100", "7505-0050", "5205-0071", "7505-0025", "0502-0036", "1306-0100", "1506-0032", "0121-0004", "1305-0007", "7204-0038", "7204-0065", "0302-0057", "5701-0020", "0302-0095", "3201-0003", "1106-0012", "1406-0015", "1206-0008", "3601-0102", "1306-0025", "0211-0018", "0602-0045", "0602-0060", "0602-0105", "1630-0080", "3901-0300", "7505-0250", "2502-0035", "1306-0120", "1106-0010", "3201-0001", "1406-0008", "3601-0202", "3601-0402", "3611-0142", "2502-0020", "2502-0075", "5205-0035", "2702-0030", "2702-0060", "0211-0060", "0211-0080", "0211-0100", "0221-0040", "0221-0070", "0302-0025", "0302-0172", "0302-0229", "0302-0287", "0302-0350", "0302-0500", "0406-0054", "0406-0115", "0406-0161", "0406-0206", "0503-0036", "0602-0200", "0602-0240", "0602-0320", "1106-0015", "1206-0010", "1206-0014", "1209-0001", "1209-0002", "1209-0004", "1209-0006", "1209-0007", "1209-0008", "1209-0013", "1306-0044", "1306-0060", "1406-0025", "1506-0011", "1506-0012", "1506-0015", "1506-0019", "1506-0025", "1506-0030", "1506-0037", "2602-0015", "2602-0025", "2602-0035", "2602-0045", "2702-0040", "3530-0015", "3530-0036", "3601-0204", "3601-0302", "3701-0200", "3805-0120", "4504-0021", "4504-0028", "5405-0150", "5701-0010", "7106-0035", "7505-0125", "7505-0200", "7505-0500", "7505-0700", "9010-0003", "9010-0006", "9010-0010", "9010-0012", "9010-0020", "9010-0022", "9010-0033", "9010-0040", "9010-0044", "9010-0060", "9010-0080", "9020-0010", "9020-0015", "9020-0016", "9020-0022", "9020-0035", "9020-0050", "9020-0072", "9020-0160", "9020-0180", "9020-0200", "9050-0075", "9050-0150", "9050-0450", "9050-0750", "9050-0850", "9090-0800", "9090-1000", "9090-1200", "9090-1300", "9090-1400", "9090-1500", "9090-1600", "3108-0060", "3108-0080", "3108-0100", "3108-0120", "9010-0120", "9010-0200"], "값": "1730-0015"}}, {"열쇠": "지역", "기호": "S", "아래": "지역", "위": "", "이름": "지역", "단위": "", "출처": "건설품셈 공통 8-1-7 5", "연결": {"갈래": "선택", "목록": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], "값": "전국평균"}}, {"열쇠": "손료", "기호": "C", "아래": "손료", "위": "", "이름": "손료", "단위": "", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4", "연결": {"갈래": "식"}}, {"열쇠": "연료", "기호": "S", "아래": "연료", "위": "", "이름": "연료", "단위": "", "출처": "건설품셈 공통 8-4 주①㉰", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "주연료종류"}}, @@ -306,7 +306,7 @@ "결과단위": "원/시간", "출처": "건설품셈 공통 8-1-5 · 8-1-6", "변수": [ - {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["7620-0003", "7621-0100", "7995-0050", "5210-0013", "5210-0027", "5220-0015", "7730-0150", "6516-0060", "6516-0120", "6516-0250", "6516-0300", "6105-0190", "6202-0060", "0230-0002", "6410-0080", "6410-0100", "6410-0120", "6410-0150", "6410-0200", "6901-0010", "6530-0090", "6530-0120", "6517-0100", "6517-0150", "6517-0200", "6517-0250", "6517-0300", "6510-0100", "6510-0150", "6510-0200", "6510-0250", "6510-0300", "6518-0100", "6518-0150", "6518-0200", "6518-0250", "6518-0300", "5501-0080", "7750-0016", "7750-0025", "7730-0050", "5401-0017", "5202-0127", "5202-0240", "5202-0300", "7611-0200", "7612-0200", "5204-0200", "5204-0300", "5204-0400", "5204-0500", "5204-0600", "5702-0095", "3430-0400", "7431-1300", "7740-0080", "7740-0150", "2101-0010", "2101-0015", "2101-0020", "2101-0025", "2101-0030", "2101-0035", "2101-0040", "2101-0050", "2101-0070", "2101-0080", "2101-0100", "2101-0150", "2101-0220", "2101-0280", "2101-0300", "2104-0010", "2104-0015", "2104-0020", "2104-0025", "2104-0030", "2104-0035", "2104-0040", "2104-0045", "2104-0050", "2104-0060", "2104-0070", "2104-0080", "2104-0100", "2104-0130", "2104-0160", "2104-0200", "2104-0220", "2104-0250", "2104-0300", "2208-5008", "2208-5010", "2208-5012", "2208-5016", "2208-5020", "2105-0002", "2105-0003", "2105-0005", "2105-0010", "2105-0015", "2105-0018", "0201-0012", "0201-0020", "0201-0040", "0201-0060", "0201-0070", "0201-0080", "0201-0100", "0201-0120", "0201-0200", "6105-0390", "6202-0125", "7830-0081", "7950-0149", "7951-0066", "7952-0246", "7953-3400", "7954-1119", "7611-0300", "7431-1100", "7431-2300", "7431-2500", "7611-0400", "7611-0500", "7614-0300", "0250-0100", "5210-0010", "5210-0019", "0103-0016", "0103-0019", "0103-0023", "0103-0027", "0103-0032", "0230-0004", "0230-0006", "0230-0007", "0230-0008", "0230-0010", "0240-0007", "0250-0080", "0407-0054", "0407-0092", "0407-0107", "0407-0161", "0407-0206", "0610-0150", "0610-0200", "0610-0240", "2115-0024", "2115-0031", "2115-0036", "2116-0031", "2116-0036", "2117-0022", "2117-0027", "2118-0010", "2210-0145", "3430-0300", "3450-0642", "4115-0100", "4115-0150", "4115-0200", "4115-0300", "4505-0015", "4505-0026", "4611-0075", "5105-0050", "5105-0100", "5105-0150", "5105-0200", "5111-0040", "5111-0050", "5111-0060", "5111-0076", "5111-0091", "5112-0001", "5112-0002", "5112-0003", "5112-0004", "5112-0005", "5113-0001", "5113-0002", "5113-0003", "5113-0004", "5113-0005", "5113-0006", "5113-0007", "5113-0008", "5113-0009", "5113-0010", "5113-0011", "5114-0001", "5114-0002", "5114-0003", "5114-0004", "5114-0005", "5114-0006", "5114-0007", "5114-0008", "5115-0030", "5115-0055", "5115-0075", "5115-0095", "5116-0001", "5116-0002", "5116-0003", "5116-0004", "5116-0005", "5116-0006", "5116-0007", "5116-0008", "5117-0001", "5117-0002", "5117-0003", "5117-0004", "5117-0005", "5117-0006", "5117-0007", "5117-0008", "5118-0001", "5118-0002", "5118-0003", "5118-0004", "5118-0005", "5118-0006", "5118-0007", "5119-0625", "5203-1800", "5203-2200", "5203-2700", "5203-3500", "5203-4500", "5330-0074", "5805-0002", "5805-0003", "5901-0006", "5901-0010", "5901-0016", "6202-0200", "6408-0015", "6408-0020", "6408-0030", "6408-0040", "6408-0050", "6408-0085", "6408-0100", "6515-0090", "6530-0030", "6530-0040", "6530-0045", "6530-0060", "6532-0220", "6550-0130", "7103-0010", "7104-0010", "7202-0008", "7202-0010", "7205-0125", "7206-0020", "7206-0070", "7206-0100", "7430-1100", "7430-1300", "7430-1500", "7430-2300", "7430-2500", "7612-0300", "7612-0400", "7613-0075", "7613-0150", "7613-0300", "7613-0400", "7613-0600", "7613-0900", "7620-0002", "7730-0080", "7730-0100", "7730-0125", "7740-0100", "7770-0004", "7930-0001", "7930-0002", "7930-0003", "7930-0005", "7930-0007", "7930-0010", "7930-0015", "7930-0020", "7930-0025", "7930-0030", "7930-0040", "7930-0050", "7930-0075", "7930-0100", "7935-0180", "7991-0050", "7991-0100", "7991-0500", "7993-0020", "8201-0100", "8202-0100", "8203-0019", "8204-0100", "8205-0100", "9060-0060", "9060-0100", "9060-0200", "9060-0300", "9060-0500", "9060-0600", "9080-0050", "9080-0080", "9080-0100", "9080-0120", "9080-0150", "9080-0200", "9080-0300", "9080-0500", "9080-0700", "9080-1000", "9080-1100", "9080-1400", "9080-1500", "9080-1750", "9080-2000", "9080-3000"], "값": "7620-0003"}}, + {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["7620-0003", "7621-0100", "7995-0050", "5210-0013", "5210-0027", "5220-0015", "7730-0150", "6516-0060", "6516-0120", "6516-0250", "6516-0300", "6105-0190", "6202-0060", "0230-0002", "6410-0080", "6410-0100", "6410-0120", "6410-0150", "6410-0200", "6901-0010", "6530-0090", "6530-0120", "6517-0100", "6517-0150", "6517-0200", "6517-0250", "6517-0300", "6510-0100", "6510-0150", "6510-0200", "6510-0250", "6510-0300", "6518-0100", "6518-0150", "6518-0200", "6518-0250", "6518-0300", "5501-0080", "7750-0016", "7750-0025", "7730-0050", "5401-0017", "5202-0127", "5202-0240", "5202-0300", "7611-0200", "7612-0200", "5204-0200", "5204-0300", "5204-0400", "5204-0500", "5204-0600", "5702-0095", "3430-0400", "7431-1300", "7740-0080", "7740-0150", "2101-0010", "2101-0015", "2101-0020", "2101-0025", "2101-0030", "2101-0035", "2101-0040", "2101-0050", "2101-0070", "2101-0080", "2101-0100", "2101-0150", "2101-0220", "2101-0280", "2101-0300", "2104-0010", "2104-0015", "2104-0020", "2104-0025", "2104-0030", "2104-0035", "2104-0040", "2104-0045", "2104-0050", "2104-0060", "2104-0070", "2104-0080", "2104-0100", "2104-0130", "2104-0160", "2104-0200", "2104-0220", "2104-0250", "2104-0300", "2105-0002", "2105-0003", "2105-0005", "2105-0010", "2105-0015", "2105-0018", "0201-0012", "0201-0020", "0201-0040", "0201-0060", "0201-0070", "0201-0080", "0201-0100", "0201-0120", "0201-0200", "6105-0390", "6202-0125", "7830-0081", "7950-0149", "7951-0066", "7952-0246", "7953-3400", "7954-1119", "7611-0300", "7431-1100", "7431-2300", "7431-2500", "7611-0400", "7611-0500", "7614-0300", "0250-0100", "5210-0010", "5210-0019", "0103-0016", "0103-0019", "0103-0023", "0103-0027", "0103-0032", "0230-0004", "0230-0006", "0230-0007", "0230-0008", "0230-0010", "0240-0007", "0250-0080", "0407-0054", "0407-0092", "0407-0107", "0407-0161", "0407-0206", "0610-0150", "0610-0200", "0610-0240", "2115-0024", "2115-0031", "2115-0036", "2116-0031", "2116-0036", "2117-0022", "2117-0027", "2118-0010", "2210-0145", "3430-0300", "4115-0100", "4115-0150", "4115-0200", "4115-0300", "4505-0015", "4505-0026", "4611-0075", "5111-0040", "5111-0050", "5111-0060", "5111-0076", "5111-0091", "5112-0001", "5112-0002", "5112-0003", "5112-0004", "5112-0005", "5113-0001", "5113-0002", "5113-0003", "5113-0004", "5113-0005", "5113-0006", "5113-0007", "5113-0008", "5113-0009", "5113-0010", "5113-0011", "5114-0001", "5114-0002", "5114-0003", "5114-0004", "5114-0005", "5114-0006", "5114-0007", "5114-0008", "5115-0030", "5115-0055", "5115-0075", "5115-0095", "5116-0001", "5116-0002", "5116-0003", "5116-0004", "5116-0005", "5116-0006", "5116-0007", "5116-0008", "5117-0001", "5117-0002", "5117-0003", "5117-0004", "5117-0005", "5117-0006", "5117-0007", "5117-0008", "5118-0001", "5118-0002", "5118-0003", "5118-0004", "5118-0005", "5118-0006", "5118-0007", "5203-1800", "5203-2200", "5203-2700", "5203-3500", "5203-4500", "5330-0074", "5901-0006", "5901-0010", "5901-0016", "6202-0200", "6408-0015", "6408-0020", "6408-0030", "6408-0040", "6408-0050", "6408-0085", "6408-0100", "6515-0090", "6530-0030", "6530-0040", "6530-0045", "6530-0060", "6532-0220", "6550-0130", "7103-0010", "7104-0010", "7206-0020", "7206-0070", "7206-0100", "7430-1100", "7430-1300", "7430-1500", "7430-2300", "7430-2500", "7612-0300", "7612-0400", "7613-0075", "7613-0150", "7613-0300", "7613-0400", "7613-0600", "7613-0900", "7620-0002", "7730-0080", "7730-0100", "7730-0125", "7740-0100", "7770-0004", "7930-0001", "7930-0002", "7930-0003", "7930-0005", "7930-0007", "7930-0010", "7930-0015", "7930-0020", "7930-0025", "7930-0030", "7930-0040", "7930-0050", "7930-0075", "7930-0100", "7935-0180", "8201-0100", "8202-0100", "8203-0019", "8204-0100", "8205-0100", "9060-0060", "9060-0100", "9060-0200", "9060-0300", "9060-0500", "9060-0600", "9080-0050", "9080-0080", "9080-0100", "9080-0120", "9080-0150", "9080-0200", "9080-0300", "9080-0500", "9080-0700", "9080-1000", "9080-1100", "9080-1400", "9080-1500", "9080-1750", "9080-2000", "9080-3000"], "값": "7620-0003"}}, {"열쇠": "손료", "기호": "C", "아래": "손료", "위": "", "이름": "손료", "단위": "", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4", "연결": {"갈래": "식"}}, {"열쇠": "P_기계", "기호": "P", "아래": "기계", "위": "", "이름": "EQ:{기계}.가격", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "가격"}}, {"열쇠": "k_기계손료", "기호": "k", "아래": "기계손료", "위": "", "이름": "EQ:{기계}.손료계수", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "손료계수"}} @@ -412,7 +412,7 @@ "갈래": "기계경비식" }, "변수": [ - {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["5401-0017", "4108-0060", "4108-0090", "4108-0120", "4108-0150", "4108-0180", "4108-0210", "5401-0015"], "값": "5401-0017"}}, + {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["5401-0017", "4108-0060", "4108-0090", "4108-0120", "4108-0150", "4108-0180", "4108-0210", "5401-0015", "2208-5008", "2208-5010", "2208-5012", "2208-5016", "2208-5020", "5105-0050", "5105-0100", "5105-0150", "5105-0200", "5119-0625", "7205-0125"], "값": "5401-0017"}}, {"열쇠": "지역", "기호": "S", "아래": "지역", "위": "", "이름": "지역", "단위": "", "출처": "건설품셈 공통 8-1-7 5", "연결": {"갈래": "선택", "목록": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], "값": "전국평균"}}, {"열쇠": "손료", "기호": "C", "아래": "손료", "위": "", "이름": "손료", "단위": "", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4", "연결": {"갈래": "식"}}, {"열쇠": "직종", "기호": "S", "아래": "직종", "위": "", "이름": "직종", "단위": "", "출처": "건설품셈 공통 8-1-3 5", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "조종원직종"}}, @@ -2213,6 +2213,145 @@ "비목": {"방법": "버림", "자리": 0}, "끝전": "비목버림" } + }, + { + "꼴": "수식문서", + "판": 1, + "키": "GC001196", + "이름": "기계 시간당 기계경비(전력 기종)", + "결과단위": "원/시간", + "출처": "건설품셈 공통 8-1-5 · 8-1-6 · 8-1-6 3 가", + "부품": { + "갈래": "기계경비식" + }, + "변수": [ + {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["7991-0050", "7202-0008", "7202-0010", "7991-0100", "7991-0500", "7992-0001", "7993-0020"], "값": "7991-0050"}}, + {"열쇠": "전력 계약종별", "기호": "S", "아래": "전력계약종별", "위": "", "이름": "전력 계약종별", "단위": "", "출처": "유가전력 한전 전기요금표 2025-01-01", "연결": {"갈래": "선택", "목록": ["일반용(갑)Ⅰ 저압", "주택용 저압"], "값": "일반용(갑)Ⅰ 저압"}}, + {"열쇠": "손료", "기호": "C", "아래": "손료", "위": "", "이름": "손료", "단위": "", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4", "연결": {"갈래": "식"}}, + {"열쇠": "P_기계", "기호": "P", "아래": "기계", "위": "", "이름": "EQ:{기계}.가격", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "가격", "계약종별": "S_전력계약종별"}}, + {"열쇠": "k_기계손료", "기호": "k", "아래": "기계손료", "위": "", "이름": "EQ:{기계}.손료계수", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "손료계수", "계약종별": "S_전력계약종별"}}, + {"열쇠": "P_기계전력", "기호": "P", "아래": "기계전력", "위": "", "이름": "EQ:{기계}.전력", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "전력", "계약종별": "S_전력계약종별"}}, + {"열쇠": "P_전력", "기호": "P", "아래": "전력", "위": "", "이름": "전력단가", "단위": "", "연결": {"갈래": "요소", "재료": {"이름": "전력량요금", "계약종별": "입력"}, "단위": "㎾h", "계약종별": "S_전력계약종별"}}, + {"열쇠": "r_기계잡재료", "기호": "r", "아래": "기계잡재료", "위": "", "이름": "EQ:{기계}.잡재료율", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "잡재료율", "계약종별": "S_전력계약종별"}} + ], + "식": [ + {"기호": "C_손료", "식": "버림(P_기계 × 1000 × k_기계손료 ÷ 10000000, 0)"} + ], + "금액": [ + {"이름": "전력", "단위": "㎾h", "비목": "재료비", "수량": "P_기계전력", "단가": "P_전력", "출처": "건설품셈 공통 8-1-6 3 가"}, + {"이름": "잡재료비", "단위": "식", "비목": "재료비", "단가": "재료비 × r_기계잡재료 ÷ 100", "출처": "건설품셈 공통 8-4 주②"}, + {"이름": "기계손료", "단위": "식", "비목": "경비", "단가": "C_손료", "출처": "건설품셈 공통 8-1-6 2"} + ], + "끝수": { + "줄": {"방법": "버림", "자리": 1}, + "비목": {"방법": "버림", "자리": 0}, + "끝전": "비목버림" + } + }, + { + "꼴": "수식문서", + "판": 1, + "키": "GC001197", + "이름": "기계 시간당 기계경비(전력 · 조종원 기종)", + "결과단위": "원/시간", + "출처": "건설품셈 공통 8-1-5 · 8-1-6 · 8-1-6 3 가", + "부품": { + "갈래": "기계경비식" + }, + "변수": [ + {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["5805-0002", "5805-0003"], "값": "5805-0002"}}, + {"열쇠": "전력 계약종별", "기호": "S", "아래": "전력계약종별", "위": "", "이름": "전력 계약종별", "단위": "", "출처": "유가전력 한전 전기요금표 2025-01-01", "연결": {"갈래": "선택", "목록": ["일반용(갑)Ⅰ 저압", "주택용 저압"], "값": "일반용(갑)Ⅰ 저압"}}, + {"열쇠": "손료", "기호": "C", "아래": "손료", "위": "", "이름": "손료", "단위": "", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4", "연결": {"갈래": "식"}}, + {"열쇠": "직종", "기호": "S", "아래": "직종", "위": "", "이름": "직종", "단위": "", "출처": "건설품셈 공통 8-1-3 5", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "조종원직종", "계약종별": "S_전력계약종별"}}, + {"열쇠": "시간환산", "기호": "Q", "아래": "시간환산", "위": "", "이름": "시간환산", "단위": "", "출처": "건설품셈 공통 1-1-3 2(일일 작업시간 8시간)", "연결": {"갈래": "식"}}, + {"열쇠": "P_기계", "기호": "P", "아래": "기계", "위": "", "이름": "EQ:{기계}.가격", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "가격", "계약종별": "S_전력계약종별"}}, + {"열쇠": "k_기계손료", "기호": "k", "아래": "기계손료", "위": "", "이름": "EQ:{기계}.손료계수", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "손료계수", "계약종별": "S_전력계약종별"}}, + {"열쇠": "Q_분자^표", "기호": "Q", "아래": "분자", "위": "표", "이름": "건설기계 조종원 노임 시간 환산 계수.분자", "단위": "", "연결": {"갈래": "표", "표": "CC000336", "조건": {"구분": "'시간환산'"}, "칸": "분자"}}, + {"열쇠": "Q_분모^표", "기호": "Q", "아래": "분모", "위": "표", "이름": "건설기계 조종원 노임 시간 환산 계수.분모", "단위": "", "연결": {"갈래": "표", "표": "CC000336", "조건": {"구분": "'시간환산'"}, "칸": "분모"}}, + {"열쇠": "P_기계전력", "기호": "P", "아래": "기계전력", "위": "", "이름": "EQ:{기계}.전력", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "전력", "계약종별": "S_전력계약종별"}}, + {"열쇠": "P_전력", "기호": "P", "아래": "전력", "위": "", "이름": "전력단가", "단위": "", "연결": {"갈래": "요소", "재료": {"이름": "전력량요금", "계약종별": "입력"}, "단위": "㎾h", "계약종별": "S_전력계약종별"}}, + {"열쇠": "Q_기계조종원", "기호": "Q", "아래": "기계조종원", "위": "", "이름": "EQ:{기계}.조종원", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "조종원", "계약종별": "S_전력계약종별"}}, + {"열쇠": "w_기계운전사", "기호": "w", "아래": "기계운전사", "위": "", "이름": "건설기계운전사노임", "단위": "", "연결": {"갈래": "요소", "키": "LB:{S_직종}", "계약종별": "S_전력계약종별"}}, + {"열쇠": "r_기계잡재료", "기호": "r", "아래": "기계잡재료", "위": "", "이름": "EQ:{기계}.잡재료율", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "잡재료율", "계약종별": "S_전력계약종별"}} + ], + "식": [ + {"기호": "C_손료", "식": "버림(P_기계 × 1000 × k_기계손료 ÷ 10000000, 0)"}, + {"기호": "Q_시간환산", "식": "Q_분자^표 ÷ Q_분모^표"} + ], + "금액": [ + {"이름": "전력", "단위": "㎾h", "비목": "재료비", "수량": "P_기계전력", "단가": "P_전력", "출처": "건설품셈 공통 8-1-6 3 가"}, + {"이름": "조종원", "단위": "인", "비목": "노무비", "수량": "Q_기계조종원 × Q_시간환산", "단가": "w_기계운전사", "출처": "건설품셈 공통 8-1-3 6"}, + {"이름": "잡재료비", "단위": "식", "비목": "재료비", "단가": "재료비 × r_기계잡재료 ÷ 100", "출처": "건설품셈 공통 8-4 주②"}, + {"이름": "기계손료", "단위": "식", "비목": "경비", "단가": "C_손료", "출처": "건설품셈 공통 8-1-6 2"} + ], + "끝수": { + "줄": {"방법": "버림", "자리": 1}, + "비목": {"방법": "버림", "자리": 0}, + "끝전": "비목버림" + } + }, + { + "꼴": "수식문서", + "판": 1, + "키": "GC001198", + "이름": "기계 시간당 기계경비(조종원 없는 기종 · 손료계수 한 칸)", + "결과단위": "원/시간", + "출처": "건설품셈 공통 8-1-5 · 8-1-6", + "부품": { + "갈래": "기계경비식" + }, + "변수": [ + {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": ["7994-0050"], "값": "7994-0050"}}, + {"열쇠": "지역", "기호": "S", "아래": "지역", "위": "", "이름": "지역", "단위": "", "출처": "건설품셈 공통 8-1-7 5", "연결": {"갈래": "선택", "목록": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], "값": "전국평균"}}, + {"열쇠": "손료", "기호": "C", "아래": "손료", "위": "", "이름": "손료", "단위": "", "출처": "건설품셈 공통 8-1-5 12 · 8-1-6 4", "연결": {"갈래": "식"}}, + {"열쇠": "연료", "기호": "S", "아래": "연료", "위": "", "이름": "연료", "단위": "", "출처": "건설품셈 공통 8-4 주①㉰", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "주연료종류"}}, + {"열쇠": "P_기계", "기호": "P", "아래": "기계", "위": "", "이름": "EQ:{기계}.가격", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "가격"}}, + {"열쇠": "k_기계손료", "기호": "k", "아래": "기계손료", "위": "", "이름": "EQ:{기계}.손료계수", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "손료계수"}}, + {"열쇠": "P_기계주연료", "기호": "P", "아래": "기계주연료", "위": "", "이름": "EQ:{기계}.주연료", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "주연료"}}, + {"열쇠": "P_휘발유", "기호": "P", "아래": "휘발유", "위": "", "이름": "휘발유단가", "단위": "", "연결": {"갈래": "요소", "키": "MO:{S_연료}:{S_지역}"}}, + {"열쇠": "r_기계잡재료", "기호": "r", "아래": "기계잡재료", "위": "", "이름": "EQ:{기계}.잡재료율", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "잡재료율"}} + ], + "식": [ + {"기호": "C_손료", "식": "버림(P_기계 × 1000 × k_기계손료 ÷ 10000000, 0)"} + ], + "금액": [ + {"이름": "주연료", "단위": "ℓ", "비목": "재료비", "수량": "P_기계주연료", "단가": "P_휘발유", "출처": "건설품셈 공통 8-4-8"}, + {"이름": "잡재료비", "단위": "식", "비목": "재료비", "단가": "재료비 × r_기계잡재료 ÷ 100", "출처": "건설품셈 공통 8-4 주②"}, + {"이름": "기계손료", "단위": "식", "비목": "경비", "단가": "C_손료", "출처": "건설품셈 공통 8-1-6 2"} + ], + "끝수": { + "줄": {"방법": "버림", "자리": 1}, + "비목": {"방법": "버림", "자리": 0}, + "끝전": "비목버림" + } + }, + { + "꼴": "수식문서", + "판": 1, + "키": "GC001199", + "이름": "기계 시간당 기계경비(직접 값)", + "결과단위": "원/시간", + "출처": "자체", + "부품": { + "갈래": "기계경비식" + }, + "변수": [ + {"열쇠": "기계", "기호": "S", "아래": "기계", "위": "", "이름": "기계", "단위": "", "연결": {"갈래": "선택", "목록": [], "값": 0}}, + {"열쇠": "P_EQ기계", "기호": "P", "아래": "EQ기계", "위": "", "이름": "EQ:{기계}", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "노무비"}}, + {"열쇠": "P_EQ기계2", "기호": "P", "아래": "EQ기계2", "위": "", "이름": "EQ:{기계}", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "재료비"}}, + {"열쇠": "P_EQ기계3", "기호": "P", "아래": "EQ기계3", "위": "", "이름": "EQ:{기계}", "단위": "", "연결": {"갈래": "요소", "키": "EQ:{S_기계}", "칸": "경비"}} + ], + "식": [], + "금액": [ + {"이름": "시간당 노무비", "단위": "식", "비목": "노무비", "단가": "P_EQ기계", "출처": "자체"}, + {"이름": "시간당 재료비", "단위": "식", "비목": "재료비", "단가": "P_EQ기계2", "출처": "자체"}, + {"이름": "시간당 경비", "단위": "식", "비목": "경비", "단가": "P_EQ기계3", "출처": "자체"} + ], + "끝수": { + "줄": {"방법": "버림", "자리": 1}, + "비목": {"방법": "버림", "자리": 0}, + "끝전": "비목버림" + } } ] } From 062fb07668244468239b58dd97b96b292d463247 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 18:12:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(822-2):=20=EC=8B=9D=20=EA=B3=A0?= =?UTF-8?q?=EB=A6=84=20=ED=95=9C=20=EA=B3=B3(=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=E2=80=BA=20=EA=BC=B4)=20=C2=B7=20=EA=B9=8C=EB=8B=AD=20?= =?UTF-8?q?=EA=B8=80=20=C2=B7=20=EC=83=88=20=EA=B8=B0=EA=B3=84=20=EC=8B=9D?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=C2=B7=20=EC=93=B0=EB=8A=94=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=EC=8B=9D=20=C2=B7=20=EC=A7=81=EC=A0=91=20=EA=B0=92=20?= =?UTF-8?q?=C2=B7=20=EC=A0=84=EB=A0=A5=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - M02_Machine_Hourly.pick = 목록 › 그 기계 식 › 꼴(내 기계와 같은 규칙) · why = 값 모자란 까닭(가격 없음 · 범위 · 일 단위 손료 …) - 데이터 흐름 · 사본 모으기 · M01 · 불러오기 · 줄 쌓기 모두 pick 을 씀(데이터 흐름 옛 짝 고름 지움) - 내 기계 꼴: 전력 · 전력 + 조종원 · 직종 입력 · 손료계수 한 칸 · 직접(GC001199 — 줄 쌓기 회색 풀림) · 연료 + 전력 = 400 - M01 [새 기계] 저장 = 꼴 식 「기계」 목록에 등록 · M01 에서 만든 기계 지우면 목록에서 뺌 · 상세 「쓰는 계산식」(POST /machine/formula) · 목록 시간당 「—」 대신 까닭 글 - 미리보기 짝: 잡품 = (주연료 + 전력) × %(전력 기종 = 전력비의 %) - 시험 test_822_2 새로 · test_804_2 갈래에 전력 줄 더함(손료만 식과 전력 식은 다른 갈래 — 모르타르 믹서) Co-Authored-By: Claude Opus 5.5 --- M01_MasterData/M01_MasterData_Machine_Api.ts | 20 ++ M01_MasterData/M01_MasterData_Router.py | 6 + M01_MasterData/M01_MasterData_Store_Make.py | 1 + M01_MasterData/M01_MasterData_UI_Machine.ts | 3 +- .../M01_MasterData_UI_MachineDetail.ts | 28 +- M02_MasterTemplete/DataFlow/M02_DF_Values.py | 45 +--- M02_MasterTemplete/M02_Item_Prices.py | 9 +- M02_MasterTemplete/M02_Machine_Hourly.py | 87 +++++- .../M02_MasterTemplete_Find_Pick.ts | 4 +- M02_MasterTemplete/M02_MyMachine.py | 56 +++- M02_MasterTemplete/M02_MyMachine_Text.ts | 2 +- common_util/common_util_machine_coef.ts | 4 +- .../scripts/master_machine_coef.py | 5 +- resources/tester/test_804_2_line_stack.py | 4 +- .../tester/test_822_2_machine_hourly_all.py | 252 ++++++++++++++++++ 15 files changed, 458 insertions(+), 68 deletions(-) create mode 100644 resources/tester/test_822_2_machine_hourly_all.py diff --git a/M01_MasterData/M01_MasterData_Machine_Api.ts b/M01_MasterData/M01_MasterData_Machine_Api.ts index 6598fec05..8270f5854 100644 --- a/M01_MasterData/M01_MasterData_Machine_Api.ts +++ b/M01_MasterData/M01_MasterData_Machine_Api.ts @@ -53,5 +53,25 @@ export const fetchMachineUses = async (key: string): Promise => export const findMachines = async (q: string): Promise => (await get<{ rows: Row[] }>("/find", { q })).rows; +/** 쓰는 계산식(822-2) — 방법 = 목록 · 꼴 · 그 기계 식 · 까닭 = 값이 모자란 칸 */ +export interface MachineFormula { + 키?: string; + 이름?: string; + 방법: "목록" | "꼴" | "그 기계 식"; + 까닭: string; +} + +export async function fetchMachineFormula(row: Row): Promise { + const res = await fetch(`${API_BASE_URL}/m01/machine/formula`, { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(row), + }); + const body = (await res.json()) as MachineFormula & { detail?: unknown }; + if (!res.ok) throw new Error(String(body.detail ?? `HTTP ${res.status}`)); + return body; +} + export const fetchMachineRow = (key: string): Promise<{ version: string; row: Row }> => get("/row", { key }); diff --git a/M01_MasterData/M01_MasterData_Router.py b/M01_MasterData/M01_MasterData_Router.py index c9398209d..cc47eac9a 100644 --- a/M01_MasterData/M01_MasterData_Router.py +++ b/M01_MasterData/M01_MasterData_Router.py @@ -288,6 +288,12 @@ def get_machine_row(key: str) -> dict: return _call(machine.row, key) +@router.post("/machine/formula") +def post_machine_formula(row: dict[str, Any]) -> dict: + """기계 줄(저장 전 고친 값도) → 쓰는 계산식 {키 · 이름 · 방법 · 까닭}(822-2).""" + return _call(machine.formula, row) + + @router.post("/calc") def post_calc(body: CalcBody) -> dict: with store.cm.mf.mp.choosing(body.고름): diff --git a/M01_MasterData/M01_MasterData_Store_Make.py b/M01_MasterData/M01_MasterData_Store_Make.py index 291b7b78c..944658852 100644 --- a/M01_MasterData/M01_MasterData_Store_Make.py +++ b/M01_MasterData/M01_MasterData_Store_Make.py @@ -294,6 +294,7 @@ def save(batch: list[dict], confirmed: bool = False) -> dict: row.update(별칭=text) if text else row.pop("별칭") machine.fix(part) # 기계 줄 = 계수 서버 재계산 · 단위 · 출처 채움(821-2) _keep_same(part) + machine.register(batch) # [새 기계] = 꼴 식 목록 등록(822-2 · 개정확인 밖 — 자동 고침) fresh = False for part in batch: if part.get("file") != OWN: diff --git a/M01_MasterData/M01_MasterData_UI_Machine.ts b/M01_MasterData/M01_MasterData_UI_Machine.ts index 5e0e3f6b8..14c218259 100644 --- a/M01_MasterData/M01_MasterData_UI_Machine.ts +++ b/M01_MasterData/M01_MasterData_UI_Machine.ts @@ -132,7 +132,8 @@ export function renderMachines( if (!want.length) return; try { const got = await fetchMachineHourly(want); - for (const [k, h] of Object.entries(got)) hourly.set(k, h.계 === undefined ? "—" : num(h.계)); + for (const [k, h] of Object.entries(got)) + hourly.set(k, h.계 === undefined ? (h.reason ?? "—") : num(h.계)); } catch { for (const k of want) hourly.set(k, "—"); } diff --git a/M01_MasterData/M01_MasterData_UI_MachineDetail.ts b/M01_MasterData/M01_MasterData_UI_MachineDetail.ts index d1cad8a5f..2ab3d096d 100644 --- a/M01_MasterData/M01_MasterData_UI_MachineDetail.ts +++ b/M01_MasterData/M01_MasterData_UI_MachineDetail.ts @@ -1,7 +1,7 @@ /* ============================================================================= * M01_MasterData_UI_MachineDetail.ts * M01 기계 상세 페이지(PLAN 821-2 · 요구 790-2 · 791) — 기계 양식 부품 + 원문번호 · 원문 · 구분 · - * 상세구분 · 별칭 · 비고 · 쓰는 곳 · 지우기(쓰는 곳 보이고 확인). + * 상세구분 · 별칭 · 비고 · 쓰는 계산식(822-2 · 서버 식 고름) · 쓰는 곳 · 지우기(쓰는 곳 보이고 확인). * 고침은 초안(sessionStorage)에 바로 쌓임 → 페이지 [저장] 한 번에 서버로(자동저장 없음) · * 서버가 계수를 계수 짝으로 다시 셈 · 단위 · 출처 칸을 채움(`M01_MasterData_Machine.fix`). * 대상 = 키(EQ…) · 저장 전 새 줄 `add:<차례>` · 새 기계 `new`(비슷한 기계에서 시작이 펼쳐짐). @@ -22,6 +22,7 @@ import { setLeaveGuard } from "../A00_Common/router"; import type { Row } from "./M01_MasterData_Api_Fetch"; import { addRow, editRow, peek, removeAdd, setAdd, toggleDelete } from "./M01_MasterData_Draft"; import { + fetchMachineFormula, fetchMachineLists, fetchMachineRow, fetchMachineUses, @@ -154,6 +155,29 @@ async function build(host: HTMLElement, opts: MachineDetailOptions, alive: () => } cur = ordered; checkSame(s.name, s.spec); + paintFormula(); + }; + + /* --- 쓰는 계산식(서버 식 고름 · 고친 값으로) --- */ + const formulaBox = el("section", { className: "m01-machine-detail__uses" }); + let formulaTimer: number | undefined; + const paintFormula = (): void => { + window.clearTimeout(formulaTimer); + formulaTimer = window.setTimeout(async () => { + const got = await fetchMachineFormula(cur).catch(() => null); + if (!got || !alive()) return; + const how = + got.방법 === "목록" + ? L("M01_MachineFormulaListed") + : got.방법 === "그 기계 식" + ? L("M01_MachineFormulaOwn") + : L("M01_MachineFormulaShape"); + formulaBox.replaceChildren( + el("strong", { text: L("M01_MachineFormula") }), + createText(got.키 ? `${got.키} ${got.이름 ?? ""} · ${how}` : L("M01_MachineFormulaNone")), + ...(got.까닭 ? [createText(got.까닭, "warning")] : []), + ); + }, 300); }; /* --- 같은 이름 · 규격 알림 --- */ @@ -275,10 +299,12 @@ async function build(host: HTMLElement, opts: MachineDetailOptions, alive: () => notice, metaBox, form.root, + formulaBox, usesBox, ], }), ); + paintFormula(); if (opts.target === "new") host.querySelector("details")?.setAttribute("open", ""); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Values.py b/M02_MasterTemplete/DataFlow/M02_DF_Values.py index 060bad891..2d0224d59 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Values.py +++ b/M02_MasterTemplete/DataFlow/M02_DF_Values.py @@ -258,59 +258,32 @@ def _user(uid: str, local: list[dict]) -> dict: MACHINE_SEP = "#" # 입력 판(sub3) 기계 요소 ref 「EQ키#칸」 HOURLY = "시간당 경비" # 기계 칸 이름 — 기계 시간당 경비 로직으로 셈(기종 → 로직 짝) -_MACHINE_PLAIN = {"기계", "지역", "보정작업", "조종원직종"} # 이 밖의 입력 = 부착 장비 전용 - - -def _machine_family() -> list[dict]: - """기계 시간당 경비 로직 — 「건설기계」 장 파일 안 · 입력 「기계」 고르기가 있는 돈 로직.""" - files = store.loaded()[0] - return [ - row - for name, data in files.items() - if data.get("그룹") == "로직" and "건설기계" in name - for row in data.get("줄") or [] - if str(row.get("결과단위") or "").startswith("원") - and any(i.get("이름") == "기계" and i.get("고르기") for i in row.get("입력") or []) - ] - - -def _machine_rank(row: dict) -> tuple: - """짝 차례(옛 EasyCalc `pickMachine` 와 같음) — 부착 전용 · 손료만 · 보정 · 기종 수 · 키.""" - names = [i.get("이름") for i in row.get("입력") or []] - codes = next(i.get("고르기") for i in row["입력"] if i.get("이름") == "기계") - attach = any(n not in _MACHINE_PLAIN for n in names) - loss = "지역" not in names - return (attach, loss, loss and "보정작업" in names, len(codes), str(row.get("키"))) def _machine_hourly(key: str, inputs: dict) -> dict: - """기계 한 시간 값 — 기종을 받는 기계경비 로직 가운데 짝 차례 첫째 · 빈 입력은 첫 고르기.""" + """기계 한 시간 값 — 식 고름 = `M02_Machine_Hourly.pick`(목록 › 꼴 · 822-2) · 빈 입력은 첫 고르기.""" + from M02_MasterTemplete import M02_Machine_Hourly as mh + row, why = _brief(key, "기계") if row is None: return {"ok": False, "reason": why} code = str(row.get("원문번호") or "") - fit = [r for r in _machine_family() if code in [str(c) for c in _codes(r)]] - if not fit: - return {"ok": False, "reason": f"「{row.get('이름')}」 은 시간당 경비를 셀 자료가 없음"} - logic = min(fit, key=_machine_rank) + logic = mh.pick(row) + if lack := mh.why(row, logic): + return {"ok": False, "reason": f"「{row.get('이름')}」 — {lack}"} given = {} for spec in logic.get("입력") or []: name = spec.get("이름") if name != "기계": given[name] = inputs.get(name, (spec.get("고르기") or [None])[0]) - args = "".join(f", {n}='{v}'" for n, v in given.items()) - ref = f"로직({logic['키']}, 기계='{code}'{args})" + args = "".join(f", {n if n.isidentifier() else repr(n)}='{v}'" for n, v in given.items()) + mine = f", 기계='{code}'" if mh.takes_machine(logic) else "" + ref = f"로직({logic['키']}{mine}{args})" line = {"종류": "로직", "요소": ref, "이름": "기계", "단위": "시간", "수량": "1"} got = _money(_calc({}, _row(line, ())), "계") return {**got, "name": row.get("이름"), "unit": "원/시간"} if got["ok"] else got -def _codes(row: dict) -> list: - return next( - (i.get("고르기") or [] for i in row.get("입력") or [] if i.get("이름") == "기계"), [] - ) - - def one_value(item: dict, local: list[dict] | None = None) -> dict: """물음 한 줄 `CfQuery {id, group, ref? · table? · unit? · field? · inputs?}` → `CfAnswer`. diff --git a/M02_MasterTemplete/M02_Item_Prices.py b/M02_MasterTemplete/M02_Item_Prices.py index 287b0e2fc..1c3c33176 100644 --- a/M02_MasterTemplete/M02_Item_Prices.py +++ b/M02_MasterTemplete/M02_Item_Prices.py @@ -186,10 +186,11 @@ class _Collector: return self.add(base) if field == dv.HOURLY: - code = str((self.whole.index.get(base[:2], {}).get(base) or {}).get("원문번호") or "") - for fit in dv._machine_family(): - if code in [str(c) for c in dv._codes(fit)]: - self.logic(str(fit.get("키"))) + from M02_MasterTemplete import M02_Machine_Hourly as mh + + row = self.whole.index.get(base[:2], {}).get(base) or {} + if got := mh.pick(row): # 식 고름 한 곳(822-2) + self.logic(str(got.get("키"))) def logic(self, key: str) -> None: if key in self.logics: diff --git a/M02_MasterTemplete/M02_Machine_Hourly.py b/M02_MasterTemplete/M02_Machine_Hourly.py index 03aa70c0b..0e4fb4952 100644 --- a/M02_MasterTemplete/M02_Machine_Hourly.py +++ b/M02_MasterTemplete/M02_Machine_Hourly.py @@ -7,17 +7,30 @@ 마스터 파일은 읽기만 · 로그인 사용자 모두. 기계표 = M01 + 지금 층 내 기계(821-1 · `M02_MyMachine` — 식은 채운 칸으로 고른 같은 GC 식 · 직접 = 그 값) · 층을 모르는 길(`/api/m02/logic-lib/machine-hourly`)은 M01 만. +식 고름 한 곳(822-2 · `pick`) = ㉠ 원문번호가 식 「기계」 목록에 있으면 그 식(짝 차례) ㉡ 그 기계만의 식 +(`_own`) ㉢ 없으면 꼴(채운 칸 · `M02_MyMachine.logic_key`) — M01 [새 기계] · 불러오기 · 데이터 흐름 · +줄 쌓기 · 단가산출 기초가 다 이것을 씀 · 못 고름 · 값 모자람 = 까닭 글(`why`). """ from __future__ import annotations +import re from typing import Any from M01_MasterData import M01_MasterData_Store as store from M02_MasterTemplete import M02_MyMachine as mym cm = store.cm -_PLAIN = {"기계", "지역", "보정작업", "조종원직종"} # 이 밖의 입력 = 부착 장비 전용 +_PLAIN = { + "기계", + "지역", + "보정작업", + "조종원직종", + "전력 계약종별", +} # 이 밖의 입력 = 부착 장비 전용 +_TEXT = {"주연료종류", "조종원직종"} # 글 칸 — 나머지 기계 줄 값 칸은 수 +_FIELD = re.compile(r"EQ:\{기계\}\.([^\s*/+()\-,]+)") +HOUR_UNIT = "10^-7/시간" def _family() -> list[dict]: @@ -54,12 +67,27 @@ def _operated(code: str) -> bool: def logic_of(code: str) -> dict | None: - """기계 원문번호 → 그 기계 시간당 경비 로직(짝 차례 첫째) · 없으면 None(813-2 M01 화면 · - 불러오기 `M02_Doc_Import.hourly_link` 도 씀). 기계표 조종원 > 0 = 조종원 호표 있는 식 먼저 - (「조종원 없는 기종」 목록에 겹친 기계 — 진동롤러(핸드가이드식) · 고소작업차 …).""" - fit = [r for r in _family() if code in [str(c) for c in _codes(r)]] + """기계 원문번호 → 그 기계 시간당 경비 로직 · 없으면 None(813-2 M01 화면 · 불러오기 + `M02_Doc_Import.hourly_link` 도 씀) — `pick` 과 같음(그 원문번호 기계 줄로).""" + index = mym.whole() + row = (index.numbers.get("EQ") or {}).get(code) or {"원문번호": code} + return pick(row) + + +def listed(code: str) -> bool: + """원문번호가 어느 기계경비식 「기계」 목록에 있는지(M01 [새 기계] 식 등록 · 상세 「쓰는 계산식」).""" + return bool(code) and any(code in [str(c) for c in _codes(r)] for r in _family()) + + +def pick(row: dict) -> dict | None: + """기계 줄 → 시간당 경비 식 — ㉠ 목록 ㉡ 그 기계만의 식 ㉢ 꼴 · 못 고름 = None(까닭 = `why`).""" + code = str(row.get("원문번호") or "") + fit = [r for r in _family() if code and code in [str(c) for c in _codes(r)]] if not fit: - return _own(code) or mym.logic_for(code) + if got := _own(code): + return got + key = None if _day_loss(row) else mym.logic_key(row) + return mym.whole().logic(key) if key else None op = _operated(code) def order(row: dict) -> tuple: @@ -84,6 +112,47 @@ def _own(code: str) -> dict | None: return None +def _day_loss(row: dict) -> str: + """손료계수 단위가 시간당(10⁻⁷)이 아님(산림품셈 「1일 1대당」) — 그 단위 글 · 아니면 빈 글.""" + unit = str((row.get("단위") or {}).get("손료계수") or HOUR_UNIT) + return "" if unit == HOUR_UNIT or "손료계수" not in (row.get("값") or {}) else unit + + +def _is_num(v: Any) -> bool: + try: + float(v) + except (TypeError, ValueError): + return False + return not isinstance(v, bool) + + +def why(row: dict, logic: dict | None) -> str: + """시간당 경비를 못 내는 까닭 한 줄(값이 정말 모자람) · 셀 수 있으면 빈 글.""" + vals = row.get("값") or {} + if logic is None: + if unit := _day_loss(row): + return f"손료계수 단위 「{unit}」 — 시간당 식 없음(일 단위 손료)" + if not _is_num(vals.get("주연료")) and vals.get("주연료") not in (None, ""): + return f"주연료 「{vals['주연료']}」 — 수 아님" + if float(vals.get("조종원") or 0) > 0 and not vals.get("조종원직종"): + return "조종원직종 없음" + if vals.get("주연료") and vals.get("전력"): + return "주연료 · 전력 둘 다 — 식 없음" + return "손료 계수 셋(상각 · 정비 · 관리) 없음" + text = " ".join(str(x.get(k)) for k in ("식", "수량") for p in ("중간", "호표", "덧줄") + for x in logic.get(p) or []) # fmt: skip + lost, odd = [], [] + for name in dict.fromkeys(_FIELD.findall(text)): + v = vals.get(name) + if v in (None, ""): + lost.append(name) + elif name not in _TEXT and not _is_num(v): + odd.append(f"{name} 「{v}」 — {'범위' if '∼' in str(v) else '수 아님'}") + elif name == "주연료종류" and f"{v}:전국평균" not in (mym.whole().numbers.get("MO") or {}): + odd.append(f"주연료종류 「{v}」 — 유가 표에 없음") + return " · ".join([*([f"{' · '.join(lost)} 없음"] if lost else []), *odd]) + + def takes_machine(logic: dict) -> bool: """식이 「기계」 입력(원문번호 고르기)을 받는지 — 안 받는 식(GF000145)엔 기계='…' 를 안 넘김.""" return any(i.get("이름") == "기계" for i in logic.get("입력") or []) @@ -100,9 +169,9 @@ def hourly(key: str) -> dict[str, Any]: if mym.mine(key): return mym.hourly(row, whole) code = str(row.get("원문번호") or "") - logic = logic_of(code) - if not logic: - return {"ok": False, "reason": f"「{row.get('이름')}」 은 시간당 경비를 셀 자료가 없음"} + logic = pick(row) + if lack := why(row, logic): + return {"ok": False, "reason": lack, **({"로직": logic["키"]} if logic else {})} args = "".join( # 빈칸 든 입력 이름(「전력 계약종별」 · 820-1 파워트로웰)은 따옴표로 f", {n if (n := i['이름']).isidentifier() else repr(n)}='{(i.get('고르기') or [''])[0]}'" for i in logic.get("입력") or [] diff --git a/M02_MasterTemplete/M02_MasterTemplete_Find_Pick.ts b/M02_MasterTemplete/M02_MasterTemplete_Find_Pick.ts index b1d684bd6..191987444 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Find_Pick.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Find_Pick.ts @@ -18,7 +18,7 @@ * · (785-3) `oil` = M01 유가전력(MO · `/logic-lib/elements?group=재료` 의 MO 줄) 고름 · 고정 줄 꼴 = 이음 유가 MO · * 재료비 = 지금 값(셈은 M01 값) · 「M01 전체」 탭 재료 아래 * · (821-3) `mine` = 개인 층 내 기계 묶음(품셈 기계 위 · 구분 「내 기계」 · 출처 「개인 키」 · 금액 = 서버 시간당) · - * 머리 [새 기계 만들기](양식 창 → 저장하면 그 기계로 고름) · 「시간당 값 직접」 내 기계 = 줄 쌓기 식 없음 → 회색 + * 머리 [새 기계 만들기](양식 창 → 저장하면 그 기계로 고름) · 「시간당 값 직접」 내 기계 = 직접 값 식 GC001199(822-2) · 식 없는 내 기계 = 회색 * ========================================================================== */ import { createDataTable } from "@ui/ui_template_data_table"; @@ -288,7 +288,7 @@ export function openFindPicker(opt: FindPickOptions): void { : take(() => put(e)), }; }); - /** 821-3 내 기계 줄 — 금액 = 서버 시간당 · 직접 = 회색(줄 쌓기 식 없음) */ + /** 821-3 내 기계 줄 — 금액 = 서버 시간당 · 식 없음(서버 로직 없음) = 회색 · 직접 = GC001199(822-2) */ const mineLines = (q: string): Line[] => { const words = q.toLowerCase().split(/\s+/).filter(Boolean); return mines diff --git a/M02_MasterTemplete/M02_MyMachine.py b/M02_MasterTemplete/M02_MyMachine.py index d7ec59cac..af0326b21 100644 --- a/M02_MasterTemplete/M02_MyMachine.py +++ b/M02_MasterTemplete/M02_MyMachine.py @@ -10,8 +10,9 @@ 내 기계 없는 층 = M01 그대로. 단가산출 사본 · [단가 갱신] · 「단가새값」(`M02_Sangeun_Copy`) · 기계 찾기(`M02_Machine_Find`) · 시간당(`M02_Machine_Hourly`) · 사본 모으기(`M02_Item_Prices.collect`)가 이 색인을 읽음 — 층을 모르는 길(`/api/m02/logic-lib/machine-hourly` 등)은 M01 만. -식 고름(채운 칸) = 연료 · 조종원 둘 다 GC000268 · 연료만 GC000272 · 조종원만 GC000276 · 없음 GC000273. -전력 칸은 담기만(M01 기계경비 식에 전력 줄 없음). +식 고름(채운 칸 · `logic_key` — M01 기계 목록 밖 줄도 같은 규칙 · 822-2) = 연료 + 조종원 GC000268(직종 없음 +GC000275) · 연료만 GC000272(손료계수 한 칸 GC001198) · 조종원만 GC000276 · 전력 GC001196(+ 조종원 GC001197) · +없음 GC000273 · 직접 GC001199(시간당 값 = 셈 꼴 값 칸 노무비 · 재료비 · 경비). 권한(본인 개인 층 · 맡은 회사 관리자)은 길(`M02_MyMachine_Router`) 몫. """ @@ -43,6 +44,8 @@ UNITS = { } # fmt: skip LOGIC = {(True, True): "GC000268", (True, False): "GC000272", (False, True): "GC000276", (False, False): "GC000273"} # fmt: skip +JOB_IN, ONE_FUEL = "GC000275", "GC001198" # 직종 입력 · 손료계수 한 칸 연료 +POWER, POWER_CREW, DIRECT_KEY = "GC001196", "GC001197", "GC001199" def mine(key: Any) -> bool: @@ -80,6 +83,8 @@ def rows(folder: Path | None = None) -> list[dict]: def engine(row: dict) -> dict: """셈 꼴 — 바로 = 상각비계수 ← 손료계수 · 정비 · 관리 0 · 빈 연료 · 조종원 칸 = 0 · 수 = Decimal.""" vals = dict(row.get("값") or {}) + if row.get("방식") == DIRECT: # 직접 식(GC001199)이 읽는 칸 + vals = {k: (row.get("시간당") or {}).get(k) or 0 for k in COSTS} if row.get("방식") == EASY: vals |= {"상각비계수": vals.get("손료계수") or 0, "정비비계수": 0, "관리비계수": 0} if row.get("방식") != DIRECT: @@ -123,18 +128,39 @@ def keys_in(doc: Any) -> list[str]: return sorted(set(FIND.findall(json.dumps(doc, ensure_ascii=False, default=str)))) -def logic_key(row: dict) -> str: +def _pos(v: Any) -> bool: + try: + return not isinstance(v, bool) and float(v) > 0 + except (TypeError, ValueError): + return False + + +def logic_key(row: dict) -> str | None: + """꼴(채운 칸) → 시간당 경비 식 키 · 못 고름(직종 없는 조종원만 · 연료 + 전력 · 수 아닌 연료 글) = None · + 계수 셋(상각 · 정비 · 관리) 없음 = 손료계수 한 칸 식(연료만) · 그 밖은 그 식 + 모자란 칸 까닭.""" + if row.get("방식") == DIRECT: + return DIRECT_KEY vals = row.get("값") or {} - return LOGIC[(float(vals.get("주연료") or 0) > 0, float(vals.get("조종원") or 0) > 0)] + fuel, crew, power = (_pos(vals.get(k)) for k in ("주연료", "조종원", "전력")) + job = bool(vals.get("조종원직종")) + three = all(vals.get(k) is not None for k in COEFS) + if vals.get("주연료") not in (None, "", 0) and not fuel or fuel and power: + return None + if power: + return POWER_CREW if crew and job else None if crew else POWER + if fuel and not crew: + return LOGIC[(True, False)] if three else ONE_FUEL + if crew: + return LOGIC[(fuel, True)] if job else JOB_IN if fuel else None + return LOGIC[(False, False)] def logic_for(code: str) -> dict | None: """내 기계 원문번호 → 시간당 경비 식(채운 칸으로 고름) · 내 기계 아님 = None.""" index = whole() row = (index.numbers.get("EQ") or {}).get(code) - if row is None or not mine(row.get("키")) or row.get("방식") == DIRECT: - return None - return index.logic(logic_key(row)) + key = logic_key(row) if row is not None and mine(row.get("키")) else None + return index.logic(key) if key else None def hourly(row: dict, index: Any) -> dict[str, Any]: @@ -142,11 +168,21 @@ def hourly(row: dict, index: Any) -> dict[str, Any]: head = {"이름": row.get("이름"), "규격": row.get("규격"), "원문번호": row.get("원문번호")} if row.get("방식") == DIRECT: sums = {k: float((row.get("시간당") or {}).get(k) or 0) for k in COSTS} - return {"ok": True, **head, "로직": None, "직접": True, **sums, "계": sum(sums.values())} + return { + "ok": True, + **head, + "로직": DIRECT_KEY, + "직접": True, + **sums, + "계": sum(sums.values()), + } import master_cut as mcut mf = m01.cm.mf - logic = index.logic(logic_key(row)) + key = logic_key(row) + if key is None: + return {"ok": False, **head, "reason": "식 없음 — 연료 · 전력은 하나만"} + logic = index.logic(key) given = {i["이름"]: (i.get("고르기") or [""])[0] for i in logic.get("입력") or []} given["기계"] = str(row.get("원문번호")) try: @@ -203,6 +239,8 @@ def clean(raw: Any) -> dict[str, Any]: need = ("가격", "손료계수") if how == EASY else ("가격", *RATIOS) if lost := [k for k in need if not vals.get(k)]: raise _bad(f"빈 칸 — {' · '.join(lost)}") + if vals.get("주연료") and vals.get("전력"): + raise _bad("주연료 · 전력은 하나만(원문 운전경비 표 한 칸)") drop = (*RATIOS, *COEFS) if how == EASY else () vals = {k: v for k, v in vals.items() if k not in drop} if how == BOOK: diff --git a/M02_MasterTemplete/M02_MyMachine_Text.ts b/M02_MasterTemplete/M02_MyMachine_Text.ts index 19b1e1732..20522733e 100644 --- a/M02_MasterTemplete/M02_MyMachine_Text.ts +++ b/M02_MasterTemplete/M02_MyMachine_Text.ts @@ -50,7 +50,7 @@ const TEXT = { // 기계 고르기 창 · 줄 쌓기 Pick_Group: "내 기계", Pick_New: "새 기계 만들기", - Pick_Direct: "시간당 값 직접 기계 — 줄 쌓기 식이 없어 못 넣음", + Pick_Direct: "시간당 경비 식이 없는 기계 — 줄 쌓기에 못 넣음", To_Mine: "복사해 내 기계로", // 내 품목 Items_Note: diff --git a/common_util/common_util_machine_coef.ts b/common_util/common_util_machine_coef.ts index b5a66f2d8..83316e0ee 100644 --- a/common_util/common_util_machine_coef.ts +++ b/common_util/common_util_machine_coef.ts @@ -5,7 +5,7 @@ * ⚠ 짝: `resources/master_data/scripts/master_machine_coef.py` — 같은 셈 · 같은 차례 * (거울 시험 `resources/tester/test_821_2_machine_coef.py`). 한쪽만 고치면 화면과 저장이 갈림. * 계수 = 건설품셈 공통 8-1-5(계수마다 정수 반올림 · 손료계수 = 합) · 미리보기 끝수 = 서버 - * 기계경비식(줄 0.1 원 아래 버림 · 잡품 = 버린 주연료 × % · 비목 합 원 미만 버림) · 정본은 서버 식. + * 기계경비식(줄 0.1 원 아래 버림 · 잡품 = 버린 (주연료 + 전력) × % · 비목 합 원 미만 버림) · 정본은 서버 식. * ========================================================================== */ export const MACHINE_INPUTS = [ @@ -73,8 +73,8 @@ export function machineHourly( ): MachineHourly { const loss = Math.floor((num(values["가격"]) * 1000 * num(values["손료계수"])) / 1e7 + E); const oil = tenth(num(values["주연료"]) * num(fuel)); - const misc = tenth((oil * num(values["잡재료율"])) / 100); const elec = tenth(num(values["전력"]) * num(power)); + const misc = tenth(((oil + elec) * num(values["잡재료율"])) / 100); const crew = tenth(num(values["조종원"]) * hour * num(wage)); const labor = Math.floor(crew + E); const material = Math.floor(oil + misc + elec + E); diff --git a/resources/master_data/scripts/master_machine_coef.py b/resources/master_data/scripts/master_machine_coef.py index 61ff5dc53..e9ae5f8c6 100644 --- a/resources/master_data/scripts/master_machine_coef.py +++ b/resources/master_data/scripts/master_machine_coef.py @@ -10,7 +10,8 @@ 계수마다 정수로 반올림 · 손료계수 = 셋의 합. 미리보기 = 서버 기계경비식(GC000267 ~)을 기본 옵션(보정 없음)으로 부른 값과 같은 끝수 — 손료 = 가격(천원) × 1000 × 손료계수 ÷ 10^7 원 미만 버림 · 줄 금액은 0.1 원 아래 버림 · - 잡품 = 버린 주연료 금액 × 잡재료율 % · 비목 합은 원 미만 버림(B09 끝수 `master_cut.engine`). + 잡품 = 버린 (주연료 + 전력) 금액 × 잡재료율 %(전력 기종 = 전력비의 % · 822-2 GC001196 · 7) · + 비목 합은 원 미만 버림(B09 끝수 `master_cut.engine`). 정본은 서버 식(`M02_Machine_Hourly`) — 이것은 화면 즉시 셈용. """ @@ -61,8 +62,8 @@ def preview( """값 묶음 + 유가(원/ℓ) · 노임(원/일) · 전력(원/kWh) · 시간환산 → 시간당 줄 · 비목 · 계.""" loss = math.floor(_num(values.get("가격")) * 1000 * _num(values.get("손료계수")) / 1e7 + _E) oil = _tenth(_num(values.get("주연료")) * _num(fuel)) - misc = _tenth(oil * _num(values.get("잡재료율")) / 100) elec = _tenth(_num(values.get("전력")) * _num(power)) + misc = _tenth((oil + elec) * _num(values.get("잡재료율")) / 100) crew = _tenth(_num(values.get("조종원")) * hour * _num(wage)) labor, material = math.floor(crew + _E), math.floor(oil + misc + elec + _E) return { diff --git a/resources/tester/test_804_2_line_stack.py b/resources/tester/test_804_2_line_stack.py index f0d9f6832..dedd53bee 100644 --- a/resources/tester/test_804_2_line_stack.py +++ b/resources/tester/test_804_2_line_stack.py @@ -116,7 +116,9 @@ def _hourly_inputs(logic_key: str) -> dict: def _kind(row: dict) -> tuple: - return (*mh._rank(row)[:3], any(x.get("이름") == "조종원" for x in row.get("호표") or [])) + """갈래 = 부착 · 손료만 · 보정 · 조종원 줄 · 전력 줄(822-2 전력 기종 식 — 손료만 식과 갈림).""" + lines = {x.get("이름") for x in row.get("호표") or []} + return (*mh._rank(row)[:3], "조종원" in lines, "전력" in lines) # 기계표 조종원 1 인 · 「조종원 없는 기종」 GC000272 목록에도 겹친 기계(815-3) — 조종원 든 식 diff --git a/resources/tester/test_822_2_machine_hourly_all.py b/resources/tester/test_822_2_machine_hourly_all.py new file mode 100644 index 000000000..3a2359d0c --- /dev/null +++ b/resources/tester/test_822_2_machine_hourly_all.py @@ -0,0 +1,252 @@ +# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀 +"""822-2 기계 시간당 경비 식 완성 — 지킴 시험(정본 고침은 임시 사본 폴더 · 임시 storage 만). + +① 모든 기계 = 식 있음 또는 까닭 글(「셀 자료가 없음」 0) ② 식 고름 = 목록 › 꼴(채운 칸) 한 함수 +③ M01 [새 기계] 저장 = 꼴 식 목록 등록 · 지우면 목록에서 뺌 · 로직 파일 글 그대로(임시 마스터) +④ 직접 값 식(GC001199) — 내 기계 「직접」 시간당 · 줄 쌓기 식 ⑤ 전력 줄 — 점보드릴 135㎾ × 전력 단가 + +잡재료 6 % + 손료 + 조종원 · 계약종별 바꿈 · 내 기계 전력 ⑥ 옮긴 기계 견본(아스팔트 믹싱플랜트 · 타워크레인 · +직종 입력 = 아스팔트 스프레어) ⑦ 미리보기 = 서버 값 ⑧ 안 옮긴 기계 금액 그대로 ⑨ POWER TROWEL 이름. +""" + +from __future__ import annotations + +import json +import shutil +import sys +from decimal import Decimal +from pathlib import Path + +import pytest +from fastapi import Depends, FastAPI +from fastapi.testclient import TestClient + +from common_util import common_util_auth as auth +from M01_MasterData import M01_MasterData_Router as router_module +from M01_MasterData import M01_MasterData_Store as m01 +from M02_MasterTemplete import M02_Labor_Ref as refs +from M02_MasterTemplete import M02_Machine_Hourly as mh +from M02_MasterTemplete import M02_MasterTemplete_Store as store +from M02_MasterTemplete import M02_MyMachine as mym +from M02_MasterTemplete import M02_Sangeun_Calc as calc +from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층 + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "resources" / "master_data" / "scripts")) +import master_machine_coef as mc # noqa: E402 + +MACHINE, LOGIC = "기계.json", "로직_건설품셈_08장_건설기계.json" +POWER, POWER_CREW, ONE_FUEL, DIRECT = "GC001196", "GC001197", "GC001198", "GC001199" +NAME = "sub1임시기계822" +# 안 옮긴 기계 — 822-2 전 금액(전국평균 · 기본 옵션) +KEEP = {"EQ000001": 68550, "EQ000022": 108958} + + +def _rows() -> dict[str, dict]: + return {r["키"]: r for r in m01.read(MACHINE)[0]["줄"]} + + +def _wage(job: str) -> Decimal: + return Decimal(str(m01.loaded()[1].get(f"LB:{job}")["값"])) + + +def _codes(key: str) -> list[str]: + row = next(r for r in m01.read(LOGIC)[0]["줄"] if r["키"] == key) + return [str(c) for c in next(i for i in row["입력"] if i["이름"] == "기계").get("고르기", [])] + + +# ① · ② ─────────────────────────────────────────────────────────────── +def test_every_machine_has_formula_or_reason(): + """모든 기계 — 식을 고르고 값이 다 있음 · 아니면 까닭 글(빈 답 0).""" + silent = [] + for key, row in _rows().items(): + row = m01.loaded()[1].get(key) + logic = mh.pick(row) + if not logic and not mh.why(row, logic): + silent.append(key) + assert silent == [] + assert mh.hourly("EQ000343")["reason"] == "가격 없음" # TBM 3.0m + assert "일 단위" in mh.hourly("EQ000621")["reason"] # 산림품셈 예취기(1일 1대당) + assert mh.hourly("EQ000202")["reason"] == "주연료 「73.7+」 — 수 아님" # 표층재생기 · 안 풂 + assert "범위" in mh.hourly("EQ000552")["reason"] # 펌프 준설선 잡재료 13∼18 + assert ( + "중유" in mh.hourly("EQ000187")["reason"] + ) # 아스팔트 믹싱플랜트 40t — 유가 표에 중유 없음 + + +def test_pick_listed_then_shape(): + """목록에 있으면 그 식(짝 차례) · 없으면 꼴 — 내 기계와 같은 규칙.""" + assert mh.logic_of("0101-0007")["키"] in {"GC000268", "GC000269"} # 목록 + shape = mym.logic_key + full = {"가격": 1, "손료계수": 3, "상각비계수": 1, "정비비계수": 1, "관리비계수": 1} + assert shape({"값": {**full, "주연료": 1, "조종원": 1, "조종원직종": "1048"}}) == "GC000268" + assert shape({"값": {**full, "주연료": 1, "조종원": 1}}) == "GC000275" + assert shape({"값": {**full, "주연료": 1}}) == "GC000272" + assert shape({"값": {"가격": 1, "손료계수": 3, "주연료": 1}}) == ONE_FUEL + assert shape({"값": {**full, "조종원": 1, "조종원직종": "1048"}}) == "GC000276" + assert shape({"값": {**full, "전력": 5}}) == POWER + assert shape({"값": {**full, "전력": 5, "조종원": 1, "조종원직종": "1048"}}) == POWER_CREW + assert shape({"값": full}) == "GC000273" + assert shape({"방식": "직접", "값": {}}) == DIRECT + assert shape({"값": {**full, "주연료": 1, "전력": 5}}) is None + assert shape({"값": {**full, "조종원": 1}}) is None # 직종 없는 조종원만 — 까닭 + assert shape({"값": {**full, "주연료": "73.7+"}}) is None + + +# ③ M01 [새 기계] 식 등록 ────────────────────────────────────────────── +@pytest.fixture +def copy(tmp_path: Path, monkeypatch) -> Path: + for path in m01.FOLDER.glob("*.json"): + if not path.name.startswith("_") or path.name == m01.mk.BOOK.name: + shutil.copy(path, tmp_path / path.name) + monkeypatch.setattr(m01, "FOLDER", tmp_path) + return tmp_path + + +def _app() -> TestClient: + app = FastAPI() + app.dependency_overrides[auth.verify_session] = lambda: { + "user_id": 1, "name": "시험", "role": "SYSTEM_ADMIN", "company_id": 1, + } # fmt: skip + deps = [Depends(auth.verify_session), Depends(auth.require_system_admin)] + app.include_router(router_module.router, dependencies=deps) + return TestClient(app) + + +def _save(client: TestClient, changes: list[dict]) -> None: + version = client.get("/api/m01/machine/row", params={"key": "EQ000022"}).json()["version"] + body = {"files": [{"file": MACHINE, "version": version, "changes": changes}]} + res = client.post("/api/m01/save", json=body) + assert res.status_code == 200, res.text + + +def test_new_machine_registers_and_unregisters(copy: Path): + client = _app() + logic_text = (copy / LOGIC).read_text(encoding="utf-8") + seed = json.loads(json.dumps(_rows()["EQ000022"], default=float)) + row = {**seed, "키": "", "원문번호": "9999-0822", "이름": NAME, "출처": {}, "단위": {}} + del row["별칭"] + draft = client.post("/api/m01/machine/formula", json=row).json() + assert draft["키"] == "GC000268" and draft["방법"] == "꼴" and not draft["까닭"] + _save(client, [{"op": "add", "row": row}]) + assert "9999-0822" in _codes("GC000268") + key = next(k for k, r in _rows().items() if r["원문번호"] == "9999-0822") + hour = client.get("/api/m01/machine/hourly", params={"keys": key}).json()[key] + assert hour["로직"] == "GC000268" and hour["계"] == mh.hourly("EQ000022")["계"] + saved = json.loads(json.dumps(_rows()[key], default=float)) + got = client.post("/api/m01/machine/formula", json=saved).json() + assert got["방법"] == "목록" and got["키"] == "GC000268" + _save(client, [{"op": "delete", "key": key}]) + assert "9999-0822" not in _codes("GC000268") + assert (copy / LOGIC).read_text(encoding="utf-8") == logic_text + + +# ④ 직접 값 식 ───────────────────────────────────────────────────────── +def _at(folder, fn, *args): + with store.at(folder): + return fn(*args) + + +def test_direct_formula_my_machine(world): + me = world["personal"] + body = { + "이름": NAME, + "규격": "직접", + "방식": "직접", + "시간당": {"노무비": 10, "재료비": 20, "경비": 30}, + } + made = _at(me, mym.create, body) + assert made["시간당"]["로직"] == DIRECT and made["시간당"]["계"] == 60 + no = made["줄"]["원문번호"] + line = {"종류": "로직", "로직": DIRECT, "입력값": {"기계": no}, "수량": "2"} + name = _at( + me, lambda: refs.create("sangeun", {"이름": NAME, "단위": "시간", "더한줄": [line]})["이름"] + ) + doc = _at(me, lambda: store.read("sangeun", name)["문서"]) + got = _at(me, calc.calc_doc, doc, None, False)["기준"] + assert (got["노무비"], got["재료비"], got["경비"]) == (20, 40, 60) + + +# ⑤ 전력 줄 ──────────────────────────────────────────────────────────── +def _preview(key: str) -> dict: + v = json.loads(json.dumps(_rows()[key]["값"], default=float)) + fuel = m01.loaded()[1].numbers["MO"].get(f"{v.get('주연료종류')}:전국평균") + wage = float(_wage(v["조종원직종"])) if v.get("조종원직종") else 0 + return mc.preview(v, float((fuel or {}).get("값") or 0), wage, 114.4) + + +def test_power_line_jumbo_drill(): + h = mh.hourly("EQ000347") # 점보드릴 2붐 135㎾ · 잡재료 6 % · 조종원 1(건설기계운전사) + assert h["로직"] == POWER_CREW + loss = 605472 * 1000 * 2515 // 10**7 + elec = Decimal("135") * Decimal("114.4") + misc = int(elec * 6) / Decimal(100) + assert h["경비"] == loss and h["재료비"] == int(elec + misc) + assert h["노무비"] == int(_wage("1048") / 8) + p = _preview("EQ000347") + assert (p["노무비"], p["재료비"], p["경비"], p["계"]) == ( + h["노무비"], + h["재료비"], + h["경비"], + h["계"], + ) + home = m01.calc(POWER_CREW, {"기계": "5805-0002", "전력 계약종별": "주택용 저압"}) + assert home["ok"] and float(home["sums"]["재료비"]) > h["재료비"] # 120 원/kWh > 114.4 + + +def test_power_my_machine(world): + me = world["personal"] + body = { + "이름": NAME, + "규격": "전력", + "방식": "바로", + "값": {"가격": 1000, "손료계수": 2000, "전력": 10, "잡재료율": 5}, + } + hour = _at(me, mym.create, body)["시간당"] + assert hour["로직"] == POWER + want = mc.preview({"가격": 1000, "손료계수": 2000, "전력": 10, "잡재료율": 5}, 0, 0, 114.4) + assert (hour["재료비"], hour["경비"]) == (want["재료비"], want["경비"]) == (1201, 200) + with pytest.raises(store.StoreError): + _at(me, mym.create, {**body, "값": {**body["값"], "주연료": 1, "주연료종류": "경유"}}) + + +# ⑥ · ⑦ 옮긴 기계 견본 ───────────────────────────────────────────────── +def test_moved_samples(): + plant = mh.hourly("EQ000188") # 아스팔트 믹싱플랜트 60t — 연료 + 조종원 2 인 + assert plant["로직"] == "GC000268" and plant["노무비"] == int(_wage("1048") * 2 / 8) + assert plant["재료비"] > 0 + tower = mh.hourly("EQ000165") # 타워크레인 — 건설기계운전사 조종원 줄(요구 799) + assert tower["로직"] == "GC000276" and tower["노무비"] == int(_wage("1048") / 8) + spray = m01.loaded()[1].get("EQ000198") # 아스팔트 스프레어 — 직종 빈 칸 = 직종 입력 식 + assert mh.pick(spray)["키"] == "GC000275" and "조종원직종" in json.dumps( + mh.pick(spray)["입력"], ensure_ascii=False + ) + assert "가격" in mh.hourly("EQ000198")["reason"] + assert "3450-0642" not in _codes("GC000273") # 손료만 식에서 뺌 + for key, logic in ( + ("EQ000529", POWER), + ("EQ000534", ONE_FUEL), + ("EQ000532", POWER), + ("EQ000426", POWER), + ): + h = mh.hourly(key) + p = _preview(key) + assert h["로직"] == logic and (p["재료비"], p["경비"], p["계"]) == ( + h["재료비"], + h["경비"], + h["계"], + ), key + for key in ("EQ000188", "EQ000165"): + h, p = mh.hourly(key), _preview(key) + assert (p["노무비"], p["재료비"], p["경비"]) == (h["노무비"], h["재료비"], h["경비"]), key + + +# ⑧ · ⑨ ──────────────────────────────────────────────────────────────── +def test_unmoved_same_and_name(): + for key, total in KEEP.items(): + assert mh.hourly(key)["계"] == total + row = _rows()["EQ000534"] + assert (row["이름"], row["옛이름"], row["별칭"]) == ( + "POWER TROWEL", + "POWER TLOWEL", + "파워트로웰", + ) From f22b506d0eb88f3aa67402e93981bfd08f61402b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 18:12:43 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat(801-2):=20=EA=B8=B0=EA=B3=84=20?= =?UTF-8?q?=EC=96=91=EC=8B=9D=20=EC=A7=81=EC=A2=85=20=EA=B3=A0=EB=A5=B4?= =?UTF-8?q?=EA=B8=B0=EC=97=90=20=EC=B0=BE=EA=B8=B0=20=ED=8C=90=20=C2=B7=20?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20=EA=B3=84=EC=82=B0=EC=8B=9D=20=EA=B8=80=20?= =?UTF-8?q?=C2=B7=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EC=9E=A1?= =?UTF-8?q?=ED=92=88=20=EA=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 부품 ui_template_pick_search(새) — 항목 많은 고르기(열둘 넘음)를 열면 찾기 판(팝오버 + 찾기 목록 · 높이 240 · 스크롤) · 글자로 거름 · 비우면 전부 · 고른 항목 둘레로 열림 · Esc · 바깥 누름 닫음 · 연료처럼 적은 고르기는 그대로 - 찾기 목록 setRows 둘째 인자 = 처음 짚을 줄(가운데로) - 기계 양식(M01 상세 · 내 기계 · 줄 쌓기 새 기계 셋 다): 직종 고르기에 붙임 · 전력 줄을 잡품 위로 · 잡품 = (주연료 + 전력) × % - 글: 쓰는 계산식 · 목록에 있음 · 꼴로 고름 · 글자로 찾기 Co-Authored-By: Claude Opus 5.5 --- ui_template/ui_template_find_list.ts | 15 ++-- ui_template/ui_template_locale_machine.ts | 9 +++ ui_template/ui_template_machine_form.ts | 6 +- ui_template/ui_template_pick_search.css | 12 ++++ ui_template/ui_template_pick_search.ts | 88 +++++++++++++++++++++++ 5 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 ui_template/ui_template_pick_search.css create mode 100644 ui_template/ui_template_pick_search.ts diff --git a/ui_template/ui_template_find_list.ts b/ui_template/ui_template_find_list.ts index f4fcab62f..2be679a48 100644 --- a/ui_template/ui_template_find_list.ts +++ b/ui_template/ui_template_find_list.ts @@ -4,7 +4,7 @@ * 키보드 — 찾기 칸에서 ↑ ↓ 로 옮기고 Enter 로 고름. 창 겉틀은 부르는 쪽 몫. * * const find = createFindList({ placeholder, filters: [sub.root, detail.root] }); - * find.setRows([{ key, tag, name, sub, unit, onPick }]); + * find.setRows([{ key, tag, name, sub, unit, onPick }]); // 둘째 인자 = 처음 짚을 줄 key(가운데로 보임) * ========================================================================== */ import "./ui_template_find_list.css"; @@ -27,7 +27,8 @@ export interface FindList { root: HTMLElement; search: HTMLInputElement; hint: HTMLElement; - setRows: (rows: FindRow[]) => void; + /** active = 처음 짚을 줄 key(없으면 첫 줄) — 그 줄이 가운데로 보임 */ + setRows: (rows: FindRow[], active?: string) => void; } export function createFindList(opts: { @@ -42,12 +43,12 @@ export function createFindList(opts: { let rows: FindRow[] = []; let at = -1; - const mark = (i: number): void => { + const mark = (i: number, block: ScrollLogicalPosition = "nearest"): void => { list.children[at]?.classList.remove("is-active"); at = i; const row = list.children[at]; row?.classList.add("is-active"); - row?.scrollIntoView({ block: "nearest" }); + row?.scrollIntoView({ block }); }; field.input.addEventListener("keydown", (ev) => { if (ev.key === "ArrowDown" || ev.key === "ArrowUp") { @@ -61,7 +62,7 @@ export function createFindList(opts: { } }); - const setRows = (next: FindRow[]): void => { + const setRows = (next: FindRow[], active?: string): void => { rows = next; at = -1; list.replaceChildren( @@ -89,7 +90,9 @@ export function createFindList(opts: { return row; }), ); - if (rows.length) mark(0); + const pin = active === undefined ? -1 : rows.findIndex((r) => r.key === active); + if (pin >= 0) mark(pin, "center"); + else if (rows.length) mark(0); }; const filters = opts.filters?.length diff --git a/ui_template/ui_template_locale_machine.ts b/ui_template/ui_template_locale_machine.ts index e45ba5d55..b5376cb30 100644 --- a/ui_template/ui_template_locale_machine.ts +++ b/ui_template/ui_template_locale_machine.ts @@ -40,6 +40,7 @@ export const ui_locales_machine = { "National average · instant · server recalculates on save", ], MF_NoPrice: ["단가 없음", "no price"], + MF_PickFind: ["글자로 찾기", "Type to filter"], MF_Similar: ["비슷한 기계에서 시작", "Start from a similar machine"], MF_SimilarFind: ["기계 이름 · 규격으로 찾기", "Find by name or spec"], MF_SimilarNone: ["찾은 기계 없음", "No machines found"], @@ -51,6 +52,14 @@ export const ui_locales_machine = { M01_MachineMeta: ["원문 · 구분", "Source · group"], M01_MachineUses: ["쓰는 곳", "Used by"], M01_MachineNoUses: ["쓰는 곳 없음", "Not used"], + M01_MachineFormula: ["쓰는 계산식", "Hourly formula"], + M01_MachineFormulaListed: ["목록에 있음", "listed"], + M01_MachineFormulaShape: [ + "꼴로 고름(채운 칸) — 저장하면 그 식 목록에 등록", + "picked by filled fields — registered on save", + ], + M01_MachineFormulaOwn: ["그 기계만의 식", "machine's own formula"], + M01_MachineFormulaNone: ["식 없음", "no formula"], M01_MachineSame: [ "같은 이름 · 규격 기계가 있음 — {value}", "A machine with the same name and spec exists — {value}", diff --git a/ui_template/ui_template_machine_form.ts b/ui_template/ui_template_machine_form.ts index ac1c3019c..7060d5757 100644 --- a/ui_template/ui_template_machine_form.ts +++ b/ui_template/ui_template_machine_form.ts @@ -20,6 +20,7 @@ import { import { createCalcFormula, createCalcFormulaRow } from "./ui_template_calc_row"; import { el, createInputField, createSelectField } from "./ui_template_elements"; import { createFindList } from "./ui_template_find_list"; +import { attachPickSearch } from "./ui_template_pick_search"; import { t as L } from "./ui_template_locale"; import { createSegment } from "./ui_template_segment"; import { createText } from "./ui_template_text"; @@ -237,6 +238,7 @@ export function createMachineForm(opts: MachineFormOptions): MachineFormHandle { const field = createSelectField({ label: tr(label), options, value: now, size: 28 }); field.select.dataset.key = key; field.select.disabled = Boolean(opts.readOnly); + attachPickSearch(field.select, { placeholder: L("MF_PickFind") }); // 항목 많은 고르기(직종) = 찾기 판(801-2) const mark = (): void => { field.root.classList.toggle("is-changed", !same(values[key] ?? null, init[key] ?? null)); }; @@ -448,11 +450,11 @@ export function createMachineForm(opts: MachineFormOptions): MachineFormHandle { const rows = [ line(L("MF_Loss"), `${num("가격")}천원 × 1,000 × ${num("손료계수")} × 10^-7^`, p.손료), line(L("MF_Fuel"), `${num("주연료")}ℓ × ${fmt(fuel)}원`, p.주연료), - line(L("MF_Misc"), `${fmt(p.주연료)} × ${num("잡재료율")}%`, p.잡품), - line(L("MF_Crew"), `${num("조종원")}인 × ${fmt(wage)}원 × ${fmt(hour)}`, p.조종원), ...(values["전력"] ? [line(L("MF_Power"), `${num("전력")}kWh × ${fmt(opts.power ?? 0)}원`, p.전력)] : []), + line(L("MF_Misc"), `${fmt(p.주연료 + p.전력)} × ${num("잡재료율")}%`, p.잡품), + line(L("MF_Crew"), `${num("조종원")}인 × ${fmt(wage)}원 × ${fmt(hour)}`, p.조종원), ]; side.replaceChildren( ...head, diff --git a/ui_template/ui_template_pick_search.css b/ui_template/ui_template_pick_search.css new file mode 100644 index 000000000..097dc7c1b --- /dev/null +++ b/ui_template/ui_template_pick_search.css @@ -0,0 +1,12 @@ +/* 고르기 찾기 판(ui_template_pick_search.ts · 801-2) — 고르기 칸 바로 아래 · 목록 높이 제한 */ +.ui-pick-search-host { + position: relative; +} + +.ui-pick-search .ui-float-panel__body { + padding: var(--spacing-8); +} + +.ui-pick-search .ui-find__list { + max-height: 240px; +} diff --git a/ui_template/ui_template_pick_search.ts b/ui_template/ui_template_pick_search.ts new file mode 100644 index 000000000..9aa9b14f6 --- /dev/null +++ b/ui_template/ui_template_pick_search.ts @@ -0,0 +1,88 @@ +/* ============================================================================= + * ui_template_pick_search.ts + * 고르기 칸에 찾기 붙이기(요구 801-2) — 항목이 많은 `createSelectField` 고르기를 열면 바로 아래에 + * 찾기 판(팝오버 `createFloatPanel` + 찾기 목록 `createFindList` · 높이 제한 · 스크롤)이 뜸. + * 글자를 치면 이름에 든 것만 · 찾기를 비우면 다시 전부 · 고른 항목이 있으면 그 줄 둘레로 열림 + * 고름 = select 값 바꿈 + change 알림(부르는 쪽 코드 그대로) · Esc · 바깥 누름 = 닫음 + * 항목이 적으면(`min` 이하) 아무것도 안 함 — 보통 고르기 그대로 + * 쓰는 법 = attachPickSearch(field.select, { placeholder: "글자로 찾기" }) + * ========================================================================== */ + +import { createFindList } from "./ui_template_find_list"; +import { createFloatPanel } from "./ui_template_float_panel"; +import "./ui_template_pick_search.css"; + +const squash = (s: string): string => s.replace(/\s+/g, "").toLowerCase(); + +export function attachPickSearch( + select: HTMLSelectElement, + opts: { placeholder: string; min?: number }, +): void { + if (select.options.length <= (opts.min ?? 12)) return; + const host = select.parentElement; + if (!host) return; + host.classList.add("ui-pick-search-host"); + let panel: HTMLElement | null = null; + + const close = (): void => { + panel?.remove(); + panel = null; + document.removeEventListener("pointerdown", outside, true); + }; + const outside = (ev: PointerEvent): void => { + if (panel && !panel.contains(ev.target as Node) && ev.target !== select) close(); + }; + + const open = (): void => { + if (panel || select.disabled) return; + const fp = createFloatPanel({ popover: true, width: Math.max(select.offsetWidth, 260) }); + fp.root.classList.add("ui-pick-search"); + fp.root.style.left = `${select.offsetLeft}px`; + fp.root.style.top = `${select.offsetTop + select.offsetHeight + 2}px`; + const find = createFindList({ placeholder: opts.placeholder }); + fp.body.append(find.root); + const all = [...select.options].map((o) => ({ value: o.value, text: o.text })); + const show = (): void => { + const q = squash(find.search.value); + const rows = q ? all.filter((o) => squash(o.text).includes(q)) : all; + find.setRows( + rows.map((o) => ({ + key: o.value, + name: o.text, + onPick: () => { + select.value = o.value; + select.dispatchEvent(new Event("change", { bubbles: true })); + close(); + select.focus(); + }, + })), + q ? undefined : select.value, + ); + }; + find.search.addEventListener("input", show); + find.search.addEventListener("keydown", (ev) => { + if (ev.key === "Escape") { + ev.preventDefault(); + close(); + select.focus(); + } + }); + host.append(fp.root); + panel = fp.root; + show(); + find.search.focus(); + document.addEventListener("pointerdown", outside, true); + }; + + select.addEventListener("mousedown", (ev) => { + ev.preventDefault(); + if (panel) close(); + else open(); + }); + select.addEventListener("keydown", (ev) => { + if (["Enter", " ", "ArrowDown"].includes(ev.key)) { + ev.preventDefault(); + open(); + } + }); +}