Files
Aislo/A00_Common/design/design_structure_box.ts
T
eomsangdonandClaude Opus 5.5 77e8aa2709 fix(B06): 박스 흙막이 기본 높이 = 박스 높이 값 · 저장 x 벽 종전 자리 그대로 · 흙막이 조정창 제목 · 박스 날개벽 툴팁(79b)
- 박스 흙막이 높이 빈 칸 = 박스 높이 값(2 m 박스 → 2 m) · 윗면 = 본체 윗면 · 바닥 = 윗면 − 높이 · 세월교는 그대로
- x 원점(사면 경사길이 4 m 지점) 되돌림 — 손대지 않은 벽(x · d 빈 칸)만 성토사면 제일 위 · 손으로 x 를 넣은 독립 기슭막이 · 옹벽 · 관 보호공은 종전 자리
- 세월교 · 박스 흙막이 조정창 제목 「흙막이(유입 · 유출)」 · 박스 에이프런 툴팁 유입 · 유출 날개벽 이름 바로잡음
- 시험: test_79_guard_shape — 박스 높이 값 · 저장 x 있는 관 보호공 · 독립 기슭막이 = 종전 자리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
2026-10-08 00:26:10 +09:00

133 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* design_structure_box.ts
* 횡단배수 › 박스암거 명세 — 사용자 구조물 표 02 고친 판(`docs/raw/구조물정의_표/02_횡단배수_박스암거.md`) 그대로.
*
* 컨테이너: 본체 · 유입 구조 · 유출 구조(필수) · 흙막이(선택 · 여러 개) · 도수로/돌붙임(선택 · 하나 · 65-2).
* 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈.
* 저장 키 = pipe_points.json 옛 키 그대로(B06 횡단 · B07 · B08 이 읽는 자리):
* · 본체 = `body_width_m` · `body_height_m` 수 — 규격 칸은 저장 안 함(폭 · 높이로 되찾음 · `bodyOf`)
* · 사용자 입력에 두 칸 다 비면 `body_custom` = 「사용자 입력」 표시만
* · 유입 · 유출 구조 = `wing_in` · `wing_out`(없음 · 날개벽 · 흙막이 · 옛 「있음」 = 날개벽)
* — 엔진은 날개벽만 세움(`_wing_spec` ↔ `wingSpec` 짝) · 흙막이 = 배관과 같은 양식(`{side}_revet_*` · 65-2) ·
* 「없음」 = 목록 램프 · 잇기 줄 없음(65-3)
* 표 02 에 없는 옛 날개벽 치수 칸은 화면에서 빼고 값은 남김(지난 정의 값).
* import = 같은 폴더 명세뿐 — 시험이 이 폴더 파일만 옮겨 돌림.
* ========================================================================== */
import type { Instance, SpecContainer, SpecItem, StructureSpec } from "./design_structure_spec";
import { bodyGuardItems, CHUTE_ADD, SOIL_ADD } from "./design_structure_guard";
export const BOX_CUSTOM = "사용자 입력";
/** 사용자 입력에 가로 · 세로 둘 다 빈 채 저장했다는 표시(폭 · 높이 수가 없을 때만 적음). */
export const BOX_CUSTOM_KEY = "body_custom";
/** 본체 규격 → [가로, 세로](m) — 실무 관측 규격(2026-08-17 사용자 확정). */
const BOX_PRESETS: Record<string, readonly [number, number]> = {
"2.0×2.0": [2, 2],
"3.0×3.0": [3, 3],
};
/** 유입 · 유출 구조 — 같은 선택지 · 키만 다름 · 흙막이면 흙막이 칸(65-2). */
const guardContainer = (
id: "inlet" | "outlet",
title: string,
link: string,
key: string,
): SpecContainer => ({
id,
title,
required: true,
none: "없음",
link,
items: [
{
id: "structure",
label: "구조",
kind: "select",
key,
choices: [
{ value: "없음", label: "없음" },
{ value: "날개벽", label: "날개벽" },
{ value: "흙막이", label: "흙막이" },
],
},
...bodyGuardItems(id, "박스 높이(자동)"),
],
});
/** 사용자 입력일 때만 서는 숫자 칸. */
const customSide = (id: string, label: string, key: string): SpecItem => ({
id,
label,
kind: "number",
step: 0.1,
by: { item: "size", map: { [BOX_CUSTOM]: { key } } },
});
export const BOX_SPEC: StructureSpec = {
typeId: "box_culvert",
containers: [
{
id: "body",
title: "본체",
required: true,
link: "gboxbody",
items: [
{
id: "size",
label: "본체 규격",
kind: "select",
choices: [
...Object.keys(BOX_PRESETS).map((value) => ({
value,
label: value.replace("×", " × "),
})),
{ value: BOX_CUSTOM, label: BOX_CUSTOM },
],
},
customSide("width", "가로 (m)", "body_width_m"),
customSide("height", "세로 (m)", "body_height_m"),
],
},
guardContainer("inlet", "유입 구조", "gboxin", "wing_in"),
guardContainer("outlet", "유출 구조", "gboxout", "wing_out"),
SOIL_ADD,
CHUTE_ADD,
],
// 표 02 에 없는 옛 날개벽 치수 — 화면에서 빼고 값은 남김(지난 정의 값)
retired: ["in", "out"].flatMap((side) =>
["height_m", "length_m", "angle_deg"].map((field) => `wing_${side}_${field}`),
),
};
/** 옛 저장 → 폼에 줄 옵션 — 설치 「있음」 = 날개벽(같은 뜻 · 저장하면 새 값으로 굳음). */
export function boxOptionsIn(
options: Record<string, string | number>,
): Record<string, string | number> {
const out = { ...options };
for (const key of ["wing_in", "wing_out"]) if (out[key] === "있음") out[key] = "날개벽";
return out;
}
/** 저장 폭 · 높이 → 본체 칸 값 — 규격과 맞으면 그 규격 · 손 값이 하나라도 있거나 사용자 입력 표시면
* 사용자 입력(빈 칸은 빈 채) · 아니면 빈 값. */
export function bodyOf(options: Record<string, string | number>): Instance {
const width = Number(options.body_width_m);
const height = Number(options.body_height_m);
const preset = Object.entries(BOX_PRESETS).find(
([, [w, h]]) => Math.abs(w - width) < 0.05 && Math.abs(h - height) < 0.05,
);
if (preset) return { size: preset[0] };
if (!(width > 0) && !(height > 0) && options[BOX_CUSTOM_KEY] !== BOX_CUSTOM) return { size: "" };
const side = (n: number): string => (n > 0 ? String(n) : "");
return { size: BOX_CUSTOM, width: side(width), height: side(height) };
}
/** 본체 칸 값 → 저장 폭 · 높이 — 규격이면 규격 수(사용자 입력은 명세 키가 적음) · 사용자 입력에 두 칸 다
* 비면 표시 키만(다시 열 때 「사용자 입력」). */
export function bodyOptions(body: Instance | undefined): Record<string, string | number> {
const preset = BOX_PRESETS[body?.size ?? ""];
if (preset) return { body_width_m: preset[0], body_height_m: preset[1] };
const blank = !(body?.width ?? "").trim() && !(body?.height ?? "").trim();
return body?.size === BOX_CUSTOM && blank ? { [BOX_CUSTOM_KEY]: BOX_CUSTOM } : {};
}