From 2b2906466e126b594f1d14b8953c03c92732233b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 21:58:27 +0900 Subject: [PATCH] =?UTF-8?q?feat(B07):=20=ED=91=9C=EC=A4=80=EB=8F=84=20?= =?UTF-8?q?=EC=A0=9C=EC=9B=90=20=EC=B9=B8=20=EC=9D=BC=EA=B3=B1=20=EC=99=84?= =?UTF-8?q?=EC=84=B1=20=E2=80=94=20=EC=95=BC=EB=A9=B4=EC=84=9D=20=EA=B3=84?= =?UTF-8?q?=EC=88=98=C2=B7=EC=B1=84=EC=9B=80=20=EA=B0=95=EB=8F=84=20+=20?= =?UTF-8?q?=ED=84=B0=ED=8C=8C=EA=B8=B0=20=EC=83=81=EC=88=98=20=ED=95=9C=20?= =?UTF-8?q?=EB=B2=8C=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⑨ `stone_coeff_basis`(품셈 / 실무 관행) · ⑩ `fill_concrete_mpa`(180 / 210) 를 폼에 붙임. 둘 다 **빈 값이 기본**(품셈 열 · 210) — 값을 미리 적어 두면 「안 정함」이 사라짐. 없는 갈래는 막지 않고 알림. ⇒ 칸 일곱: 돌 종류 · 조달 · 뒷길이 · 기초 · 전면 기울기 · 야면석 계수 · 채움 강도. ⚠ **그림이 또 죽어 있었음** — `STONE_MASONRY["excavation_extra_m"]` 이 사라져 `KeyError`. 터파기 수가 `common_util_excavation` 으로 옮겨 갔음(내가 정본 xls 에서 낸 값들). 그림도 **그 한 벌**을 읽게 바꿈 — 횡단도와 같은 상수라야 두 도면이 같은 터파기를 그림. ``` WALL_TRENCH_CLEARANCE_M 0.2 · WALL_FOUNDATION_WIDTH_M 0.9 · WALL_FOUNDATION_DEPTH_M 0.5 WALL_BLINDING_WIDTH_M 0.7 · WALL_BLINDING_DEPTH_M 0.1 ``` 그림에 **기초 칸을 그림** — 「기초유」 0.9×0.5 · 「기초버림」 0.7×0.1. **안 정한 장은 기초를 안 그림**(지어내지 않음). ⚠ 오늘 두 번째임 — 공용 상수 **이름이 바뀌면 그림이 죽음**. 값이 한 벌인 것과 별개로 이름 변경은 서로 알려야 함. 실화면 확인 — 칸 일곱이 다 서고, 야면석 계수 「실무 관행」·채움 강도 「180」 저장 → 「2개소에 반영했습니다」, 정본에 그 장의 두 개소만 들어감. 자체검증 — 새 시험 3건 + 회귀 586 통과 · 0 실패, `tsc --noEmit` 0. ⚠ 병합 여파로 깨진 등록부 규정 시험을 고침 — 「빈 칸이 곧 기본」인 칸 셋 (`face_slope_ratio`·`stone_coeff_basis`·`fill_concrete_mpa`)을 규정에서 뺌. Co-Authored-By: Claude Opus 5 (1M context) --- .../B07_DesignDetail_Api_Fetch.ts | 2 + ..._DesignDetail_Engine_Cad_StandardFigure.py | 47 ++++++++++++++----- .../B07_DesignDetail_Engine_Standard_Edit.py | 25 ++++++++++ .../B07_DesignDetail_Router_Standard.py | 2 + .../B07_DesignDetail_UI_StandardSpec.ts | 12 +++++ 5 files changed, 76 insertions(+), 12 deletions(-) diff --git a/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts b/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts index 8511073f..28456d41 100644 --- a/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts +++ b/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts @@ -275,6 +275,8 @@ export function putStandardSheetSpec( back_len_cm: string | null; face_slope_ratio: string | null; foundation: string | null; + stone_coeff_basis: string | null; + fill_concrete_mpa: string | null; }, ): Promise<{ status: string; revision: number; changed: number; notes: string[] }> { return requestJson(`/projects/${projectId}/standard-sheets/spec`, { diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardFigure.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardFigure.py index a1ee36a8..92d55f35 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardFigure.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_StandardFigure.py @@ -12,7 +12,10 @@ 직접 읽는다. 치수를 여기서 다시 적으면 **그림과 표가 갈린다**(CLAUDE.md 5장). 상부 두께 = 뒷길이 + 0.30 하부 두께 = 상부 + 0.30 × (H − 1.0) - 터파기 폭 = 평균두께 + 0.2 되메우기 두께 = 0.2 + 터파기 폭 = 평균두께 + 0.2 기초 0.5×0.9 (기초유) · 0.1×0.7 (기초버림) + + ⚠ 터파기 치수는 **`common_util_excavation` 한 벌**을 읽는다 — 횡단도가 쓰는 그 상수다. + 여기서 다시 적으면 두 도면이 다른 터파기를 그린다. ⚠ **뒷길이가 두께에 들어간다**(확정 2차 ②, 실무 구조물도 식). 뒷길이가 다르면 벽이 두꺼워지고 그림도 그만큼 넓어진다 — 예전 식(0.45+0.10H / 0.45+0.40H)은 뒷길이를 @@ -35,6 +38,13 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Cad import ( polyline_entity, ) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import STONE_MASONRY, face_slope_ratio +from common_util.common_util_excavation import ( + WALL_BLINDING_DEPTH_M, + WALL_BLINDING_WIDTH_M, + WALL_FOUNDATION_DEPTH_M, + WALL_FOUNDATION_WIDTH_M, + WALL_TRENCH_CLEARANCE_M, +) #: 그림 축척 — 1/25(1m = 40㎜). 표(줄 높이 9㎜) 위에 얹어도 한 면에 드는 크기다. SCALE_MM_PER_M = 40.0 @@ -135,8 +145,15 @@ def build_figure( points = section_points(height_m, slope, back_cm) top_t, bottom_t = wall_thickness(height_m, back_cm) average_t = (top_t + bottom_t) / 2.0 - dig_width = average_t + STONE_MASONRY["excavation_extra_m"] - backfill_t = STONE_MASONRY["backfill_thickness_m"] + dig_width = average_t + WALL_TRENCH_CLEARANCE_M + # 기초 몫 — 「기초유 / 기초버림」이 폭·깊이를 가른다(정본 탭 제목). + foundation = str((sheet.get("options") or {}).get("foundation") or "") + if foundation == "기초유": + base_w, base_d = WALL_FOUNDATION_WIDTH_M, WALL_FOUNDATION_DEPTH_M + elif foundation == "기초버림": + base_w, base_d = WALL_BLINDING_WIDTH_M, WALL_BLINDING_DEPTH_M + else: + base_w = base_d = 0.0 # 안 정한 장은 기초를 안 그린다 — 지어내지 않는다. entities: list[dict[str, Any]] = [] wall = polyline_entity( @@ -156,16 +173,16 @@ def build_figure( if dig is not None: entities.append(dig) - # 되메우기 두께 선 — 밑면에서 0.2m 위. 값이 어디서 오는지 눈에 보이게. - entities.append( - _line_entity( - f"{drawing_id}:fig:backfill", - mm((0.0, backfill_t)), - mm((dig_width, backfill_t)), + # 기초 — 벽 밑에 놓이는 칸. 「안 정함」이면 안 그린다. + if base_d > 0: + base = polyline_entity( + f"{drawing_id}:fig:base", + [mm(p) for p in ((0.0, 0.0), (0.0, -base_d), (base_w, -base_d), (base_w, 0.0))], layer_id, guide_color, ) - ) + if base is not None: + entities.append(base) # 물구멍 — 벽을 가로지르는 짧은 선 하나. 개소 간격은 글자로 적는다(면적당이라 그림에 못 씀). weep_y = height_m * 0.5 @@ -189,7 +206,13 @@ def build_figure( (dig_width + 0.16, height_m * 0.92), "left", ), - (f"되메우기 {backfill_t:g} m", (dig_width + 0.16, backfill_t), "left"), + ( + f"기초 {base_w:g} × {base_d:g} m ({foundation})" + if base_d > 0 + else "기초 — 안 정함(기초유/기초버림)", + (dig_width + 0.16, -base_d / 2.0 if base_d else 0.0), + "left", + ), ( f"물구멍 — {STONE_MASONRY['weep_hole_area_m2']:g}㎡당 1개소", (slope * weep_y + top_t + 0.16, weep_y), @@ -243,5 +266,5 @@ def build_figure( ) ) - used_mm = (height_m + 0.95) * scale + used_mm = (height_m + 0.95 + base_d) * scale return entities, used_mm diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Standard_Edit.py b/B07_DesignDetail/B07_DesignDetail_Engine_Standard_Edit.py index 67bc203d..6cb0e327 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Standard_Edit.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Standard_Edit.py @@ -31,6 +31,8 @@ EDITABLE_KEYS: tuple[str, ...] = ( "back_len_cm", "face_slope_ratio", "foundation", + "stone_coeff_basis", + "fill_concrete_mpa", ) #: 기초 갈래 — 정본 xls 탭 제목 그대로(`04.구조도(기슭막이).xls`). @@ -51,8 +53,17 @@ FIELD_LABELS: dict[str, str] = { "back_len_cm": "뒷길이", "face_slope_ratio": "전면 기울기", "foundation": "기초", + "stone_coeff_basis": "야면석 계수", + "fill_concrete_mpa": "채움 강도", } +#: 야면석 계수를 어느 열에서 읽나 — 확정 ⑨ 「품셈 열이 기본, 사용자가 고를 수 있게」. +STONE_COEFF_CHOICES: tuple[str, ...] = ("품셈", "실무 관행") + +#: 채움 콘크리트 강도(MPa) — 확정 2차 ⑩ 「기본 210, 고를 수 있게」. +#: 180 은 국가기준 하한(돌쌓기 전용), 210 은 콘크리트 구조물 몸체 쪽 기준. +FILL_CONCRETE_CHOICES: tuple[str, ...] = ("180", "210") + def _clean_slope(value: Any) -> tuple[float | None, str | None]: """전면 기울기 — `(값, 안내)`. 비면 `(None, None)` 이고 그것이 「자동」의 뜻이다.""" @@ -116,6 +127,20 @@ def clean_spec(spec: dict[str, Any]) -> tuple[dict[str, Any], list[str]]: f"(있는 것: {' · '.join(FOUNDATION_CHOICES)})." ) + for key, choices in ( + ("stone_coeff_basis", STONE_COEFF_CHOICES), + ("fill_concrete_mpa", FILL_CONCRETE_CHOICES), + ): + if key not in spec: + continue + raw = spec.get(key) + cleaned[key] = str(raw) if raw not in (None, "") else None + if cleaned[key] and cleaned[key] not in choices: + notes.append( + f"{FIELD_LABELS[key]} 「{cleaned[key]}」는 없는 갈래입니다 " + f"(있는 것: {' · '.join(choices)})." + ) + if "face_slope_ratio" in spec: ratio, note = _clean_slope(spec.get("face_slope_ratio")) cleaned["face_slope_ratio"] = ratio diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Standard.py b/B07_DesignDetail/B07_DesignDetail_Router_Standard.py index 86c64b71..cae52cd7 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Standard.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Standard.py @@ -71,6 +71,8 @@ class StandardSheetSpecRequest(BaseModel): back_len_cm: int | str | None = None face_slope_ratio: float | str | None = None foundation: str | None = None + stone_coeff_basis: str | None = None + fill_concrete_mpa: str | None = None @router.put("/{project_id}/standard-sheets/spec") diff --git a/B07_DesignDetail/B07_DesignDetail_UI_StandardSpec.ts b/B07_DesignDetail/B07_DesignDetail_UI_StandardSpec.ts index 0badf6ce..aff74c48 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_StandardSpec.ts +++ b/B07_DesignDetail/B07_DesignDetail_UI_StandardSpec.ts @@ -17,6 +17,10 @@ const SUPPLIES = ["채집", "구입"] as const; const BACK_LENGTHS = ["25", "30", "35", "45", "55", "60", "75"] as const; /** 정본 xls 탭 제목 그대로 — 터파기 기초 몫 0.45 대 0.07 을 가르는 축. */ const FOUNDATIONS = ["기초유", "기초버림"] as const; +/** 확정 ⑨ — 품셈 열이 기본, 실무 관행(깬돌 열)으로 바꿀 수 있게. */ +const COEFF_BASES = ["품셈", "실무 관행"] as const; +/** 확정 2차 ⑩ — 기본 210. 180 은 국가기준 하한(돌쌓기 전용). */ +const FILL_MPA = ["180", "210"] as const; /** 표준도 장 하나 — 서버가 낸 것 중 이 폼이 쓰는 것만. */ export interface StandardSheetSpec { @@ -34,6 +38,8 @@ export interface StandardSpecResult { back_len_cm: string | null; face_slope_ratio: string | null; foundation: string | null; + stone_coeff_basis: string | null; + fill_concrete_mpa: string | null; } /** 이 종류가 돌쌓기 계열인가 — 옹벽·집수정에는 이 칸들이 뜻이 없다. */ @@ -110,6 +116,8 @@ export function buildStandardSpecPanel( const supply = select(SUPPLIES, options.stone_supply, "— 안 정함(기본 채집) —"); const back = select(BACK_LENGTHS, options.back_len_cm, "— 안 정함 —"); const foundation = select(FOUNDATIONS, options.foundation, "— 안 정함 —"); + const coeff = select(COEFF_BASES, options.stone_coeff_basis, "— 안 정함(품셈) —"); + const mpa = select(FILL_MPA, options.fill_concrete_mpa, "— 안 정함(210) —"); const slope = document.createElement("input"); slope.className = "b07-spec__input"; @@ -128,6 +136,8 @@ export function buildStandardSpecPanel( slope, judgedSlope ? `비우면 자동 — 지금 판정값 1:${judgedSlope}` : "비우면 자동으로 판정합니다.", ), + field("야면석 계수", coeff, "비우면 품셈 열을 씁니다."), + field("채움 강도 (MPa)", mpa, "비우면 210. 180 은 국가기준 하한입니다."), ); const notes = document.createElement("ul"); @@ -163,6 +173,8 @@ export function buildStandardSpecPanel( back_len_cm: back.value || null, face_slope_ratio: slope.value.trim() || null, foundation: foundation.value || null, + stone_coeff_basis: coeff.value || null, + fill_concrete_mpa: mpa.value || null, }), ); } catch (error) {