diff --git a/B05_Profile/B05_Profile_Structure_Types.json b/B05_Profile/B05_Profile_Structure_Types.json index 38146213..e1318d77 100644 --- a/B05_Profile/B05_Profile_Structure_Types.json +++ b/B05_Profile/B05_Profile_Structure_Types.json @@ -1945,7 +1945,7 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 돌쌓기 기본값으로 서고 「기본값으로 섰음」이 줄로 뜸 — 돌이 아닌 형태도 있어 필수로 못 검" + "empty_means": "비우면 품셈 13-4-4 [주]⑩ 뒷길이 표준 하한(높이·찰/메로 · 범위 안 가장 작은 규격 — 예 메쌓기 1.5~3m 는 36~45㎝ 라 45㎝)으로 서고 그 사실이 줄로 뜸 — 돌이 아닌 형태도 있어 필수로 못 검" }, { "key": "stone_kind", diff --git a/B08_Quantity/B08_Quantity_Engine_StructureFigure.py b/B08_Quantity/B08_Quantity_Engine_StructureFigure.py index 91ed57c9..7a14596a 100644 --- a/B08_Quantity/B08_Quantity_Engine_StructureFigure.py +++ b/B08_Quantity/B08_Quantity_Engine_StructureFigure.py @@ -27,7 +27,7 @@ from __future__ import annotations from typing import Any from B08_Quantity.B08_Quantity_Engine_Pipe import NOTE_DEFAULT_WALL -from B08_Quantity.B08_Quantity_Engine_StructureSheet import slope_of +from B08_Quantity.B08_Quantity_Engine_StructureSheet import sheet_is_wet, slope_of from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( _back_length, back_length_default_note, @@ -94,9 +94,14 @@ def back_length_cm(sheet: dict[str, Any]) -> tuple[int, str | None]: 양식 장은 양식이 푼 값(`formula_sheet.vars.L3`), 전개 장은 `_back_length` — 표와 한 벌. """ options = sheet.get("options") or {} + wet, height = sheet_is_wet(sheet), float(sheet.get("height_m") or 0.0) solved = ((sheet.get("formula_sheet") or {}).get("vars") or {}).get("L3") - back = int(float(solved)) if isinstance(solved, (int, float)) else _back_length(options) - return back, back_length_default_note(options, back) + back = ( + int(float(solved)) + if isinstance(solved, (int, float)) + else _back_length(options, wet=wet, height_m=height) + ) + return back, back_length_default_note(options, back, wet=wet, height_m=height) def figure_reason(sheet: dict[str, Any]) -> str | None: diff --git a/B08_Quantity/B08_Quantity_Engine_StructureSheet.py b/B08_Quantity/B08_Quantity_Engine_StructureSheet.py index fd19cb2c..33ede3c6 100644 --- a/B08_Quantity/B08_Quantity_Engine_StructureSheet.py +++ b/B08_Quantity/B08_Quantity_Engine_StructureSheet.py @@ -103,23 +103,28 @@ SLOPE_TYPE_IDS: frozenset[str] = frozenset( REVETMENT_STONE_FORMS: dict[str, bool] = {"돌쌓기(찰)": True, "돌쌓기(메)": False} -def slope_of(sheet: dict[str, Any]) -> tuple[float, str]: - """장 하나의 전면 기울기와 근거 문구 — **판정은 B08 한 벌**을 그대로 쓴다. +def sheet_is_wet(sheet: dict[str, Any]) -> bool: + """장의 메/찰 — 표준경사·뒷길이 표준이 같은 갈래로 읽음. - 메/찰은 종류에서 온다. 큰돌쌓기는 `bond` 칸(메쌓기/찰쌓기)이 그것이고, 안 고르면 - 찰쌓기로 본다 — 품셈 13-6 은 둘 다 「1:0.3 **이상**」이라 그림 기울기가 갈리지 않는다. + 큰돌쌓기는 `bond` 칸(메쌓기/찰쌓기)이 그것이고, 안 고르면 찰쌓기로 본다 — 품셈 13-6 은 + 둘 다 「1:0.3 **이상**」이라 그림 기울기가 갈리지 않는다. """ options = sheet.get("options") or {} type_id = str(sheet.get("type_id") or "") if type_id == "masonry_dry": - wet = False - elif type_id == "boulder_masonry": - wet = options.get("bond") != "메쌓기" - elif type_id == "revetment": + return False + if type_id == "boulder_masonry": + return options.get("bond") != "메쌓기" + if type_id == "revetment": # 표(기슭막이 전개)가 넘긴 그 갈래 — 형태 「돌쌓기(메)」만 메. - wet = REVETMENT_STONE_FORMS.get(str(options.get("form") or "").strip(), True) - else: - wet = True + return REVETMENT_STONE_FORMS.get(str(options.get("form") or "").strip(), True) + return True + + +def slope_of(sheet: dict[str, Any]) -> tuple[float, str]: + """장 하나의 전면 기울기와 근거 문구 — **판정은 B08 한 벌**을 그대로 쓴다.""" + options = sheet.get("options") or {} + wet = sheet_is_wet(sheet) return face_slope_ratio( options, wet=wet, diff --git a/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py b/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py index be19dfad..f378c12b 100644 --- a/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py +++ b/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py @@ -16,7 +16,11 @@ import json from pathlib import Path from typing import Any +from B08_Quantity.B08_Quantity_Engine_UnitQuantity_StoneSpec import standard_back_length + ROOT = Path(__file__).resolve().parents[1] +#: 양식 칸 `default_from` — 빈 뒷길이를 뒷길이 표준표 하한으로(전개와 한 벌). +STANDARD_BACK_LENGTH = "standard_back_length" TEMPLATE_DIR = ROOT / "resources" / "library_structure" #: 사용자가 고친 식이 사는 칸 — B08 산출 조건 구획(`project_settings.json` 의 `quantity`). @@ -75,6 +79,12 @@ def template_vars( raw = options.get(spec["option"]) else: raw = (settings or {}).get(spec.get("setting")) + if raw in (None, "") and spec.get("default_from") == STANDARD_BACK_LENGTH: + # 안 고른 뒷길이 — 전개와 같은 표준표 하한(`standard_back_length`, 브레인 판정 ㉮). + wet = template.get("type_id") != "masonry_dry" + height = float(structure.get("height_m") or 0.0) + values[name] = float(standard_back_length(wet=wet, height_m=height)[0]) + continue values[name] = default if raw in (None, "") else _typed(raw, default) return values diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py b/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py index 725b961a..66089043 100644 --- a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py +++ b/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Masonry.py @@ -260,14 +260,14 @@ def stone_masonry( if height_m <= 0 or length_m <= 0: return [], ["높이·연장이 없어 전개하지 않음"] - back_cm = _back_length(options) + back_cm = _back_length(options, wet=wet, height_m=height_m) if back_cm not in STONE_BACK_LENGTH_TABLE: # ⚠ 접지 않는다 — 다른 규격 계수가 조용히 도는 것보다 「없다」가 낫다. return [], [ f"뒷길이 {back_cm}㎝ 는 품셈 표(25·30·35·45·55·60·75㎝)에 없어 물량이 서지 않습니다" ] table = dict(STONE_BACK_LENGTH_TABLE[back_cm]) - default_note = back_length_default_note(options, back_cm) + default_note = back_length_default_note(options, back_cm, wet=wet, height_m=height_m) if default_note: notes.append(default_note) # ⚠ **돌 종류로 계수가 갈린다** (품셈 13-4-3 · 13-4-4 [주]① · 교본 7-3). diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py b/B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py index 8e19a929..58e203d3 100644 --- a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py +++ b/B08_Quantity/B08_Quantity_Engine_UnitQuantity_StoneSpec.py @@ -444,8 +444,57 @@ def wants_blinding(options: dict[str, Any]) -> bool: BACK_LENGTH_KEYS = ("back_len_cm", "stone_back_length_cm") -def _back_length(options: dict[str, Any]) -> int: - """저장된 뒷길이(㎝)를 **그대로** 돌려준다. 안 정했으면 기본 45. +#: 뒷길이 표준표 — 품셈 13-4-4 [주]⑩. 안 고른 돌쌓기는 붙박이 45 가 아니라 이 표의 하한 +#: (2026-09-14 브레인 판정 ㉮ 「범위 안에 드는 규격 중 가장 작은 것」). 파일이 없으면 종전 45. +BACK_LENGTH_PREFIX = "masonry_back_length_" + + +@lru_cache(maxsize=1) +def load_back_length_table() -> dict[str, Any]: + """뒷길이 표준표. 없으면 빈 표 — 그때는 종전 기본 45㎝ 로 돎.""" + files = sorted(SLOPE_DIR.glob(BACK_LENGTH_PREFIX + "*.json")) if SLOPE_DIR.is_dir() else [] + return json.loads(files[-1].read_text(encoding="utf-8")) if files else {} + + +def standard_back_length(*, wet: bool, height_m: float) -> tuple[int, str]: + """(제안 뒷길이 ㎝, 근거) — 높이·메/찰 칸 범위 안 품셈 규격 중 가장 작은 것. + + ⚠ 하한이 규격(25·30·35·45·55·60·75)에 없으면 **그 위 첫 규격** — 메쌓기 ~3m 하한 36 은 45. + 내리면 품셈 범위 밖이 됨. 경계값은 표준경사표와 같이 **앞 칸**(높이 3.0m → `~3`). + """ + table = load_back_length_table() + steps = table.get("steps_m") or [] + bond = "찰쌓기" if wet else "메쌓기" + rows = (table.get("table_cm") or {}).get(bond) or [] + if not rows: + return DEFAULT_BACK_LENGTH_CM, f"뒷길이 표준표를 못 읽어 종전 {DEFAULT_BACK_LENGTH_CM}㎝" + index = next((i for i, limit in enumerate(steps) if height_m <= float(limit)), len(steps)) + low, high = rows[min(index, len(rows) - 1)] + label = f"직고 {height_m:g}m" + (f" ≤{steps[index]:g}m" if index < len(steps) else " 7m 초과") + span = f"{low}~{high}㎝" if high is not None else f"{low}㎝ 이상" + picked = next( + ( + size + for size in sorted(STONE_BACK_LENGTH_TABLE) + if size >= low and (high is None or size <= high) + ), + None, + ) + head = f"품셈 13-4-4 [주]⑩ 뒷길이 표준(원문 L7172~7181) · {bond} {label} {span}" + if picked is None: + return ( + DEFAULT_BACK_LENGTH_CM, + f"{head} 안에 품셈 규격이 없어 종전 {DEFAULT_BACK_LENGTH_CM}㎝", + ) + tail = "" if picked == low else f"(하한 {low}㎝ 는 규격 일곱(원문 L7152)에 없어 그 위 첫 규격)" + return picked, f"{head} → 하한 {picked}㎝{tail}" + + +def _back_length( + options: dict[str, Any], *, wet: bool | None = None, height_m: float | None = None +) -> int: + """저장된 뒷길이(㎝)를 **그대로** 돌려준다. 안 정했으면 뒷길이 표준표 하한(메/찰·높이를 받으면), + 못 받는 자리(골막이 등)는 종전 기본 45. ⚠ **접지 않는다.** 품셈 13-4-3·13-4-4 [주]① 이 25·30·35·45·55·60·75 **일곱 규격**을 다 주므로 접을 까닭이 없다. 그 밖의 값(40 등)은 **계수가 없다고 드러낸다** — @@ -463,13 +512,26 @@ def _back_length(options: dict[str, Any]) -> int: # 45㎝ 계수로 **조용히** 돌고 999㎝ 도 60㎝ 로 접혔다(2026-09-08 실측). # 표에 없으면 그 값을 그대로 돌려주고, 계수를 고르는 쪽이 「없다」고 드러낸다. return value - return DEFAULT_BACK_LENGTH_CM + if wet is None or height_m is None: + return DEFAULT_BACK_LENGTH_CM + return standard_back_length(wet=wet, height_m=height_m)[0] -def back_length_default_note(options: dict[str, Any], back_cm: int) -> str | None: +def back_length_default_note( + options: dict[str, Any], + back_cm: int, + *, + wet: bool | None = None, + height_m: float | None = None, +) -> str | None: """뒷길이를 안 골라 기본으로 섰을 때의 한 줄 — 표 사유·구조물도 그림이 **같은 말**을 씀.""" if any(options.get(key) not in (None, "") for key in BACK_LENGTH_KEYS): return None + if wet is not None and height_m is not None: + return ( + f"⚠ 뒷길이를 안 골라 {standard_back_length(wet=wet, height_m=height_m)[1]} 로 섰습니다 " + "— 구조물 상세에서 고르면 바뀜 · 그 값이 두께식·고임돌·돌 무게·채움콘크리트 계수를 모두 가릅니다" + ) return ( f"⚠ 뒷길이를 안 골라 기본 {back_cm}㎝ 로 섰습니다 " "— 그 값이 두께식·고임돌·돌 무게·채움콘크리트 계수를 모두 가릅니다" diff --git a/resources/data_masonry/masonry_back_length_2026-01-01.json b/resources/data_masonry/masonry_back_length_2026-01-01.json new file mode 100644 index 00000000..c8de0f01 --- /dev/null +++ b/resources/data_masonry/masonry_back_length_2026-01-01.json @@ -0,0 +1,40 @@ +{ + "schema_version": "1.0", + "dataset_id": "masonry_back_length_standard", + "effective_date": "2026-01-01", + "note": "돌쌓기 뒷길이 표준 — **높이 × 메/찰** 로 범위가 갈린다. 뒷길이를 안 고른 돌쌓기는 붙박이 45㎝ 로 서고 있었는데, 그 자리를 이 표의 **하한**으로 바꿈(2026-09-14 브레인 판정). 사용자가 칸에서 고르면 그 값이 이김.", + "source": { + "doc": "산림사업 표준품셈(산림청고시 제2025-82호) 13-4-4 [주]⑩ <뒷길이 표준>", + "line": "원문 L7172~7181", + "quote": "높은 메쌓기는 밑으로 내려갈수록 뒷길이를 길게 하는 것이 원칙이다 · 높이는 첨단으로부터 기준 한다 · 특수한 조건에서는 본 기준과 달리 적용할 수 있다" + }, + "policy": { + "suggestion": "⭐ 제안값 = **범위 안에 드는 품셈 규격 중 가장 작은 것**(브레인 판정 ㉮). 하한이 규격(25·30·35·45·55·60·75)에 없으면 그 위 첫 규격 — 메쌓기 ~3m·~5m 하한 36㎝ 는 45㎝. 35 로 내리면 품셈 범위 아래로 나가 안 됨.", + "boundary": "⚠ 원문 표기 `∼1.5 · ∼3 · ∼5 · ∼7 · 7이상` — 표준경사 표(`masonry_slope`)와 같이 「∼」를 **이하**로 읽어 경계값은 앞 칸(높이 3.0m → `~3` 칸).", + "no_write_back": "⚠ 제안값을 저장에 박지 않음 — 「안 고름」과 「45 로 고름」이 구별돼야 함(표준경사와 같은 자리)." + }, + "steps_m": [1.5, 3.0, 5.0, 7.0], + "table_cm": { + "메쌓기": [ + [25, 35], + [36, 45], + [36, 60], + [45, 75], + [75, null] + ], + "찰쌓기": [ + [25, 35], + [30, 35], + [35, 45], + [35, 55], + [45, 60] + ] + }, + "not_here": { + "note": "이 표가 쓰지 않는 칸.", + "items": [ + "돌붙이기 열(메 25~60 · 찰 20~40)은 높이로 안 갈림 — 바닥막이 돌붙임은 뒷길이가 붙박이(`_UnitQuantity_Revetment.BED_SILL_FORMS`)라 여기서 안 씀", + "골막이는 뒷길이 기본이 종전대로 45㎝ — 이 표의 대상(돌쌓기 13-4)이 아님" + ] + } +} diff --git a/resources/library_structure/masonry_wet.json b/resources/library_structure/masonry_wet.json index b9c1cc9a..bf4248e7 100644 --- a/resources/library_structure/masonry_wet.json +++ b/resources/library_structure/masonry_wet.json @@ -19,7 +19,13 @@ "source": "judged_face_slope", "note": "사용자 값이 있으면 그 값, 없으면 품셈 13-4-4 [주]⑪ 표준경사를 전개가 판정해 넘김" }, - "L3": { "label": "뒷길이(㎝)", "option": "back_len_cm", "default": 45 }, + "L3": { + "label": "뒷길이(㎝)", + "option": "back_len_cm", + "default": 45, + "default_from": "standard_back_length", + "note": "안 고르면 품셈 13-4-4 [주]⑩ 뒷길이 표준 하한(높이·메/찰 · 범위 안 가장 작은 규격) — 전개와 한 벌(2026-09-14 브레인 판정 ㉮). 45 는 표를 못 읽을 때만" + }, "돌종류": { "label": "돌 종류", "option": "stone_kind", "default": "" }, "계수기준": { "label": "야면석 계수", "option": "stone_coeff_basis", "default": "" }, "TOP_IN": { diff --git a/resources/tester/test_b08_back_length_standard.py b/resources/tester/test_b08_back_length_standard.py new file mode 100644 index 00000000..7a26c8fe --- /dev/null +++ b/resources/tester/test_b08_back_length_standard.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +"""뒷길이 표준(품셈 13-4-4 [주]⑩) — 안 고른 돌쌓기의 뒷길이는 **표의 하한** (2026-09-14 브레인 판정 ㉮). + +앞서 붙박이 45㎝ 로 섰음. 표는 높이 × 메/찰 로 범위를 줌 — 하한이 규격(25·30·35·45·55·60·75)에 +없으면 **범위 안 그 위 첫 규격**(메쌓기 ~3m·~5m 하한 36 → 45). 경계값은 앞 칸(표준경사와 같음). +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from B08_Quantity.B08_Quantity_Engine_UnitQuantity_StoneSpec import ( # noqa: E402 + _back_length, + back_length_default_note, + standard_back_length, +) + +# (높이, 메쌓기 제안, 찰쌓기 제안) — 원문 L7174~7177 +CELLS = [ + (1.0, 25, 25), + (1.5, 25, 25), # 경계값은 앞 칸 + (2.5, 45, 30), # 메 36~45 → 45 · 찰 30~35 → 30 + (3.0, 45, 30), + (4.0, 45, 35), # 메 36~60 → 45 · 찰 35~45 → 35 + (6.0, 45, 35), # 메 45~75 · 찰 35~55 + (7.5, 75, 45), # 메 75이상 · 찰 45~60 +] + + +@pytest.mark.parametrize("height, dry, wet", CELLS) +def test_표_하한(height: float, dry: int, wet: int) -> None: + assert standard_back_length(wet=False, height_m=height)[0] == dry + assert standard_back_length(wet=True, height_m=height)[0] == wet + + +def test_하한이_규격에_없으면_그_위_첫_규격_사유() -> None: + cm, basis = standard_back_length(wet=False, height_m=2.5) + assert cm == 45 + assert "36~45㎝" in basis and "하한 36㎝ 는 규격 일곱(원문 L7152)에 없어" in basis + assert "원문 L7172~7181" in basis # 사용자가 되짚을 줄 번호(브레인 2026-09-14) + + +def test_고른_값이_이긴다() -> None: + assert _back_length({"back_len_cm": "55"}, wet=True, height_m=2.5) == 55 + assert back_length_default_note({"back_len_cm": 55}, 55, wet=True, height_m=2.5) is None + + +def test_메찰_높이를_모르는_자리는_종전_45() -> None: + assert _back_length({}) == 45 + assert "기본 45㎝" in (back_length_default_note({}, 45) or "") diff --git a/resources/tester/test_b08_build_table_templates.py b/resources/tester/test_b08_build_table_templates.py index adbbf5ae..de4976bb 100644 --- a/resources/tester/test_b08_build_table_templates.py +++ b/resources/tester/test_b08_build_table_templates.py @@ -38,6 +38,9 @@ def _structures() -> list[dict]: "length_m": 10, "foundation": "기초유", "stone_kind": "야면석·호박돌", + # 2026-09-14 — 안 고르면 뒷길이 표준 하한(찰 ≤3m 30㎝)이라 야면석 관측 무게(35·45·55)가 + # 없어 돌 줄이 안 섬. 돌 줄을 보는 시험이라 45 로 고름 · 빈 칸 길은 아래 시험. + "back_len_cm": 45, }, }, { @@ -76,6 +79,21 @@ def test_양식_갈음은_값을_안_움직인다() -> None: assert stone["source"] == "uljin_library" +def test_뒷길이_안_고르면_양식도_전개와_같은_표준_하한() -> None: + """품셈 13-4-4 [주]⑩ 하한(찰 ≤3m 30㎝)을 전개와 양식이 한 벌로 씀(브레인 판정 ㉮).""" + structures = _structures()[:1] + structures[0]["options"].pop("back_len_cm") + structures[0]["options"].pop("stone_kind") + names = {"masonry_wet": "돌쌓기(찰)"} + before = build_table(structures, names, None, None, 0.2, use_templates=False)["structures"][0] + after = build_table(structures, names, None, None, 0.2)["structures"][0] + assert after["library_item"] == "돌쌓기(찰)" + assert [c["name"] for c in after["components"]] == [c["name"] for c in before["components"]] + for got, want in zip(after["components"], before["components"]): + assert got["amount"] == pytest.approx(want["amount"], rel=1e-9, abs=1e-12), got["name"] + assert any("하한 30㎝" in note for note in before["notes"]) + + def test_자재총괄_갈래와_배합_성분() -> None: names = {"masonry_wet": "돌쌓기(찰)", "masonry_dry": "돌쌓기(메)"} before = build_material_table( diff --git a/resources/tester/test_b08_structure_figure.py b/resources/tester/test_b08_structure_figure.py index ef184246..ddf46501 100644 --- a/resources/tester/test_b08_structure_figure.py +++ b/resources/tester/test_b08_structure_figure.py @@ -89,7 +89,9 @@ def test_기본값으로_그린_장은_표_사유와_같은_말을_적는다() - options = {"height_m": 2.0} _rows, table_notes = stone_masonry(2.0, 1.0, options, wet=False) table_default = [note for note in table_notes if "뒷길이를 안 골라" in note] - assert table_default == [back_length_default_note(options, 45)] + # 2026-09-14 — 기본은 뒷길이 표준 하한(메 ≤3m 36~45㎝ → 45) · 표·그림이 같은 말. + assert table_default == [back_length_default_note(options, 45, wet=False, height_m=2.0)] + assert "하한 36㎝ 는 규격 일곱" in table_default[0] texts = _texts(build_figure(_sheet())) assert table_default[0] in texts diff --git a/resources/tester/test_b08_structure_template_parity.py b/resources/tester/test_b08_structure_template_parity.py index 0fc802b8..98a61a31 100644 --- a/resources/tester/test_b08_structure_template_parity.py +++ b/resources/tester/test_b08_structure_template_parity.py @@ -61,7 +61,16 @@ CASES = [ dict(height=2.0, length=7.5, back_len_cm=45, face_slope_ratio=0.35, foundation="기초유"), dict(height=2.0, length=10.0, back_len_cm=55, thickness_top_m=0.9, foundation="기초버림"), dict(height=2.0, length=10.0, back_len_cm=45, thickness_bottom_m="1.4", stone_kind="깬잡석"), - dict(height=2.0, length=10.0, stone_kind="야면석·호박돌", stone_coeff_basis="실무 관행"), + # 뒷길이 45 — 안 고르면 표준 하한 30㎝ 라 야면석 관측 무게(35·45·55)가 없어 양식만 오류 줄(명세 13장 Ⓐ) + dict( + height=2.0, + length=10.0, + back_len_cm=45, + stone_kind="야면석·호박돌", + stone_coeff_basis="실무 관행", + ), + # 뒷길이 안 고름 — 전개·양식이 같은 표준 하한(찰 ≤3m 30㎝, 브레인 판정 ㉮) + dict(height=2.0, length=10.0, stone_kind="깬돌", foundation="기초유"), dict(height=1.5, length=10.0, back_len_cm=35, weep_hole_area_m2=3, foundation="기초유"), dict(height=2.5, length=12.0, back_len_cm=60, stone_kind="견치돌", rubble=0.3), # 줄이 안 서는 갈래 — `when` diff --git a/resources/tester/test_b08_unit_quantity.py b/resources/tester/test_b08_unit_quantity.py index 8e7249ef..3ad095f4 100644 --- a/resources/tester/test_b08_unit_quantity.py +++ b/resources/tester/test_b08_unit_quantity.py @@ -125,7 +125,8 @@ def test_실무_시트_m당_값_재현() -> None: 품셈 1-2-2 의 소수 자리는 표기 규칙이고 계산은 전정밀로 둔다(PLAN 8-16). 맞추려고 식을 비틀지 않는다 — 허용오차를 그 차이만큼 둔다. """ - result = expand(돌쌓기찰(height=1.5, length=1.0)) + # 실무 시트가 뒷길이 45 — 2026-09-14 부터 안 고르면 표준 하한(찰 ≤1.5m 25㎝)이라 고름. + result = expand(돌쌓기찰(height=1.5, length=1.0, back_len_cm=45)) assert result.height_m == 1.5 assert result.length_m == 1.0 assert 성분(result, "터파기").amount == pytest.approx(1.55, abs=0.02) @@ -135,7 +136,7 @@ def test_실무_시트_m당_값_재현() -> None: def test_평균두께는_전정밀로() -> None: """실무가 0.83 으로 반올림해 재계산한 자리 — 우리는 0.825 를 그대로 쓴다.""" - result = expand(돌쌓기찰(height=1.5, length=1.0)) + result = expand(돌쌓기찰(height=1.5, length=1.0, back_len_cm=45)) # 터파기 = 높이 × (평균두께 + 0.2) × 연장 = 1.5 × 1.025 = 1.5375 assert 성분(result, "터파기").amount == pytest.approx(1.5375)