feat(design): 구조물 명세 공용 틀 + 횡단배수 배관(표 01) 컨테이너 폼

- 공용 틀(A00_Common/design/design_structure_*) — 명세(컨테이너 필수/선택 · 여러 개 · 항목 고르기/숫자/자동 · 상위 값에 따른 선택지 · 저장 키) · 그리기([추가]/[빼기]) · 길이 = 전 + 후 맞물림(마지막에 고친 둘을 지킴)
- 배관 명세 = 관 · 관 보호공(유입) · 관 보호공(유출) · 흙막이 추가 · 도수로/돌붙임 · 저장 키는 pipe_points 옛 키 그대로 · 옛 관종 · 날개벽 · 집수정 형식은 화면에서 빼고 지난 정의 값으로 남김
- 컨테이너 인스턴스마다 「구조물 조합」 잇기(group_items · 그룹 [고르기] 길 그대로) · 명세 종류는 그룹 상자 · 종류 단위 줄 가림 · 종류 단위 잇기 줄은 기준 측점 바로 아래
- 관 길이 자동 = 횡단 기하 computeStoredLayouts 값(서버 Node 와 같은 함수) · 높이 빈 칸 안내 = 관경 기준 최소 높이
- 정의 판 6 — 종류.pipe.이름 표(이름 바꿈 키) · 검사기가 이름 표 받음 · 판 5 이력 보관
- 시험 test_52_structure_spec_pipe · 병합 시험 기대값(빠진 옛 칸은 남음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
2026-10-05 23:13:21 +09:00
co-authored by Claude Opus 5.5
parent 4bc6ec8106
commit 0a095c14b1
19 changed files with 4439 additions and 175 deletions
@@ -20,6 +20,12 @@ import {
staleValues,
} from "./B05_Profile_UI_Structures_Define";
import { FORM_OPTION_KEYS } from "./B05_Profile_UI_Drainage_Facility_Merge";
import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe";
import { specKeys } from "../A00_Common/design/design_structure_spec";
/** 구조물 명세 컨테이너로 그리는 종류인지 — 그때 종류 단위 「구조물 조합」 줄은 가림(PLAN 52-1). */
export const hasSpecContainers = (type: StructureType): boolean =>
!!STRUCTURE_SPECS[type.type_id]?.containers.length;
/** 패널이 쥔 값 중 옵션 그리기에 필요한 것들. 이름은 분리 전 지역변수와 같다. */
export interface OptionRenderContext {
@@ -175,12 +181,18 @@ export function paintStale(
/** 그룹 형상 키(`{그룹 id}__{칸}`) — 정의된 그룹 몫이라 지난 값이 아님. */
shapeKeys: string[] = [],
): void {
// 명세 종류(52) — 명세가 뺀 옛 키(retired)는 지난 정의 값 · 선택 컨테이너 키(머리)는 앎
const spec = type ? STRUCTURE_SPECS[type.type_id] : undefined;
const own = spec ? specKeys(spec) : { keys: [], prefixes: [] };
const known = [
...shapeKeys,
...(type?.options ?? []).map((o) => o.key),
...(facility ? (FORM_OPTION_KEYS[facility] ?? []) : []),
];
const stale = type ? staleValues(values, known) : [];
...own.keys,
].filter((key) => !spec?.retired.includes(key));
const stale = type
? staleValues(values, known).filter(([key]) => !own.prefixes.some((p) => key.startsWith(p)))
: [];
box.replaceChildren();
box.hidden = !stale.length;
if (!stale.length) return;