fix(B08): 돌쌓기 면적이 기울기 몫을 두 번 먹던 것 수정

㉘ 자기 감사 후속. 실무 시트의 「돌쌓기 = 정면적 × 1.04」에서 그 1.04 가
곧 기울기 몫임(1:0.3 → √(1+0.3²) = 1.0440). 시트가 반올림해 적은 것을
별도 계수로 오해해 hypot 위에 또 곱하고 있었음.

- 돌쌓기·큰돌쌓기 모두 기울기 몫을 한 번만 곱함
- 상수는 지우지 않고 `sheet_check_factor_at_0_3` 으로 성격을 바꿈 —
  1:0.3 에서 시트값과 맞는지 대조하는 검산 참고값
- 하드코딩 금지 이유를 주석에 남김: 큰돌쌓기는 「1:0.3 이상」이라
  기울기가 바뀌면 1.04 는 틀린 값이 되지만 hypot 은 따라감

값 변화 (실노선):
  고임돌 -3.85 % · 야면석 -3.85 % · 물구멍 -3.85 %
  막자갈 +3.20 % (입적에서 빼는 몫이 줄어 늘어남 — 식대로임)

시험
- 옛 시험이 덧곱을 계약으로 못 박고 있어 정정
- 거울 시험 추가: 기울기 0.3·0.5·1.0 에서 정면적 × hypot 과 일치
- 시트 대조 추가: 정면적 18.87 → 돌쌓기 19.62, 오차 0.5 % 안
- NULLABLE_SETTING_KEYS 계약 시험 추가 (홑 숫자 칸만 좁게 봄)

곁들여 타설 방식 안내 한 줄 — 고른 값이 아직 금액에 안 닿는다는 사실을
화면에 적음. 인계에 타설 공종 줄을 세울지는 이중계상 확인 뒤 결정.

