feat(B08): 새 칸 다섯을 골막이·개거·물구멍에 물림 — 방수로가 정면적을 가름
골막이 (erosion_check) - spillway 「있음/없음」 — 「없음」이면 파형강관 0.5024㎡ 를 안 뺌. 정면적이 밑수라 일곱 줄이 +2.1~3.1 % 움직임. 「있음」이 정본과 같은 값(정본은 방수로 치수 0 인 개소에서도 뺐음). 안 고르면 정본 쪽으로 서고 사유가 뜸. - stone_kind · back_len_cm 를 읽음. 「칸이 없다」던 사유가 「안 골랐다」로 바뀜. 돌 무게 헬퍼 한 벌 (stone_weight_per_m2) - 확정 5차 큰 것 7 규칙을 돌쌓기·골막이가 같이 씀. 계산식이 기본, 야면석만 관측표. 두 벌로 두면 같은 확정이 구조물마다 갈림. 물구멍 헬퍼 한 벌 (weep_hole_spec) - weep_hole_diameter_mm · weep_hole_area_m2 를 읽고 비우면 Ø50 · 2㎡당. 지름은 규격(spec)으로 실어 단가가 갈리게 함. - 정본이 개소당 관 길이를 평균두께(0.83m)로 잡는 것을 주석에 남김 — 우리 상수 0.5m 와 갈리는 자리이고 미결임. 개거 — ditch_spec 칸이 와서 두 규격을 고를 수 있음. 시험 — 등록부 정책 예외에 새 칸 넷(ditch_spec · weep_hole 둘 · fill_slope_ratio)과 사토장 둘을 넣음. 613 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,55 @@ STONE_WEIGHT_FORMULA_BASIS = (
|
||||
"⭐ 확정 5차 — 뒷길이 × 0.77(채움률) × 2.65(비중). ⚠ 잠정 — 실무자 협의 후 명확히 할 값"
|
||||
)
|
||||
|
||||
#: 물구멍 칸 — 2026-09-09 에 등록부에 생겼다(랩탑 메인 `0b0763ed`). **빈 칸이 기본**이다.
|
||||
#: ⚠ 기본 Ø50 은 **국가기준**이고 2㎡당 1개소는 실무 관측값이다(법은 「2~3㎡당 1개소 이상」).
|
||||
WEEP_HOLE_DIAMETER_KEY = "weep_hole_diameter_mm"
|
||||
WEEP_HOLE_AREA_KEY = "weep_hole_area_m2"
|
||||
WEEP_HOLE_DIAMETER_DEFAULT_MM = 50
|
||||
|
||||
|
||||
def weep_hole_spec(options: dict[str, Any]) -> tuple[float, int, str]:
|
||||
"""(개소당 벽면적 ㎡, 관 지름 ㎜, 근거). 안 정하면 기본으로 서고 그 사실이 근거에 남는다."""
|
||||
area = _num(options.get(WEEP_HOLE_AREA_KEY), 0.0)
|
||||
diameter = _num(options.get(WEEP_HOLE_DIAMETER_KEY), 0.0)
|
||||
area_given = area > 0
|
||||
dia_given = diameter > 0
|
||||
if not area_given:
|
||||
area = STONE_MASONRY["weep_hole_area_m2"]
|
||||
if not dia_given:
|
||||
diameter = WEEP_HOLE_DIAMETER_DEFAULT_MM
|
||||
basis = (
|
||||
f"{area:g}㎡당 1개소 · Ø{int(diameter)}"
|
||||
+ ("" if area_given else " · ⚠ 면적 안 정함 — 실무 관측 2㎡(법은 2~3㎡당 1개소 이상)")
|
||||
+ ("" if dia_given else " · ⚠ 지름 안 정함 — 국가기준 Ø50")
|
||||
)
|
||||
return area, int(diameter), basis
|
||||
|
||||
|
||||
def stone_weight_per_m2(
|
||||
back_cm: int, kind: str, observed: float | None
|
||||
) -> tuple[str, float | None, str, str]:
|
||||
"""(줄 이름, ton/㎡, 근거, source) — ⭐ 확정 5차 큰 것 7.
|
||||
|
||||
**계산식이 기본**이고 야면석만 관측표다. 돌쌓기·골막이가 같은 규칙을 쓰도록 여기 한 벌만 둔다.
|
||||
"""
|
||||
if kind in STONE_WEIGHT_OBSERVED_KINDS:
|
||||
basis = (
|
||||
f"× {observed} ton/㎡ (뒷길이 {back_cm}㎝) · {STONE_WEIGHT_BASIS}"
|
||||
if observed is not None
|
||||
else ""
|
||||
)
|
||||
return kind, observed, basis, STONE_WEIGHT_SOURCE
|
||||
ton = back_cm / 100.0 * STONE_PACKING_RATIO * STONE_DENSITY_TON_M3
|
||||
basis = (
|
||||
f"× {ton:.3f} ton/㎡ = 뒷길이 {back_cm / 100.0:g} × {STONE_PACKING_RATIO}"
|
||||
f" × {STONE_DENSITY_TON_M3}"
|
||||
+ (f" · {kind}" if kind else " · ⚠ 돌 종류를 안 골랐음")
|
||||
+ f" · {STONE_WEIGHT_FORMULA_BASIS}"
|
||||
)
|
||||
return kind or "돌", ton, basis, ""
|
||||
|
||||
|
||||
#: 뒷채움(막자갈) 폭 — ⭐ 사용자 확정 5차 작은 것 3 「막자갈 = (상 + 하) ÷ 2 × H」.
|
||||
#: ⚠⚠ **여기 상·하는 벽 두께가 아니라 뒷채움 폭이다.** 벽 두께(0.75/0.9)를 끌어다 쓰면
|
||||
#: H=2.0 에서 실무 0.75 가 1.66(입적)으로 튄다 — 옛 식이 곧 그 자리였다.
|
||||
@@ -874,35 +923,18 @@ def stone_masonry(
|
||||
)
|
||||
)
|
||||
|
||||
# 돌 무게 — ⭐ 확정 5차 큰 것 7. **계산식이 기본**이고 야면석만 관측표다.
|
||||
# 돌 무게 — ⭐ 확정 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 ""
|
||||
stone_name, stone_ton, weight_tail, weight_source = stone_weight_per_m2(
|
||||
back_cm, kind_label, table["stone_ton_per_m2"]
|
||||
)
|
||||
weight_basis = f"돌쌓기 {weight_tail}" if weight_tail else ""
|
||||
if not kind_label:
|
||||
notes.append(
|
||||
"돌 종류를 안 골라 **계산식**(뒷길이 × 0.77 × 2.65)으로 섰습니다 — "
|
||||
"야면석이면 계산식이 안 맞아 관측표로 갈립니다"
|
||||
)
|
||||
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}㎝ 의 돌중량이 원본 표에 없어 {stone_name}을 내지 못함")
|
||||
@@ -973,16 +1005,20 @@ def stone_masonry(
|
||||
# 지금 쓰는 13-4 계열에는 제잡비 행 자체가 없어 겹치지 않는다(전수 확인).
|
||||
# ⚠ 관종·지름은 미확정 — 법은 「지름 3~6㎝ 파이프」, 실무 관측은 Ø50. 규격이 정해지면
|
||||
# 이름에 붙인다(`물구멍 Ø50`). 지어내지 않고 규격 없는 이름으로 둔다.
|
||||
# ⚠ 「미확정」만 적으면 사용자가 무엇을 정해야 하는지 모른다 —
|
||||
# **지금 무슨 값으로 돌고 있는지**를 함께 적는다(원단위 미확보와 같은 방식).
|
||||
# ⓘ 정본은 개소당 관 길이를 **평균두께**로 잡는다(H=2.0 에서 0.83m). 우리는 상수 0.5m 라
|
||||
# 그만큼 짧게 선다 — 관은 벽을 가로지르므로 정본 쪽이 이치에 맞는다(미결).
|
||||
hole_area, hole_dia, hole_basis = weep_hole_spec(options)
|
||||
components.append(
|
||||
Component(
|
||||
"물구멍관",
|
||||
"m",
|
||||
masonry_area / constants["weep_hole_area_m2"] * constants["weep_hole_length_m"],
|
||||
masonry_area / hole_area * constants["weep_hole_length_m"],
|
||||
DESTINATION["물구멍관"],
|
||||
# ⚠ 「미확정」만 적으면 사용자가 무엇을 정해야 하는지 모른다 —
|
||||
# **지금 무슨 값으로 돌고 있는지**를 함께 적는다(원단위 미확보와 같은 방식).
|
||||
"돌쌓기 ÷ 2㎡/개소 × 0.5 m/개소 · ⚠ 잠정: 관 Ø 미정(법 3~6㎝ / 실무 Ø50) ·"
|
||||
" 간격 2.0㎡당 1개소(법 2~3㎡당 1개소 이상)",
|
||||
f"돌쌓기 ÷ {hole_area:g}㎡/개소 × {constants['weep_hole_length_m']:g} m/개소"
|
||||
f" · {hole_basis}",
|
||||
spec=f"Ø{hole_dia}",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ def erosion_check_dam(
|
||||
STONE_MASONRY,
|
||||
_back_length,
|
||||
fill_concrete_mpa,
|
||||
stone_weight_per_m2,
|
||||
weep_hole_spec,
|
||||
)
|
||||
|
||||
form = str(options.get("form") or "").strip()
|
||||
@@ -239,8 +241,8 @@ def erosion_check_dam(
|
||||
return [], [f"뒷길이 {back_cm}㎝ 는 품셈 표(25·30·35·45·55·60·75㎝)에 없어 계수가 없습니다"]
|
||||
if not any(options.get(key) is not None for key in ("back_len_cm", "stone_back_length_cm")):
|
||||
notes.append(
|
||||
f"⚠ 골막이 제원에 **뒷길이 칸이 없어** 기본 {back_cm}㎝ 로 섰습니다 "
|
||||
"— 그 값이 두께식·고임돌·야면석·채움콘크리트 계수를 모두 가릅니다"
|
||||
f"⚠ 뒷길이를 안 골라 기본 {back_cm}㎝ 로 섰습니다 "
|
||||
"— 그 값이 두께식·고임돌·돌 무게·채움콘크리트 계수를 모두 가릅니다"
|
||||
)
|
||||
back_m = back_cm / 100.0
|
||||
|
||||
@@ -252,13 +254,21 @@ def erosion_check_dam(
|
||||
slope_note = f"1:{slope:g} — 정본 「반수면비탈」 붙박이(안 정함)"
|
||||
|
||||
# ① 정면적(사다리꼴) − ② 방수로 파형강관 단면
|
||||
# ⚠ 「없음」이면 안 뺀다 — 정면적이 밑수라 **열한 줄이 통째로 움직인다.**
|
||||
# ⓘ 정본은 방수로 치수가 0 인 개소에서도 뺐다. 그러므로 **정본과 같은 값은 「있음」**이고,
|
||||
# 「없음」은 정본보다 크게 나온다. 안 고르면 정본 쪽(「있음」)으로 서고 그 사실을 알린다.
|
||||
trapezoid = _floor2((top_m + bottom_m) / 2 * height_m)
|
||||
spillway = str(options.get("spillway") or "").strip()
|
||||
pipe = const["spillway_pipe_r_m"] ** 2 * const["spillway_pipe_pi"]
|
||||
front_area = trapezoid - pipe
|
||||
notes.append(
|
||||
f"방수로 파형강관 {pipe:.4f}㎡ 를 정면적에서 뺐습니다 — 정본 붙박이(Ø0.8) "
|
||||
"⚠ 「방수로 없음」을 고를 칸이 등록부에 없어 늘 뺍니다"
|
||||
)
|
||||
if spillway == "없음":
|
||||
front_area = trapezoid
|
||||
notes.append("방수로 「없음」이라 파형강관 단면을 안 뺐습니다 — 정본보다 정면적이 큽니다")
|
||||
else:
|
||||
front_area = trapezoid - pipe
|
||||
notes.append(
|
||||
f"방수로 파형강관 {pipe:.4f}㎡ 를 정면적에서 뺐습니다 — 정본 붙박이(Ø0.8)"
|
||||
+ ("" if spillway else " · ⚠ 방수로를 안 골라 정본대로 뺐습니다")
|
||||
)
|
||||
|
||||
masonry = _floor2(front_area * _round2(math.hypot(slope, 1.0)))
|
||||
top_t = _floor2(back_m + const["thickness_top_per_m"] * height_m)
|
||||
@@ -285,16 +295,20 @@ def erosion_check_dam(
|
||||
),
|
||||
]
|
||||
|
||||
stone_ton = coeff["stone_ton_per_m2"]
|
||||
# 돌 무게 — ⭐ 확정 5차 큰 것 7. 돌쌓기와 **같은 헬퍼**를 쓴다(규칙이 하나여야 한다).
|
||||
kind = str(options.get("stone_kind") or "").strip()
|
||||
stone_name, stone_ton, weight_tail, _src = stone_weight_per_m2(
|
||||
back_cm, kind, coeff["stone_ton_per_m2"]
|
||||
)
|
||||
if stone_ton is None:
|
||||
notes.append(f"뒷길이 {back_cm}㎝ 에 야면석 중량 칸이 비어 있어 돌을 못 세웠습니다")
|
||||
notes.append(f"뒷길이 {back_cm}㎝ 에 {stone_name} 중량 칸이 비어 있어 돌을 못 세웠습니다")
|
||||
else:
|
||||
rows.append(
|
||||
(
|
||||
"야면석",
|
||||
stone_name,
|
||||
"ton",
|
||||
_ceil2(base_area * stone_ton),
|
||||
f"(돌쌓기+돌붙임) {base_area:.4f}㎡ × {stone_ton} ton/㎡ (뒷길이 {back_cm}㎝)",
|
||||
f"(돌쌓기+돌붙임) {base_area:.4f}㎡ {weight_tail}",
|
||||
)
|
||||
)
|
||||
wedge_per = coeff["wedge_stone_m3_per_m2"]
|
||||
@@ -312,15 +326,14 @@ def erosion_check_dam(
|
||||
"입적 − ((돌쌓기+돌붙임) × 뒷길이 × 2/3 + 고임돌)",
|
||||
)
|
||||
)
|
||||
hole_area, hole_dia, hole_basis = weep_hole_spec(options)
|
||||
rows.append(
|
||||
(
|
||||
"물구멍관",
|
||||
"m",
|
||||
_round2(
|
||||
masonry / STONE_MASONRY["weep_hole_area_m2"] * STONE_MASONRY["weep_hole_length_m"]
|
||||
),
|
||||
f"돌쌓기 ÷ {STONE_MASONRY['weep_hole_area_m2']:g}㎡/개소 × "
|
||||
f"{STONE_MASONRY['weep_hole_length_m']:g}m/개소",
|
||||
_round2(masonry / hole_area * STONE_MASONRY["weep_hole_length_m"]),
|
||||
f"돌쌓기 ÷ {hole_area:g}㎡/개소 × {STONE_MASONRY['weep_hole_length_m']:g}m/개소"
|
||||
f" · {hole_basis} (Ø{hole_dia})",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -360,9 +373,10 @@ def erosion_check_dam(
|
||||
spec=f"{mpa}",
|
||||
)
|
||||
)
|
||||
if not options.get("stone_kind"):
|
||||
if not kind:
|
||||
notes.append(
|
||||
"돌 종류 칸이 골막이 등록부에 없어 **야면석**으로 섰습니다 — 정본 탭이 그 돌입니다"
|
||||
"돌 종류를 안 골라 **계산식**(뒷길이 × 0.77 × 2.65)으로 섰습니다 "
|
||||
"— 정본 탭은 야면석이고, 야면석은 계산식이 안 맞아 관측표로 갈립니다"
|
||||
)
|
||||
return components, notes
|
||||
|
||||
@@ -389,8 +403,8 @@ def erosion_check_dam(
|
||||
#: 본체 표를 못 찾았으므로 **「원문에 없음」으로 두고 그 사실을 화면에 적는다** —
|
||||
#: 「콘크리트 개거인데 콘크리트가 0」이 우리 결함으로 읽히지 않게.
|
||||
#:
|
||||
#: ⚠ **규격이 붙박이다** — 정본에 「150×200」 하나뿐이고 `open_ditch` 등록부에 **옵션이 하나도
|
||||
#: 없다**(규격 칸 없음). 칸이 생기면 이 표를 열면 된다.
|
||||
#: ✅ **규격 칸이 생겼다**(2026-09-09 랩탑 메인 `0b0763ed`) — 두 규격을 고를 수 있고,
|
||||
#: 비우면 「콘크리트 개거 150×200」으로 선다.
|
||||
OPEN_DITCH_FORMS: dict[str, dict[str, Any]] = {
|
||||
"콘크리트 개거 150×200": {
|
||||
"rows": (
|
||||
@@ -500,8 +514,8 @@ def open_ditch(length_m: float, options: dict[str, Any]) -> tuple[list[Component
|
||||
for name, unit, per_m, basis in table["rows"]
|
||||
]
|
||||
notes: list[str] = []
|
||||
if "ditch_spec" not in options:
|
||||
notes.append(f"규격이 「{spec}」 붙박이입니다 — 개거 제원에 규격 칸이 없습니다")
|
||||
if not options.get("ditch_spec"):
|
||||
notes.append(f"규격을 안 골라 「{spec}」으로 섰습니다 — 제원에서 고를 수 있습니다")
|
||||
if spec == "콘크리트 개거 150×200":
|
||||
notes.append(
|
||||
"⚠ 이 표에는 **콘크리트 본체 줄이 없습니다** — 정본 원문 그대로입니다"
|
||||
|
||||
Reference in New Issue
Block a user