From 252e6b5447360991188ce3731f11be815ff969d0 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 9 Sep 2026 12:08:47 +0900 Subject: [PATCH] =?UTF-8?q?feat(B08):=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=99=95=EC=A0=95=205=EC=B0=A8=20=EB=84=B7=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=E2=80=94=20=EB=A7=89=EC=9E=90=EA=B0=88=20=EC=8B=A4?= =?UTF-8?q?=EB=AC=B4=EC=8B=9D=20=C2=B7=20=EC=84=9D=EC=A0=81=20=C2=B7=20?= =?UTF-8?q?=EB=8F=8C=20=EB=AC=B4=EA=B2=8C=20=EA=B3=84=EC=82=B0=EC=8B=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 막자갈 (작은 것 3) - 옛 식(입적 − 몸통 − 고임돌)에서 (뒷채움 상 + 하) ÷ 2 × H 로 갈아탐. - 상·하는 벽 두께가 아니라 뒷채움 폭임. 정본 04.구조도(기슭막이).xls 여섯 탭(H=1.0~3.0 · 메/찰 · 기초유/버림) 전부 상 0.30 · 하 0.45 로 같음. 소광리는 같은 식에 0.30/0.60 을 써서 0.900 이 나옴 — 그 사실을 사유로 냄. - H=2.0 에서 정본 0.75 와 일치(옛 식은 1.66 쪽으로 튀던 자리). 석적 (작은 것 4) - 정면적 × 뒷길이 × 0.77 로 새 줄. 정본에 없고 소광리에만 있는 줄임을 사유에 적음. - 「체적」은 이미 「입적」 이름으로 내고 있어 이름만 맞음 — 새로 만들지 않음. 돌 무게 (큰 것 7) - 뒷길이 × 0.77(채움률) × 2.65(비중) 계산식이 기본. H=2.0 에서 정본 1.92 톤과 일치. - 야면석만 관측표(35→0.575 · 45→0.880 · 55→1.100) — 공극이 커 계산식이 안 맞음. - 종류를 안 고르면 정본 줄 이름 그대로 「돌」로 서고 그 사실이 사유로 뜸. - 잠정임(실무자 협의 후 명확히)을 화면 근거에 적음. 터파기 (큰 것 1) — 기초 + 비탈 그대로. 코드 변경 없음. 채집석 밑수도 새 줄을 따라감 — 석적 + 고임돌 + 막자갈. 거울 시험 신설: 정본 H=2.0 여섯 줄 일치, 일부러 다른 두 줄(입적·물구멍관)의 차이까지 못박음. Co-Authored-By: Claude Opus 5 (1M context) --- .../B08_Quantity_Engine_UnitQuantity.py | 144 ++++++++++++++---- 1 file changed, 111 insertions(+), 33 deletions(-) diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py index d69923b7..86bc6673 100644 --- a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py +++ b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py @@ -86,6 +86,32 @@ STONE_WEIGHT_BASIS = ( " 품셈·교본에는 돌중량표가 없음" ) +#: ⭐ 사용자 확정 5차 큰 것 7 — **돌 무게는 계산식**: `뒷길이 × 0.77 × 2.65`. +#: 정본 여섯 탭이 모두 그 식을 그대로 적어 둔다(「2.09 × 0.45 × 0.77 × 2.65 ton/m3」). +#: ⚠ **야면석은 그 식이 안 맞는다** — 공극이 커 ㎥당 무게가 달라 원문이 값을 직접 준다 +#: (35→0.575 · 45→0.880 · 55→1.100 ton/㎡). 그래서 **종류로 갈래를 둔다.** +#: ⚠ **잠정이다** — 확정 문구가 「나중에 실무자 협의 후 명확히」이므로 화면 근거에 적는다. +STONE_PACKING_RATIO = 0.77 # 채움률 — 돌 사이 공극을 뺀 몫 +STONE_DENSITY_TON_M3 = 2.65 # 석재 비중 +#: 계산식이 안 맞아 **관측표**를 쓰는 종류. 안 고른 경우도 이쪽(지금까지 쓰던 값이 그것). +STONE_WEIGHT_OBSERVED_KINDS = frozenset({"야면석·호박돌", "야면석"}) +STONE_WEIGHT_FORMULA_BASIS = ( + "⭐ 확정 5차 — 뒷길이 × 0.77(채움률) × 2.65(비중). ⚠ 잠정 — 실무자 협의 후 명확히 할 값" +) + +#: 뒷채움(막자갈) 폭 — ⭐ 사용자 확정 5차 작은 것 3 「막자갈 = (상 + 하) ÷ 2 × H」. +#: ⚠⚠ **여기 상·하는 벽 두께가 아니라 뒷채움 폭이다.** 벽 두께(0.75/0.9)를 끌어다 쓰면 +#: H=2.0 에서 실무 0.75 가 1.66(입적)으로 튄다 — 옛 식이 곧 그 자리였다. +#: 정본 `04.구조도(기슭막이).xls` **여섯 탭 전부** 상 0.30 · 하 0.45 로 같다 +#: (H=1.0~3.0 · 메/찰 · 기초유/버림 전수 확인, 2026-09-09). +#: ⓘ 소광리는 같은 식에 **0.30 / 0.60** 을 쓴다(그래서 H=2.0 에서 0.900). 문서마다 폭이 +#: 다르므로 **값이 적힌 구조물은 그 값**을 쓰고, 없으면 이 정본 값으로 서되 그 사실을 알린다. +STONE_BACKFILL_WIDTH_M = {"top": 0.30, "bottom": 0.45} + +#: 석적 — ⭐ 확정 5차 작은 것 4. **정본에는 없고 소광리에만 있는 줄**이라 사유에 적는다. +#: 소광리 식 `H × ℓ3 × 0.77`(면적 × 뒷길이 × 채움률) 그대로. +STONE_PILE_BASIS = "⚠ 정본에 없는 줄 — 소광리 시트에만 있음(정면적 × 뒷길이 × 0.77)" + #: 돌 종류별 계수표 — 품셈 13-4-3·13-4-4 [주]① · 교본 7-3. #: ⚠ 지금까지 **건설품셈 참고자료 한 벌**(돌 종류로 안 갈리는 표)로만 돌고 있었다. @@ -313,8 +339,16 @@ DESTINATION = { # ⚠ **보여 주기만 하는 줄** — 자재도 토공도 일위대가도 아니다. 정본 계산표 좌측 열에 # 「입적」으로 실려 있어 그 이름 그대로 낸다(2026-09-08 확정 ⑦). 값은 이미 식 안에 # 있던 것을 줄로 꺼낸 것뿐이고, `material`·`earthwork` 어디에도 안 섞인다. - # ⚠ 「석적」은 정본에 없다(소광리 시트에만) — **안 낸다.** + # ⚠ 「석적」은 정본에 없고 소광리 시트에만 있으나 **확정 5차로 세운다** — 같은 + # 보여 주기 줄이고, 「소광리에만 있는 줄」임을 사유에 적는다. "입적": "reference", + "석적": "reference", + # 돌 종류를 고르면 그 이름으로 줄이 선다 — 자재총괄이 이름으로 찾으므로 넷 다 둔다. + "야면석·호박돌": "material", + "깬잡석": "material", + "견치돌": "material", + # 종류를 안 고른 경우의 이름 — 정본 계산표 줄 이름 그대로다(「돌 ℓ3=45cm」). + "돌": "material", # 기초잡석(품셈 12-25) — 운반·부설·다짐 품이 붙는 **공종**이라 일위대가로 간다. # 자재총괄로 보내면 같은 잡석이 재료로 한 번 더 선다. RUBBLE_BASE_NAME: "unit_price", @@ -755,12 +789,9 @@ def stone_masonry( for key in ("wedge_stone_m3_per_m2", "fill_concrete_m3_per_m2"): if picked.get(key) is not None: table[key] = picked[key] - #: ⚠ **표는 「뒤채움 몫」, 우리 식은 「빼는 몫」** — 뜻이 반대라 1 에서 뺀다. - #: 교본은 「뒤채움 = 뒷길이 × (깬돌·잡석 1/2, 야면석 1/3)」이고, 우리 식이 입적에서 - #: 빼는 것은 **돌 몸통**이라 `1 − 뒤채움몫` 이다. 종전 2/3 이 곧 야면석(1 − 1/3)이었다. - #: ⚠ 그대로 넣었더니 미지정 값이 15.130 → 19.045 로 바뀌었다(만들다 잡음). - backfill_share = picked.get("backfill_ratio") - body_ratio = 1.0 - float(backfill_share) if backfill_share is not None else 2.0 / 3.0 + # ⚠ **교본의 「뒤채움 몫」(깬돌·잡석 1/2 · 야면석 1/3)은 이제 막자갈 밑수가 아니다** — + # ⭐ 확정 5차 작은 것 3 이 막자갈을 **뒷채움 폭 사다리꼴**로 못 박았다. 표는 그대로 + # 두되(다른 자리에서 쓸 수 있다) 여기서는 안 쓴다. kind_label = str(picked.get("kind") or "") # ⚠ `face_slope_ratio` 는 **2026-09-09 에 칸이 생겼다**(돌쌓기 계열 여섯 종류 · 표준도 # 제원 폼). 빈 값이 「자동」의 뜻이라 비어 있으면 아래 표준경사표가 돌고, 채우면 그 값이 @@ -779,7 +810,8 @@ def stone_masonry( # 실무 시트의 「정면적 × 1.04」가 바로 이 값이다(1:0.3 에서 1.0440 ≈ 1.04). masonry_area = face_area * math.hypot(1.0, slope_ratio) # 벽 두께 — 실무 구조물도 식(확정 2차 ②). **뒷길이가 들어간다.** - top_thickness = back_cm / 100.0 + constants["thickness_top_add_m"] + back_m = back_cm / 100.0 + top_thickness = back_m + constants["thickness_top_add_m"] bottom_thickness = top_thickness + constants["thickness_slope_per_m"] * max( height_m - constants["thickness_height_base_m"], 0.0 ) @@ -829,38 +861,82 @@ def stone_masonry( ) ) - stone_ton = table["stone_ton_per_m2"] + # 석적 — ⭐ 확정 5차 작은 것 4. 「체적」은 위 **입적** 줄이 이미 그것이고, 이쪽만 새 줄이다. + stone_pile = face_area * back_m * STONE_PACKING_RATIO + components.append( + Component( + "석적", + "㎥", + stone_pile, + DESTINATION["석적"], + f"정면적 × 뒷길이 {back_m:g}m × {STONE_PACKING_RATIO}(채움률) · {STONE_PILE_BASIS}", + ) + ) + + # 돌 무게 — ⭐ 확정 5차 큰 것 7. **계산식이 기본**이고 야면석만 관측표다. + # ⚠ 안 고른 경우도 **계산식**으로 선다 — 정본 여섯 탭이 종류를 안 적고 그 식을 쓰고, + # 그래야 정본 H=2.0 의 「1.92 톤」과 맞는다(관측표 0.88 로 서면 1.84 로 4 % 낮다). + # 이름은 정본 줄 이름 그대로 **「돌」**이고, 종류를 고르면 그 이름으로 바뀐다. + if kind_label in STONE_WEIGHT_OBSERVED_KINDS: + # 야면석은 공극이 커 계산식이 안 맞는다 — 원문 관측표를 그대로 쓴다. + stone_name = kind_label + stone_ton = table["stone_ton_per_m2"] + weight_basis = ( + f"돌쌓기 × {stone_ton} ton/㎡ (뒷길이 {back_cm}㎝) · {STONE_WEIGHT_BASIS}" + if stone_ton is not None + else "" + ) + weight_source = STONE_WEIGHT_SOURCE + else: + stone_name = kind_label or "돌" + stone_ton = back_m * STONE_PACKING_RATIO * STONE_DENSITY_TON_M3 + weight_basis = ( + f"돌쌓기 × {stone_ton:.3f} ton/㎡ = 뒷길이 {back_m:g} × {STONE_PACKING_RATIO}" + f" × {STONE_DENSITY_TON_M3}" + + (f" · {kind_label}" if kind_label else " · ⚠ 돌 종류를 안 골랐음") + + f" · {STONE_WEIGHT_FORMULA_BASIS}" + ) + weight_source = "" + if not kind_label: + notes.append( + "돌 종류를 안 골라 **계산식**(뒷길이 × 0.77 × 2.65)으로 섰습니다 — " + "야면석이면 계산식이 안 맞아 관측표로 갈립니다" + ) if stone_ton is None: # 원본 표가 비어 있는 칸이다 — 지어내지 않고 알린다(PLAN 8-8 ㉮). - notes.append(f"뒷길이 {back_cm}㎝ 의 돌중량이 원본 표에 없어 야면석을 내지 못함") + notes.append(f"뒷길이 {back_cm}㎝ 의 돌중량이 원본 표에 없어 {stone_name}을 내지 못함") else: components.append( Component( - "야면석", + stone_name, "ton", masonry_area * stone_ton, - DESTINATION["야면석"], - f"돌쌓기 × {stone_ton} ton/㎡ (뒷길이 {back_cm}㎝) · {STONE_WEIGHT_BASIS}", - source=STONE_WEIGHT_SOURCE, + DESTINATION.get(stone_name, "material"), + weight_basis, + source=weight_source, ) ) - # 막자갈 = 입적 − (면적 × 뒷길이 × 뒤채움몫 + 고임돌). - # 뒤채움 몫은 **돌 종류로 갈린다** — 깬돌·잡석 1/2 · 야면석 1/3 (교본 7-3). - wedge = masonry_area * _num(table["wedge_stone_m3_per_m2"]) # None 이면 0 — 막자갈에서 안 뺌 - stone_body = masonry_area * (back_cm / 100.0) * body_ratio # 면석 몸통 체적 - rubble = volume - (stone_body + wedge) - if rubble > 0: - components.append( - Component( - "막자갈", - "㎥", - rubble, - DESTINATION["막자갈"], - f"입적 − (면적×뒷길이×{body_ratio:.4g} + 고임돌) · {thickness_basis}" - + (f" · {kind_label}" if kind_label else ""), - ) + # 막자갈(뒷채움) — ⭐ 확정 5차 작은 것 3 「(상 + 하) ÷ 2 × H」. **뒷채움 폭**이지 벽 두께가 + # 아니다. 옛 식(입적 − 몸통 − 고임돌)에서 갈아탄 자리다. + backfill_top = STONE_BACKFILL_WIDTH_M["top"] + backfill_bottom = STONE_BACKFILL_WIDTH_M["bottom"] + rubble = (backfill_top + backfill_bottom) / 2.0 * height_m * length_m + components.append( + Component( + "막자갈", + "㎥", + rubble, + DESTINATION["막자갈"], + f"(뒷채움 상 {backfill_top:g} + 하 {backfill_bottom:g}) ÷ 2 × H {height_m:g}m" + f" × 연장 {length_m:g}m · 정본 여섯 탭 공통값", ) + ) + notes.append( + f"막자갈 뒷채움 폭이 정본(`04.구조도(기슭막이).xls`) 값 상 {backfill_top:g} · " + f"하 {backfill_bottom:g}m 붙박이입니다 — 구조물 제원에 뒷채움 폭 칸이 없습니다" + "(소광리는 같은 식에 0.30/0.60 을 씁니다)" + ) if wet: mpa, mpa_basis = fill_concrete_mpa(options) @@ -910,8 +986,11 @@ def stone_masonry( ) # 채집석 — **캐서 쓰는 구조물**의 돌 체적. 사토에서 뺄 밑수이고 **여기서 빼지 않는다.** + # ⚠ 밑수가 확정 5차로 바뀌었다 — 면석 몸통은 이제 **석적**(정면적×뒷길이×0.77)이고 + # 막자갈은 **뒷채움 사다리꼴**이다. 옛 「입적 − 몸통 − 고임돌」 몫이 아니다. if is_collected_stone(options): - collected = max(stone_body + wedge + max(rubble, 0.0), 0.0) + wedge = masonry_area * _num(table["wedge_stone_m3_per_m2"]) # None 이면 0 + collected = max(stone_pile + wedge + rubble, 0.0) if collected > 0: components.append( Component( @@ -919,9 +998,8 @@ def stone_masonry( "㎥", collected, DESTINATION["채집석"], - f"면석 몸통 {stone_body:.3f} + 고임돌 {wedge:.3f} + 막자갈" - f" {max(rubble, 0.0):.3f} ㎥ · 현장 채집분 ·" - " ⚠ 여기서 빼지 않음 — 사토에서 한 번만 뺌", + f"석적 {stone_pile:.3f} + 고임돌 {wedge:.3f} + 막자갈 {rubble:.3f} ㎥" + " · 현장 채집분 · ⚠ 여기서 빼지 않음 — 사토에서 한 번만 뺌", ) )