From 47f260e0129ddbb179fdb46de65a2cf85a293776 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 7 Nov 2026 17:44:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(801-3):=20=EC=88=98=EC=8B=9D=20?= =?UTF-8?q?=ED=83=AD=20=EC=86=8C=EA=B3=84=20=C2=B7=20=ED=95=A9=EA=B3=84=20?= =?UTF-8?q?=EB=AC=B6=EC=9D=8C=20=EB=8B=A4=EC=8B=9C=20=E2=80=94=20=EB=8F=88?= =?UTF-8?q?=20=EB=AC=B6=EC=9D=8C=20=ED=95=98=EB=82=98=20=3D=20=EB=A8=B8?= =?UTF-8?q?=EB=A6=AC=20=EC=97=86=EC=9D=8C=20=C2=B7=20=EA=B3=84=20/=20?= =?UTF-8?q?=EB=91=98=20=EC=9D=B4=EC=83=81=20=3D=20=EC=86=8C=EA=B3=84=20/?= =?UTF-8?q?=20=EB=8B=A8=EA=B0=80=EC=82=B0=EC=B6=9C=20=EB=91=98=20=EC=9D=B4?= =?UTF-8?q?=EC=83=81=20=3D=20=ED=95=A9=EA=B3=84=20=C2=B7=20=EC=95=88?= =?UTF-8?q?=EC=AA=BD=20=EB=A7=90=200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 묶음 한 곳 master_equation_group.bundles: 부른 · 더한 단가산출 하나씩 + 직접 줄(인력 · 재료 · 기계 · 덧줄) 한 묶음 · 돈 없는 줄 · 뺀 줄 = 묶음 아님 · 합계 밖 줄 = 밖 글 - M01 subtotals · M02 M02_Sangeun_Equation_Full 같은 꼴 · 끝수 셈 그대로 · 금액 전후 같음 - 부품 ui_template_equation_sum: 자기 자원 · 덧줄 글 0 · 묶음 하나 = 머리 없음 · 계 · 둘 이상 = 가. 머리 · 소 계 · 단가산출 둘 이상 = 합계 - 옛 시험 기대 고침: test_799_1(갈래 자원 · 덧줄 → 직접 한 묶음) · test_796_2c(줄마다 한 행 → 묶음 · 낱줄 · 밖) — 묶음 꼴이 바뀜 - 새 test_801_3_groups 5 Co-Authored-By: Claude Sonnet 5.5 --- .../M02_Sangeun_Equation_Full.py | 26 +++- .../master_data/scripts/master_equation.py | 29 ++--- .../scripts/master_equation_group.py | 68 ++++++++++ .../tester/test_796_2c_m02_equation_full.py | 47 +++---- resources/tester/test_799_1_subtotals.py | 6 +- resources/tester/test_801_3_groups.py | 117 ++++++++++++++++++ ui_template/ui_template_equation.ts | 16 ++- ui_template/ui_template_equation_sum.ts | 70 ++++++----- 8 files changed, 294 insertions(+), 85 deletions(-) create mode 100644 resources/master_data/scripts/master_equation_group.py create mode 100644 resources/tester/test_801_3_groups.py diff --git a/M02_MasterTemplete/M02_Sangeun_Equation_Full.py b/M02_MasterTemplete/M02_Sangeun_Equation_Full.py index 58d305186..1a9f8ca7d 100644 --- a/M02_MasterTemplete/M02_Sangeun_Equation_Full.py +++ b/M02_MasterTemplete/M02_Sangeun_Equation_Full.py @@ -4,10 +4,10 @@ (`M02_Sangeun_Calc.calc_doc` = 화면 · 저장 · B09 가 쓰는 한 벌)을 얹어 화면 금액 = SK 금액 (셈은 안 바꿈 · 보이기만): 합계 = 셈 답 `기준`(노 · 재 · 경 · 계 · 1 단위) — 바탕 줄만 셈한 합계 대신 - 소계 = 셈 답 `줄` 한 줄마다 한 행 {cid, 갈래(자원 | 블록 | 덧줄 | 율), 이름, 상태, 수량, - 노 · 재 · 경 · 계, 덮개?, 원래?, 까닭?, 나무?, 합밖?, 밖금액?} + - 끝수(합계 − 행 합 · 까닭 글) - 합밖 줄(엔진이 합계에서 뺌) = 행 금액 0 + 밖금액 + 소계 = 셈 답 `줄` → 돈 묶음(801-3 · `master_equation_group`) — 더한 · 부른 단가산출(갈래 블록) 하나씩 + {cid, 이름, 상태, 수량, 노 · 재 · 경 · 계, 덮개?, 원래?, 까닭?, 나무?} + 직접 줄(인력 · 재료 · 기계 · + 덧줄 · 율) 한 묶음 {갈래 직접, 이름, 노 · 재 · 경 · 계, 낱줄?(더함 · 덮음 줄)} + + 끝수(합계 − 묶음 합 · 까닭 글) · 밖(합계 밖 줄 — 엔진이 합계에서 뺌 · 이름 · 밖금액) 상태 = 바탕(그대로) · 덮음(품 · 단가 · 상수 · 표값 · 식 덮개 · 변수줄) · 더함(더한줄) · 뺌(뺀줄 · 금액 0) 나무 = 더한 기초(더한줄 로직)의 수식(세부 덮개 · 뺀줄 얹은 줄) — 화면은 접힘 블록 @@ -31,6 +31,7 @@ SUMS = (*COSTS, "계") _ADD = re.compile(r"^add:#(\d+)$") _HEAD = {"labor": "자원", "material": "자원", "machine": "자원", "logic": "블록", "pct": "덧줄"} _ADD_KIND = {"인력": "자원", "재료": "자원", "기계": "자원"} +_WORD = {"labor": "인력", "material": "재료", "machine": "기계"} # 직접 묶음 이름(801-3) def _d(value: Any) -> Decimal: @@ -69,6 +70,15 @@ def _kind(cid: str, adds: list) -> str: return _ADD_KIND.get(str(one.get("종류")), "블록") +def _word(cid: str, adds: list) -> str: + """직접 줄의 자원 갈래 말 — 인력 · 재료 · 기계(그 밖 = 빈 글).""" + hit = _ADD.match(cid) + if not hit: + return _WORD.get(cid.split(":", 1)[0], "") + one = adds[int(hit[1])] if int(hit[1]) < len(adds) else {} + return str(one.get("종류")) if one.get("종류") in _ADD_KIND else "" + + def _row(cid: str, kind: str, name: str, state: str, parts: dict | None, **more: Any) -> dict: cost = {k: _d((parts or {}).get(k)) for k in COSTS} return { @@ -112,6 +122,7 @@ def rows(doc: dict, ans: dict, tree: Any, out_ids: set[str] = frozenset()) -> li if outside else None, 수량=line.get("수량"), + 종류=_word(cid, adds), 덮개=cover, 원래=info.get("원래") if state == "덮음" else None, 까닭=line.get("까닭") or info.get("까닭"), @@ -209,6 +220,8 @@ def attach( eq: dict, doc: dict, whole: Any, base: dict, inputs: dict | None, ans: dict | None ) -> dict: """수식 `eq` 에 SK 셈 답 합계 · 소계 · 표지를 얹음(제자리) — 답 없음 = `답까닭` 만.""" + import master_equation_group as grp # M01 이 길을 둔 뒤 + if ans is None: return eq | {"답까닭": "단가산출 셈 답이 서지 않음 — 바탕 줄 합계를 보임"} _mark(eq, base, ans, doc) @@ -219,8 +232,9 @@ def attach( lambda k: _basic_tree(whole, doc, adds[k], inputs) if k >= 0 else None, _outside(base), ) + got = grp.bundles(table) # 801-3 묶음 — 부른 · 더한 단가산출 하나씩 + 직접 줄 한 묶음 now = total(ans) or {} - rest = {k: now[k] - sum((r[k] for r in table), Decimal(0)) for k in SUMS} + rest = {k: now[k] - sum((r[k] for r in got["줄"]), Decimal(0)) for k in SUMS} eq["합계"] = now - eq["소계"] = {"줄": table, "끝수": rest, "끝수까닭": _why(ans)} + eq["소계"] = {"줄": got["줄"], "밖": got["밖"], "끝수": rest, "끝수까닭": _why(ans)} return eq diff --git a/resources/master_data/scripts/master_equation.py b/resources/master_data/scripts/master_equation.py index 0c5184e18..386a19c7f 100644 --- a/resources/master_data/scripts/master_equation.py +++ b/resources/master_data/scripts/master_equation.py @@ -28,6 +28,7 @@ from pathlib import Path import master_auto as ma import master_cut as mcut +import master_equation_group as grp import master_equation_view as view import master_formula as mf import master_material as mm @@ -401,36 +402,30 @@ def _line(ctx: _Ctx, kind: str, key: str, text: str, value, **more) -> dict: def subtotals(logic: dict, cut: dict, total: dict) -> dict: - """호표 줄 → 소계(799-1 · 보이기만) — 하위 로직 줄 = 블록마다 · 그 밖 자원 · 덧줄은 한 줄씩 + 끝수. + """호표 줄 → 소계 묶음(799-1 · 801-3 · 보이기만) — 부른 단가산출 = 묶음 하나씩 · 직접 줄 = 한 묶음 + 끝수. 값 = `master_cut.engine` 이 버린 줄 비목(0.1 원) 그대로 · 합계 = 엔진 비목 합(1 원 버림) · - 끝수 = 합계 − 줄 합(금액 셈은 안 바꿈 · 사용자 확정 뒤에 정함). + 끝수 = 합계 − 묶음 합(금액 셈은 안 바꿈 · 사용자 확정 뒤에 정함). 묶음 꼴 = `master_equation_group`. """ - items, adds = logic.get("호표") or [], logic.get("덧줄") or [] + items = logic.get("호표") or [] skip = mcut.ms.skipped(logic) if mcut.ms.uses(logic) else set() rows: list[dict] = [] - pool: dict[str, dict] = {} for n, line in enumerate(cut.get("줄") or []): if n in skip: continue split = {k: line.get("비목", {}).get(k) or Decimal(0) for k in mf.COST_ITEMS} - if n < len(items) and items[n].get("종류") == "로직": - rows.append({"갈래": "블록", "이름": str(items[n].get("이름") or ""), **split}) - continue - kind = "덧줄" if n >= len(items) else "자원" - one = pool.setdefault( - kind, {"갈래": kind, "이름": kind, **dict.fromkeys(mf.COST_ITEMS, Decimal(0))} + item = items[n] if n < len(items) else {} + kind = "블록" if item.get("종류") == "로직" else "자원" if item else "덧줄" + rows.append( + {"갈래": kind, "이름": str(item.get("이름") or ""), "종류": item.get("종류"), **split} + | {"계": sum(split.values(), Decimal(0))} ) - for k in mf.COST_ITEMS: - one[k] += split[k] - rows += [pool[k] for k in ("자원", "덧줄") if k in pool] - for one in rows: - one["계"] = sum((one[k] for k in mf.COST_ITEMS), Decimal(0)) + got = grp.bundles(rows) rest = { - k: (total.get(k) or Decimal(0)) - sum((one[k] for one in rows), Decimal(0)) + k: (total.get(k) or Decimal(0)) - sum((one[k] for one in got["줄"]), Decimal(0)) for k in mf.BUNDLE } - return {"줄": rows, "끝수": rest} + return {"줄": got["줄"], "끝수": rest} def export(master, key: str, given: dict | None = None, depth: int = 0) -> dict: diff --git a/resources/master_data/scripts/master_equation_group.py b/resources/master_data/scripts/master_equation_group.py new file mode 100644 index 000000000..427f4c860 --- /dev/null +++ b/resources/master_data/scripts/master_equation_group.py @@ -0,0 +1,68 @@ +"""수식 탭 소계 묶음(PLAN 801-3 · 요구 662-3) — 돈 묶음 한 곳 · M01(`master_equation`) · M02(`M02_Sangeun_Equation_Full`) 같은 꼴. + +묶음 = 부른 · 더한 단가산출(갈래 블록) 하나씩 + 직접 줄(인력 · 재료 · 기계 · 덧줄 · 율) 전부 한 묶음(갈래 직접). +돈 없는 줄(계 0 · 바탕) · 뺀 줄 = 묶음 아님(식 줄로만 보임) · 합계 밖 줄 = `밖` 으로만. +머리 · 소계 · 합계 · 계 글자 판정 = 화면 부품(`ui_template_equation_sum.ts`) — 묶음 둘 이상 = 머리 · 소계 · +단가산출(블록) 둘 이상 = 합계 · 아니면 계. +시험 `resources/tester/test_801_3_groups.py`. +""" + +from __future__ import annotations + +from decimal import Decimal + +COSTS = ("노무비", "재료비", "경비") +_WORDS = ("인력", "재료", "기계") # 직접 묶음 이름 차례 + + +def _zero(row: dict) -> bool: + """돈 없는 바탕 줄 · 뺀 줄 — 묶음 아님.""" + if row.get("상태") == "뺌": + return True + return row["계"] == 0 and row.get("상태") in (None, "바탕") + + +def _name(direct: list[dict]) -> str: + """직접 묶음 이름 — 자원 갈래 말(인력 · 재료 · 기계) · 율 줄 = 이름 (율 %) · 덧줄뿐 = 비목 말.""" + words = [w for w in _WORDS if any(r.get("종류") == w for r in direct)] + words += [ + f"{r['이름']} ({r['수량']}%)" if r.get("수량") not in (None, "") else str(r["이름"]) + for r in direct + if r["갈래"] == "율" + ] + if not words: + words = [k for k in COSTS if any(r[k] != 0 for r in direct)] + return " · ".join(words) + + +def bundles(rows: list[dict]) -> dict: + """줄(갈래 블록 | 자원 | 덧줄 | 율 · 비목 칸 · 계) → `{줄: 묶음들, 밖: 합계 밖 줄들}` — 금액은 더하기만(셈 안 바꿈).""" + blocks: list[dict] = [] + direct: list[dict] = [] + out: list[dict] = [] + for row in rows: + if row.get("합밖"): + out.append({"이름": row["이름"], "밖금액": row.get("밖금액")}) + elif _zero(row): + continue + elif row["갈래"] == "블록": + blocks.append(row) + else: + direct.append(row) + lines = list(blocks) + if direct: + one = { + "갈래": "직접", + "이름": _name(direct), + **{k: sum((r[k] for r in direct), Decimal(0)) for k in COSTS}, + } + one["계"] = sum((one[k] for k in COSTS), Decimal(0)) + mine = [ # 더한 · 덮은 줄 — 묶음 속에 한 줄씩(고친 줄을 숨기지 않음) + {k: r[k] for k in ("이름", "상태", "계", "원래") if r.get(k) is not None} + for r in direct + if r.get("상태") in ("더함", "덮음") + ] + if mine: + one["낱줄"] = mine + lines.append(one) + return {"줄": lines, "밖": out} diff --git a/resources/tester/test_796_2c_m02_equation_full.py b/resources/tester/test_796_2c_m02_equation_full.py index 41a9eb897..0375c2a8d 100644 --- a/resources/tester/test_796_2c_m02_equation_full.py +++ b/resources/tester/test_796_2c_m02_equation_full.py @@ -41,7 +41,11 @@ def _read(folder, doc, inputs=None): def _rows(eq) -> dict: - return {r["cid"]: r for r in eq["소계"]["줄"]} + """묶음(801-3) — 더한 · 부른 단가산출 = cid 로 · 직접 줄 묶음 속 더함 · 덮음 줄 = 이름으로.""" + got = {r["cid"]: r for r in eq["소계"]["줄"] if "cid" in r} + for r in eq["소계"]["줄"]: + got |= {x["이름"]: x for x in r.get("낱줄", [])} + return got def _closes(eq, ans) -> None: @@ -80,21 +84,19 @@ def test_A_added_removed_covered_total_equals_sk(made): _closes(eq, ans) rows = _rows(eq) # 덮음 — 형틀목공 품 덮개 · 원래 = 고침 없는 SK 의 같은 줄 금액 - cover = rows["labor:LB000007#0"] + cover = rows["형틀목공"] # 직접 묶음 속 덮은 줄(이름) was = next(x for x in bare["줄"] if x["cid"] == "labor:LB000007#0")["금액"] - assert cover["상태"] == "덮음" and "품" in cover["덮개"] + assert cover["상태"] == "덮음" assert _d(cover["원래"]) == _d(was) - assert cover["노무비"] == D("137895") # 품 0.5 × 단가 275,790 - # 뺌 — 지운 줄 = 금액 0 · 원래 금액만 - gone = rows["labor:LB000002#0"] - assert gone["상태"] == "뺌" and gone["계"] == 0 - assert _d(gone["원래"]) == _d(next(x for x in bare["줄"] if x["cid"] == gone["cid"])["금액"]) - # 더함 — 기초(굴착기 641 × 2) · 인력 · 기초는 수식 나무 - basic, man = rows["add:#0"], rows["add:#1"] - assert ( - basic["상태"] == man["상태"] == "더함" and basic["갈래"] == "블록" and man["갈래"] == "자원" - ) - assert basic["계"] > 0 and man["노무비"] > 0 + assert cover["계"] == D("137895") # 품 0.5 × 단가 275,790 + # 뺌 — 지운 줄 = 묶음 아님(식 줄 표지로만 · 아래 `out["뺌"]`) + assert "labor:LB000002#0" not in rows and "보통인부" not in rows + # 더함 — 기초(굴착기 641 × 2) = 묶음 · 인력 = 직접 묶음 속 더한 줄 · 기초는 수식 나무 + basic = rows["add:#0"] + man = next(x for r in eq["소계"]["줄"] for x in r.get("낱줄", []) if x["상태"] == "더함") + assert basic["상태"] == man["상태"] == "더함" and basic["갈래"] == "블록" + assert [r["갈래"] for r in eq["소계"]["줄"]] == ["블록", "직접"] + assert basic["계"] > 0 and man["계"] > 0 assert basic["나무"]["열쇠"] == "GC001148" and basic["나무"]["줄"] # 나무 합계 × 수량 = 행(같은 입력) assert abs(basic["나무"]["합계"]["계"] * 2 - basic["계"]) <= 2 @@ -106,7 +108,8 @@ def test_A_added_removed_covered_total_equals_sk(made): assert out["뺌"] is True and out["표지"] == [] # 고침 없는 SK(plain)는 표지도 더함도 없음 — 합계 = 같은 바탕 금액 flat = _read(me, _doc(me, plain)) - assert {r["상태"] for r in flat["소계"]["줄"]} == {"바탕"} + assert {r.get("상태", "바탕") for r in flat["소계"]["줄"]} == {"바탕"} + assert [r["갈래"] for r in flat["소계"]["줄"]] == ["직접"] # 801-3 한 묶음 · 낱줄 없음 assert flat["합계"]["계"] == _d(bare["기준"]["계"]) assert not any("표지" in x for x in flat["줄"]) @@ -151,9 +154,9 @@ def test_B_unit_cover_rate_and_reference(world): eq = _read(me, doc) _closes(eq, ans) rows = _rows(eq) - price = rows["labor:LB000002#0"] - assert price["상태"] == "덮음" and "단가" in price["덮개"] and price["원래"] is not None - assert rows["add:#0"]["갈래"] == "율" and rows["add:#0"]["경비"] > 0 + pool = next(r for r in eq["소계"]["줄"] if r["갈래"] == "직접") + price = next(x for x in pool["낱줄"] if x["상태"] == "덮음") + assert price["원래"] is not None and "sub2임시 율" in pool["이름"] and pool["경비"] > 0 assert rows["add:#1"]["갈래"] == "블록" and rows["add:#1"]["상태"] == "더함" assert _line(eq, "호표", price["이름"])["표지"] == ["단가"] @@ -171,7 +174,7 @@ def test_C_imported_sk_same_rule(world): eq = _read(me, doc) _closes(eq, ans) rows = _rows(eq) - assert rows["add:#0"]["상태"] == "더함" and rows[cid]["상태"] == "덮음" + assert [x["상태"] for x in eq["소계"]["줄"][-1]["낱줄"]] == ["덮음", "더함"], rows def test_plain_sk_has_answer_table(world): @@ -183,7 +186,7 @@ def test_plain_sk_has_answer_table(world): def test_D_imported_outside_total_line_not_in_sum(world): - """D 가져온 식 `합밖` 줄 = 합계에 안 듦 → 행 0 · 밖금액 · 끝수에 안 섞임.""" + """D 가져온 식 `합밖` 줄 = 합계에 안 듦 → 묶음 아님 · `밖` 글 · 끝수에 안 섞임.""" me = world["personal"] row = _imported_row() row["호표"][1] = {**row["호표"][1], "합밖": True} @@ -193,6 +196,6 @@ def test_D_imported_outside_total_line_not_in_sum(world): ans = _calc(me, doc) eq = _read(me, doc) _closes(eq, ans) - out = [r for r in eq["소계"]["줄"] if r.get("합밖")] - assert len(out) == 1 and out[0]["계"] == 0 and out[0]["밖금액"] > 0 + out = eq["소계"]["밖"] + assert len(out) == 1 and out[0]["밖금액"] > 0 assert abs(eq["소계"]["끝수"]["계"]) < 1 diff --git a/resources/tester/test_799_1_subtotals.py b/resources/tester/test_799_1_subtotals.py index 83870dfa7..7b012419a 100644 --- a/resources/tester/test_799_1_subtotals.py +++ b/resources/tester/test_799_1_subtotals.py @@ -62,10 +62,10 @@ def test_굴착기_블록_한_줄_끝수(): def test_거푸집_자원_덧줄_지게_자원(): eq = _export("GC000211") - assert [r["갈래"] for r in eq["소계"]["줄"]] == ["자원", "덧줄"] + assert [r["갈래"] for r in eq["소계"]["줄"]] == ["직접"] # 801-3 직접 줄 한 묶음 _closes(eq) eq = _export("GC001140") - assert [r["갈래"] for r in eq["소계"]["줄"]] == ["자원"] + assert [r["갈래"] for r in eq["소계"]["줄"]] == ["직접"] _closes(eq) @@ -96,7 +96,7 @@ def test_결과_로직은_소계_없음(): def test_화면_부품이_서버_칸을_읽음(): ts = Path("ui_template/ui_template_equation_sum.ts").read_text(encoding="utf-8") - for name in ("노무비", "재료비", "경비", "끝수", "합계", "자원", "덧줄"): + for name in ("노무비", "재료비", "경비", "끝수", "합계"): assert name in ts line = Path("M01_MasterData/M01_MasterData_UI_LogicLab_Equation.ts").read_text(encoding="utf-8") assert "createEqSum(eq, open" in line # 796-2e 덧단추 자리 인자가 뒤에 붙음 diff --git a/resources/tester/test_801_3_groups.py b/resources/tester/test_801_3_groups.py new file mode 100644 index 000000000..16ece1cee --- /dev/null +++ b/resources/tester/test_801_3_groups.py @@ -0,0 +1,117 @@ +"""801-3 수식 탭 소계 묶음(요구 662-3) 지킴 — 금액 셈은 그대로 · 묶음만 다시. + +① 묶음 = 부른 단가산출(갈래 블록) 하나씩 + 직접 줄 한 묶음(갈래 직접) · 돈 없는 줄 = 묶음 아님 · 안쪽 말 0 +② M01 전수 — 묶음 합 + 끝수 = 합계(엔진 비목 합 그대로) · 직접 묶음 하나뿐 +③ 부품 — 묶음 하나 = 머리 없음 · 계 / 둘 이상 = 머리 · 소계 / 단가산출 둘 이상 = 합계 · 안쪽 말 글 0 +""" + +from __future__ import annotations + +import importlib +from decimal import Decimal as D +from pathlib import Path + +from M01_MasterData import M01_MasterData_Store as store + +mf = importlib.import_module("master_formula") +meq = importlib.import_module("master_equation") +grp = importlib.import_module("master_equation_group") + +COSTS = ("노무비", "재료비", "경비", "계") +BAD = ("자기", "자원", "덧줄", "블록") # 코드 안쪽 말 — 화면 글에 0 + + +def _w(): + return store.loaded()[1] + + +def _export(key: str) -> dict: + w = _w() + return meq.export(w, key, meq.given_of(w, w.logic(key), None)) + + +def _row(kind, name, cost=(0, 0, 0), **more): + got = dict(zip(COSTS[:3], (D(x) for x in cost), strict=True)) + return {"갈래": kind, "이름": name, **got, "계": sum(got.values(), D(0))} | more + + +def test_묶음_함수_하나뿐_직접_한_묶음(): + got = grp.bundles( + [ + _row("블록", "굴착기", (1, 2, 3)), + _row("블록", "쉬는 단가산출"), # 돈 0 = 묶음 아님 + _row("자원", "보통인부", (5, 0, 0), 종류="인력"), + _row("자원", "시멘트", (0, 7, 0), 종류="재료"), + _row("덧줄", "공구손료", (0, 0, 1)), + ] + ) + assert [r["갈래"] for r in got["줄"]] == ["블록", "직접"] + assert got["줄"][0]["이름"] == "굴착기" and got["줄"][1]["이름"] == "인력 · 재료" + assert (got["줄"][1]["노무비"], got["줄"][1]["재료비"], got["줄"][1]["경비"]) == (5, 7, 1) + only = grp.bundles([_row("덧줄", "공구손료", (0, 0, 1))])["줄"] + assert [r["이름"] for r in only] == ["경비"] and not grp.bundles([_row("블록", "빈")])["줄"] + + +def test_M02_상태_줄(): + got = grp.bundles( + [ + _row("자원", "보통인부", (5, 0, 0), 상태="더함", 종류="인력"), + _row("자원", "형틀목공", (4, 0, 0), 상태="덮음", 원래=D(9), 종류="인력"), + _row("자원", "지운 줄", 상태="뺌", 종류="인력"), + _row("자원", "밖 줄", 합밖=True, 밖금액=D(8)), + ] + ) + assert [x["이름"] for x in got["줄"][0]["낱줄"]] == ["보통인부", "형틀목공"] + assert got["밖"] == [{"이름": "밖 줄", "밖금액": D(8)}] and got["줄"][0]["계"] == 9 + + +def test_거푸집_한_묶음_굴착기_한_묶음(): + eq = _export("GC000211") + assert [r["갈래"] for r in eq["소계"]["줄"]] == ["직접"] + assert eq["소계"]["줄"][0]["이름"] == "인력 · 재료" + eq = _export("GC001148") + rows = eq["소계"]["줄"] + assert [(r["갈래"], r["이름"]) for r in rows] == [("블록", "굴착기(무한궤도)")] + assert eq["합계"]["계"] == D("641.0") and eq["소계"]["끝수"]["계"] == D("-0.3") + + +def test_M01_전수_묶음_합_끝수_합계_안쪽_말_0(): + w = _w() + many = seen = 0 + for tid, rows in sorted(w.index.items()): + if mf.mk.GROUP_OF.get(tid) != "로직": + continue + for logic in rows.values(): + if not logic.get("호표"): + continue + try: + eq = _export(logic["키"]) + except mf.FormulaError: + continue + sub = eq.get("소계") + if not sub: + continue + seen += 1 + lines = sub["줄"] + assert sum(r["갈래"] == "직접" for r in lines) <= 1, logic["키"] + assert all(r["계"] != 0 for r in lines), logic["키"] + for r in lines: + assert not any(b in r["이름"] for b in BAD), (logic["키"], r["이름"]) + for k in COSTS: + got = sum((r[k] for r in lines), D(0)) + sub["끝수"][k] + assert got == eq["합계"][k], (logic["키"], k) + many += sum(r["갈래"] == "블록" for r in lines) >= 2 + assert seen > 400 and many >= 10, (seen, many) + + +def test_부품_머리_소계_합계_규칙_안쪽_말_0(): + ts = Path("ui_template/ui_template_equation_sum.ts").read_text(encoding="utf-8") + assert "sub.줄.length >= 2" in ts # 묶음 하나 = 머리 · 소계 없음 + assert '"합계" : "계"' in ts and '=== "블록").length >= 2' in ts # 합계 = 단가산출 둘 이상 + ui = "".join( + line + for line in ts.splitlines(keepends=True) + if not line.lstrip().startswith(("*", "/*", "//", "/**")) + ) + for word in ("자기 자원", "덧줄", "KIND"): + assert word not in ui, word diff --git a/ui_template/ui_template_equation.ts b/ui_template/ui_template_equation.ts index b438eef97..777799ce6 100644 --- a/ui_template/ui_template_equation.ts +++ b/ui_template/ui_template_equation.ts @@ -45,7 +45,7 @@ export interface EqNode { 사슬?: boolean; } -/** 단가산출 소계 줄(799-1) — 갈래 블록 | 자원 | 덧줄 | 율 · M02 SK(796-2c) 는 셈 답 한 줄마다 + 상태 */ +/** 단가산출 소계 묶음(799-1 · 801-3) — 갈래 블록(부른 · 더한 단가산출) | 직접(인력 · 재료 · 기계 · 덧줄 한 묶음) · M02 SK 는 상태 */ export interface EqSumRow { 갈래: string; 이름: string; @@ -61,9 +61,8 @@ export interface EqSumRow { /** 덮음 · 뺌 — 고치기 전 금액 */ 원래?: unknown; 까닭?: string; - /** 합계 밖 줄(엔진이 비목 합에서 뺌) — 행 금액은 0 · 밖금액에 원래 금액 */ - 합밖?: boolean; - 밖금액?: unknown; + /** 직접 묶음 속 더한 · 덮은 줄(M02 SK) */ + 낱줄?: { 이름: string; 상태: string; 계: unknown; 원래?: unknown }[]; /** 더한 기초의 수식(접힘 블록) */ 나무?: EqTree | null; /** 더한 기초 차례(M02 SK 더한줄 · 연결 끊기 796-2e) */ @@ -86,8 +85,13 @@ export interface EqTree { 줄: EqLine[]; 변수표: EqVarRow[]; 합계: Record; - /** 799-1 — 하위 로직 블록마다 노 · 재 · 경 소계 + 끝수(합계 − 줄 합) · 결과 로직은 없음 */ - 소계?: { 줄: EqSumRow[]; 끝수: Record; 끝수까닭?: string }; + /** 799-1 · 801-3 — 묶음마다 노 · 재 · 경 소계 + 끝수(합계 − 묶음 합) · 합계 밖 줄 · 결과 로직은 없음 */ + 소계?: { + 줄: EqSumRow[]; + 끝수: Record; + 끝수까닭?: string; + 밖?: { 이름: string; 밖금액: unknown }[]; + }; /** M02 SK — 셈 답이 안 서서 바탕 줄 합계를 보일 때의 까닭 */ 답까닭?: string; /** 800-2 — 오른쪽 변수 정의 영역(고른 옵션 갈래만 · 같은 기호 한 줄 · 값 · 단위 · DB 열쇠 없음) */ diff --git a/ui_template/ui_template_equation_sum.ts b/ui_template/ui_template_equation_sum.ts index eab9950c9..38e65556d 100644 --- a/ui_template/ui_template_equation_sum.ts +++ b/ui_template/ui_template_equation_sum.ts @@ -1,11 +1,11 @@ /* ============================================================================= * ui_template_equation_sum.ts - * 부품 「수식」 셋째 파일(799-1 · 796-2c · 800-4 · 요구 655-1 · 661 · 요구서_655 2 장 4) — 단가산출 소계 · 합계(보이기만 · 금액 셈 그대로). - * 묶음 = 기준 단가산출마다(하위 로직 블록 · 자기 자원 · 덧줄) 「가. 이름」 머리 → 노무비 · 재료비 · 경비 줄 → 소 계(노 · 재 · 경 · 계 칸) - * 맨 끝 「소 계」 묶음 = 묶음 소계를 더한 식(노무비 : 2,299.4 + 5,162 = 7,461.4) → 끝수(합계 − 묶음 합 · 0 이면 줄 없음) → 합계(엔진 비목 합) - * 값 = 서버 `소계`(M01 `master_equation.subtotals` · M02 `M02_Sangeun_Equation_Full` = SK 셈 답) · 소계가 없으면(결과 로직) 아무것도 안 그림 - * M02 SK 묶음 = 상태 알약(더함 · 덮음 · 뺌) · 덮음 갈래 · 원래 금액 · 수량 · 더한 기초는 접힘 수식 블록 · 뺌 = 흐리게 · 금액 0 - * 자리 = 하위 로직 블록 묶음은 그 블록 식 줄 바로 아래(`inline` · 줄 열쇠 = 블록 이름 · `createEqChain` 의 `after`) · 나머지(자기 자원 · 덧줄 · 더한 기초) · 맨 끝 소 계 · 끝수 · 합계는 `tail` + * 부품 「수식」 셋째 파일(799-1 · 796-2c · 800-4 · 801-3 · 요구 655-1 · 661 · 662-3 · 요구서_655 2 장 4) — 단가산출 소계 · 합계(보이기만 · 금액 셈 그대로). + * 묶음 = 부른 · 더한 단가산출 하나씩 + 직접 줄(인력 · 재료 · 기계 · 덧줄) 한 묶음 — 값 = 서버 `소계`(M01 `master_equation.subtotals` · M02 `M02_Sangeun_Equation_Full` · 꼴 = `master_equation_group`) · 소계가 없으면(결과 로직) 아무것도 안 그림 + * 묶음 하나뿐 = 머리 없음 · 노무비 · 재료비 · 경비 줄 → 「계」 한 줄(끝수가 있으면 끝수 줄 먼저) + * 묶음 둘 이상 = 묶음마다 「가. 이름」 머리 → 노무비 · 재료비 · 경비 줄 → 소 계 · 맨 끝 묶음 소계를 더한 식 → 끝수 → 합계(단가산출이 둘 이상) 아니면 계 + * M02 SK = 상태 알약(더함 · 덮음) · 원래 금액 · 더한 기초는 접힘 수식 블록(더한 기초 하나뿐이어도 머리 줄은 둠 — 연결 끊기 단추 자리) · 합계 밖 줄은 글 한 줄 + * 자리 = 하위 로직 블록 묶음은 그 블록 식 줄 바로 아래(`inline` · 줄 열쇠 = 블록 이름 · `createEqChain` 의 `after`) · 나머지(직접 줄 · 더한 기초) · 맨 끝 합계 · 끝수는 `tail` * 쓰는 법 = `createEqSum(tree, open?, hooks?, lineKeys?)` → { inline, tail } | null — `lineKeys` = 수식 영역 호표 줄 열쇠들 · `open` = 펼친 블록 이름표 모음 · * `hooks` = 페이지가 더한 기초 줄 머리 단추(`head`) · 접힘 속(`body`)을 바꿔 끼움(796-2e 연결 끊기) * ========================================================================== */ @@ -18,7 +18,6 @@ import { createEqChain } from "./ui_template_equation_line"; const COLS = ["노무비", "재료비", "경비", "계"] as const; const PARTS = ["노무비", "재료비", "경비"] as const; type Col = (typeof COLS)[number]; -const KIND: Record = { 자원: "자기 자원", 덧줄: "덧줄" }; const TONE: Record = { 더함: "success", 덮음: "warning", 뺌: "neutral" }; const ORDER = "가나다라마바사아자차카타파하"; @@ -66,17 +65,23 @@ const part = (label: string, text: string): HTMLElement => { }; /** 기준 단가산출 묶음 하나 — 「가. 이름」 머리(+ 상태 · 접힘 수식) → 노 · 재 · 경 줄 → 소 계 */ -function group(r: EqSumRow, open: Set, at: number, hooks?: EqSumHooks): HTMLElement { +function group( + r: EqSumRow, + open: Set, + at: number, + heads: boolean, + hooks?: EqSumHooks, +): HTMLElement { const box = node("div", "ui-eq__grp"); box.dataset.eqSumItem = r.갈래; box.dataset.eqSumState = r.상태 ?? "바탕"; const name = node("div", "ui-eq__grp-head"); - const title = r.갈래 === r.이름 ? (KIND[r.갈래] ?? r.이름) : r.이름; + const title = r.이름; const tree = r.나무; const id = `sum:${at}:${r.이름}`; const body = node("div", "ui-eq__sum-tree"); body.hidden = true; - name.append(node("span", "ui-eq__grp-mark", `${mark(at)}.`)); + if (heads) name.append(node("span", "ui-eq__grp-mark", `${mark(at)}.`)); if (tree) { const head = createButton({ label: title, @@ -107,35 +112,32 @@ function group(r: EqSumRow, open: Set, at: number, hooks?: EqSumHooks): name.append(createBadge(`${r.상태}${detail}`, TONE[r.상태] ?? "neutral", { size: 18 })); } name.append(...(hooks?.head?.(r, at) ?? [])); - const out = !!r.합밖; - if (out) name.append(createBadge("합 밖", "neutral", { size: 18 })); const notes: string[] = []; - if (out) notes.push(`합계에 안 듦 ${eqText(r.밖금액) || "0"}`); if (r.상태 === "더함" && r.수량 !== undefined && r.수량 !== null) notes.push(`× ${eqText(r.수량)}`); if (r.원래 !== undefined && r.원래 !== null && r.상태 !== "더함") notes.push(`원래 ${eqText(r.원래)}`); if (r.까닭) notes.push(r.까닭); if (notes.length) name.append(node("span", "ui-eq__sum-was", notes.join(" · "))); - box.append(name, body); + const shown = heads || !!tree || notes.length > 0 || (!!r.상태 && r.상태 !== "바탕"); + if (shown) box.append(name, body); for (const k of PARTS) if (num(r[k]) !== 0) box.append(part(k, eqText(r[k]))); - const total = row("소계", "소 계", (k) => r[k]); - total.classList.add("ui-eq__grp-total"); - if (out) total.dataset.eqSumOut = "1"; - box.append(total); + for (const x of r.낱줄 ?? []) { + const was = x.원래 !== undefined && x.원래 !== null ? ` · 원래 ${eqText(x.원래)}` : ""; + box.append(part(`${x.이름} · ${x.상태}`, `${eqText(x.계) || "0"}${was}`)); + } + if (heads) { + const total = row("소계", "소 계", (k) => r[k]); + total.classList.add("ui-eq__grp-total"); + box.append(total); + } return box; } -/** 맨 끝 「소 계」 묶음 — 묶음 소계를 더한 식 */ -function final(rows: EqSumRow[], at: number): HTMLElement { +/** 맨 끝 — 묶음 소계를 더한 식(노무비 : 2,299.4 + 5,162 = 7,461.4) */ +function final(rows: EqSumRow[]): HTMLElement { const box = node("div", "ui-eq__grp"); - box.dataset.eqSumItem = "소계"; - const head = node("div", "ui-eq__grp-head"); - head.append( - node("span", "ui-eq__grp-mark", `${mark(at)}.`), - node("span", "ui-eq__sum-label", "소 계"), - ); - box.append(head); + box.dataset.eqSumItem = "합"; for (const k of PARTS) { const adds = rows.map((r) => num(r[k])).filter((v) => v !== 0); if (!adds.length) continue; @@ -160,21 +162,27 @@ export function createEqSum( lineKeys: string[] = [], ): EqSumParts | null { const sub = tree.소계; - if (!sub?.줄.length) return null; + if (!sub) return null; const inline = new Map(); const box = node("div", "ui-eq__sum"); box.dataset.eqSum = ""; + const heads = sub.줄.length >= 2; // 묶음 하나뿐 = 머리 · 소계 없음 + const word = sub.줄.filter((r) => r.갈래 === "블록").length >= 2 ? "합계" : "계"; // 합계 = 단가산출 둘 이상 sub.줄.forEach((r, at) => { - const one = group(r, open, at, hooks); + const one = group(r, open, at, heads, hooks); if (r.갈래 === "블록" && lineKeys.includes(r.이름) && !inline.has(r.이름)) inline.set(r.이름, one); else box.append(one); }); - box.append(final(sub.줄, sub.줄.length)); + if (heads) box.append(final(sub.줄)); const rest = sub.끝수; const odd = COLS.some((k) => Number(rest[k] ?? 0) !== 0); if (odd) box.append(row("끝수", "끝수", (k) => rest[k])); - box.append(row("합계", "합계", (k) => tree.합계[k])); + box.append(row(word, word, (k) => tree.합계[k])); + for (const x of sub.밖 ?? []) + box.append( + node("div", "ui-eq__sum-note", `합계에 안 듦 · ${x.이름} ${eqText(x.밖금액) || "0"}`), + ); const note = [odd ? sub.끝수까닭 : "", tree.답까닭].filter(Boolean).join(" · "); if (note) box.append(node("div", "ui-eq__sum-note", note)); return { inline, tail: box }; From d7e6fe5cf86d4c513dc60b4d33650a4b86dcaa4b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 7 Nov 2026 18:00:30 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat(801-5):=20M01=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=EA=B8=80=20=EA=B1=B4=EC=84=A4=ED=92=88?= =?UTF-8?q?=EC=85=88=20=EC=9B=90=EB=AC=B8=20=EA=BC=B4=20=E2=80=94=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EB=8B=B9=20=E3=8E=A5=20=C2=B7=20=E3=8E=A1=20?= =?UTF-8?q?=E2=86=92=20hr=20=C2=B7=20=EC=B4=88=20=E2=86=92=20sec=20=C2=B7?= =?UTF-8?q?=20mm=20=C2=B7=20cm=20=C2=B7=20kg=20=E2=86=92=20=E3=8E=9C=20?= =?UTF-8?q?=C2=B7=20=E3=8E=9D=20=C2=B7=20=E3=8E=8F=20=C2=B7=20TON=20=C2=B7?= =?UTF-8?q?=20t=20=E2=86=92=20ton=20=C2=B7=20km/hr=20=E2=86=92=20=E3=8E=9E?= =?UTF-8?q?/hr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 대응표 master_unit_text.RULES(규칙마다 원문 근거 절) · 빌더 build_단위표기_801_5(--check · --list · 셈 지문 전후 같을 때만 씀) - M01 로직 53 개 · 칸 79 개 글만 바꿈(변수 입력 · 중간 단위 · 결과단위) · 계수 · 소요량 표 값칸 · 호표 줄 단위 · 원문이 두 꼴을 다 쓰는 단위(㎞ · km · 분 · min)는 안 건드림 - M02 사본 새로 씀 도구 M02_Copy_Units(M02_Copy_Symbols 의 fill 만 바꿔 끼움) — 사본 16 문서 28 칸 · 금액 전후 같음 - 검토 목록 docs/raw/요구_1104/_목록_801-5_단위표기.xlsx - 옛 시험 기대 고침: test_796_3 굴착기 cm 단위 초 → sec(원문 표 머리 「싸이클시간(Sec)」) - 새 test_801_5_units 6 Co-Authored-By: Claude Sonnet 5.5 --- M02_MasterTemplete/M02_Copy_Symbols.py | 10 +- M02_MasterTemplete/M02_Copy_Units.py | 55 +++++++ docs/raw/요구_1104/_목록_801-5_단위표기.xlsx | Bin 0 -> 9934 bytes .../scripts/build_단위표기_801_5.py | 142 ++++++++++++++++++ .../master_data/scripts/master_unit_text.py | 89 +++++++++++ .../로직_건설품셈_01장_적용기준.json | 6 +- .../로직_건설품셈_01장_철골공사.json | 12 +- .../로직_건설품셈_02장_가설공사.json | 2 +- .../로직_건설품셈_05장_기초공사.json | 4 +- .../로직_건설품셈_07장_항만공사.json | 8 +- .../로직_건설품셈_08장_건설기계.json | 24 +-- .../로직_건설품셈_08장_지반조사.json | 16 +- .../로직_건설품셈_10장_창호_및_유리공사.json | 2 +- .../로직_건설품셈_13장_플랜트설비공사.json | 2 +- .../로직_산림품셈_03장_작업장_관리.json | 2 +- .../로직_산림품셈_05장_식재_및_녹화.json | 6 +- .../로직_산림품셈_06장_생육보조.json | 2 +- .../master_data/로직_산림품셈_09장_토공.json | 46 +++--- .../로직_산림품셈_10장_자재․장비_운반.json | 10 +- .../master_data/로직_산림품셈_11장_가설.json | 4 +- .../로직_산림품셈_13장_구조물.json | 6 +- resources/master_data/로직_자체.json | 4 +- resources/master_template/labor/item1.json | 6 +- resources/master_template/labor/item1_2.json | 6 +- resources/master_template/labor/item3.json | 4 +- resources/master_template/labor/item3_2.json | 6 +- resources/master_template/labor/item3_3.json | 6 +- resources/tester/test_796_3_var_names.py | 2 +- resources/tester/test_801_5_units.py | 138 +++++++++++++++++ 29 files changed, 522 insertions(+), 98 deletions(-) create mode 100644 M02_MasterTemplete/M02_Copy_Units.py create mode 100644 docs/raw/요구_1104/_목록_801-5_단위표기.xlsx create mode 100644 resources/master_data/scripts/build_단위표기_801_5.py create mode 100644 resources/master_data/scripts/master_unit_text.py create mode 100644 resources/tester/test_801_5_units.py diff --git a/M02_MasterTemplete/M02_Copy_Symbols.py b/M02_MasterTemplete/M02_Copy_Symbols.py index d93d7b995..3a1c6b46d 100644 --- a/M02_MasterTemplete/M02_Copy_Symbols.py +++ b/M02_MasterTemplete/M02_Copy_Symbols.py @@ -59,8 +59,8 @@ def fill(copy: dict, whole: Any) -> tuple[dict, int, int]: return {**copy, "로직": rows, "판": skc._fp(rows)}, done, lost -def check(kind: str, path: Path) -> dict[str, Any]: - """문서 하나 — {상태: 있음 | 채울것없음 | 같음 | 다름 | 못셈, 채움, 못닿음, 사본?}.""" +def check(kind: str, path: Path, fill_fn=None) -> dict[str, Any]: + """문서 하나 — {상태: 있음 | 채울것없음 | 같음 | 다름 | 못셈, 채움, 못닿음, 사본?} · `fill_fn` = 다른 고침(801-5 단위 글).""" from M02_MasterTemplete import M02_MasterTemplete_Store as store doc = json.loads(path.read_bytes()) @@ -69,7 +69,7 @@ def check(kind: str, path: Path) -> dict[str, Any]: try: with store.at(path.parent.parent): live = skc.whole_() - new, done, lost = fill(doc[skc.KEY], live) + new, done, lost = (fill_fn or fill)(doc[skc.KEY], live) if not done: return {"상태": "채울것없음", "못닿음": lost} clean = mig._clean(kind, doc, path.stem) @@ -81,7 +81,7 @@ def check(kind: str, path: Path) -> dict[str, Any]: return {**row, "사본": new} if before == after else {**row, "전": before, "후": after} -def run(apply: bool = False, backup: Path | None = None) -> list[dict[str, Any]]: +def run(apply: bool = False, backup: Path | None = None, fill_fn=None) -> list[dict[str, Any]]: """마스터 · 회사 · 개인 층 문서마다 {어디, 종류, 키, 상태, 씀, …} · `apply` = 같음만 씀.""" if apply and backup is None: raise ValueError("--apply 는 --backup 폴더가 있어야 함") @@ -89,7 +89,7 @@ def run(apply: bool = False, backup: Path | None = None) -> list[dict[str, Any]] for where_, kind, path, project in mig._files(): if project: continue - got = check(kind, path) + got = check(kind, path, fill_fn) copy = got.pop("사본", None) row = {"어디": where_, "종류": kind, "키": path.stem, **got, "씀": False} if apply and copy: diff --git a/M02_MasterTemplete/M02_Copy_Units.py b/M02_MasterTemplete/M02_Copy_Units.py new file mode 100644 index 000000000..0710552b8 --- /dev/null +++ b/M02_MasterTemplete/M02_Copy_Units.py @@ -0,0 +1,55 @@ +"""옛 로직 사본 단위 글 새로 쓰기 — 한 번 돌리는 도구(PLAN 801-5 · 요구 663-2). + +M01 로직 단위 글을 건설품셈 원문 꼴(`master_unit_text`)로 바꾼 뒤, 사본 줄의 입력 · 중간 `단위` · `결과단위` 를 +같은 규칙으로 바꿈(`M02_Copy_Symbols` 와 같은 길 — 금액 전후 같을 때만 씀 · 쓰기 전 `--backup` 사본 · +사본 `판` 다시 셈 · 프로젝트 층 문서는 안 건드림). + + ./venv/Scripts/python.exe -m M02_MasterTemplete.M02_Copy_Units # 보기만 + ./venv/Scripts/python.exe -m M02_MasterTemplete.M02_Copy_Units --apply --backup tmp/801_5 +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + +from M02_MasterTemplete import M02_Copy_Symbols as cs +from M02_MasterTemplete import M02_Sangeun_Copy as skc + + +def fill(copy: dict, whole: Any) -> tuple[dict, int, int]: + """(새 사본, 바꾼 칸 수, 0) — 줄 단위 글만 고치고 `판` 다시 셈.""" + import master_unit_text as ut # M01 이 길을 둔 뒤 + + done, rows = 0, [] + for row in copy["로직"]: + new = {**row} + for part in ("입력", "중간"): + specs = [] + for spec in row.get(part) or []: + now = ut.canon(spec.get("단위")) + done += now != (spec.get("단위") or "") + specs.append({**spec, "단위": now} if now != (spec.get("단위") or "") else spec) + if row.get(part): + new[part] = specs + if row.get("결과단위"): + now = ut.canon(row["결과단위"]) + done += now != row["결과단위"] + new["결과단위"] = now + rows.append(new) + return {**copy, "로직": rows, "판": skc._fp(rows)}, done, 0 + + +if __name__ == "__main__": # 브레인 지시 뒤만 --apply + args = sys.argv[1:] + at = Path(args[args.index("--backup") + 1]) if "--backup" in args else None + rows = cs.run("--apply" in args, at, fill) + for one in rows: + if one["상태"] not in ("사본없음", "채울것없음", "같음"): + print(json.dumps(one, ensure_ascii=False, default=str)) + count: dict[str, int] = {} + for one in rows: + count[one["상태"]] = count.get(one["상태"], 0) + 1 + print(json.dumps(count, ensure_ascii=False), "바꿈", sum(r.get("채움", 0) for r in rows)) diff --git a/docs/raw/요구_1104/_목록_801-5_단위표기.xlsx b/docs/raw/요구_1104/_목록_801-5_단위표기.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8734267747c5bee54c8e7b471e994a7168815f10 GIT binary patch literal 9934 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 O|3R-_i=FcK+y4MPt}`Y8 literal 0 HcmV?d00001 diff --git a/resources/master_data/scripts/build_단위표기_801_5.py b/resources/master_data/scripts/build_단위표기_801_5.py new file mode 100644 index 000000000..cd2f58d86 --- /dev/null +++ b/resources/master_data/scripts/build_단위표기_801_5.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +"""M01 로직 단위 글을 건설품셈 원문 꼴로 (PLAN 801-5 · 요구 663-2) — 글만 바꿈 · 셈 지문 그대로. + +돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_단위표기_801_5.py [--check | --list 파일.xlsx] +- 대상 = M01 로직 줄의 입력 · 중간 변수 `단위` · `결과단위` — 규칙 · 근거 절 = `master_unit_text.RULES`. +- 계수 · 소요량 표 값칸 · 호표 줄 단위 · 사용자가 이미 원문 꼴로 쓴 칸은 안 건드림(이미 같으면 그대로 — 두 번 돌려도 같음). +- 쓰기 전 지문 대조 = 줄마다 엔진 셈 · 끝수 지문이 전후 같을 때만 씀(다르면 멈춤). +- `--check` = 쓰지 않고 쓸 것이 남았는지만(남았으면 종료 코드 1) · `--list` = 검토 목록 xlsx 만 씀(마스터 안 씀). +""" + +from __future__ import annotations + +import copy +import hashlib +import json +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE.parents[2])) +import master_auto as ma # noqa: E402 +import master_cut as mcut # noqa: E402 +import master_formula as mf # noqa: E402 +import master_unit_text as ut # noqa: E402 + +from M01_MasterData import M01_MasterData_Store as st # noqa: E402 + + +def plan(files: dict[str, dict]) -> tuple[list[dict], list[dict], dict[str, dict]]: + """(저장 묶음, 목록 줄들, 새 files) — 읽기 전용.""" + batch, rows, new_files = [], [], {} + for name, data in files.items(): + if data.get("그룹") != "로직": + new_files[name] = data + continue + changes, new_data = [], copy.deepcopy(data) + for row in new_data.get("줄", []): + hit = False + for kind in ("입력", "중간"): + for spec in row.get(kind) or []: + now = ut.canon(spec.get("단위")) + if now != (spec.get("단위") or ""): + rows.append(_item(row, kind, spec["이름"], spec["단위"], now)) + spec["단위"], hit = now, True + if row.get("결과단위"): + now = ut.canon(row["결과단위"]) + if now != row["결과단위"]: + rows.append(_item(row, "결과단위", "", row["결과단위"], now)) + row["결과단위"], hit = now, True + if hit: + changes.append({"op": "edit", "key": row["키"], "row": row}) + new_files[name] = new_data + if changes: + batch.append({"file": name, "version": st.read(name)[1], "changes": changes}) + return batch, rows, new_files + + +def _item(row: dict, kind: str, var: str, before: str, after: str) -> dict: + return { + "로직": row["키"], + "이름": row.get("이름", ""), + "칸": kind, + "변수": var, + "지금": before, + "원문 꼴": after, + "규칙": ut.rule_of(before), + } + + +def fingerprint(whole: mf.Master, row: dict) -> str: + """줄 하나의 엔진 셈 · 끝수 지문 — 멈춤도 지문.""" + try: + key = str(row["키"]) + raw = mf.run( + whole, + key, + { + **ma.values(whole, row), + **{k: v for k, v in ma.defaults(whole, row).items() if v is not None}, + }, + ) + text = json.dumps(raw, default=str, sort_keys=True, ensure_ascii=False) + text += json.dumps( + mcut.engine(whole, key, raw), default=str, sort_keys=True, ensure_ascii=False + ) + except Exception as e: # noqa: BLE001 + text = f"ERR {type(e).__name__} {e}" + return hashlib.sha256(text.encode()).hexdigest()[:16] + + +def differing(old: dict[str, dict], new: dict[str, dict]) -> list[str]: + """지문이 바뀐 로직 키들 — 고친 줄만 대조.""" + before, after = mf.Master(old), mf.Master(new) + out = [] + for name, data in new.items(): + if data.get("그룹") != "로직": + continue + was = {r["키"]: r for r in old[name]["줄"]} + for row in data["줄"]: + if row != was[row["키"]] and fingerprint(before, was[row["키"]]) != fingerprint( + after, row + ): + out.append(row["키"]) + return out + + +def write_list(rows: list[dict], path: Path) -> None: + import openpyxl + + book = openpyxl.Workbook() + sheet = book.active + sheet.title = "바뀜" + cols = ["로직", "이름", "칸", "변수", "지금", "원문 꼴", "규칙"] + sheet.append(cols) + for r in rows: + sheet.append([r[c] for c in cols]) + rule = book.create_sheet("대응표") + rule.append(["규칙", "바꿀 글", "근거 절(건설공사 표준품셈 본문 아래)", "바뀐 칸 수"]) + for name, _, to, _, files in ut.RULES: + rule.append([name, to, " · ".join(files), sum(name in r["규칙"] for r in rows)]) + path.parent.mkdir(parents=True, exist_ok=True) + book.save(path) + + +def main() -> None: + files, _ = st.loaded() + batch, rows, new_files = plan(files) + print(f"쓸 것 — 로직 {len({r['로직'] for r in rows})} · 칸 {len(rows)}") + if "--list" in sys.argv: + write_list(rows, Path(sys.argv[sys.argv.index("--list") + 1])) + return + if "--check" in sys.argv: + raise SystemExit(1 if batch else 0) + if batch: + bad = differing(files, new_files) + if bad: + raise SystemExit(f"셈 지문이 달라지는 로직 {len(bad)} — 멈춤: {bad[:10]}") + print(st.save(batch)) + + +if __name__ == "__main__": + main() diff --git a/resources/master_data/scripts/master_unit_text.py b/resources/master_data/scripts/master_unit_text.py new file mode 100644 index 000000000..f2bea8989 --- /dev/null +++ b/resources/master_data/scripts/master_unit_text.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +"""M01 로직 단위 글 → 건설품셈 원문 꼴(PLAN 801-5 · 요구 663-2) — 글만 바꿈 · 셈 · 환산 안 바꿈. + +`canon(단위 글)` = 아래 `RULES` 를 차례로 적용(이미 원문 꼴이면 그대로 · 두 번 돌려도 같음). +`RULES` 한 줄 = (이름, 지금 글 정규식, 바꿀 글, 원문 글, 근거 절들) — 근거 절 = `EVIDENCE_ROOT` 아래 파일 · +그 파일 안에 원문 글이 실제로 있어야 함(시험 `test_801_5_units`). 원문이 두 꼴을 다 쓰는 단위(㎞ · km · 분 · min)는 안 건드림. +단위 칸 중 원문 표를 그대로 옮긴 계수 · 소요량 표 값칸 · 호표 줄 단위(요소 단위)는 대상 아님. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +EVIDENCE_ROOT = Path("resources/knowledge/original/원가계산/건설공사_표준품셈/본문") +_A = r"(? str: + """원문 꼴 단위 글 — 규칙에 안 걸리면 그대로.""" + text = str(unit or "") + for _, pat, to, _, _ in _COMPILED: + text = pat.sub(to, text) + return text + + +def rule_of(before: str) -> str: + """바뀐 단위가 걸린 규칙 이름들(검토 목록 · 대응표용).""" + out, text = [], str(before or "") + for name, pat, to, _, _ in _COMPILED: + if pat.search(text): + out.append(name) + text = pat.sub(to, text) + return " · ".join(out) diff --git a/resources/master_data/로직_건설품셈_01장_적용기준.json b/resources/master_data/로직_건설품셈_01장_적용기준.json index db6024264..e2ecee7d4 100644 --- a/resources/master_data/로직_건설품셈_01장_적용기준.json +++ b/resources/master_data/로직_건설품셈_01장_적용기준.json @@ -217,7 +217,7 @@ { "이름": "인원", "식": "무게 / 운반량", "단위": "인" }, { "이름": "환산계수", "식": "만약(경사각도 > 0, 찾기(CC000347, 각도=경사각도).환산계수, 1)" }, { "이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "km" }, - { "이름": "왕복평균속도", "식": "찾기(CC000345, 도로상태=도로상태).왕복평균속도", "단위": "km/hr" }, + { "이름": "왕복평균속도", "식": "찾기(CC000345, 도로상태=도로상태).왕복평균속도", "단위": "㎞/hr" }, { "이름": "준비작업시간", "식": "찾기(CC000346).준비작업시간", "단위": "분" }, { "이름": "작업시간", "식": "만약(실작업시간 > 0, 실작업시간, 찾기(CC000342).실작업시간)", "단위": "분" } ], @@ -280,7 +280,7 @@ "구분": "건설품셈 공통", "상세구분": "01장 적용기준", "이름": "건설폐기물 발생량 — 건설 공통 1-5-4", - "결과단위": "TON", + "결과단위": "ton", "출처": "건설품셈 공통 1-5-4 2", "개정": [2011, 2014, 2017, 2020], "소유": "공용", @@ -293,7 +293,7 @@ { "이름": "면적", "설명": "단위면적 산출 면적", "단위": "㎡", "출처": "건설품셈 공통 1-5-4 2" }, { "이름": "증가율", "설명": "폐콘크리트 수량 증가(%) — 주③ 파일 15 · 20% 이내 · 주④ 세륜 개소당 3% 이내 · 0 = 없음", "단위": "%", "출처": "건설품셈 공통 1-5-4 2 주③ ④" } ], - "중간": [{ "이름": "원단위", "식": "찾기(CC000519, 공사=공사, 구조=구조, 폐기물=폐기물).발생량", "단위": "TON/㎡" }, { "이름": "증가", "식": "만약(폐기물 == '폐콘크리트류', 증가율 / 100, 0)" }], + "중간": [{ "이름": "원단위", "식": "찾기(CC000519, 공사=공사, 구조=구조, 폐기물=폐기물).발생량", "단위": "ton/㎡" }, { "이름": "증가", "식": "만약(폐기물 == '폐콘크리트류', 증가율 / 100, 0)" }], "결과": { "식": "면적 * 원단위 * (1 + 증가)", "출처": "건설품셈 공통 1-5-4 2" }, "끝수": null, "변수표": { diff --git a/resources/master_data/로직_건설품셈_01장_철골공사.json b/resources/master_data/로직_건설품셈_01장_철골공사.json index 7a61f589c..b14759e61 100644 --- a/resources/master_data/로직_건설품셈_01장_철골공사.json +++ b/resources/master_data/로직_건설품셈_01장_철골공사.json @@ -12,13 +12,13 @@ "구분": "건설품셈 건축", "상세구분": "01장 철골공사", "이름": "철골 공장가공조립(기본철골공수)", - "결과단위": "원/t", + "결과단위": "원/ton", "출처": "건설품셈 건축 1-1-1", "개정": [2008, 2013], "소유": "공용", "비고": "공수에 비계 · 보조공 포함(주①) · 제경비에 산재보험료 · 간접노무비 · 일반관리비 · 이윤 없음(주③) · 용접은 1-1-3 · 부자재는 1-1-2(현장 세우기 제외) · 서비스볼트 규격은 설계에 따름 · 해석: 작업난이도(1-1-2)를 공수에 곱함", "입력": [ - { "이름": "강재총사용량", "단위": "t", "출처": "건설품셈 건축 1-1-1" }, + { "이름": "강재총사용량", "단위": "ton", "출처": "건설품셈 건축 1-1-1" }, { "이름": "부재", "고르기": ["전용접부재", "H형강부재"], "출처": "건설품셈 건축 1-1-1 비고", "비고": "전용접부재(Built up) · H형강부재(Rolled shape)" }, { "이름": "작업난이도", "단위": "배", "범위": [0.8, 1.2], "출처": "건설품셈 건축 1-1-2", "비고": "구조공별 — 가공부재 종류 적은 구조 0.8∼0.95 · 표준라멘 1.0 · 종류 많은 구조 1.05∼1.2" }, { "이름": "공수직종", "고르기": ["1011", "1012"], "출처": "건설품셈 건축 1-1-1", "비고": "원문 「인·일」에 직종 없음 — 설계자가 고름(1011 철골공 · 1012 용접공) · 공장제작 노임을 쓰면 관리자가 바꿈" } @@ -81,13 +81,13 @@ "구분": "건설품셈 건축", "상세구분": "01장 철골공사", "이름": "철골 공장용접(기본용접공수)", - "결과단위": "원/t", + "결과단위": "원/ton", "출처": "건설품셈 건축 1-1-3", "소유": "공용", "비고": "해석: 기본용접공수 = 용접공 · 1-1-4 보정계수(강재총사용량)를 공수에 곱함 · 용접재료는 환산용접길이(m/t) × m당 양 · 제경비에 산재보험료 · 간접노무비 · 일반관리비 · 이윤 없음(주③) · 특수 구조물은 기계설비부문 플랜트용접 참조(주⑤)", "입력": [ - { "이름": "환산용접길이", "단위": "m/t", "출처": "건설품셈 건축 1-1-3 주④", "비고": "용접길이 × 환산계수(필릿 · V·K·X 판두께별 계수표) — 필릿 각장 6㎜ 환산" }, - { "이름": "강재총사용량", "단위": "t", "출처": "건설품셈 건축 1-1-4" }, + { "이름": "환산용접길이", "단위": "m/ton", "출처": "건설품셈 건축 1-1-3 주④", "비고": "용접길이 × 환산계수(필릿 · V·K·X 판두께별 계수표) — 필릿 각장 6㎜ 환산" }, + { "이름": "강재총사용량", "단위": "ton", "출처": "건설품셈 건축 1-1-4" }, { "이름": "부재", "고르기": ["전용접부재", "H형강부재"], "출처": "건설품셈 건축 1-1-1 비고", "비고": "전용접부재(Built up) · H형강부재(Rolled shape)" }, { "이름": "용접방식", "고르기": ["수용접", "반자동용접", "자동용접"], "출처": "건설품셈 건축 1-1-4", "비고": "반자동용접이 표준" } ], @@ -348,7 +348,7 @@ "개정": [2008, 2018, 2023], "소유": "공용", "비고": "본조임 · 조임검사 포함 · 철골설계수량 300ton 이상은 보정계수 a를 곱함(주④)", - "입력": [{ "이름": "볼트본수본t", "단위": "본/t", "출처": "건설품셈 건축 1-2-4" }, { "이름": "강재총사용량", "단위": "t", "출처": "건설품셈 건축 1-2-4 주④" }], + "입력": [{ "이름": "볼트본수본t", "단위": "본/ton", "출처": "건설품셈 건축 1-2-4" }, { "이름": "강재총사용량", "단위": "ton", "출처": "건설품셈 건축 1-2-4 주④" }], "중간": [ { "이름": "보정a", "식": "만약(강재총사용량 < 300, 1, 찾기(CC000165, '강재총사용량(t)'=강재총사용량, '1t당 볼트본수(본)'=볼트본수본t).'보정계수 a')", "출처": "건설품셈 건축 1-2-4 주④" }, { "이름": "철골공", "식": "찾기(QC001195, '1t당 볼트본수(본/t)'=볼트본수본t).철골공 * 보정a" }, diff --git a/resources/master_data/로직_건설품셈_02장_가설공사.json b/resources/master_data/로직_건설품셈_02장_가설공사.json index e1da75c16..96567f50f 100644 --- a/resources/master_data/로직_건설품셈_02장_가설공사.json +++ b/resources/master_data/로직_건설품셈_02장_가설공사.json @@ -1740,7 +1740,7 @@ "개정": [2018], "소유": "공용", "비고": "재료량 할증 포함 · 그물망 손율 100% · 보강재 손율 원문 없음 — 재료량 그대로", - "입력": [{ "이름": "결속선량", "단위": "kg/10㎡", "범위": [0.3, 0.4], "출처": "건설품셈 공통 2-8-7 재료량" }], + "입력": [{ "이름": "결속선량", "단위": "㎏/10㎡", "범위": [0.3, 0.4], "출처": "건설품셈 공통 2-8-7 재료량" }], "중간": [{ "이름": "비계공", "식": "찾기(QC000084, 구분='비계공').수량 / 10" }, { "이름": "보통인부", "식": "찾기(QC000084, 구분='보통인부').수량 / 10" }], "호표": [ { "종류": "인력", "요소": "LB000006", "이름": "비계공", "단위": "인", "수량": "비계공", "비목": "노무비" }, diff --git a/resources/master_data/로직_건설품셈_05장_기초공사.json b/resources/master_data/로직_건설품셈_05장_기초공사.json index 04b775b60..1f4eecfae 100644 --- a/resources/master_data/로직_건설품셈_05장_기초공사.json +++ b/resources/master_data/로직_건설품셈_05장_기초공사.json @@ -1609,9 +1609,9 @@ "이름": "t1", "식": "만약(타설깊이 <= 25, 찾기(QC000348, L='25이하').t_1, 만약(타설깊이 <= 30, 찾기(QC000348, L='30이하').t_1, 만약(타설깊이 <= 35, 찾기(QC000348, L='35이하').t_1, 만약(타설깊이 <= 40, 찾기(QC000348, L='40이하').t_1, 만약(타설깊이 <= 45, 찾기(QC000348, L='45이하').t_1, 만약(타설깊이 <= 50, 찾기(QC000348, L='50이하').t_1, 만약(타설깊이 <= 55, 찾기(QC000348, L='55이하').t_1, 0)))))))", "출처": "건설품셈 공통 5-2-3 준비 및 이동시간", - "단위": "초" + "단위": "sec" }, - { "이름": "cm", "식": "t1 + 타설깊이 / 찾기(QC000349, N치=N치).V_1 + 타설깊이 / 찾기(QC000349, N치=N치).V_2", "출처": "건설품셈 공통 5-2-3 cm", "단위": "초" }, + { "이름": "cm", "식": "t1 + 타설깊이 / 찾기(QC000349, N치=N치).V_1 + 타설깊이 / 찾기(QC000349, N치=N치).V_2", "출처": "건설품셈 공통 5-2-3 cm", "단위": "sec" }, { "이름": "Q", "식": "3600 * 타설깊이 * 찾기(QC000347, 구분=현장).효율 / cm", "출처": "건설품셈 공통 5-2-3 Q", "단위": "m/hr" }, { "이름": "T", "식": "1 / Q" }, { "이름": "특별인부", "식": "찾기(QC000344, 구분='특별인부').수량 * T / 8" }, diff --git a/resources/master_data/로직_건설품셈_07장_항만공사.json b/resources/master_data/로직_건설품셈_07장_항만공사.json index dd00df9cf..a9dc00bcd 100644 --- a/resources/master_data/로직_건설품셈_07장_항만공사.json +++ b/resources/master_data/로직_건설품셈_07장_항만공사.json @@ -168,7 +168,7 @@ "구분": "건설품셈 토목", "상세구분": "07장 항만공사", "이름": "수중고르기 잠수부 1조 시간당 고르기 면적", - "결과단위": "㎡/시간", + "결과단위": "㎡/hr", "출처": "건설품셈 토목 7-2-5", "개정": [2021], "소유": "공용", @@ -323,7 +323,7 @@ "입력": [ { "이름": "관경", - "단위": "mm", + "단위": "㎜", "고르기": ["250이하", "300", "350", "400", "510", "560", "610", "630", "660", "685", "710", "760", "840", "860"], "출처": "건설품셈 토목 7-4-1" }, @@ -376,7 +376,7 @@ "소유": "공용", "비고": "크레인 규격 띄우개 관경 430∼1,400 10톤 · 1,500 이상 15톤(주③ 장비규격 표) · 같은 톤 크롤러·타이어 크레인 가능(주④) · 철거는 본 품 30%까지 감(주①) — 감률 설계자 입력 · 소운반 포함 · 체결부 절단 별도", "입력": [ - { "이름": "관경", "단위": "mm", "고르기": [430, 500, 600, 700, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600], "출처": "건설품셈 토목 7-4-2" }, + { "이름": "관경", "단위": "㎜", "고르기": [430, 500, 600, 700, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600], "출처": "건설품셈 토목 7-4-2" }, { "이름": "작업", "고르기": ["설치", "철거"], "출처": "건설품셈 토목 7-4-2 주①" }, { "이름": "철거감률", "단위": "%", "범위": [0, 30], "출처": "건설품셈 토목 7-4-2 주①" }, { @@ -419,7 +419,7 @@ "소유": "공용", "비고": "크레인 규격 관경 200∼710 10톤 · 760 이상 15톤(주④ 장비규격 표) · 해상관 1set = 배송관 1본+고무슬리브 1본+띄우개 1본 · 침설관 1set = 배송관 2본+고무슬리브 1본 · 배송관 예인·침설 제외(주①)", "입력": [ - { "이름": "배송관관경", "단위": "mm", "고르기": [200, 250, 300, 350, 400, 510, 560, 610, 630, 660, 685, 710, 760, 840, 860], "출처": "건설품셈 토목 7-4-3" }, + { "이름": "배송관관경", "단위": "㎜", "고르기": [200, 250, 300, 350, 400, 510, 560, 610, 630, 660, 685, 710, 760, 840, 860], "출처": "건설품셈 토목 7-4-3" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], diff --git a/resources/master_data/로직_건설품셈_08장_건설기계.json b/resources/master_data/로직_건설품셈_08장_건설기계.json index bef20560b..4c05877a6 100644 --- a/resources/master_data/로직_건설품셈_08장_건설기계.json +++ b/resources/master_data/로직_건설품셈_08장_건설기계.json @@ -1308,13 +1308,13 @@ { "이름": "협소현장", "설명": "주택가 상하수도관로부설 등 협소현장(주⑦)", "고르기": ["해당 없음", "상하수도관로부설 등 협소현장"], "출처": "건설품셈 공통 8-2-3 2 주⑦" }, { "이름": "f", "설명": "체적환산계수 — 토질 · 상태 변화율(건설 공통 1-2-3)", "출처": "건설품셈 공통 8-1-4" }, { "이름": "E입력", "설명": "작업효율을 직접 넣을 때 — 0 이면 원문 표 값", "범위": [0, 1], "출처": "건설품셈 공통 8-2-3" }, - { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "초", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" } + { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "sec", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" } ], "중간": [ { "이름": "K", "식": "찾기(CC000042, 현장조건=버킷조건).K" }, { "이름": "보정", "식": "만약(E보정 == '해당 없음', 0, 찾기(CC000046, 항목=E보정).보정값)" }, { "이름": "E", "식": "만약(E입력 > 0, E입력, 만약(협소현장 == '해당 없음', 찾기(CC000043, 현장조건=현장조건, 등급=등급, 토질명=토질명).E, 찾기(CC000044, 등급=등급, 토질명=토질명).E) - 보정)" }, - { "이름": "cm", "식": "만약(싸이클입력 > 0, 싸이클입력, 찾기(CC000045, 규격=규격구분, 각도=선회각도).시간)", "단위": "초" } + { "이름": "cm", "식": "만약(싸이클입력 > 0, 싸이클입력, 찾기(CC000045, 규격=규격구분, 각도=선회각도).시간)", "단위": "sec" } ], "결과": { "식": "3600 * 버킷용량 * K * f * E / cm", "출처": "건설품셈 공통 8-2-3" }, "끝수": { "대상": "결과", "자리": 3, "방법": "반올림", "출처": "건설품셈 공통 8-1-4 주①" }, @@ -1385,7 +1385,7 @@ { "이름": "협소현장", "설명": "주택가 상하수도관로부설 등 협소현장(주⑦)", "고르기": ["해당 없음", "상하수도관로부설 등 협소현장"], "출처": "건설품셈 공통 8-2-3 2 주⑦" }, { "이름": "f", "설명": "체적환산계수 — 토질 · 상태 변화율(건설 공통 1-2-3)", "출처": "건설품셈 공통 8-1-4" }, { "이름": "E입력", "설명": "작업효율을 직접 넣을 때 — 0 이면 원문 표 값", "범위": [0, 1], "출처": "건설품셈 공통 8-2-3" }, - { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "초", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" }, + { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "sec", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" }, { "이름": "기계", "설명": "굴착기(무한궤도) 규격", @@ -1528,12 +1528,12 @@ { "이름": "E입력", "설명": "작업효율을 직접 넣을 때 — 0 이면 원문 표 값", "범위": [0, 1], "출처": "건설품셈 공통 8-2-5" } ], "중간": [ - { "이름": "t1", "식": "찾기(CC000047, 기종=기종, 작업방법=작업방법, 현장조건=적재조건).t1", "단위": "초" }, + { "이름": "t1", "식": "찾기(CC000047, 기종=기종, 작업방법=작업방법, 현장조건=적재조건).t1", "단위": "sec" }, { "이름": "K", "식": "만약(K표 == '산적상태 적재', 찾기(CC000048, 현장조건=K조건).계수, 찾기(CC000049, 현장조건=K조건).계수)" }, - { "이름": "m", "식": "찾기(CC000051, 기종=기종).m", "단위": "초/m" }, + { "이름": "m", "식": "찾기(CC000051, 기종=기종).m", "단위": "sec/m" }, { "이름": "l", "식": "만약(주행거리 > 0, 주행거리, 찾기(CC000052, 항목='편도주행거리(표준)').값)", "단위": "m" }, - { "이름": "t2", "식": "찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값", "단위": "초" }, - { "이름": "cm", "식": "m * l + t1 + t2", "단위": "초" }, + { "이름": "t2", "식": "찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값", "단위": "sec" }, + { "이름": "cm", "식": "m * l + t1 + t2", "단위": "sec" }, { "이름": "보정", "식": "만약(E보정 == '해당 없음', 0, 찾기(CC000053, 항목=E보정).보정값)" }, { "이름": "E", "식": "만약(E입력 > 0, E입력, 찾기(CC000050, 현장조건=현장조건, 등급=등급, 토질명=토질명).E - 보정)" } ], @@ -1907,7 +1907,7 @@ }, "입력": [ { "이름": "적재용량", "설명": "덤프트럭의 적재용량(T)", "단위": "ton", "출처": "건설품셈 공통 8-2-8" }, - { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "t/㎥", "출처": "건설품셈 공통 8-2-8" }, + { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "ton/㎥", "출처": "건설품셈 공통 8-2-8" }, { "이름": "체적변화율", "설명": "체적환산계수에서의 체적변화율(L = 흐트러진/자연)", "출처": "건설품셈 공통 8-2-8" }, { "이름": "f", "설명": "체적환산계수", "출처": "건설품셈 공통 8-2-8" }, { "이름": "운반거리", "설명": "운반거리(편도)", "단위": "㎞", "출처": "건설품셈 공통 8-2-8 2" }, @@ -1943,7 +1943,7 @@ { "이름": "덮개", "설명": "적재함 덮개 설치 · 해체(t5)", "고르기": ["해당 없음", "인력에 의한 경우", "자동덮개시설의 경우"], "출처": "건설품셈 공통 8-2-8 6" }, { "이름": "세륜", "설명": "세륜기 통과(t6)", "고르기": ["안 함", "함"], "출처": "건설품셈 공통 8-2-8 7" }, { "이름": "적재방법", "설명": "적재시간(t1) 산출 갈래", "고르기": ["적재기계", "인력", "직접 입력"], "출처": "건설품셈 공통 8-2-8 1 · 8 · 9" }, - { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "초", "출처": "건설품셈 공통 8-2-8 8" }, + { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "sec", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재버킷", "설명": "적재기계의 버킷용량(q)", "단위": "㎥", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "버킷계수", "설명": "적재기계의 버킷계수(k)", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재효율", "설명": "적재기계의 작업효율(Es)", "출처": "건설품셈 공통 8-2-8 8" }, @@ -2032,7 +2032,7 @@ }, "입력": [ { "이름": "적재용량", "설명": "덤프트럭의 적재용량(T)", "단위": "ton", "출처": "건설품셈 공통 8-2-8" }, - { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "t/㎥", "출처": "건설품셈 공통 8-2-8" }, + { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "ton/㎥", "출처": "건설품셈 공통 8-2-8" }, { "이름": "체적변화율", "설명": "체적환산계수에서의 체적변화율(L = 흐트러진/자연)", "출처": "건설품셈 공통 8-2-8" }, { "이름": "f", "설명": "체적환산계수", "출처": "건설품셈 공통 8-2-8" }, { "이름": "운반거리", "설명": "운반거리(편도)", "단위": "㎞", "출처": "건설품셈 공통 8-2-8 2" }, @@ -2068,7 +2068,7 @@ { "이름": "덮개", "설명": "적재함 덮개 설치 · 해체(t5)", "고르기": ["해당 없음", "인력에 의한 경우", "자동덮개시설의 경우"], "출처": "건설품셈 공통 8-2-8 6" }, { "이름": "세륜", "설명": "세륜기 통과(t6)", "고르기": ["안 함", "함"], "출처": "건설품셈 공통 8-2-8 7" }, { "이름": "적재방법", "설명": "적재시간(t1) 산출 갈래", "고르기": ["적재기계", "인력", "직접 입력"], "출처": "건설품셈 공통 8-2-8 1 · 8 · 9" }, - { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "초", "출처": "건설품셈 공통 8-2-8 8" }, + { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "sec", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재버킷", "설명": "적재기계의 버킷용량(q)", "단위": "㎥", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "버킷계수", "설명": "적재기계의 버킷계수(k)", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재효율", "설명": "적재기계의 작업효율(Es)", "출처": "건설품셈 공통 8-2-8 8" }, @@ -3431,7 +3431,7 @@ "중간": [ { "이름": "f", "식": "만약(f입력 > 0, f입력, 찾기(CC000474, 분류=f분류, 상태=f상태, N값=fN값).f)" }, { "이름": "k", "식": "만약(k입력 > 0, k입력, 찾기(CC000475, 분류=k분류, 상태=k상태, N값=kN값, 버킷용량=버킷용량).k)" }, - { "이름": "cm", "식": "찾기(CC000476, 버킷용량=버킷용량).싸이클시간 + (평균수심 - 찾기(CC000481, 항목='기준 수심').값) * 찾기(CC000481, 항목='수심 1m 증감당 싸이클시간 증감').값", "단위": "초" }, + { "이름": "cm", "식": "찾기(CC000476, 버킷용량=버킷용량).싸이클시간 + (평균수심 - 찾기(CC000481, 항목='기준 수심').값) * 찾기(CC000481, 항목='수심 1m 증감당 싸이클시간 증감').값", "단위": "sec" }, { "이름": "E", "식": "찾기(CC000477, 구분=E1).E * 찾기(CC000479, 구분=E2).E" } ], "결과": { "식": "3600 * 버킷용량 * k * f * E / cm", "출처": "건설품셈 공통 8-2-30" }, diff --git a/resources/master_data/로직_건설품셈_08장_지반조사.json b/resources/master_data/로직_건설품셈_08장_지반조사.json index b8e14f4ab..4a09b29f5 100644 --- a/resources/master_data/로직_건설품셈_08장_지반조사.json +++ b/resources/master_data/로직_건설품셈_08장_지반조사.json @@ -744,7 +744,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -805,7 +805,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -866,7 +866,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -927,7 +927,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -989,7 +989,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1051,7 +1051,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1122,7 +1122,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1193,7 +1193,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], diff --git a/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json b/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json index bec39851c..deee13ab2 100644 --- a/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json +++ b/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json @@ -181,7 +181,7 @@ "구분": "건설품셈 건축", "상세구분": "10장 창호 및 유리공사", "이름": "커튼월 프레임 설치(스틱월)", - "결과단위": "원/kg", + "결과단위": "원/㎏", "출처": "건설품셈 건축 10-1-8", "개정": [2014, 2020, 2026], "소유": "공용", diff --git a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json index 0957158b1..5c0bda9fd 100644 --- a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json +++ b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json @@ -770,7 +770,7 @@ { "이름": "용접공품", "단위": "인", "출처": "건설품셈 기계설비 13-2-2", "비고": "원문 범위 — 예 두께 3㎜ 0.0055∼0.0037" }, { "이름": "특별인부품", "단위": "인", "출처": "건설품셈 기계설비 13-2-2", "비고": "원문 범위 — 예 두께 3㎜ 0.0027∼0.0019" }, { "이름": "산소량", "단위": "ℓ", "출처": "건설품셈 기계설비 13-2-2 주④", "비고": "원문 범위 — 예 두께 3㎜ 16.5∼25.1" }, - { "이름": "LPG량", "단위": "kg", "출처": "건설품셈 기계설비 13-2-2 주④", "비고": "원문 범위 — 예 두께 3㎜ 0.016∼0.025" } + { "이름": "LPG량", "단위": "㎏", "출처": "건설품셈 기계설비 13-2-2 주④", "비고": "원문 범위 — 예 두께 3㎜ 0.016∼0.025" } ], "중간": [], "호표": [ diff --git a/resources/master_data/로직_산림품셈_03장_작업장_관리.json b/resources/master_data/로직_산림품셈_03장_작업장_관리.json index 6ac21427c..7669f53be 100644 --- a/resources/master_data/로직_산림품셈_03장_작업장_관리.json +++ b/resources/master_data/로직_산림품셈_03장_작업장_관리.json @@ -81,7 +81,7 @@ "구분": "산림품셈", "상세구분": "03장 작업장 관리", "이름": "임산물 운반로 신설 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 3-4-2 1", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json b/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json index 74600f8fe..e4f409b5b 100644 --- a/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json +++ b/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json @@ -1078,7 +1078,7 @@ "시험입력": { "토성": "양토", "막갈이깊이": 15 }, "입력": [ { "이름": "토성", "설명": "흙의 성질", "고르기": ["사토", "양토", "식토"], "출처": "산림품셈 5-26-1" }, - { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "cm", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-1" } + { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "㎝", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-1" } ], "중간": [], "호표": [{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "찾기(QF000139, 토성=토성, '막갈이깊이(cm)'=막갈이깊이).수량", "비목": "노무비" }], @@ -1100,7 +1100,7 @@ "시험입력": { "토성": "양토", "막갈이깊이": 15 }, "입력": [ { "이름": "토성", "설명": "흙의 성질", "고르기": ["사토", "양토", "식토"], "출처": "산림품셈 5-26-2" }, - { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "cm", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-2" } + { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "㎝", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-2" } ], "중간": [], "호표": [{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "찾기(QF000140, 토성=토성, '막갈이깊이(cm)'=막갈이깊이).수량", "비목": "노무비" }], @@ -1197,7 +1197,7 @@ "구분": "산림품셈", "상세구분": "05장 식재 및 녹화", "이름": "복사이식 굴착기 시간당 작업량(Q1)", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 5-29 주④", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_06장_생육보조.json b/resources/master_data/로직_산림품셈_06장_생육보조.json index 4d5f5b655..96aee9ff8 100644 --- a/resources/master_data/로직_산림품셈_06장_생육보조.json +++ b/resources/master_data/로직_산림품셈_06장_생육보조.json @@ -10,7 +10,7 @@ "구분": "산림품셈", "상세구분": "06장 생육보조", "이름": "비료주기", - "결과단위": "원/100kg", + "결과단위": "원/100㎏", "출처": "산림품셈 6-1", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_09장_토공.json b/resources/master_data/로직_산림품셈_09장_토공.json index 6cca8eb43..b56362e4b 100644 --- a/resources/master_data/로직_산림품셈_09장_토공.json +++ b/resources/master_data/로직_산림품셈_09장_토공.json @@ -44,7 +44,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "토사깍기(기계 · 굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-3-2", "개정": [2026], "소유": "공용", @@ -55,7 +55,7 @@ { "이름": "K", "식": "찾기(QF000249, 구분='K').적용" }, { "이름": "f", "식": "찾기(CF000122, 구분='f').적용" }, { "이름": "E", "식": "E입력" }, - { "이름": "Cm", "식": "찾기(QF000249, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000249, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -156,7 +156,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "암절취 집토(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-4-2", "개정": [2026], "소유": "공용", @@ -167,7 +167,7 @@ { "이름": "K", "식": "찾기(QF000251, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000251, 구분='f').적용원문" }, { "이름": "E", "식": "찾기(QF000251, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000251, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000251, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -297,7 +297,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "발파암 집토(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-5-3", "개정": [2026], "소유": "공용", @@ -308,7 +308,7 @@ { "이름": "K", "식": "찾기(QF000254, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000254, 구분='f').적용원문" }, { "이름": "E", "식": "찾기(QF000254, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000254, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000254, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -386,7 +386,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "콘크리트 깨기 집토(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-7-1 주⑥", "개정": [2026], "소유": "공용", @@ -396,7 +396,7 @@ { "이름": "K", "식": "찾기(QF000257, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000257, 구분='f').적용" }, { "이름": "E", "식": "찾기(QF000257, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000257, 구분='㎝(초)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000257, 구분='㎝(초)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -823,7 +823,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "측구터파기 토사 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-12-1", "개정": [2026], "소유": "공용", @@ -833,7 +833,7 @@ { "이름": "K", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='k').적용" }, { "이름": "f", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='f').적용" }, { "이름": "E", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2026,7 +2026,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [2026], "소유": "공용", @@ -2036,7 +2036,7 @@ { "이름": "K", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='k').적용" }, { "이름": "f", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='f').적용" }, { "이름": "E", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2078,7 +2078,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "다짐(플레이트콤펙터) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-2", "개정": [2026], "소유": "공용", @@ -2119,7 +2119,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "표토제거 답(畓)구간(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎡/시간", + "결과단위": "㎡/hr", "출처": "산림품셈 9-15-1", "개정": [2026], "소유": "공용", @@ -2163,7 +2163,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "표토제거 답(畓)외구간(불도저 19ton) 시간당 작업량", - "결과단위": "㎡/시간", + "결과단위": "㎡/hr", "출처": "산림품셈 9-15-2", "개정": [2026], "소유": "공용", @@ -2222,7 +2222,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "노체포설 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-16-1 주②", "개정": [2026], "소유": "공용", @@ -2232,7 +2232,7 @@ { "이름": "K", "식": "찾기(CF000097, 구분='K').적용" }, { "이름": "f", "식": "찾기(CF000097, 구분='f').적용" }, { "이름": "E", "식": "찾기(CF000097, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(CF000097, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(CF000097, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q0 * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2268,13 +2268,13 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "노체다짐 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-16-2 주②", "개정": [2026], "소유": "공용", "입력": [], "중간": [ - { "이름": "V", "식": "찾기(CF000099, 구분='V(다짐속도,km/hr)').적용", "단위": "km/hr" }, + { "이름": "V", "식": "찾기(CF000099, 구분='V(다짐속도,km/hr)').적용", "단위": "㎞/hr" }, { "이름": "W", "식": "찾기(CF000099, 구분='W(롤러 유효폭,m)').적용", "단위": "m" }, { "이름": "E", "식": "찾기(CF000099, 구분='E(작업효율)').적용" }, { "이름": "D", "식": "찾기(CF000099, 구분='D(펴는 흙의 두께,m)').적용", "단위": "m" }, @@ -2339,7 +2339,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "비다짐(정지 · 불도저 19ton) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-17-2", "개정": [2026], "소유": "공용", @@ -2393,7 +2393,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "층따기(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-18", "개정": [2026], "소유": "공용", @@ -2404,7 +2404,7 @@ { "이름": "K", "식": "찾기(QF000293, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000293, 구분='f').적용원문" }, { "이름": "E", "식": "찾기(QF000293, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000293, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000293, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2639,7 +2639,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "섞기(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-22", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json index 3eb744162..f56db170c 100644 --- a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json +++ b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json @@ -10,7 +10,7 @@ "구분": "산림품셈", "상세구분": "10장 자재․장비 운반", "이름": "골재운반 덤프트럭 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 10-3-1 주", "개정": [2026], "소유": "공용", @@ -671,7 +671,7 @@ "구분": "산림품셈", "상세구분": "10장 자재․장비 운반", "이름": "불도저 운반 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 10-11 주②", "개정": [2026], "소유": "공용", @@ -778,19 +778,19 @@ { "이름": "로더cm", "식": "찾기(CC000051, 기종='타이어식').m * 찾기(CC000052, 항목='편도주행거리(표준)').값 + 찾기(CC000047, 기종='타이어식', 작업방법='산적상태에서 담을 때', 현장조건='용이한 경우').t1 + 찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값", - "단위": "초", + "단위": "sec", "출처": "건설품셈 공통 8-2-5 1" }, { "이름": "Cm", "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', 찾기(QF000336, 구분='㎝(초)').값, 찾기(CC000045, 규격=규격구분, 각도=180).시간))", - "단위": "초", + "단위": "sec", "출처": "산림품셈 10-12-1 주⑤ · 건설품셈 공통 8-2-3 3" }, { "이름": "Cms", "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', 20, 찾기(CC000045, 규격=규격구분, 각도=135).시간))", - "단위": "초", + "단위": "sec", "출처": "산림품셈 10-12-1 2. 운반 · 건설품셈 공통 8-2-3 3" }, { "이름": "Es", "식": "만약(로더 == 1, E0, 0.85)", "출처": "산림품셈 10-12-1 2. 운반" }, diff --git a/resources/master_data/로직_산림품셈_11장_가설.json b/resources/master_data/로직_산림품셈_11장_가설.json index 765f1bfa6..cfd04b648 100644 --- a/resources/master_data/로직_산림품셈_11장_가설.json +++ b/resources/master_data/로직_산림품셈_11장_가설.json @@ -91,7 +91,7 @@ "구분": "산림품셈", "상세구분": "11장 가설", "이름": "쇄석·혼합석 부설 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 11-4 주①", "개정": [2026], "소유": "공용", @@ -102,7 +102,7 @@ { "이름": "k", "식": "찾기(QF000349, 구분='k(버킷계수)').적용" }, { "이름": "f", "식": "찾기(QF000349, 구분='f(체적환산계수)').적용" }, { "이름": "E", "식": "찾기(QF000349, 구분='E(작업효율)').적용" }, - { "이름": "Cm", "식": "찾기(QF000349, 구분='㎝(sec, 1회 사이클 시간)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000349, 구분='㎝(sec, 1회 사이클 시간)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * k * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", diff --git a/resources/master_data/로직_산림품셈_13장_구조물.json b/resources/master_data/로직_산림품셈_13장_구조물.json index f4d3dd08a..42ed818c5 100644 --- a/resources/master_data/로직_산림품셈_13장_구조물.json +++ b/resources/master_data/로직_산림품셈_13장_구조물.json @@ -176,7 +176,7 @@ "소유": "공용", "비고": "원문 플레이트콤팩트 1.54ton — 기계 요소는 1.5ton 하나(1730-0015)", "입력": [ - { "이름": "규격", "단위": "mm" }, + { "이름": "규격", "단위": "㎜" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1321,7 +1321,7 @@ "구분": "산림품셈", "상세구분": "13장 구조물", "이름": "토양개량제 부설 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 13-13-2", "개정": [2026], "소유": "공용", @@ -1332,7 +1332,7 @@ { "이름": "K", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='k').적용" }, { "이름": "f", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='f').적용" }, { "이름": "E", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", diff --git a/resources/master_data/로직_자체.json b/resources/master_data/로직_자체.json index cebcff437..4c95ce3ac 100644 --- a/resources/master_data/로직_자체.json +++ b/resources/master_data/로직_자체.json @@ -37,7 +37,7 @@ { "이름": "상차", "설명": "채집한 돌을 덤프에 싣는지", "고르기": ["포함", "안 함"] }, { "이름": "덤프", "설명": "소운반 덤프트럭", "고르기": ["0602-0025", "0602-0045", "0602-0060", "0602-0080"], "출처": "건설품셈 공통 8-2-8" }, { "이름": "운반거리", "설명": "소운반 편도 거리 — 0 이면 소운반 없음", "단위": "㎞", "출처": "건설품셈 공통 8-2-8" }, - { "이름": "쌓기무게", "설명": "쌓기 1㎡ 돌 무게", "단위": "t/㎡" }, + { "이름": "쌓기무게", "설명": "쌓기 1㎡ 돌 무게", "단위": "ton/㎡" }, { "이름": "적하조건", "설명": "덤프 적하 작업조건", "고르기": ["불량", "보통", "양호"], "출처": "건설품셈 공통 8-2-8 4" }, { "이름": "지역", @@ -51,7 +51,7 @@ { "이름": "규격구분", "식": "만약(굴착기 == '0201-0020' 또는 굴착기 == '0201-0040', '0.12∼0.4', 만약(굴착기 == '0201-0100', '1.0∼1.2', '0.6∼0.8'))" }, { "이름": "K", "식": "찾기(CC000042, 현장조건='버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우').K" }, { "이름": "E", "식": "찾기(CC000043, 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암').E" }, - { "이름": "cm", "식": "찾기(CC000045, 규격=규격구분, 각도=선회각도).시간", "단위": "초" }, + { "이름": "cm", "식": "찾기(CC000045, 규격=규격구분, 각도=선회각도).시간", "단위": "sec" }, { "이름": "Q", "식": "로직(GC001147, 버킷용량=q, 규격구분=규격구분, 선회각도=선회각도, 버킷조건='버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우', 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암', E보정='해당 없음', 협소현장='해당 없음', f=1, E입력=0, 싸이클입력=0)", diff --git a/resources/master_template/labor/item1.json b/resources/master_template/labor/item1.json index 668bfd289..9f69c693a 100644 --- a/resources/master_template/labor/item1.json +++ b/resources/master_template/labor/item1.json @@ -352,7 +352,7 @@ } }, "사본": { - "판": "5bcfbe99102ef55d", + "판": "7a470ae53b9da0de", "시점": "2026-11-07T15:23:23", "로직": [ { @@ -2211,7 +2211,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -2702,7 +2702,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/master_template/labor/item1_2.json b/resources/master_template/labor/item1_2.json index 5ebb2840a..ec3a1f504 100644 --- a/resources/master_template/labor/item1_2.json +++ b/resources/master_template/labor/item1_2.json @@ -940,7 +940,7 @@ } }, "사본": { - "판": "5bcfbe99102ef55d", + "판": "7a470ae53b9da0de", "시점": "2026-11-07T15:23:23", "로직": [ { @@ -2799,7 +2799,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -3290,7 +3290,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/master_template/labor/item3.json b/resources/master_template/labor/item3.json index 85613451a..bfa937dcd 100644 --- a/resources/master_template/labor/item3.json +++ b/resources/master_template/labor/item3.json @@ -247,7 +247,7 @@ } }, "사본": { - "판": "4066584ac7f0910f", + "판": "90e75b6182c18dcb", "시점": "2026-11-07T15:23:23", "로직": [ { @@ -858,7 +858,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "노체포설 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-16-1 주②", "개정": [ 2026 diff --git a/resources/master_template/labor/item3_2.json b/resources/master_template/labor/item3_2.json index facc3ce30..a9e72fd03 100644 --- a/resources/master_template/labor/item3_2.json +++ b/resources/master_template/labor/item3_2.json @@ -508,7 +508,7 @@ } }, "사본": { - "판": "bf62fc43e50cd058", + "판": "69839ebda29c8cc5", "시점": "2026-11-07T15:23:24", "로직": [ { @@ -1622,7 +1622,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -1955,7 +1955,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/master_template/labor/item3_3.json b/resources/master_template/labor/item3_3.json index 7579c3959..3927a310e 100644 --- a/resources/master_template/labor/item3_3.json +++ b/resources/master_template/labor/item3_3.json @@ -1060,7 +1060,7 @@ } }, "사본": { - "판": "5bcfbe99102ef55d", + "판": "7a470ae53b9da0de", "시점": "2026-11-07T15:23:24", "로직": [ { @@ -2919,7 +2919,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -3410,7 +3410,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/tester/test_796_3_var_names.py b/resources/tester/test_796_3_var_names.py index 6fcf46898..d78a10233 100644 --- a/resources/tester/test_796_3_var_names.py +++ b/resources/tester/test_796_3_var_names.py @@ -163,7 +163,7 @@ def test_표본_기호_단위_원문_같음(): "작업효율", "체적환산계수", ) - assert (t["cm"]["이름"], t["cm"]["단위"]) == ("1회 싸이클시간", "초") + assert (t["cm"]["이름"], t["cm"]["단위"]) == ("1회 싸이클시간", "sec") # 801-5 원문 표 머리 「싸이클시간(Sec)」 꼴 carry = (BOOK / "제01장_적용기준/1-02_설계_및_수량.md").read_text(encoding="utf-8") for line in ( diff --git a/resources/tester/test_801_5_units.py b/resources/tester/test_801_5_units.py new file mode 100644 index 000000000..50cc98587 --- /dev/null +++ b/resources/tester/test_801_5_units.py @@ -0,0 +1,138 @@ +"""801-5 M01 로직 단위 글 = 건설품셈 원문 꼴(요구 663-2) 지킴 — 글만 바꿈 · 금액 그대로. + +① 대응표 근거 — 규칙마다 근거 절 파일이 있고 그 안에 원문 글이 실제로 있음(+ 지금 글은 같은 꼴로 안 쓰임) +② 섞임 0 — M01 로직 입력 · 중간 · 결과단위가 원문 꼴로 다 바뀌어 있음(규칙이 더 바꿀 것 없음 · 두 번 돌려도 같음) +③ 금액 같음 — 변수 단위 글을 지워도 엔진 셈 · 끝수 지문이 같음(단위 글은 셈에 안 듦) +④ M02 사본 도구 — 같은 규칙으로 사본 줄 단위만 바뀌고 `판` 다시 셈 +""" + +from __future__ import annotations + +import copy +import importlib +import re + +import pytest + +from M01_MasterData import M01_MasterData_Store as store + +ut = importlib.import_module("master_unit_text") +mf = importlib.import_module("master_formula") +build = importlib.import_module("build_단위표기_801_5") + + +def _rows(): + for data in store.loaded()[0].values(): + if data.get("그룹") == "로직": + yield from data["줄"] + + +def test_대응표_근거_절에_원문_글이_있음(): + assert len(ut.RULES) >= 7 + for name, _, to, token, files in ut.RULES: + assert files and to == token, name + for rel in files: + path = ut.EVIDENCE_ROOT / rel + assert path.is_file(), (name, rel) + assert token in path.read_text(encoding="utf-8"), (name, rel) + + +def test_규칙_예(): + got = { + u: ut.canon(u) + for u in ("㎥/시간", "㎡/시간", "㎢/시간", "원/시간", "초", "초/m", "mm", "TON/㎡") + } + assert got == { + "㎥/시간": "㎥/hr", + "㎡/시간": "㎡/hr", + "㎢/시간": "㎢/시간", # 원문 9-5-4 가 쓰는 꼴 — 안 건드림 + "원/시간": "원/시간", + "초": "sec", + "초/m": "sec/m", + "mm": "㎜", + "TON/㎡": "ton/㎡", + } + for u in got.values(): + assert ut.canon(u) == u # 두 번 돌려도 같음 + + +def test_섞임_0_원문_꼴로_다_바뀜(): + left = [] + for row in _rows(): + units = [row.get("결과단위")] + [ + s.get("단위") for k in ("입력", "중간") for s in row.get(k) or [] + ] + left += [(row["키"], u) for u in units if u and ut.canon(u) != u] + assert not left, left[:5] + + +def test_굴착기_Cm_sec_Q_hr(): + row = store.loaded()[1].logic("GF000088") # 토사깍기(굴착기 0.7㎥) 시간당 작업량 + assert {s["단위"] for s in row["중간"] if s["이름"] == "Cm"} == {"sec"} + assert row["결과단위"] == "㎥/hr" + + +_OLD = ( # 원문 꼴 → 옛 글(되돌려 본 꼴) — 두 꼴로 셈이 같음을 대조 + (re.compile(r"(? str: + for pat, old in _OLD: + text = pat.sub(old, text) + return text + + +def test_금액_같음_옛_글로_되돌려도_셈_지문_같음(): + files = store.loaded()[0] + old = copy.deepcopy(files) + for data in old.values(): + if data.get("그룹") == "로직": + for row in data["줄"]: + for k in ("입력", "중간"): + for s in row.get(k) or []: + s["단위"] = _legacy(s.get("단위") or "") + row["결과단위"] = _legacy(row.get("결과단위") or "") + now, was = mf.Master(files), mf.Master(old) + keys = [r["키"] for r in _rows()] + hit = { + r["키"] + for r in _rows() + if any( + _legacy(s.get("단위") or "") != (s.get("단위") or "") + for k in ("입력", "중간") + for s in r.get(k) or [] + ) + } + assert len(hit) >= 40 + for key in sorted(hit) + keys[::25]: + assert build.fingerprint(now, now.logic(key)) == build.fingerprint(was, was.logic(key)), key + + +def test_M02_사본_도구(): + cu = importlib.import_module("M02_MasterTemplete.M02_Copy_Units") + old = { + "로직": [ + { + "키": "GC000001", + "결과단위": "㎥/시간", + "입력": [{"이름": "a", "단위": "mm"}], + "중간": [{"이름": "Cm", "단위": "초"}], + } + ], + "판": "x", + } + new, done, _ = cu.fill(old, None) + row = new["로직"][0] + assert done == 3 and row["결과단위"] == "㎥/hr" and row["입력"][0]["단위"] == "㎜" + assert row["중간"][0]["단위"] == "sec" and new["판"] != "x" + assert cu.fill(new, None)[1] == 0 # 두 번째 = 바꿀 것 없음 + + +if __name__ == "__main__": + pytest.main([__file__, "-q"])