시험: 666 passed · 24 skipped (B05 코리도 1건 기존 깨짐, 무관) · tsc 오류 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 05:51:55 +09:00
co-authored by Claude Opus 5
parent 3e729439cb
commit 8ccd577827
3 changed files with 35 additions and 9 deletions
@@ -58,7 +58,14 @@ DEFAULT_BACK_LENGTH_CM = 45
# 돌쌓기 전개식의 상수 — 실무 수식에 박혀 있던 값을 뺀 것. # 돌쌓기 전개식의 상수 — 실무 수식에 박혀 있던 값을 뺀 것.
STONE_MASONRY = { STONE_MASONRY = {
"face_to_slope_factor": 1.04, # 돌쌓기 면적 = 정면적 × 1.04 (비탈 기울기 몫) # ⚠ **곱하는 값이 아니라 검산 참고값이다** (2026-09-08 ㉘ 에서 고침).
# 실무 시트의 「돌쌓기 = 정면적 × 1.04」에서 그 1.04 가 **곧 기울기 몫**이다
# (1:0.3 → √(1+0.3²) = 1.0440 ≈ 1.04). 시트가 반올림해 적은 것을 우리가
# **별도 계수로 오해해 `hypot` 위에 또 곱하고 있었다** — 면적이 4 % 부풀었고
# 그 면적이 고임돌·야면석·채움콘크리트·모르터·물구멍 **전부의 밑수**였다.
# ⚠ 하드코딩하면 안 되는 값이다 — 큰돌쌓기는 「1:0.3 **이상**」이라 기울기가
# 바뀔 수 있고, 그때 1.04 는 틀린 값이 되지만 `hypot` 은 따라간다.
"sheet_check_factor_at_0_3": 1.04, # 1:0.3 에서 시트값과 맞는지 대조하는 자리
"thickness_base_m": 0.45, # 평균두께 식의 밑돌 두께 "thickness_base_m": 0.45, # 평균두께 식의 밑돌 두께
"thickness_top_coeff": 0.10, # 상부 두께 계수 (0.45 + 0.10·H) "thickness_top_coeff": 0.10, # 상부 두께 계수 (0.45 + 0.10·H)
"thickness_bottom_coeff": 0.40, # 하부 두께 계수 (0.45 + 0.40·H) "thickness_bottom_coeff": 0.40, # 하부 두께 계수 (0.45 + 0.40·H)
@@ -178,7 +185,9 @@ BOULDER_DIAMETERS = ("40~60", "60~80", "80~100")
#: 큰돌쌓기 전개 상수. **재료 원단위는 품셈에 없다** — 13-6 [주]⑦ 「재료량은 설계수량을 적용한다」. #: 큰돌쌓기 전개 상수. **재료 원단위는 품셈에 없다** — 13-6 [주]⑦ 「재료량은 설계수량을 적용한다」.
#: 그래서 여기서 내는 것은 **면적과 터파기 계열까지**이고 큰돌 자체는 미확보로 둔다. #: 그래서 여기서 내는 것은 **면적과 터파기 계열까지**이고 큰돌 자체는 미확보로 둔다.
BOULDER_MASONRY = { BOULDER_MASONRY = {
"face_to_slope_factor": 1.04, # 돌쌓기 면적 = 정면적 × 1.04 (13-4 와 같은 기울기 몫) # ⚠ 검산 참고값 — 곱하지 않는다. 까닭은 `STONE_MASONRY` 의 같은 칸 주석을 볼 것.
# 큰돌쌓기는 전면 기울기가 「1:0.3 **이상**」이라 특히 하드코딩하면 안 된다.
"sheet_check_factor_at_0_3": 1.04,
"excavation_extra_m": 0.2, # 터파기 폭 여유 "excavation_extra_m": 0.2, # 터파기 폭 여유
"backfill_thickness_m": 0.2, # 되메우기 두께 "backfill_thickness_m": 0.2, # 되메우기 두께
} }
@@ -221,8 +230,8 @@ def boulder_masonry(
slope_ratio = _num(options.get("face_slope_ratio"), 0.3) # 레지스트리에 칸 없음 — 기본 0.3 slope_ratio = _num(options.get("face_slope_ratio"), 0.3) # 레지스트리에 칸 없음 — 기본 0.3
constants = BOULDER_MASONRY constants = BOULDER_MASONRY
face_area = height_m * length_m face_area = height_m * length_m
slope_area = face_area * math.hypot(1.0, slope_ratio) # 기울기 몫은 **한 번만** — 13-4 와 같은 자리다(㉘).
masonry_area = slope_area * constants["face_to_slope_factor"] masonry_area = face_area * math.hypot(1.0, slope_ratio)
components = [ components = [
Component( Component(
@@ -230,7 +239,7 @@ def boulder_masonry(
"", "",
masonry_area, masonry_area,
DESTINATION["돌쌓기"], DESTINATION["돌쌓기"],
f"비탈면적 × 1.04 · 직경 {diameter}㎝ (품셈 13-6)", f"면적 × √(1+{slope_ratio}²) · 직경 {diameter}㎝ (품셈 13-6)",
) )
] ]
@@ -300,9 +309,9 @@ def stone_masonry(
constants = STONE_MASONRY constants = STONE_MASONRY
face_area = height_m * length_m # 정면적 face_area = height_m * length_m # 정면적
# 비탈면적 = 정면적 × √(1+n²) — 기울어진 만큼 길어진다. # 돌쌓기 면적 = 정면적 × √(1+n²) — 기울어진 만큼 길어진다. **기울기 몫은 한 번만.**
slope_area = face_area * math.hypot(1.0, slope_ratio) # 실무 시트의 「정면적 × 1.04」가 바로 이 값이다(1:0.3 에서 1.0440 ≈ 1.04).
masonry_area = slope_area * constants["face_to_slope_factor"] masonry_area = face_area * math.hypot(1.0, slope_ratio)
thickness = ( thickness = (
(constants["thickness_base_m"] + constants["thickness_top_coeff"] * height_m) (constants["thickness_base_m"] + constants["thickness_top_coeff"] * height_m)
+ (constants["thickness_base_m"] + constants["thickness_bottom_coeff"] * height_m) + (constants["thickness_base_m"] + constants["thickness_bottom_coeff"] * height_m)
@@ -310,7 +319,13 @@ def stone_masonry(
volume = face_area * thickness # 입적 volume = face_area * thickness # 입적
components = [ components = [
Component("돌쌓기", "", masonry_area, DESTINATION["돌쌓기"], "비탈면적 × 1.04"), Component(
"돌쌓기",
"",
masonry_area,
DESTINATION["돌쌓기"],
f"정면적 × √(1+{slope_ratio}²) — 비탈면적",
),
Component( Component(
"고임돌", "고임돌",
"", "",
+7
View File
@@ -352,6 +352,13 @@ function buildQuantitySidePanel(
placing.is_default ? `${label} (${L("B08_Quantity_Placing_Default_Tag")})` : label, placing.is_default ? `${label} (${L("B08_Quantity_Placing_Default_Tag")})` : label,
), ),
); );
// ⚠ 고른 값이 **아직 금액에 안 닿는다**는 사실을 숨기지 않는다(2026-09-08 ㉘).
// 인계가 타설 공종 줄을 아직 안 세운다 — B09 일위대가가 타설 품을 이미 갖고 있는지
// 확인되기 전까지는 세우면 같은 콘크리트를 두 번 센다(이중계상 ㉢ 과 같은 자리).
const pending = document.createElement("p");
pending.className = "b08-quantity__note";
pending.textContent = L("B08_Quantity_Placing_NotApplied");
panel.append(pending);
} }
// ⚠ 「정하면 얼마나 달라지는지」까지 보여야 사용자가 판단한다. 이 값은 **참고 표시 전용**이고 // ⚠ 「정하면 얼마나 달라지는지」까지 보여야 사용자가 판단한다. 이 값은 **참고 표시 전용**이고
// B08 의 어떤 계산에도 안 들어간다(금액은 B09 몫). // B08 의 어떤 계산에도 안 들어간다(금액은 B09 몫).
+4
View File
@@ -628,6 +628,10 @@ export const ui_locales_b2 = {
B08_Quantity_Method_Unset: ["안 정함", "Not set"], B08_Quantity_Method_Unset: ["안 정함", "Not set"],
B08_Quantity_Method_Ripping: ["긁어내기(암절취)", "Ripping"], B08_Quantity_Method_Ripping: ["긁어내기(암절취)", "Ripping"],
B08_Quantity_Method_Blasting: ["터뜨리기(발파암)", "Blasting"], B08_Quantity_Method_Blasting: ["터뜨리기(발파암)", "Blasting"],
B08_Quantity_Placing_NotApplied: [
"⚠ 이 선택은 아직 금액에 반영되지 않습니다 — 견적의 일위대가가 타설 품을 이미 갖고 있는지 확인 중입니다.",
"Not yet applied to cost — checking whether the unit-price already covers placing labour.",
],
B08_Quantity_Side_Topsoil: ["표토제거", "Topsoil Removal"], B08_Quantity_Side_Topsoil: ["표토제거", "Topsoil Removal"],
B08_Quantity_Side_Topsoil_Label: ["표토 두께(m)", "Topsoil thickness (m)"], B08_Quantity_Side_Topsoil_Label: ["표토 두께(m)", "Topsoil thickness (m)"],
B08_Quantity_Unset_Placeholder: ["안 정함", "Not set"], B08_Quantity_Unset_Placeholder: ["안 정함", "Not set"],