feat(b08): 구조물도 옹벽 단면 그림 — 관측 원단위 줄에 단면 치수 · 표 사유와 같은 말
- 반중력식 H=2.0 관측 줄에 단면(기초 1.6×0.4 · 벽체 1.6 상단 0.3 앞면 0.118 뒷면 0.032 · 전단키 0.35×0.3 · 버림 0.1 · 물빼기 관 0.4)을 실음 — 출처 소광리 07-구조도 「옹벽2.0」 산출식·일반도 - 그 치수로 표 수량이 다시 나오는지 시험(콘크리트 1.345→1.35 · 버림 0.145→0.15 · 유로폼 3.205→3.2 · 관 0.32) - 그림: 기초·벽체·전단키 + 버림(키 자리 비움) + 기초잡석(산출 조건 두께 · 버림과 같은 폭) + 물구멍 - 표 사유에 새 한 줄 둘(그림도 같은 말): 관측 줄에 터파기·되메우기·잔토가 없음 · 물빼기 칸을 채워도 관측 값이 붙박이라 안 닿음 - 까닭은 표가 못 선 장만(형식 미정 · 자료에 없는 규격 · 단면 치수 없는 규격) — 표 사유와 같은 말 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -151,6 +151,25 @@ def billing_of(
|
|||||||
return str(found.get("unit") or "개소"), scale
|
return str(found.get("unit") or "개소"), scale
|
||||||
|
|
||||||
|
|
||||||
|
def earthwork_missing_note(entry: dict[str, Any]) -> str | None:
|
||||||
|
"""관측 줄에 토공 줄이 없으면 한 줄 — 표 사유·구조물도 그림이 **같은 말**을 씀."""
|
||||||
|
if any(item.get("destination") == "earthwork" for item in entry.get("components") or []):
|
||||||
|
return None
|
||||||
|
return "⚠ 터파기·되메우기·잔토가 안 섬 — 관측 원단위 표에 그 줄이 없음(값을 지어내지 않음)"
|
||||||
|
|
||||||
|
|
||||||
|
def weep_option_note(entry: dict[str, Any], options: dict[str, Any]) -> str | None:
|
||||||
|
"""제원의 물빼기 칸을 채웠는데 관측 값이 붙박이라 **표에 안 닿을 때** 한 줄(표·그림 같은 말)."""
|
||||||
|
weep = (entry.get("section") or {}).get("weep")
|
||||||
|
keys = ("weep_hole_diameter_mm", "weep_hole_area_m2")
|
||||||
|
if not weep or all(options.get(key) in (None, "") for key in keys):
|
||||||
|
return None
|
||||||
|
return (
|
||||||
|
f"⚠ 물빼기 지름·면적 칸이 이 표에 안 닿음 — 관측 원단위 붙박이(Ø{weep['diameter_mm']}"
|
||||||
|
f" · {weep['area_m2']:g}㎡당 1개소 · 관 {weep['pipe_length_m']:g} m)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def expand_observed(
|
def expand_observed(
|
||||||
type_id: str,
|
type_id: str,
|
||||||
spec: dict[str, Any],
|
spec: dict[str, Any],
|
||||||
@@ -189,4 +208,11 @@ def expand_observed(
|
|||||||
"source": source_key,
|
"source": source_key,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return components, [f"관측 원단위 적용 — {found.get('source_note') or source_key}"]
|
notes = [f"관측 원단위 적용 — {found.get('source_note') or source_key}"]
|
||||||
|
for note in (
|
||||||
|
earthwork_missing_note(found),
|
||||||
|
weep_option_note(found, structure.get("options") or {}),
|
||||||
|
):
|
||||||
|
if note:
|
||||||
|
notes.append(note)
|
||||||
|
return components, notes
|
||||||
|
|||||||
@@ -44,11 +44,13 @@ from common_util.common_util_excavation import (
|
|||||||
#: 그림 축척 — 1/25(1m = 40㎜). CAD 로 옮길 때 글자 크기(㎜)를 m 로 바꾸는 데도 씀.
|
#: 그림 축척 — 1/25(1m = 40㎜). CAD 로 옮길 때 글자 크기(㎜)를 m 로 바꾸는 데도 씀.
|
||||||
SCALE_MM_PER_M = 40.0
|
SCALE_MM_PER_M = 40.0
|
||||||
|
|
||||||
#: 이 그림을 그리는 종류. 옹벽·집수정처럼 단면이 다른 것은 아직 그리지 않는다.
|
#: 이 그림을 그리는 종류. 집수정처럼 단면이 다른 것은 아직 그리지 않는다.
|
||||||
#: ⚠ **큰돌쌓기(`boulder_masonry`)를 뺐다**(2026-09-09). 그림이 두께를 **뒷길이**에서 내는데
|
#: ⚠ **큰돌쌓기(`boulder_masonry`)를 뺐다**(2026-09-09). 그림이 두께를 **뒷길이**에서 내는데
|
||||||
#: 큰돌쌓기는 규격 축이 **직경**(`stone_cm`)이라 **영영 못 그린다** — 품셈 13-6(직경) ↔
|
#: 큰돌쌓기는 규격 축이 **직경**(`stone_cm`)이라 **영영 못 그린다** — 품셈 13-6(직경) ↔
|
||||||
#: 13-4(뒷길이). 「직경에서 두께를 내는 식」은 도메인 판단이라 사용자 몫.
|
#: 13-4(뒷길이). 「직경에서 두께를 내는 식」은 도메인 판단이라 사용자 몫.
|
||||||
FIGURE_TYPE_IDS: frozenset[str] = frozenset({"masonry_wet", "masonry_dry"})
|
#: ⓘ 옹벽(2026-09-14)은 **관측 원단위 줄에 실린 단면 치수**로 그림 — 표 수량이 나온 그 단면.
|
||||||
|
MASONRY_TYPE_IDS: frozenset[str] = frozenset({"masonry_wet", "masonry_dry"})
|
||||||
|
FIGURE_TYPE_IDS: frozenset[str] = MASONRY_TYPE_IDS | {"retaining_wall"}
|
||||||
|
|
||||||
#: 그림이 못 서는 장에 **까닭을 적는다** — 빈 자리를 그냥 두면 사용자가 「고장」으로 읽는다
|
#: 그림이 못 서는 장에 **까닭을 적는다** — 빈 자리를 그냥 두면 사용자가 「고장」으로 읽는다
|
||||||
#: (2026-09-09 사용자 지시). 「무엇을 받아야 서는지」까지 적는다.
|
#: (2026-09-09 사용자 지시). 「무엇을 받아야 서는지」까지 적는다.
|
||||||
@@ -57,7 +59,6 @@ _NO_FIGURE_REASONS: dict[str, str] = {
|
|||||||
"그림 없음 — 큰돌쌓기는 규격이 **직경**이라 두께를 낼 식이 아직 없습니다"
|
"그림 없음 — 큰돌쌓기는 규격이 **직경**이라 두께를 낼 식이 아직 없습니다"
|
||||||
"(뒷길이로 내는 돌쌓기 식과 축이 다름 · 사용자 확정 대기)"
|
"(뒷길이로 내는 돌쌓기 식과 축이 다름 · 사용자 확정 대기)"
|
||||||
),
|
),
|
||||||
"retaining_wall": "그림 없음 — 옹벽은 단면이 달라 아직 그리지 않습니다",
|
|
||||||
}
|
}
|
||||||
_NO_FIGURE_DEFAULT = "그림 없음 — 이 형식은 아직 단면을 그리지 않습니다"
|
_NO_FIGURE_DEFAULT = "그림 없음 — 이 형식은 아직 단면을 그리지 않습니다"
|
||||||
|
|
||||||
@@ -82,11 +83,37 @@ def figure_reason(sheet: dict[str, Any]) -> str | None:
|
|||||||
type_id = str(sheet.get("type_id") or "")
|
type_id = str(sheet.get("type_id") or "")
|
||||||
if type_id not in FIGURE_TYPE_IDS:
|
if type_id not in FIGURE_TYPE_IDS:
|
||||||
return _NO_FIGURE_REASONS.get(type_id, _NO_FIGURE_DEFAULT)
|
return _NO_FIGURE_REASONS.get(type_id, _NO_FIGURE_DEFAULT)
|
||||||
|
if type_id == "retaining_wall":
|
||||||
|
return _observed_section(sheet)[1]
|
||||||
if float(sheet.get("height_m") or 0.0) <= 0:
|
if float(sheet.get("height_m") or 0.0) <= 0:
|
||||||
return "그림 없음 — 높이가 없습니다(제원에서 높이를 넣으면 그림이 섭니다)"
|
return "그림 없음 — 높이가 없습니다(제원에서 높이를 넣으면 그림이 섭니다)"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _observed_section(sheet: dict[str, Any]) -> tuple[dict[str, Any] | None, str | None]:
|
||||||
|
"""(관측 원단위 줄, 못 그리는 까닭) — 표가 줄을 꺼낸 **그 조회**(`OBSERVED_SPEC_KEYS`)를 씀.
|
||||||
|
|
||||||
|
표가 안 선 장은 표 사유와 같은 말 앞에 「그림 없음」만 붙임.
|
||||||
|
"""
|
||||||
|
from B08_Quantity.B08_Quantity_Engine_ObservedUnit import load_observed_table
|
||||||
|
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import OBSERVED_SPEC_KEYS
|
||||||
|
from B08_Quantity.B08_Quantity_Wording import option_missing, spec_missing
|
||||||
|
|
||||||
|
type_id = str(sheet.get("type_id") or "")
|
||||||
|
keys = OBSERVED_SPEC_KEYS.get(type_id) or ()
|
||||||
|
options = sheet.get("options") or {}
|
||||||
|
spec = {key: options[key] for key in keys if options.get(key) is not None}
|
||||||
|
table = load_observed_table()
|
||||||
|
if not spec:
|
||||||
|
return None, f"그림 없음 — {option_missing(keys[0], type_id)}"
|
||||||
|
entry = table.find(type_id, spec)
|
||||||
|
if entry is None:
|
||||||
|
return None, f"그림 없음 — {spec_missing(type_id, table.specs_for(type_id))}"
|
||||||
|
if not entry.get("section"):
|
||||||
|
return None, "그림 없음 — 이 규격은 자료에 수량만 있고 단면 치수가 없습니다"
|
||||||
|
return entry, None
|
||||||
|
|
||||||
|
|
||||||
def _path(points: list[tuple[float, float]], role: str, *, dash: bool = False) -> dict[str, Any]:
|
def _path(points: list[tuple[float, float]], role: str, *, dash: bool = False) -> dict[str, Any]:
|
||||||
return {"kind": "path", "points": [list(p) for p in points], "role": role, "dash": dash}
|
return {"kind": "path", "points": [list(p) for p in points], "role": role, "dash": dash}
|
||||||
|
|
||||||
@@ -97,10 +124,23 @@ def _text(
|
|||||||
return {"kind": "text", "text": text, "at": list(at), "align": align, "size": size}
|
return {"kind": "text", "text": text, "at": list(at), "align": align, "size": size}
|
||||||
|
|
||||||
|
|
||||||
def build_figure(sheet: dict[str, Any]) -> list[dict[str, Any]] | None:
|
def build_figure(
|
||||||
"""장 하나의 그림 — 모양 목록(밑면 앞끝이 원점). 못 그리면 `None`(까닭은 `figure_reason`)."""
|
sheet: dict[str, Any], rubble_thickness_m: float | None = None
|
||||||
|
) -> list[dict[str, Any]] | None:
|
||||||
|
"""장 하나의 그림 — 모양 목록(밑면 앞끝이 원점). 못 그리면 `None`(까닭은 `figure_reason`).
|
||||||
|
|
||||||
|
`rubble_thickness_m` — 산출 조건 기초잡석 두께(표의 기초잡석 줄과 같은 값 · 비면 기본).
|
||||||
|
"""
|
||||||
if figure_reason(sheet) is not None:
|
if figure_reason(sheet) is not None:
|
||||||
return None
|
return None
|
||||||
|
if sheet.get("type_id") == "retaining_wall":
|
||||||
|
entry, _reason = _observed_section(sheet)
|
||||||
|
return _wall_figure(sheet, entry or {}, rubble_thickness_m)
|
||||||
|
return _masonry_figure(sheet)
|
||||||
|
|
||||||
|
|
||||||
|
def _masonry_figure(sheet: dict[str, Any]) -> list[dict[str, Any]]:
|
||||||
|
"""돌쌓기 단면 — 두께는 수량표 함수 한 벌."""
|
||||||
options = sheet.get("options") or {}
|
options = sheet.get("options") or {}
|
||||||
height_m = float(sheet.get("height_m") or 0.0)
|
height_m = float(sheet.get("height_m") or 0.0)
|
||||||
back_cm, default_note = back_length_cm(sheet)
|
back_cm, default_note = back_length_cm(sheet)
|
||||||
@@ -188,8 +228,132 @@ def build_figure(sheet: dict[str, Any]) -> list[dict[str, Any]] | None:
|
|||||||
return shapes
|
return shapes
|
||||||
|
|
||||||
|
|
||||||
def attach_figures(payload: dict[str, Any]) -> None:
|
def _wall_figure(
|
||||||
|
sheet: dict[str, Any], entry: dict[str, Any], rubble_thickness_m: float | None
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""옹벽 단면 — 관측 원단위 줄의 `section`(표 수량이 나온 그 단면)으로 그림.
|
||||||
|
|
||||||
|
x 는 앞굽 끝이 0(앞면 왼쪽), y 는 기초 밑면이 0. 버림·기초잡석은 기초 밑, 전단키가 뚫고 내려감.
|
||||||
|
⚠ 기초잡석 두께는 표의 기초잡석 줄과 같은 산출 조건 값(비면 기본) — 폭은 버림과 같음.
|
||||||
|
"""
|
||||||
|
from B08_Quantity.B08_Quantity_Engine_ObservedUnit import (
|
||||||
|
earthwork_missing_note,
|
||||||
|
weep_option_note,
|
||||||
|
)
|
||||||
|
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import RUBBLE_BASE_THICKNESS_M
|
||||||
|
|
||||||
|
section = entry["section"]
|
||||||
|
footing, wall, key = section["footing"], section["wall"], section["key"]
|
||||||
|
blinding, weep = section["blinding"], section["weep"]
|
||||||
|
f_w, f_t, toe = footing["width_m"], footing["thickness_m"], footing["toe_m"]
|
||||||
|
w_h, w_top = wall["height_m"], wall["top_m"]
|
||||||
|
front, back = wall["front_batter_m"], wall["back_batter_m"]
|
||||||
|
w_bottom = w_top + front + back
|
||||||
|
heel = f_w - toe - w_bottom
|
||||||
|
k_w, k_d = key["width_m"], key["depth_m"]
|
||||||
|
k_left = f_w - key["from_heel_m"] - k_w
|
||||||
|
b_t, over = blinding["thickness_m"], blinding["overhang_m"]
|
||||||
|
rubble = RUBBLE_BASE_THICKNESS_M if rubble_thickness_m is None else float(rubble_thickness_m)
|
||||||
|
top = f_t + w_h
|
||||||
|
|
||||||
|
shapes = [
|
||||||
|
_path(
|
||||||
|
[
|
||||||
|
(0.0, 0.0),
|
||||||
|
(0.0, f_t),
|
||||||
|
(toe, f_t),
|
||||||
|
(toe + front, top),
|
||||||
|
(toe + front + w_top, top),
|
||||||
|
(toe + w_bottom, f_t),
|
||||||
|
(f_w, f_t),
|
||||||
|
(f_w, 0.0),
|
||||||
|
(k_left + k_w, 0.0),
|
||||||
|
(k_left + k_w, -k_d),
|
||||||
|
(k_left, -k_d),
|
||||||
|
(k_left, 0.0),
|
||||||
|
(0.0, 0.0),
|
||||||
|
],
|
||||||
|
"wall",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
# 버림(키 자리는 비움) · 그 밑 기초잡석(같은 폭).
|
||||||
|
layers = [(0.0, b_t, False)] + ([(b_t, rubble, True)] if rubble > 0 else [])
|
||||||
|
for depth, thickness, dash in layers:
|
||||||
|
for x0, x1 in ((-over, k_left), (k_left + k_w, f_w + over)):
|
||||||
|
shapes.append(
|
||||||
|
_path(
|
||||||
|
[
|
||||||
|
(x0, -depth),
|
||||||
|
(x0, -depth - thickness),
|
||||||
|
(x1, -depth - thickness),
|
||||||
|
(x1, -depth),
|
||||||
|
],
|
||||||
|
"guide",
|
||||||
|
dash=dash,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
weep_y = f_t + w_h * 0.5
|
||||||
|
ratio = (weep_y - f_t) / w_h
|
||||||
|
shapes.append(
|
||||||
|
_path([(toe + front * ratio, weep_y), (toe + w_bottom - back * ratio, weep_y)], "guide")
|
||||||
|
)
|
||||||
|
|
||||||
|
right = f_w + over + 0.2
|
||||||
|
blinding_width = f_w + 2 * over - k_w
|
||||||
|
shapes += [
|
||||||
|
_text(
|
||||||
|
f"{sheet.get('title') or '옹벽'} {sheet.get('options', {}).get('form') or ''}"
|
||||||
|
f" (축척 1/{int(1000 / SCALE_MM_PER_M)})",
|
||||||
|
(0.0, top + 0.52),
|
||||||
|
"left",
|
||||||
|
_LABEL_SIZE,
|
||||||
|
),
|
||||||
|
_text(f"H = {top:g} m", (-0.28, top / 2.0), "right"),
|
||||||
|
_text(f"상단 {w_top:.2f} m", (toe + front + w_top / 2.0, top + 0.14), "center"),
|
||||||
|
_text(
|
||||||
|
f"벽체 높이 {w_h:.2f} · 하단 {w_bottom:.2f} m (앞면 {front:g} · 뒷면 {back:g} 기움)",
|
||||||
|
(right, top * 0.9),
|
||||||
|
"left",
|
||||||
|
),
|
||||||
|
_text(
|
||||||
|
f"물구멍 Ø{weep['diameter_mm']} — {weep['area_m2']:g}㎡당 1개소"
|
||||||
|
f" · 관 {weep['pipe_length_m']:g} m",
|
||||||
|
(right, weep_y),
|
||||||
|
"left",
|
||||||
|
),
|
||||||
|
_text(
|
||||||
|
f"기초 {f_w:.2f} × {f_t:.2f} m (앞굽 {toe:.2f} · 뒷굽 {heel:.2f})",
|
||||||
|
(right, f_t / 2.0),
|
||||||
|
"left",
|
||||||
|
),
|
||||||
|
_text(
|
||||||
|
f"버림 T={b_t:.2f} · 폭 {f_w + 2 * over:.2f} − 키 {k_w:.2f} = {blinding_width:.2f} m",
|
||||||
|
(right, -b_t / 2.0),
|
||||||
|
"left",
|
||||||
|
),
|
||||||
|
_text(f"전단키 {k_w:.2f} × {k_d:.2f} m", (k_left + k_w / 2.0, -k_d - 0.14), "center"),
|
||||||
|
]
|
||||||
|
if rubble > 0:
|
||||||
|
shapes.append(
|
||||||
|
_text(
|
||||||
|
f"기초잡석 T={rubble:g} m — 산출 조건 · 폭은 버림과 같음",
|
||||||
|
(right, -b_t - rubble / 2.0),
|
||||||
|
"left",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
below = -max(k_d, b_t + rubble) - 0.36
|
||||||
|
notes = [
|
||||||
|
f"단면 — 관측 원단위 {entry.get('source_note') or ''}의 수량이 나온 그 단면",
|
||||||
|
earthwork_missing_note(entry),
|
||||||
|
weep_option_note(entry, sheet.get("options") or {}),
|
||||||
|
]
|
||||||
|
for index, note in enumerate(note for note in notes if note):
|
||||||
|
shapes.append(_text(note, (0.0, below - index * 0.16), "left"))
|
||||||
|
return shapes
|
||||||
|
|
||||||
|
|
||||||
|
def attach_figures(payload: dict[str, Any], rubble_thickness_m: float | None = None) -> None:
|
||||||
"""장마다 `figure`(모양 목록 또는 `None`)와 `figure_reason` 을 실음 — 화면은 둘 중 하나."""
|
"""장마다 `figure`(모양 목록 또는 `None`)와 `figure_reason` 을 실음 — 화면은 둘 중 하나."""
|
||||||
for sheet in payload.get("sheets") or []:
|
for sheet in payload.get("sheets") or []:
|
||||||
sheet["figure_reason"] = figure_reason(sheet)
|
sheet["figure_reason"] = figure_reason(sheet)
|
||||||
sheet["figure"] = build_figure(sheet)
|
sheet["figure"] = build_figure(sheet, rubble_thickness_m)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def project_structure_sheets(
|
|||||||
# 양식이 있는 종류는 줄마다 식·설명·반올림·갈 곳을 실음(PLAN 3장 ④ · 명세 13장).
|
# 양식이 있는 종류는 줄마다 식·설명·반올림·갈 곳을 실음(PLAN 3장 ④ · 명세 13장).
|
||||||
apply_templates(payload, settings, templates)
|
apply_templates(payload, settings, templates)
|
||||||
# 상단 그림(읽기 전용 SVG 자료) — 못 그리는 장은 까닭(PLAN 12장).
|
# 상단 그림(읽기 전용 SVG 자료) — 못 그리는 장은 까닭(PLAN 12장).
|
||||||
attach_figures(payload)
|
attach_figures(payload, settings.get("rubble_base_thickness_m"))
|
||||||
# 왜 안 실렸는지 — 「구조물이 없다」와 「걸러졌다」를 화면이 가릴 수 있어야 한다.
|
# 왜 안 실렸는지 — 「구조물이 없다」와 「걸러졌다」를 화면이 가릴 수 있어야 한다.
|
||||||
payload["skipped_structures"] = skipped
|
payload["skipped_structures"] = skipped
|
||||||
return payload
|
return payload
|
||||||
|
|||||||
@@ -33,6 +33,14 @@
|
|||||||
"unit": "m",
|
"unit": "m",
|
||||||
"source": "uljin_library",
|
"source": "uljin_library",
|
||||||
"source_note": "§7 옹벽류 — 반중력식옹벽 H=2.0",
|
"source_note": "§7 옹벽류 — 반중력식옹벽 H=2.0",
|
||||||
|
"section": {
|
||||||
|
"note": "단면 치수(m) — 위 수량이 나온 그 단면. 출처 소광리 07-구조도 「옹벽2.0」 탭 산출식(T22 기초 0.4×1.6+0.3×0.35 · T23 벽체 (0.3+0.45)÷2×1.6 · T25 버림 0.1×(1.8−0.35) · T26·T27 유로폼 √(1.6²+0.032²)+√(1.6²+0.118²) · T31 파이프 1.6÷2×0.4)과 같은 탭 일반도(앞굽 950·뒷굽 200·키 자리 뒷굽 끝에서 500). 원문 1.345·0.145·3.205 를 라이브러리가 1.35·0.15·3.2 로 적음",
|
||||||
|
"footing": { "width_m": 1.6, "thickness_m": 0.4, "toe_m": 0.95 },
|
||||||
|
"wall": { "height_m": 1.6, "top_m": 0.3, "front_batter_m": 0.118, "back_batter_m": 0.032 },
|
||||||
|
"key": { "width_m": 0.35, "depth_m": 0.3, "from_heel_m": 0.5 },
|
||||||
|
"blinding": { "thickness_m": 0.1, "overhang_m": 0.1 },
|
||||||
|
"weep": { "area_m2": 2.0, "diameter_mm": 50, "pipe_length_m": 0.4 }
|
||||||
|
},
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"name": "콘크리트",
|
"name": "콘크리트",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -12,9 +13,14 @@ ROOT = Path(__file__).resolve().parents[2]
|
|||||||
if str(ROOT) not in sys.path:
|
if str(ROOT) not in sys.path:
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from B08_Quantity.B08_Quantity_Engine_ObservedUnit import ( # noqa: E402
|
||||||
|
expand_observed,
|
||||||
|
load_observed_table,
|
||||||
|
)
|
||||||
from B08_Quantity.B08_Quantity_Engine_StructureFigure import ( # noqa: E402
|
from B08_Quantity.B08_Quantity_Engine_StructureFigure import ( # noqa: E402
|
||||||
attach_figures,
|
attach_figures,
|
||||||
build_figure,
|
build_figure,
|
||||||
|
figure_reason,
|
||||||
)
|
)
|
||||||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402
|
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402
|
||||||
back_length_default_note,
|
back_length_default_note,
|
||||||
@@ -96,6 +102,69 @@ def test_기본값으로_그린_장은_표_사유와_같은_말을_적는다() -
|
|||||||
assert back_length_default_note({"back_len_cm": 45}, 45) is None
|
assert back_length_default_note({"back_len_cm": 45}, 45) is None
|
||||||
|
|
||||||
|
|
||||||
|
def _wall_sheet(**options) -> dict:
|
||||||
|
return {
|
||||||
|
"title": "옹벽 H=2",
|
||||||
|
"type_id": "retaining_wall",
|
||||||
|
"height_m": 2.0,
|
||||||
|
"options": {"height_m": 2.0, "form": "반중력식", **options},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_옹벽_단면_치수가_표의_관측_수량을_그대로_낸다() -> None:
|
||||||
|
"""표 = 그림 — 그림이 쓰는 단면으로 표 수량(라이브러리 반올림)이 다시 나와야 함."""
|
||||||
|
entry = load_observed_table().find("retaining_wall", {"form": "반중력식", "height_m": 2.0})
|
||||||
|
section = entry["section"]
|
||||||
|
amounts = {item["name"]: item["amount"] for item in entry["components"]}
|
||||||
|
footing, wall, key, blinding = (section[k] for k in ("footing", "wall", "key", "blinding"))
|
||||||
|
bottom = wall["top_m"] + wall["front_batter_m"] + wall["back_batter_m"]
|
||||||
|
assert abs(bottom - 0.45) < 1e-9
|
||||||
|
concrete = (
|
||||||
|
footing["width_m"] * footing["thickness_m"]
|
||||||
|
+ key["width_m"] * key["depth_m"]
|
||||||
|
+ (wall["top_m"] + bottom) / 2 * wall["height_m"]
|
||||||
|
)
|
||||||
|
blind = blinding["thickness_m"] * (
|
||||||
|
footing["width_m"] + 2 * blinding["overhang_m"] - key["width_m"]
|
||||||
|
)
|
||||||
|
form = math.hypot(wall["height_m"], wall["back_batter_m"]) + math.hypot(
|
||||||
|
wall["height_m"], wall["front_batter_m"]
|
||||||
|
)
|
||||||
|
weep = section["weep"]
|
||||||
|
pipe = wall["height_m"] / weep["area_m2"] * weep["pipe_length_m"]
|
||||||
|
assert round(concrete, 2) == amounts["콘크리트"] # 1.345
|
||||||
|
assert round(blind, 2) == amounts["버림콘크리트"] # 0.145
|
||||||
|
assert round(form, 1) == amounts["유로폼"] # 3.205
|
||||||
|
assert round(pipe, 2) == amounts["물구멍관"]
|
||||||
|
assert footing["thickness_m"] + wall["height_m"] == 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_옹벽_그림은_기초잡석_두께를_산출_조건에서_받고_사유는_표와_같다() -> None:
|
||||||
|
shapes = build_figure(_wall_sheet(), rubble_thickness_m=0.3)
|
||||||
|
texts = _texts(shapes)
|
||||||
|
assert "기초잡석 T=0.3 m — 산출 조건 · 폭은 버림과 같음" in texts
|
||||||
|
assert "버림 T=0.10 · 폭 1.80 − 키 0.35 = 1.45 m" in texts
|
||||||
|
wall = _paths(shapes)[0]["points"]
|
||||||
|
assert [0.95, 0.4] in wall and [1.068, 2.0] in wall and [1.368, 2.0] in wall
|
||||||
|
# 표 사유와 같은 말 — 터파기 줄이 없다는 것
|
||||||
|
_rows, table_notes = expand_observed(
|
||||||
|
"retaining_wall",
|
||||||
|
{"form": "반중력식", "height_m": 2.0},
|
||||||
|
_wall_sheet() | {"options": {"length_m": 10}},
|
||||||
|
)
|
||||||
|
missing = [note for note in table_notes if "터파기" in note]
|
||||||
|
assert missing and missing[0] in texts
|
||||||
|
assert not any(text.startswith("기초잡석") for text in _texts(build_figure(_wall_sheet(), 0)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_옹벽_못_그리는_장은_표와_같은_까닭() -> None:
|
||||||
|
assert "형식" in figure_reason({**_wall_sheet(), "options": {"height_m": 2.0}})
|
||||||
|
other = figure_reason(_wall_sheet(height_m=1.6) | {"height_m": 1.6})
|
||||||
|
assert other.startswith("그림 없음 — ") and "자료에 없습니다" in other
|
||||||
|
note = [t for t in _texts(build_figure(_wall_sheet(weep_hole_area_m2=3))) if "안 닿음" in t]
|
||||||
|
assert note and "Ø50" in note[0]
|
||||||
|
|
||||||
|
|
||||||
def test_양식_장은_양식이_푼_뒷길이로_그린다() -> None:
|
def test_양식_장은_양식이_푼_뒷길이로_그린다() -> None:
|
||||||
sheet = {**_sheet(), "formula_sheet": {"vars": {"L3": 55}}}
|
sheet = {**_sheet(), "formula_sheet": {"vars": {"L3": 55}}}
|
||||||
texts = _texts(build_figure(sheet))
|
texts = _texts(build_figure(sheet))
|
||||||
|
|||||||
Reference in New Issue
Block a user