fix(B08): 뒷길이를 접지 않음 — 일곱 규격을 다 씀
조율 창 지시로 KeyError 를 막으러 갔다가 더 나쁜 것을 찾음. 죽지는 않고 있었고 대신 조용히 접히고 있었음. 40㎝ → 45㎝ 계수로 돎 999㎝ → 60㎝ 계수로 돎 25·30 → 35 로, 75 → 60 으로 접힘 ⚠ 접을 까닭이 없었음 — 품셈 13-4-3·13-4-4 [주]① 이 25·30·35·45·55·60·75 일곱 규격을 다 줌. 우리 표가 네 칸만 들고 있었을 뿐임. - 표를 일곱 규격으로 채움(건설품셈 참고자료 값, 고임돌은 「깬돌」 열) - 표에 없는 값(40 등)은 접지 않고 「품셈 표에 없어 물량이 서지 않습니다」 - ⚠ 원문 「-」인 칸은 0 줄을 만들지 않음 — 0 은 「없음」과 구별이 안 되고 받는 쪽이 「값이 0 인 자재」로 읽음 - ⚠ 고른 종류의 빈 칸은 빈 칸으로 덮음 — None 이라고 안 덮었더니 「야면석 75㎝」처럼 원문에 「-」인 칸에 종전 값(깬돌 0.25)이 섰음(만들다 잡음) ⚠ 옛 시험이 「덮는 위 칸으로 접는다」를 계약으로 못 박고 있었음 — 오늘 네 번째. 시험 넷 — 접지 않음 · 표에 없으면 물량 안 냄 · 고른 종류의 빈 칸은 줄 없음 · 일곱 규격이 다 섬. 시험: 723 passed · 24 skipped (B05 코리도 1건 기존 깨짐, 무관). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,11 +52,17 @@ from common_util.common_util_quantity_spread import spread_by_unit
|
||||
# ── 계수표 — 식에 박지 않고 여기서 고른다 ─────────────────────────────
|
||||
# 돌 뒷길이(㎝)별 원단위. 출처: `original/실무문서/_원단위라이브러리_울진소광.md` 「돌뒷길이별 원단위표」.
|
||||
# ⚠ 60㎝ 돌중량은 원본이 비어 있다 — 지어내지 않고 None 으로 둔다(PLAN 8-8 ㉮).
|
||||
#: ⚠ **일곱 규격**이다 — 품셈 13-4-3·13-4-4 [주]① 이 25·30·35·45·55·60·75 를 다 준다.
|
||||
#: 앞서 네 칸(35·45·55·60)만 들고 25·30 을 35 로, 75 를 60 으로 **접고** 있었다.
|
||||
#: `stone_ton_per_m2`(돌중량)는 **실무 관측값**이라 그 넷에만 있다 — 없는 칸은 `None`.
|
||||
STONE_BACK_LENGTH_TABLE: dict[int, dict[str, float | None]] = {
|
||||
25: {"fill_concrete_m3_per_m2": 0.11, "wedge_stone_m3_per_m2": None, "stone_ton_per_m2": None},
|
||||
30: {"fill_concrete_m3_per_m2": 0.14, "wedge_stone_m3_per_m2": 0.10, "stone_ton_per_m2": None},
|
||||
35: {"fill_concrete_m3_per_m2": 0.16, "wedge_stone_m3_per_m2": 0.12, "stone_ton_per_m2": 0.575},
|
||||
45: {"fill_concrete_m3_per_m2": 0.20, "wedge_stone_m3_per_m2": 0.15, "stone_ton_per_m2": 0.88},
|
||||
55: {"fill_concrete_m3_per_m2": 0.25, "wedge_stone_m3_per_m2": 0.18, "stone_ton_per_m2": 1.10},
|
||||
60: {"fill_concrete_m3_per_m2": 0.27, "wedge_stone_m3_per_m2": 0.20, "stone_ton_per_m2": None},
|
||||
75: {"fill_concrete_m3_per_m2": 0.34, "wedge_stone_m3_per_m2": 0.25, "stone_ton_per_m2": None},
|
||||
}
|
||||
DEFAULT_BACK_LENGTH_CM = 45
|
||||
|
||||
@@ -96,6 +102,8 @@ def stone_coefficients(options: dict[str, Any], back_cm: int) -> tuple[dict[str,
|
||||
kind = str(options.get(STONE_KIND_OPTION) or "").strip()
|
||||
if not table:
|
||||
return {}, ""
|
||||
if key not in [str(x) for x in (table.get("back_lengths_cm") or [])]:
|
||||
return {}, (f"뒷길이 {back_cm}㎝ 는 품셈 표(25·30·35·45·55·60·75㎝)에 없어 계수가 없습니다")
|
||||
if not kind:
|
||||
fallback = table.get("fallback") or {}
|
||||
return {
|
||||
@@ -218,11 +226,11 @@ BACK_LENGTH_KEYS = ("back_len_cm", "stone_back_length_cm")
|
||||
|
||||
|
||||
def _back_length(options: dict[str, Any]) -> int:
|
||||
"""뒷길이(㎝). 표에 없는 값이면 **가장 가까운 아래 칸**이 아니라 기본으로 간다.
|
||||
"""저장된 뒷길이(㎝)를 **그대로** 돌려준다. 안 정했으면 기본 45.
|
||||
|
||||
⚠ 표는 35·45·55·60 네 칸뿐인데 레지스트리 선택지는 25·30·35·45·55·60·75 일곱이다.
|
||||
25·30 은 35 로, 75 는 60 으로 접는다 — **원단위표가 그 구간을 그렇게 덮는다**
|
||||
(품셈 13-4 뒷길이 표준). 접은 사실은 근거 문구에 적는다.
|
||||
⚠ **접지 않는다.** 품셈 13-4-3·13-4-4 [주]① 이 25·30·35·45·55·60·75 **일곱 규격**을
|
||||
다 주므로 접을 까닭이 없다. 그 밖의 값(40 등)은 **계수가 없다고 드러낸다** —
|
||||
접으면 다른 규격 계수가 조용히 돈다.
|
||||
"""
|
||||
for key in BACK_LENGTH_KEYS:
|
||||
raw = options.get(key)
|
||||
@@ -232,13 +240,10 @@ def _back_length(options: dict[str, Any]) -> int:
|
||||
value = int(float(raw))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if value in STONE_BACK_LENGTH_TABLE:
|
||||
return value
|
||||
# 표에 없는 값 — 그 값을 덮는 **가장 가까운 위 칸**으로 접는다(작은 돌이 큰 칸에 들어감).
|
||||
larger = [cm for cm in sorted(STONE_BACK_LENGTH_TABLE) if cm >= value]
|
||||
if larger:
|
||||
return larger[0]
|
||||
return max(STONE_BACK_LENGTH_TABLE)
|
||||
# ⚠ **접지 않는다.** 앞서 「가장 가까운 위 칸」으로 접고 있었는데, 그러면 40㎝ 가
|
||||
# 45㎝ 계수로 **조용히** 돌고 999㎝ 도 60㎝ 로 접혔다(2026-09-08 실측).
|
||||
# 표에 없으면 그 값을 그대로 돌려주고, 계수를 고르는 쪽이 「없다」고 드러낸다.
|
||||
return value
|
||||
return DEFAULT_BACK_LENGTH_CM
|
||||
|
||||
|
||||
@@ -367,6 +372,11 @@ def stone_masonry(
|
||||
return [], ["높이·연장이 없어 전개하지 않음"]
|
||||
|
||||
back_cm = _back_length(options)
|
||||
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])
|
||||
# ⚠ **돌 종류로 계수가 갈린다** (품셈 13-4-3 · 13-4-4 [주]① · 교본 7-3).
|
||||
# 안 고르면 종전 값(건설품셈 참고자료)으로 돌되 그 사실을 알린다 — 값을 못 낸다고
|
||||
@@ -374,9 +384,15 @@ def stone_masonry(
|
||||
picked, kind_note = stone_coefficients(options, back_cm)
|
||||
if kind_note:
|
||||
notes.append(kind_note)
|
||||
for key in ("wedge_stone_m3_per_m2", "fill_concrete_m3_per_m2"):
|
||||
if picked.get(key) is not None:
|
||||
table[key] = picked[key]
|
||||
# ⚠ **고른 종류의 빈 칸은 빈 칸으로 덮는다.** `None` 이라고 안 덮으면 종전 값(깬돌 계열)이
|
||||
# 남아 「야면석 75㎝」처럼 **원문에 「-」인 칸에 값이 서는** 일이 생긴다(만들다 잡음).
|
||||
if picked.get("kind"):
|
||||
for key in ("wedge_stone_m3_per_m2", "fill_concrete_m3_per_m2"):
|
||||
table[key] = picked.get(key)
|
||||
else:
|
||||
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)이었다.
|
||||
@@ -411,14 +427,27 @@ def stone_masonry(
|
||||
DESTINATION["돌쌓기"],
|
||||
f"정면적 × √(1+{slope_ratio}²) — 비탈면적",
|
||||
),
|
||||
Component(
|
||||
"고임돌",
|
||||
"㎥",
|
||||
masonry_area * _num(table["wedge_stone_m3_per_m2"]),
|
||||
DESTINATION["고임돌"],
|
||||
f"돌쌓기 × {table['wedge_stone_m3_per_m2']} ㎥/㎡ (뒷길이 {back_cm}㎝)",
|
||||
),
|
||||
]
|
||||
# ⚠ 고임돌 계수가 **원문에서 「-」**인 칸이 있다(견치돌 25·30 · 야면석 75 · 깬돌 25).
|
||||
# 그 규격에 그 돌을 안 쓴다는 뜻이라 **0 줄을 만들지 않는다** — 0 은 「없음」과
|
||||
# 구별이 안 되고, 받는 쪽이 「값이 0 인 자재」로 읽는다.
|
||||
if table["wedge_stone_m3_per_m2"] is None:
|
||||
notes.append(
|
||||
f"고임돌 계수가 품셈 표에 없습니다 — 뒷길이 {back_cm}㎝"
|
||||
+ (f" · {kind_label}" if kind_label else "")
|
||||
+ " 칸이 「-」입니다"
|
||||
)
|
||||
else:
|
||||
components.append(
|
||||
Component(
|
||||
"고임돌",
|
||||
"㎥",
|
||||
masonry_area * _num(table["wedge_stone_m3_per_m2"]),
|
||||
DESTINATION["고임돌"],
|
||||
f"돌쌓기 × {table['wedge_stone_m3_per_m2']} ㎥/㎡ (뒷길이 {back_cm}㎝)"
|
||||
+ (f" · {kind_label}" if kind_label else ""),
|
||||
)
|
||||
)
|
||||
|
||||
stone_ton = table["stone_ton_per_m2"]
|
||||
if stone_ton is None:
|
||||
@@ -437,7 +466,7 @@ def stone_masonry(
|
||||
|
||||
# 막자갈 = 입적 − (면적 × 뒷길이 × 뒤채움몫 + 고임돌).
|
||||
# 뒤채움 몫은 **돌 종류로 갈린다** — 깬돌·잡석 1/2 · 야면석 1/3 (교본 7-3).
|
||||
wedge = masonry_area * _num(table["wedge_stone_m3_per_m2"])
|
||||
wedge = masonry_area * _num(table["wedge_stone_m3_per_m2"]) # None 이면 0 — 막자갈에서 안 뺌
|
||||
rubble = volume - (masonry_area * (back_cm / 100.0) * body_ratio + wedge)
|
||||
if rubble > 0:
|
||||
components.append(
|
||||
|
||||
@@ -29,20 +29,89 @@
|
||||
"unset_is_flagged": "돌 종류를 안 고르면 **건설품셈 참고자료 한 벌**로 돌되 그 사실을 알린다 — 값을 못 낸다고 멈추면 이미 저장된 프로젝트가 통째로 빈다.",
|
||||
"no_interpolation": "표에 없는 뒷길이·「-」 칸은 **지어내지 않는다.**"
|
||||
},
|
||||
"kinds": ["야면석·호박돌", "깬잡석", "깬돌", "견치돌"],
|
||||
"kinds": [
|
||||
"야면석·호박돌",
|
||||
"깬잡석",
|
||||
"깬돌",
|
||||
"견치돌"
|
||||
],
|
||||
"wedge_stone_m3_per_m2": {
|
||||
"note": "고임돌 — 품셈 13-4-3. 키는 뒷길이(㎝). `null` 은 원문 「-」.",
|
||||
"야면석·호박돌": { "25": 0.06, "30": 0.07, "35": 0.09, "45": 0.11, "55": 0.14, "60": 0.15, "75": null },
|
||||
"깬잡석": { "25": 0.09, "30": 0.11, "35": 0.13, "45": 0.16, "55": 0.19, "60": 0.21, "75": 0.26 },
|
||||
"깬돌": { "25": null, "30": 0.10, "35": 0.12, "45": 0.15, "55": 0.18, "60": 0.20, "75": 0.25 },
|
||||
"견치돌": { "25": null, "30": null, "35": 0.12, "45": 0.15, "55": 0.18, "60": 0.20, "75": 0.25 }
|
||||
"야면석·호박돌": {
|
||||
"25": 0.06,
|
||||
"30": 0.07,
|
||||
"35": 0.09,
|
||||
"45": 0.11,
|
||||
"55": 0.14,
|
||||
"60": 0.15,
|
||||
"75": null
|
||||
},
|
||||
"깬잡석": {
|
||||
"25": 0.09,
|
||||
"30": 0.11,
|
||||
"35": 0.13,
|
||||
"45": 0.16,
|
||||
"55": 0.19,
|
||||
"60": 0.21,
|
||||
"75": 0.26
|
||||
},
|
||||
"깬돌": {
|
||||
"25": null,
|
||||
"30": 0.1,
|
||||
"35": 0.12,
|
||||
"45": 0.15,
|
||||
"55": 0.18,
|
||||
"60": 0.2,
|
||||
"75": 0.25
|
||||
},
|
||||
"견치돌": {
|
||||
"25": null,
|
||||
"30": null,
|
||||
"35": 0.12,
|
||||
"45": 0.15,
|
||||
"55": 0.18,
|
||||
"60": 0.2,
|
||||
"75": 0.25
|
||||
}
|
||||
},
|
||||
"fill_concrete_m3_per_m2": {
|
||||
"note": "채움 콘크리트 — 품셈 13-4-4 [주]①. **두 줄뿐**이라 견치돌·깬잡석·깬돌이 한 값을 쓴다.",
|
||||
"야면석·호박돌": { "25": 0.08, "30": 0.10, "35": 0.12, "45": 0.15, "55": 0.18, "60": 0.20, "75": 0.25 },
|
||||
"깬잡석": { "25": 0.11, "30": 0.14, "35": 0.16, "45": 0.20, "55": 0.25, "60": 0.27, "75": 0.34 },
|
||||
"깬돌": { "25": 0.11, "30": 0.14, "35": 0.16, "45": 0.20, "55": 0.25, "60": 0.27, "75": 0.34 },
|
||||
"견치돌": { "25": 0.11, "30": 0.14, "35": 0.16, "45": 0.20, "55": 0.25, "60": 0.27, "75": 0.34 }
|
||||
"야면석·호박돌": {
|
||||
"25": 0.08,
|
||||
"30": 0.1,
|
||||
"35": 0.12,
|
||||
"45": 0.15,
|
||||
"55": 0.18,
|
||||
"60": 0.2,
|
||||
"75": 0.25
|
||||
},
|
||||
"깬잡석": {
|
||||
"25": 0.11,
|
||||
"30": 0.14,
|
||||
"35": 0.16,
|
||||
"45": 0.2,
|
||||
"55": 0.25,
|
||||
"60": 0.27,
|
||||
"75": 0.34
|
||||
},
|
||||
"깬돌": {
|
||||
"25": 0.11,
|
||||
"30": 0.14,
|
||||
"35": 0.16,
|
||||
"45": 0.2,
|
||||
"55": 0.25,
|
||||
"60": 0.27,
|
||||
"75": 0.34
|
||||
},
|
||||
"견치돌": {
|
||||
"25": 0.11,
|
||||
"30": 0.14,
|
||||
"35": 0.16,
|
||||
"45": 0.2,
|
||||
"55": 0.25,
|
||||
"60": 0.27,
|
||||
"75": 0.34
|
||||
}
|
||||
},
|
||||
"backfill_ratio_of_back_length": {
|
||||
"note": "뒤채움이 뒷길이에서 차지하는 몫 — 교본 7-3. 나머지가 잡석(막자갈)이 된다.",
|
||||
@@ -52,9 +121,25 @@
|
||||
"견치돌": 0.5
|
||||
},
|
||||
"fallback": {
|
||||
"note": "돌 종류 미지정일 때 — 건설품셈 참고자료 한 벌. **지금까지 쓰던 값 그대로**라 저장된 프로젝트의 값이 안 바뀐다.",
|
||||
"wedge_stone_m3_per_m2": { "35": 0.12, "45": 0.15, "55": 0.18, "60": 0.20 },
|
||||
"fill_concrete_m3_per_m2": { "35": 0.16, "45": 0.20, "55": 0.25, "60": 0.27 },
|
||||
"note": "돌 종류 미지정일 때 — 건설품셈 [참고자료] 돌쌓기 규격별 소요량 한 벌(고임돌은 「깬돌」 열). ⚠ 일곱 규격을 다 싣는다 — 앞서 네 칸만 들고 있어 25·30·75 를 가까운 칸으로 **접어 올리고** 있었다.",
|
||||
"wedge_stone_m3_per_m2": {
|
||||
"25": null,
|
||||
"30": 0.1,
|
||||
"35": 0.12,
|
||||
"45": 0.15,
|
||||
"55": 0.18,
|
||||
"60": 0.2,
|
||||
"75": 0.25
|
||||
},
|
||||
"fill_concrete_m3_per_m2": {
|
||||
"25": 0.11,
|
||||
"30": 0.14,
|
||||
"35": 0.16,
|
||||
"45": 0.2,
|
||||
"55": 0.25,
|
||||
"60": 0.27,
|
||||
"75": 0.34
|
||||
},
|
||||
"backfill_ratio_of_back_length": 0.3333333333333333,
|
||||
"backfill_note": "⚠ 이 값은 **뒤채움이 차지하는 몫**이다. 우리 막자갈 식은 입적에서 **돌 몸통**을 빼므로 코드가 `1 − 이 값` 을 쓴다. 종전 하드코딩 2/3 이 곧 야면석(1 − 1/3)이었다.",
|
||||
"message": "돌 종류를 안 정해 건설품셈 참고자료 값으로 섰습니다 — 구조물 상세 입력에서 고르면 산림품셈 계수로 바뀝니다"
|
||||
@@ -65,5 +150,15 @@
|
||||
"돌중량(ton/㎡) — 우리 값 0.575/0.88/1.10 은 **실무 관측**(울진 라이브러리)이다. 품셈은 「돌의 중량은 형상·종류·부피를 고려하고 건설품셈 1-3-3 재료의 단위중량을 참고하여 계상한다」로만 두어 **표를 안 준다.** 돌 종류 축과 짝이 맞는지 사용자 확정 대기.",
|
||||
"전면면적이 정면적인지 비탈면적인지 — 2026 개정안 [주] 「시공량은 석재의 **전면면적**(㎡)을 기준한다」로 용어만 확인됐다."
|
||||
]
|
||||
}
|
||||
},
|
||||
"back_lengths_cm": [
|
||||
25,
|
||||
30,
|
||||
35,
|
||||
45,
|
||||
55,
|
||||
60,
|
||||
75
|
||||
],
|
||||
"no_folding": "⚠ 표에 없는 뒷길이를 **가까운 칸으로 접지 않는다.** 접으면 40㎝ 가 45㎝ 계수로 조용히 돌고 999㎝ 도 60㎝ 로 접혔다(2026-09-08 실측). 표에 없으면 **계수가 없다고 드러낸다.**"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user