feat(structure): 65-1~5 세월교 · 박스암거 유입 · 유출 흙막이 양식 공통 · 없음 = 램프 뺌 · 세월교 기본값 · 물넘이 월류 폭 · 높이
- 흙막이 양식 한 벌(design_structure_guard) — 배관 · 박스 · 세월교가 같은 칸(형태 · 길이 · 높이 · 측점 전후) · 추가 흙막이 · 도수로/돌붙임 상자
- 세월교 · 박스 유입 · 유출: 흙막이 고르면 흙막이 칸(키 {side}_revet_* · 기본 찰 10 · 1.5 · 5 · 5) · [연동][경사] 옵션 줄
- B06 측벽 높이 · 좌우 · 상하 · 바닥판 읽기 줄을 세월교 · 박스 유입 · 유출 칸에 안 옮김(강조만)
- 유입 · 유출 「없음」 = 목록 램프 · 구조물 조합 잇기 줄 없음(명세 none)
- 세월교: 수량(련) 관경 바로 뒤 · 련 기본 2 · 새 시설 빈 월류 높이 = 유량 오면 필요 수심 · 종류 바꿀 때 유량 넘김
- 물넘이 포장: 경사각 기본 3° · 월류 폭 · 높이 칸(세월교와 같은 키 ford_width_m · ford_height_m · 같은 계산)
- 구조물정의 판 18(판_17 이력) · 이름 표 · 집계표 선택 컨테이너 짝 · 저장 병합 키
- 시험: 세월교 · 박스 · 물넘이 명세 기대값 · 흙막이 짝 자리 옮김
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
@@ -2,17 +2,20 @@
|
||||
* design_structure_box.ts
|
||||
* 횡단배수 › 박스암거 명세 — 사용자 구조물 표 02 고친 판(`docs/raw/구조물정의_표/02_횡단배수_박스암거.md`) 그대로.
|
||||
*
|
||||
* 컨테이너 셋(모두 필수): 본체 · 유입 구조 · 유출 구조. 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈.
|
||||
* 컨테이너: 본체 · 유입 구조 · 유출 구조(필수) · 흙막이(선택 · 여러 개) · 도수로/돌붙임(선택 · 하나 · 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` 짝)
|
||||
* — 엔진은 날개벽만 세움(`_wing_spec` ↔ `wingSpec` 짝) · 흙막이 = 배관과 같은 양식(`{side}_revet_*` · 65-2) ·
|
||||
* 「없음」 = 목록 램프 · 잇기 줄 없음(65-3)
|
||||
* 표 02 에 없는 옛 날개벽 치수 칸은 화면에서 빼고 값은 남김(지난 정의 값).
|
||||
* import 없음(꼴만) — 시험이 이 파일만 옮겨 돌림.
|
||||
* import = 같은 폴더 명세뿐 — 시험이 이 폴더 파일만 옮겨 돌림.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { Instance, SpecItem, StructureSpec } from "./design_structure_spec";
|
||||
import type { Instance, SpecContainer, SpecItem, StructureSpec } from "./design_structure_spec";
|
||||
import { CHUTE_ADD, guardItems, revetKeys, SOIL_ADD } from "./design_structure_guard";
|
||||
|
||||
export const BOX_CUSTOM = "사용자 입력";
|
||||
/** 사용자 입력에 가로 · 세로 둘 다 빈 채 저장했다는 표시(폭 · 높이 수가 없을 때만 적음). */
|
||||
@@ -23,16 +26,31 @@ const BOX_PRESETS: Record<string, readonly [number, number]> = {
|
||||
"3.0×3.0": [3, 3],
|
||||
};
|
||||
|
||||
/** 유입 · 유출 구조 — 같은 선택지 · 키만 다름. */
|
||||
const structureItem = (key: string): SpecItem => ({
|
||||
id: "structure",
|
||||
label: "구조",
|
||||
kind: "select",
|
||||
key,
|
||||
choices: [
|
||||
{ value: "없음", label: "없음" },
|
||||
{ value: "날개벽", label: "날개벽" },
|
||||
{ value: "흙막이", label: "흙막이" },
|
||||
/** 유입 · 유출 구조 — 같은 선택지 · 키만 다름 · 흙막이면 흙막이 칸(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: "흙막이" },
|
||||
],
|
||||
},
|
||||
...guardItems({ 흙막이: revetKeys(id) }, undefined, true),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -70,20 +88,10 @@ export const BOX_SPEC: StructureSpec = {
|
||||
customSide("height", "세로 (m)", "body_height_m"),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "inlet",
|
||||
title: "유입 구조",
|
||||
required: true,
|
||||
link: "gboxin",
|
||||
items: [structureItem("wing_in")],
|
||||
},
|
||||
{
|
||||
id: "outlet",
|
||||
title: "유출 구조",
|
||||
required: true,
|
||||
link: "gboxout",
|
||||
items: [structureItem("wing_out")],
|
||||
},
|
||||
guardContainer("inlet", "유입 구조", "gboxin", "wing_in"),
|
||||
guardContainer("outlet", "유출 구조", "gboxout", "wing_out"),
|
||||
SOIL_ADD,
|
||||
CHUTE_ADD,
|
||||
],
|
||||
// 표 02 에 없는 옛 날개벽 치수 — 화면에서 빼고 값은 남김(지난 정의 값)
|
||||
retired: ["in", "out"].flatMap((side) =>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* =============================================================================
|
||||
* design_structure_ford.ts
|
||||
* 횡단배수 › 세월교 명세 — 사용자 구조물 표 04(`docs/raw/구조물정의_표/04_횡단배수_세월교.md`) 그대로.
|
||||
* 횡단배수 › 세월교 명세 — 사용자 구조물 표 04(`docs/raw/구조물정의_표/04_횡단배수_세월교.md`) + 65장.
|
||||
*
|
||||
* 컨테이너 셋: 세월교(필수) · 유입 구조(필수) · 유출 구조(필수). 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈.
|
||||
* 저장 키 = pipe_points.json 옛 키 그대로(B06 세월교 세트 · B07 · B08 이 읽는 자리) — 새 키 = 유입 · 유출 구조
|
||||
* 컨테이너: 세월교(필수) · 유입 구조(필수) · 유출 구조(필수) · 흙막이(선택 · 여러 개) · 도수로/돌붙임(선택 · 하나).
|
||||
* 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈.
|
||||
* 저장 키 = pipe_points.json 옛 키 그대로(B06 세월교 세트 · B07 · B08 이 읽는 자리) — 유입 · 유출 구조
|
||||
* (`inlet_type` · `outlet_type` = 없음 · 날개벽 · 흙막이) · 고르면 옛 날개벽 설치 칸(`wing_in` · `wing_out`)도 함께 적음.
|
||||
* 흙막이 = 배관과 같은 양식(`design_structure_guard` · 키 `{side}_revet_*` · 65-2) · 구조 「없음」 = 목록 램프 · 잇기 줄 없음(65-3).
|
||||
* 월류 높이 = 월류 폭과 연동(폭을 바꾸면 필요 수심으로 채움 · 그 아래로는 안 받음) — 계산은 `design_structure_ford_calc`.
|
||||
* import 없음 — 시험이 이 파일만 옮겨 돌림.
|
||||
* import = 같은 폴더 명세뿐 — 시험이 이 폴더 파일만 옮겨 돌림.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SpecContainer, SpecChoice, StructureSpec } from "./design_structure_spec";
|
||||
import { CHUTE_ADD, guardItems, revetKeys, SOIL_ADD } from "./design_structure_guard";
|
||||
|
||||
/** 유입 · 유출 구조 — 날개벽만 옛 설치 칸이 「있음」. */
|
||||
const guard = (side: "in" | "out"): SpecChoice[] => [
|
||||
@@ -22,6 +25,7 @@ const guardContainer = (id: "inlet" | "outlet", title: string, link: string): Sp
|
||||
id,
|
||||
title,
|
||||
required: true,
|
||||
none: "없음",
|
||||
link,
|
||||
items: [
|
||||
{
|
||||
@@ -31,6 +35,7 @@ const guardContainer = (id: "inlet" | "outlet", title: string, link: string): Sp
|
||||
key: `${id}_type`,
|
||||
choices: guard(id === "inlet" ? "in" : "out"),
|
||||
},
|
||||
...guardItems({ 흙막이: revetKeys(id) }, undefined, true),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -51,13 +56,16 @@ export const FORD_SPEC: StructureSpec = {
|
||||
asNumber: true,
|
||||
choices: ["800", "1000", "1200", "1500"].map((v) => ({ value: v, label: v })),
|
||||
},
|
||||
{ id: "count", label: "수량 (련)", kind: "number", key: "pipe_count", step: 1 }, // 관경 바로 뒤(65-4)
|
||||
// 월류 폭 · 높이 = 물넘이 포장과 같은 키 · 같은 계산(65-5 · B06 횡단이 읽음)
|
||||
{ id: "width", label: "월류 폭 (m)", kind: "number", key: "ford_width_m", step: 0.1 },
|
||||
{ id: "height", label: "월류 높이 (m)", kind: "number", key: "ford_height_m", step: 0.01 },
|
||||
{ id: "count", label: "수량 (련)", kind: "number", key: "pipe_count", step: 1 },
|
||||
],
|
||||
},
|
||||
guardContainer("inlet", "유입 구조", "gfordin"),
|
||||
guardContainer("outlet", "유출 구조", "gfordout"),
|
||||
SOIL_ADD,
|
||||
CHUTE_ADD,
|
||||
],
|
||||
// 표 04 에 없는 옛 칸 — 화면에서 빼고 값은 남김(지난 정의 값)
|
||||
retired: [
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* design_structure_ford_pavement.ts
|
||||
* 횡단배수 › 물넘이 포장 명세 — 사용자 구조물 표 03(`docs/raw/구조물정의_표/03_횡단배수_물넘이포장.md`) 그대로.
|
||||
*
|
||||
* 컨테이너 둘: 경사각도(필수 · 각도 손 입력 · 도) · 도수로/돌붙임(선택 · 구조 = 도수로 · 돌붙임).
|
||||
* 저장 = pipe_points.json `options` — 새 키 `ford_slope_deg` · 선택 컨테이너 `ford_chute_{n}_structure`.
|
||||
* 컨테이너 둘: 경사각도(필수 · 각도 손 입력 · 도 + 월류 폭 · 높이 · 65-5) · 도수로/돌붙임(선택 · 구조 = 도수로 · 돌붙임).
|
||||
* 저장 = pipe_points.json `options` — 새 키 `ford_slope_deg` · 선택 컨테이너 `ford_chute_{n}_structure` ·
|
||||
* 월류 폭 · 높이 = 세월교와 같은 키(`ford_width_m` · `ford_height_m`) · 같은 계산(`design_structure_ford_calc`).
|
||||
* 경사각도는 옛 「바닥 경사(%)」(`ford_slope_pct` · B06 파임 기울기)와 뜻이 같은지 또렷하지 않아 잇지 않음.
|
||||
* import 없음 — 시험이 이 파일만 옮겨 돌림.
|
||||
* ========================================================================== */
|
||||
@@ -20,6 +21,8 @@ export const FORD_PAVEMENT_SPEC: StructureSpec = {
|
||||
link: "gfordpaveslope",
|
||||
items: [
|
||||
{ id: "angle", label: "경사각도 (°)", kind: "number", key: "ford_slope_deg", step: 0.1 },
|
||||
{ id: "width", label: "월류 폭 (m)", kind: "number", key: "ford_width_m", step: 0.1 },
|
||||
{ id: "height", label: "월류 높이 (m)", kind: "number", key: "ford_height_m", step: 0.01 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -43,5 +46,5 @@ export const FORD_PAVEMENT_SPEC: StructureSpec = {
|
||||
},
|
||||
],
|
||||
// 표 03 에 없는 옛 칸 — 화면에서 빼고 값은 남김(지난 정의 값)
|
||||
retired: ["ford_width_m", "ford_height_m", "ford_slope_pct", "thickness_cm", "length_m"],
|
||||
retired: ["ford_slope_pct", "thickness_cm", "length_m"],
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
choiceLabel,
|
||||
choicesOf,
|
||||
containerTitle,
|
||||
isNone,
|
||||
itemLabel,
|
||||
linkOf,
|
||||
readState,
|
||||
@@ -190,11 +191,13 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
for (const r of ["length", "before", "after"] as SplitRole[])
|
||||
values[role(r).id] = solved[r] ?? "";
|
||||
}
|
||||
// 위 항목이 바뀌면 갈래(선택지 · 저장 키)가 달라짐 — 선택지 밖 값은 비움
|
||||
// 위 항목이 바뀌면 갈래(선택지 · 저장 키)가 달라짐 — 선택지 밖 값은 비움 · 새로 선 빈 칸은 기본값(65-2)
|
||||
for (const child of c.items.filter((i) => i.by?.item === item.id)) {
|
||||
const allowed = choicesOf(child, values);
|
||||
if (child.kind === "select" && !allowed.some((ch) => ch.value === values[child.id]))
|
||||
values[child.id] = "";
|
||||
if (child.default && !values[child.id] && branchOf(child, values))
|
||||
values[child.id] = child.default;
|
||||
}
|
||||
paint();
|
||||
hooks.onChange?.(c, item);
|
||||
@@ -252,7 +255,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
}
|
||||
box.body.append(slot);
|
||||
}
|
||||
if (links) {
|
||||
if (links && !isNone(c, values)) {
|
||||
const key = linkOf(c, index + 1);
|
||||
const row = buildSummaryRow();
|
||||
row.root.querySelector(".b05-structure__summary-label")!.textContent = "구조물 조합";
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/* =============================================================================
|
||||
* design_structure_guard.ts
|
||||
* 흙막이 공통 양식 — 배관(표 01) · 박스암거(표 02) · 세월교(표 04)가 같은 칸을 씀(PLAN 65-2).
|
||||
*
|
||||
* · 흙막이 = 형태 6 · 길이 · 높이 · 기준측점 전 · 후(길이 = 전 + 후 맞물림)
|
||||
* · 필수 보호공(유입 · 유출)은 구조 갈래마다 저장 키(`{side}_revet_*` 옛 키) · 선택 흙막이 · 도수로/돌붙임은
|
||||
* 인스턴스 키(`soil_add_{n}_*` · `chute_add_{n}_*`)
|
||||
* import = 꼴만 — 시험이 이 폴더 파일만 옮겨 돌림.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SpecChoice, SpecContainer, SpecItem } from "./design_structure_spec";
|
||||
|
||||
export const SOIL_FORMS: SpecChoice[] = [
|
||||
{ value: "돌쌓기(찰)", label: "돌 쌓기(찰)" },
|
||||
{ value: "돌쌓기(메)", label: "돌 쌓기(메)" },
|
||||
{ value: "콘크리트", label: "콘크리트" },
|
||||
{ value: "돌망태", label: "돌망태" },
|
||||
{ value: "통나무·목재틀", label: "통나무/목재틀" },
|
||||
{ value: "바자", label: "바자" },
|
||||
];
|
||||
|
||||
/** 흙막이 기본값(54-36 · 사면안정 흙막이 정의 기본값과 같음). */
|
||||
const SOIL_DEFAULTS: Record<string, string> = {
|
||||
form: "돌쌓기(찰)",
|
||||
length: "10",
|
||||
height: "1.5",
|
||||
before: "5",
|
||||
after: "5",
|
||||
};
|
||||
|
||||
/** 형태 · 길이 · 높이 · 전 · 후 — 필수 보호공은 구조 갈래마다 옛 키(`keys`) · 선택 흙막이는 인스턴스 키.
|
||||
* `forms` = 갈래별 형태 선택지(배관 집수정 등) · `defaults` = 새 상자 · 흙막이를 고를 때 칸에 실제 값. */
|
||||
export function guardItems(
|
||||
keys?: Record<string, Record<string, string>>,
|
||||
forms?: Record<string, SpecChoice[]>,
|
||||
defaults = !keys,
|
||||
): SpecItem[] {
|
||||
const by = (field: string, choices?: Record<string, SpecChoice[]>) =>
|
||||
keys
|
||||
? {
|
||||
item: "structure",
|
||||
map: Object.fromEntries(
|
||||
// 키 없는 갈래 = 그 구조에 없는 칸(집수정 길이 · 전 · 후) — 가림
|
||||
Object.entries(keys)
|
||||
.filter(([, k]) => k[field])
|
||||
.map(([structure, k]) => [
|
||||
structure,
|
||||
{ key: k[field], choices: choices?.[structure] },
|
||||
]),
|
||||
),
|
||||
}
|
||||
: undefined;
|
||||
const items: SpecItem[] = [
|
||||
{ id: "form", label: "형태", kind: "select", choices: SOIL_FORMS, by: by("form", forms) },
|
||||
{ id: "length", label: "길이 (m)", kind: "number", split: "length", step: 1, by: by("length") },
|
||||
{ id: "height", label: "높이 (m)", kind: "number", step: 0.1, by: by("height") },
|
||||
{
|
||||
id: "before",
|
||||
label: "기준측점 전 (m)",
|
||||
kind: "number",
|
||||
split: "before",
|
||||
step: 0.5,
|
||||
by: by("before"),
|
||||
},
|
||||
{
|
||||
id: "after",
|
||||
label: "기준측점 후 (m)",
|
||||
kind: "number",
|
||||
split: "after",
|
||||
step: 0.5,
|
||||
by: by("after"),
|
||||
},
|
||||
];
|
||||
return defaults ? items.map((item) => ({ ...item, default: SOIL_DEFAULTS[item.id] })) : items;
|
||||
}
|
||||
|
||||
export const revetKeys = (side: "inlet" | "outlet"): Record<string, string> => ({
|
||||
form: `${side}_revet_form`,
|
||||
length: `${side}_revet_length_m`,
|
||||
height: `${side}_revet_height_m`,
|
||||
before: `${side}_revet_before_m`,
|
||||
after: `${side}_revet_after_m`,
|
||||
});
|
||||
|
||||
/** 추가 흙막이(선택 · 여러 개) — 사면안정 흙막이(표 09 · 54-26)와 같은 칸 · 구조 고르기 없음(54-30). */
|
||||
export const SOIL_ADD: SpecContainer = {
|
||||
id: "soil_add",
|
||||
title: "흙막이",
|
||||
required: false,
|
||||
prefix: "soil_add",
|
||||
link: "gsoiladd",
|
||||
items: guardItems(),
|
||||
};
|
||||
|
||||
/** 도수로/돌붙임(선택 · 하나만 · 54-48) — 옛 둘 이상은 보이되 [추가]만 숨김. */
|
||||
export const CHUTE_ADD: SpecContainer = {
|
||||
id: "chute_add",
|
||||
title: "도수로/돌붙임",
|
||||
required: false,
|
||||
prefix: "chute_add",
|
||||
link: "gchuteadd",
|
||||
max: 1,
|
||||
items: [
|
||||
{
|
||||
id: "structure",
|
||||
label: "구조",
|
||||
kind: "select",
|
||||
choices: [
|
||||
{ value: "도수로", label: "도수로" },
|
||||
{ value: "돌붙임", label: "돌붙임" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -13,6 +13,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SpecChoice, SpecItem, StructureSpec } from "./design_structure_spec";
|
||||
import { CHUTE_ADD, guardItems, revetKeys, SOIL_ADD, SOIL_FORMS } from "./design_structure_guard";
|
||||
import { BOX_SPEC } from "./design_structure_box";
|
||||
import { FORD_SPEC } from "./design_structure_ford";
|
||||
import { FORD_PAVEMENT_SPEC } from "./design_structure_ford_pavement";
|
||||
@@ -20,83 +21,14 @@ import { ETC_SPEC, PAVEMENT_SPEC } from "./design_structure_road";
|
||||
|
||||
const SOIL = "기슭막이"; // 저장 값(옛 키 그대로) — 화면 이름은 흙막이
|
||||
const BASIN = "집수정";
|
||||
const SOIL_FORMS: SpecChoice[] = [
|
||||
{ value: "돌쌓기(찰)", label: "돌 쌓기(찰)" },
|
||||
{ value: "돌쌓기(메)", label: "돌 쌓기(메)" },
|
||||
{ value: "콘크리트", label: "콘크리트" },
|
||||
{ value: "돌망태", label: "돌망태" },
|
||||
{ value: "통나무·목재틀", label: "통나무/목재틀" },
|
||||
{ value: "바자", label: "바자" },
|
||||
];
|
||||
const BASIN_FORMS: SpecChoice[] = [
|
||||
{ value: "I", label: "I형" },
|
||||
{ value: "L", label: "L형" },
|
||||
{ value: "U", label: "ㄷ형" },
|
||||
];
|
||||
|
||||
/** 추가 흙막이 기본값(54-36 · 사면안정 흙막이 정의 기본값과 같음) — 필수 보호공 기본값은 정의 파일. */
|
||||
const SOIL_ADD_DEFAULTS: Record<string, string> = {
|
||||
form: "돌쌓기(찰)",
|
||||
length: "10",
|
||||
height: "1.5",
|
||||
before: "5",
|
||||
after: "5",
|
||||
};
|
||||
|
||||
/** 형태 · 길이 · 높이 · 전 · 후 — 필수 보호공은 구조 갈래마다 옛 키 · 선택 흙막이는 인스턴스 키. */
|
||||
function guardItems(keys?: Record<string, Record<string, string>>): SpecItem[] {
|
||||
const by = (field: string, choices?: Record<string, SpecChoice[]>) =>
|
||||
keys
|
||||
? {
|
||||
item: "structure",
|
||||
map: Object.fromEntries(
|
||||
// 키 없는 갈래 = 그 구조에 없는 칸(집수정 길이 · 전 · 후) — 가림
|
||||
Object.entries(keys)
|
||||
.filter(([, k]) => k[field])
|
||||
.map(([structure, k]) => [
|
||||
structure,
|
||||
{ key: k[field], choices: choices?.[structure] },
|
||||
]),
|
||||
),
|
||||
}
|
||||
: undefined;
|
||||
const items: SpecItem[] = [
|
||||
{
|
||||
id: "form",
|
||||
label: "형태",
|
||||
kind: "select",
|
||||
choices: SOIL_FORMS,
|
||||
by: by("form", { [BASIN]: BASIN_FORMS, [SOIL]: SOIL_FORMS }),
|
||||
},
|
||||
{ id: "length", label: "길이 (m)", kind: "number", split: "length", step: 1, by: by("length") },
|
||||
{ id: "height", label: "높이 (m)", kind: "number", step: 0.1, by: by("height") },
|
||||
{
|
||||
id: "before",
|
||||
label: "기준측점 전 (m)",
|
||||
kind: "number",
|
||||
split: "before",
|
||||
step: 0.5,
|
||||
by: by("before"),
|
||||
},
|
||||
{
|
||||
id: "after",
|
||||
label: "기준측점 후 (m)",
|
||||
kind: "number",
|
||||
split: "after",
|
||||
step: 0.5,
|
||||
by: by("after"),
|
||||
},
|
||||
];
|
||||
return keys ? items : items.map((item) => ({ ...item, default: SOIL_ADD_DEFAULTS[item.id] }));
|
||||
}
|
||||
|
||||
const revetKeys = (side: "inlet" | "outlet"): Record<string, string> => ({
|
||||
form: `${side}_revet_form`,
|
||||
length: `${side}_revet_length_m`,
|
||||
height: `${side}_revet_height_m`,
|
||||
before: `${side}_revet_before_m`,
|
||||
after: `${side}_revet_after_m`,
|
||||
});
|
||||
/** 보호공 형태 · 길이 · 높이 · 전 · 후 — 구조 갈래마다 옛 키(흙막이 양식 = `design_structure_guard`). */
|
||||
const protectItems = (keys: Record<string, Record<string, string>>): SpecItem[] =>
|
||||
guardItems(keys, { [BASIN]: BASIN_FORMS, [SOIL]: SOIL_FORMS });
|
||||
|
||||
export const PIPE_SPEC: StructureSpec = {
|
||||
typeId: "pipe",
|
||||
@@ -134,7 +66,7 @@ export const PIPE_SPEC: StructureSpec = {
|
||||
{ value: SOIL, label: "흙막이", also: { inlet_structure: "revet" } },
|
||||
],
|
||||
},
|
||||
...guardItems({
|
||||
...protectItems({
|
||||
[BASIN]: { form: "inlet_structure", height: "inlet_basin_height_m" },
|
||||
[SOIL]: revetKeys("inlet"),
|
||||
}),
|
||||
@@ -153,37 +85,11 @@ export const PIPE_SPEC: StructureSpec = {
|
||||
key: "outlet_type",
|
||||
choices: [{ value: SOIL, label: "흙막이" }],
|
||||
},
|
||||
...guardItems({ [SOIL]: revetKeys("outlet") }),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "soil_add",
|
||||
title: "흙막이",
|
||||
required: false,
|
||||
prefix: "soil_add",
|
||||
link: "gsoiladd",
|
||||
// 사면안정 흙막이(표 09 · 54-26)와 같은 칸 — 형태 6 · 길이 · 높이 · 전 · 후 · 구조 고르기 없음(54-30)
|
||||
items: guardItems(),
|
||||
},
|
||||
{
|
||||
id: "chute_add",
|
||||
title: "도수로/돌붙임",
|
||||
required: false,
|
||||
prefix: "chute_add",
|
||||
link: "gchuteadd",
|
||||
max: 1, // 하나만(54-48) — 옛 둘 이상은 보이되 [추가]만 숨김
|
||||
items: [
|
||||
{
|
||||
id: "structure",
|
||||
label: "구조",
|
||||
kind: "select",
|
||||
choices: [
|
||||
{ value: "도수로", label: "도수로" },
|
||||
{ value: "돌붙임", label: "돌붙임" },
|
||||
],
|
||||
},
|
||||
...protectItems({ [SOIL]: revetKeys("outlet") }),
|
||||
],
|
||||
},
|
||||
SOIL_ADD, // 개수 = 유출 「추가 기슭막이(단)」(54-1)
|
||||
CHUTE_ADD,
|
||||
],
|
||||
// 표 01 에 없는 옛 칸 — 화면에서 빼고 값은 남김(지난 정의 값)
|
||||
retired: ["pipe_kind", "wing_wall_type", "inlet_basin_form", "inlet_basin_material"],
|
||||
|
||||
@@ -59,6 +59,8 @@ export interface SpecContainer {
|
||||
max?: number;
|
||||
/** 구조물 조합 잇기 키 — 필수는 정의 그룹 id 그대로 · 선택은 뒤에 n. */
|
||||
link: string;
|
||||
/** 첫 항목이 이 값이면 그 컨테이너는 없는 셈 — 목록 램프 · 잇기 줄 뺌(65-3 · 세월교 · 박스 유입 · 유출 「없음」). */
|
||||
none?: string;
|
||||
items: SpecItem[];
|
||||
}
|
||||
|
||||
@@ -103,6 +105,10 @@ export function choicesOf(item: SpecItem, values: Instance): SpecChoice[] {
|
||||
return branchOf(item, values)?.choices ?? item.choices ?? [];
|
||||
}
|
||||
|
||||
/** 컨테이너가 「없음」으로 골라졌는지(`none`). */
|
||||
export const isNone = (c: SpecContainer, values: Instance): boolean =>
|
||||
!!c.none && values[c.items[0]?.id] === c.none;
|
||||
|
||||
/** 인스턴스 잇기 키 — 필수 = 그룹 id · 선택 = `{link}{n}`. */
|
||||
export const linkOf = (c: SpecContainer, n: number): string =>
|
||||
c.required ? c.link : `${c.link}${n}`;
|
||||
|
||||
@@ -46,9 +46,14 @@ import {
|
||||
WING_IN_KEYS,
|
||||
WING_OUT_KEYS,
|
||||
} from "./B05_Profile_UI_Drainage_Facility_Fields";
|
||||
import { revetChoiceRow } from "./B05_Profile_UI_Drainage_Revet_Choice";
|
||||
import { guardChoiceFields, revetChoiceRow } from "./B05_Profile_UI_Drainage_Revet_Choice";
|
||||
import { optionDefaults } from "./B05_Profile_UI_Structures_Fields";
|
||||
import { createSpecForm, type SpecLinks } from "../A00_Common/design/design_structure_form";
|
||||
import {
|
||||
createSpecForm,
|
||||
type SpecForm,
|
||||
type SpecLinks,
|
||||
} from "../A00_Common/design/design_structure_form";
|
||||
import type { SpecContainer, SpecItem } from "../A00_Common/design/design_structure_spec";
|
||||
import { PIPE_SPEC } from "../A00_Common/design/design_structure_pipe";
|
||||
import {
|
||||
BOX_SPEC,
|
||||
@@ -219,6 +224,8 @@ export interface FacilityOptionsForm {
|
||||
/** 옵션 정의 기본값 — 필수 상자 빈 고르기 칸이 받는 값(54-43). */
|
||||
options?: Array<{ key: string; default: string | number | null }>;
|
||||
} | null,
|
||||
/** 새 시설(등록부 기본값으로 염) — 세월교 · 물넘이 빈 월류 높이를 유량이 오면 필요 수심으로(65-4). */
|
||||
fresh?: boolean,
|
||||
) => void;
|
||||
/** 배관 명세 컨테이너 「구조물 조합」 잇기 자리 — 폼이 패널 그룹 값에 이음(PLAN 52-1). */
|
||||
bindLinks: (links: SpecLinks) => void;
|
||||
@@ -372,20 +379,40 @@ export function createFacilityOptionsForm(
|
||||
: "auto";
|
||||
};
|
||||
|
||||
// ── 세월교 = 사용자 구조물 표 04 명세(PLAN 52-4) — 세월교 · 유입 구조 · 유출 구조 ──
|
||||
// 월류 높이 = 월류 폭과 연동(폭을 바꾸면 필요 수심으로 채움 · 그 아래는 안 받음 · `design_structure_ford_calc`)
|
||||
// ── 세월교 = 사용자 구조물 표 04 명세(PLAN 52-4 · 65장) — 세월교 · 유입 구조 · 유출 구조 · 흙막이 · 도수로/돌붙임 ──
|
||||
// 월류 높이 = 월류 폭과 연동(폭을 바꾸면 필요 수심으로 채움 · 그 아래는 안 받음 · `design_structure_ford_calc`) —
|
||||
// 물넘이 포장도 같은 키 · 같은 계산(65-5) · 새 시설은 유량이 오면 빈 높이를 필요 수심으로(65-4)
|
||||
let fordFlow: number | null = null;
|
||||
const fordNeed = (): number | null =>
|
||||
fordHeightM(fordFlow, Number(fordForm.state().ford?.[0]?.width ?? ""));
|
||||
const fordForm = createSpecForm(specLinks, {
|
||||
onChange: (c, item) => {
|
||||
const need = c.id === "ford" ? fordNeed() : null;
|
||||
const height = fordForm.state().ford?.[0]?.height ?? "";
|
||||
const low = height !== "" && !(Number(height) >= (need ?? 0));
|
||||
if (need !== null && (item.id === "width" || (item.id === "height" && low)))
|
||||
fordForm.setValue("ford", "height", need.toFixed(2));
|
||||
emit();
|
||||
},
|
||||
let heightPending = false;
|
||||
const overflow = (form: () => SpecForm, box: string) => {
|
||||
const values = () => form().state()[box]?.[0] ?? {};
|
||||
const need = (): number | null => fordHeightM(fordFlow, Number(values().width ?? ""));
|
||||
return {
|
||||
onChange: (c: SpecContainer, item: SpecItem) => {
|
||||
if (c.id === box && item.id === "height") heightPending = false;
|
||||
const want = c.id === box ? need() : null;
|
||||
const height = values().height ?? "";
|
||||
const low = height !== "" && !(Number(height) >= (want ?? 0));
|
||||
if (want !== null && (item.id === "width" || (item.id === "height" && low)))
|
||||
form().setValue(box, "height", want.toFixed(2));
|
||||
emit();
|
||||
},
|
||||
/** 새 시설 빈 높이 = 필요 수심(사용자가 넣은 것처럼 · 알림 없음 — [추가] · [수정]이 읽음). */
|
||||
fill: () => {
|
||||
const want = heightPending && !values().height ? need() : null;
|
||||
if (want === null) return;
|
||||
form().setValue(box, "height", want.toFixed(2));
|
||||
heightPending = false;
|
||||
},
|
||||
};
|
||||
};
|
||||
const fordFlowBox = overflow(() => fordForm, "ford");
|
||||
const fordForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: fordFlowBox.onChange,
|
||||
fields: guardChoiceFields(
|
||||
() => chainage,
|
||||
() => fordForm.refresh(),
|
||||
),
|
||||
});
|
||||
|
||||
// ── 추가 기슭막이(다단) — 횡단도에서 다단 벽을 고를 때만 뜬다(2026-08-30 사용자
|
||||
@@ -440,8 +467,14 @@ export function createFacilityOptionsForm(
|
||||
return merged;
|
||||
};
|
||||
|
||||
// ── BOX암거 = 사용자 구조물 표 02 명세(PLAN 52-2) — 본체 · 유입 구조 · 유출 구조 ──
|
||||
const boxForm = createSpecForm(specLinks, { onChange: () => emit() });
|
||||
// ── BOX암거 = 사용자 구조물 표 02 명세(PLAN 52-2 · 65장) — 본체 · 유입 구조 · 유출 구조 · 흙막이 · 도수로/돌붙임 ──
|
||||
const boxForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: () => emit(),
|
||||
fields: guardChoiceFields(
|
||||
() => chainage,
|
||||
() => boxForm.refresh(),
|
||||
),
|
||||
});
|
||||
// 날개벽 칸 — 세월교 · BOX 가 같은 조각을 쓰던 자리 · 조정 행은 명세 유입 · 유출 칸으로 옮겨 붙음
|
||||
const wingInFields = createWingFields("날개벽(유입)", WING_IN_KEYS);
|
||||
const wingOutFields = createWingFields("날개벽(유출)", WING_OUT_KEYS);
|
||||
@@ -450,9 +483,10 @@ export function createFacilityOptionsForm(
|
||||
|
||||
const hiddenOptions = createHiddenOptions(root);
|
||||
|
||||
// ── 물넘이 포장 = 사용자 구조물 표 03 명세(PLAN 52-3) — 경사각도 · 도수로/돌붙임 ·
|
||||
// 옛 월류 폭 · 높이 · 바닥 경사 · 두께 · 길이 칸은 뺌(지난 정의 값) ──
|
||||
const paveForm = createSpecForm(specLinks, { onChange: () => emit() });
|
||||
// ── 물넘이 포장 = 사용자 구조물 표 03 명세(PLAN 52-3) — 경사각도(+ 월류 폭 · 높이 · 65-5) · 도수로/돌붙임 ·
|
||||
// 옛 바닥 경사 · 두께 · 길이 칸은 뺌(지난 정의 값) ──
|
||||
const paveFlowBox = overflow(() => paveForm, "slope");
|
||||
const paveForm: SpecForm = createSpecForm(specLinks, { onChange: paveFlowBox.onChange });
|
||||
|
||||
// 10-A ⑲ 저장 규칙 글은 [?] 설명서 `구조물_<시설>.md` 로 옮김(54-2) — 병합은 `_Drainage_Facility_Merge`.
|
||||
|
||||
@@ -567,8 +601,9 @@ export function createFacilityOptionsForm(
|
||||
setHiddenOptions(facility, registryKeys) {
|
||||
hiddenOptions.set(facility, registryKeys);
|
||||
},
|
||||
setFacility(facility, options = {}, designFlow = null, definition = null) {
|
||||
setFacility(facility, options = {}, designFlow = null, definition = null, fresh = false) {
|
||||
current = facility;
|
||||
heightPending = fresh;
|
||||
hiddenOptions.remember(options);
|
||||
fordFlow = designFlow ?? null;
|
||||
if (facility === null) {
|
||||
@@ -609,6 +644,8 @@ export function createFacilityOptionsForm(
|
||||
);
|
||||
if (facility === "ford_pavement")
|
||||
paveForm.write(FORD_PAVEMENT_SPEC, options, names, groups, defaults);
|
||||
if (facility === "ford_bridge") fordFlowBox.fill();
|
||||
if (facility === "ford_pavement") paveFlowBox.fill();
|
||||
wingInFields.write(options);
|
||||
wingOutFields.write(options);
|
||||
revetSide.value = text("side") || "양쪽";
|
||||
@@ -622,7 +659,8 @@ export function createFacilityOptionsForm(
|
||||
},
|
||||
setDesignFlow(designFlow) {
|
||||
fordFlow = designFlow ?? null;
|
||||
if (current === "ford_bridge") fordForm.refresh(); // 높이 안내
|
||||
if (current === "ford_bridge") fordFlowBox.fill();
|
||||
if (current === "ford_pavement") paveFlowBox.fill();
|
||||
syncVisibility();
|
||||
},
|
||||
readOptions() {
|
||||
|
||||
@@ -33,10 +33,19 @@ export const FORM_OPTION_KEYS: Readonly<Record<string, readonly string[]>> = {
|
||||
...revetKeys("outlet"),
|
||||
],
|
||||
// BOX = 표 02 명세 키(PLAN 52-2) — 옛 날개벽 치수는 폼에서 빠져 이어 붙임(지난 정의 값)
|
||||
box_culvert: ["body_width_m", "body_height_m", "body_custom", "wing_in", "wing_out"],
|
||||
// 물넘이 포장 = 표 03 명세 키(PLAN 52-3 · `design_structure_ford_pavement`) — 옛 월류 · 경사 · 두께 · 길이는
|
||||
// 폼에서 빠져 이어 붙임 · 도수로/돌붙임 키는 `FORM_OPTION_PREFIXES`
|
||||
ford_pavement: ["ford_slope_deg"],
|
||||
// 흙막이 = 배관과 같은 키(65-2) · 추가 흙막이 · 도수로/돌붙임은 `FORM_OPTION_PREFIXES`
|
||||
box_culvert: [
|
||||
"body_width_m",
|
||||
"body_height_m",
|
||||
"body_custom",
|
||||
"wing_in",
|
||||
"wing_out",
|
||||
...revetKeys("inlet"),
|
||||
...revetKeys("outlet"),
|
||||
],
|
||||
// 물넘이 포장 = 표 03 명세 키(PLAN 52-3 · `design_structure_ford_pavement`) + 월류 폭 · 높이(65-5) — 옛 경사 ·
|
||||
// 두께 · 길이는 폼에서 빠져 이어 붙임 · 도수로/돌붙임 키는 `FORM_OPTION_PREFIXES`
|
||||
ford_pavement: ["ford_slope_deg", "ford_width_m", "ford_height_m"],
|
||||
// 세월교 = 표 04 명세 키(PLAN 52-4 · `design_structure_ford`) — 옛 관종 · 날개벽 치수는 폼에서 빠져 이어 붙임
|
||||
ford_bridge: [
|
||||
"pipe_diameter_mm",
|
||||
@@ -47,6 +56,8 @@ export const FORM_OPTION_KEYS: Readonly<Record<string, readonly string[]>> = {
|
||||
"outlet_type",
|
||||
"wing_in",
|
||||
"wing_out",
|
||||
...revetKeys("inlet"),
|
||||
...revetKeys("outlet"),
|
||||
],
|
||||
revetment: ["side", ...revetKeys("inlet"), ...revetKeys("outlet")],
|
||||
};
|
||||
@@ -54,6 +65,8 @@ export const FORM_OPTION_KEYS: Readonly<Record<string, readonly string[]>> = {
|
||||
/** 폼이 인스턴스 번호를 붙여 적는 키 머리(명세 선택 컨테이너 · `{머리}{n}_{항목}`) — 빼면 지워짐. */
|
||||
export const FORM_OPTION_PREFIXES: Readonly<Record<string, readonly string[]>> = {
|
||||
pipe: ["soil_add_", "chute_add_"],
|
||||
box_culvert: ["soil_add_", "chute_add_"],
|
||||
ford_bridge: ["soil_add_", "chute_add_"],
|
||||
ford_pavement: ["ford_chute_"],
|
||||
};
|
||||
|
||||
|
||||
@@ -53,3 +53,21 @@ export function revetChoiceRow(
|
||||
}
|
||||
return { paint };
|
||||
}
|
||||
|
||||
/** 세월교 · 박스 흙막이 상자 줄(65-2 · 배관과 같은 줄) — 흙막이를 고른 유입 · 유출 · 추가 흙막이 상자마다 한 줄.
|
||||
* 명세 폼 `fields` 자리에 그대로 씀. */
|
||||
export function guardChoiceFields(
|
||||
chainageOf: () => number | null,
|
||||
written: () => void,
|
||||
): (c: { id: string }, values: Record<string, string>) => HTMLElement[] {
|
||||
const sides = {
|
||||
inlet: revetChoiceRow(chainageOf, written),
|
||||
outlet: revetChoiceRow(chainageOf, written),
|
||||
};
|
||||
return (c, values) =>
|
||||
c.id === "soil_add"
|
||||
? [revetChoiceRow(chainageOf, written).paint(false)]
|
||||
: c.id === "inlet" || c.id === "outlet"
|
||||
? [sides[c.id].paint(values.structure !== "흙막이")]
|
||||
: [];
|
||||
}
|
||||
|
||||
@@ -24,7 +24,13 @@ import {
|
||||
} from "../common_util/common_util_summary_item";
|
||||
import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe";
|
||||
import { pipeExtraCount } from "../A00_Common/design/design_structure_pipe_calc";
|
||||
import { containerTitle, linkOf, readState } from "../A00_Common/design/design_structure_spec";
|
||||
import {
|
||||
containerTitle,
|
||||
isNone,
|
||||
linkOf,
|
||||
readState,
|
||||
} from "../A00_Common/design/design_structure_spec";
|
||||
import { withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
||||
import { asRoad } from "./B05_Profile_UI_Structures_Define";
|
||||
|
||||
export interface StructureListParams {
|
||||
@@ -100,11 +106,20 @@ export function linkLamps(
|
||||
// 배관 흙막이 = 명세 이름 「흙막이」가 정의 옛 이름 「흙막이 추가」보다 앞섬(54-1 · 시설 폼과 같음)
|
||||
const { soil_add: _old, ...pipeNames } = type?.spec_names ?? {};
|
||||
const names = source.type_id === "pipe" ? pipeNames : (type?.spec_names ?? {});
|
||||
const state = readState(spec, source.options ?? {});
|
||||
// 세월교 옛 날개벽 설치 칸 → 구조(시설 폼과 같은 길 · 「없음」이면 램프 없음 · 65-3)
|
||||
const options = source.options ?? {};
|
||||
const state = readState(
|
||||
spec,
|
||||
source.type_id === "ford_bridge" ? withLegacyGuards(options) : options,
|
||||
);
|
||||
return spec.containers.flatMap((c): LinkLamp[] => {
|
||||
const title = containerTitle(c, names, groupNames[c.link]);
|
||||
if (c.required) return [{ title, item: links[c.link] ?? null, required: true }];
|
||||
const n = (c.id === "soil_add" ? pipeExtraCount(chainageM) : null) ?? state[c.id].length;
|
||||
if (c.required)
|
||||
return isNone(c, state[c.id][0])
|
||||
? []
|
||||
: [{ title, item: links[c.link] ?? null, required: true }];
|
||||
const pipeSoil = source.type_id === "pipe" && c.id === "soil_add";
|
||||
const n = (pipeSoil ? pipeExtraCount(chainageM) : null) ?? state[c.id].length;
|
||||
return Array.from({ length: n }, (_, i) => ({
|
||||
title: `${title} ${i + 1}`,
|
||||
item: links[linkOf(c, i + 1)] ?? null,
|
||||
|
||||
@@ -250,7 +250,9 @@ export function createStructuresSection(
|
||||
): void {
|
||||
const kind = facilityFormKind(currentType());
|
||||
facilityOptions.setChainage(selectedPipeChainage ?? tempPipeChainage); // 관 길이(자동 · 52-1)
|
||||
facilityOptions.setFacility(kind, options ?? newDefaults(), designFlow, currentType());
|
||||
// 값 없이 열면 새 시설 — 빈 월류 높이를 유량이 오면 필요 수심으로(65-4)
|
||||
const fresh = options === undefined;
|
||||
facilityOptions.setFacility(kind, options ?? newDefaults(), designFlow, currentType(), fresh);
|
||||
if (kind)
|
||||
paintStale(staleBox, currentType(), options, kind, groupBoxes.shapeKeys(currentType()));
|
||||
// 「구조물 옵션 보이기」에서 끈 옵션은 시설 폼에서도 가림(값은 옛 값 그대로 저장)
|
||||
|
||||
@@ -106,7 +106,7 @@ export function bindStructuresEvents(ctx: StructuresEventContext): void {
|
||||
ctx.setSummary(readSummaryItem(back?.summary_item));
|
||||
ctx.groupBoxes.set(readGroupItems(back?.group_items));
|
||||
ctx.groupBoxes.setShapes(back?.options ?? {});
|
||||
ctx.syncFacilityForm(back?.options);
|
||||
ctx.syncFacilityForm(back?.options, pipeItem?.design_flow_m3s ?? null); // 유량 = 월류 높이(65-4)
|
||||
ctx.liveCommit();
|
||||
// 반영이 막혀(측점 등) 기록이 그대로면 옛 기록으로 되돌림
|
||||
if (record && draft && ctx.structures[index] === draft) ctx.structures[index] = record;
|
||||
|
||||
@@ -65,8 +65,10 @@ const PIPE_INLET_ROWS = [
|
||||
].join(", ");
|
||||
const PIPE_OUTLET_ROWS = ".b06-structure-panel__struct--basin-size";
|
||||
|
||||
/** 받을 칸에 맞는 줄 — 배관 유입 · 유출 칸이 아니면 옛 줄 그대로. */
|
||||
function rowsFor(slot: HTMLElement): string {
|
||||
/** 받을 칸에 맞는 줄 — 배관 유입 · 유출 칸이 아니면 옛 줄 그대로 · 세월교 · 박스 유입 · 유출 칸은 안 받음
|
||||
* (높이 · 측벽 좌우 · 상하 · 바닥판 읽기는 칸에 안 둠 · 강조만 · 65-1). */
|
||||
function rowsFor(slot: HTMLElement): string | null {
|
||||
if (slot === sideSlots?.wingInlet || slot === sideSlots?.wingOutlet) return null;
|
||||
if (slot === sideSlots?.outlet) return PIPE_OUTLET_ROWS;
|
||||
return slot === sideSlots?.inlet ? PIPE_INLET_ROWS : SIDE_ROWS;
|
||||
}
|
||||
@@ -133,7 +135,8 @@ function clearSideSlots(): void {
|
||||
}
|
||||
|
||||
/** 행 DOM을 제자리 표식과 함께 옮긴다 — 숨은 행은 그대로 둔다. */
|
||||
function moveRows(panelRoot: HTMLElement, selector: string, target: HTMLElement): void {
|
||||
function moveRows(panelRoot: HTMLElement, selector: string | null, target: HTMLElement): void {
|
||||
if (!selector) return;
|
||||
panelRoot.querySelectorAll<HTMLElement>(selector).forEach((row) => {
|
||||
if (row.classList.contains("is-hidden")) return;
|
||||
// 제자리에 표식을 남기고 옮긴다 — 선택이 바뀌면 이 자리로 되돌린다.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
집계 항목 = 잇기 `group_items[{잇기}{n}]` · 복사본 `{구조물 id}__{잇기}{n}` · 옵션 = 그 컨테이너 값.
|
||||
열 바인딩이 읽는 칸이 갈래마다 달라(독립 기슭막이 `form` · `length_m` … · 관 `{유입|유출}_revet_*`)
|
||||
컨테이너 값을 그 칸들에 함께 얹음 — 어느 출처로 읽어도 그 컨테이너 값.
|
||||
짝: `A00_Common/design/design_structure_pipe.ts` · `_road.ts` 선택 컨테이너(머리 · 잇기 · 제목) —
|
||||
짝: `A00_Common/design/design_structure_guard.ts` · `_road.ts` 선택 컨테이너(머리 · 잇기 · 제목) —
|
||||
시험이 대조.
|
||||
기타(52-14) = 사용자가 적은 상세 종류 이름이 집계 건 이름(`그룹`) · 비면 「기타」.
|
||||
배관 흙막이 = 지형이 세운 유출 다단 수(`흙막이단`)까지만 — 넘친 컨테이너 값은 파일에만(54-11).
|
||||
@@ -18,11 +18,14 @@ from typing import Any
|
||||
from common_util.common_util_summary_item import GROUP_JOIN
|
||||
|
||||
#: 종류 → 선택 컨테이너 (머리, 잇기, 제목)
|
||||
_GUARD = (
|
||||
("soil_add", "gsoiladd", "흙막이"),
|
||||
("chute_add", "gchuteadd", "도수로/돌붙임"),
|
||||
)
|
||||
SPEC_CONTAINERS: dict[str, tuple[tuple[str, str, str], ...]] = {
|
||||
"pipe": (
|
||||
("soil_add", "gsoiladd", "흙막이"),
|
||||
("chute_add", "gchuteadd", "도수로/돌붙임"),
|
||||
),
|
||||
"pipe": _GUARD,
|
||||
"box_culvert": _GUARD, # 흙막이 양식 공통(65-2)
|
||||
"ford_bridge": _GUARD,
|
||||
"ford_pavement": (("ford_chute", "gfordpavechute", "도수로/돌붙임"),),
|
||||
"etc": (("etc_detail", "getcdetail", "상세 내용"),),
|
||||
}
|
||||
@@ -75,7 +78,12 @@ def container_rows(item: dict[str, Any], links: dict[str, Any]) -> list[dict[str
|
||||
if hit:
|
||||
found.setdefault(int(hit.group(1)), {})[hit.group(2)] = value
|
||||
for n in sorted(found):
|
||||
if prefix == "soil_add" and tiers is not None and n > tiers:
|
||||
if (
|
||||
prefix == "soil_add"
|
||||
and item["type_id"] == "pipe"
|
||||
and tiers is not None
|
||||
and n > tiers
|
||||
):
|
||||
continue
|
||||
gid = f"{link}{n}"
|
||||
rows.append(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"판": 17,
|
||||
"판": 18,
|
||||
"schema_version": 1,
|
||||
"comment": "B05 구조물 타입 레지스트리 정본. 근거·옵션 상세는 PLAN.md 구조물 리스트와 resources/knowledge/technical_info/01_임도 참조. 2026-08-17 선택지 재편: B군(종단배수)·F군(생태/녹화)·G군(노면공)은 enabled:false 보존 — B05 선택지에서 빠지고 B06 개별 횡단도 옵션으로 재사용한다. 바닥막이(bed_sill)·생태연못(eco_pond)은 임도 미사용 확정으로 삭제. 배관·BOX암거·물넘이·세월교는 pipe_points.json 정본 관리(managed_by) — structures.json에 저장하지 않으며, 배관 부속(유형/집수정/기슭막이/돌붙임)·날개벽 옵션 정의는 B06/B07 필수 승격의 선언 근거다. ★기본값 원칙(2026-08-16 크로스체크 2차): default를 두는 것은 ①법정이 등급과 무관하게 못박은 단일 수치 ②사용자가 개발 단계에서 확정한 값 ③도메인 수치가 아닌 표시용 문자열, 이 셋뿐이다. 재료·형식 같은 설계자 판단 선택지와 미확정 제원은 default 없이 required로 두어 사용자가 직접 고르게 한다 (pipe_diameter_mm 1000 = 별표2 원칙값+사용자 확정 — 화이트리스트 등재). BOX암거 본체 2.0×2.0·날개벽 유입/유출 있음·짧은쪽 높이 1m·길이 2m·각도 45°는 2026-08-17 사용자 확정값이다 — 본체 규격은 실무 관측 규격(2.0×2.0·3.0×3.0)을 폼에서 프리셋 select로 받고 필요 시 사용자 지정으로 자유 입력하되, 저장 키는 body_width_m·body_height_m 숫자 그대로다(프리셋 전용 키를 만들지 않는다). 이 값들은 B05 서브폼이 실제로 받으므로 phase를 detail로 두지 않는다. ★보호공 개편(2026-08-17 사용자 지시): 구 `*_pitching`(있음/없음) + `*_pitching_finish`(찰/메) 두 축을 `*_protection` 한 축(돌붙임(찰)·돌붙임(메)·도수로)으로 합쳤다 — 도수로·산비탈수로(B4)는 유출부에서 돌붙임 대신 쓰는 공종이라 별도 구조물이 아니라 보호공 선택지로 받는다. \"없음\"은 삭제(물이 흐르는 자리라 보호공 필수). 치수는 배타 — 돌붙임이면 `*_protection_area_m2`(10㎡), 도수로면 `*_protection_width_m`(1m, 지식DB 수치 근거 없음·사용자 확정, 실무 확인 후 개선). 구 저장분은 `common_util_drainage_pipes.parse_pipe_points`가 새 키로 옮긴다.",
|
||||
"구조물군": {
|
||||
@@ -486,6 +486,15 @@
|
||||
"quantity"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"key": "ford_slope_deg",
|
||||
"label": "경사각도",
|
||||
"input": "number",
|
||||
"unit": "°",
|
||||
"default": 3,
|
||||
"required": false,
|
||||
"default_basis": "물넘이 포장 경사각 기본 3° — 사용자 확정값(PLAN 65-5)"
|
||||
},
|
||||
{
|
||||
"key": "ford_width_m",
|
||||
"label": "월류 폭",
|
||||
@@ -575,8 +584,9 @@
|
||||
"label": "배관 수량",
|
||||
"input": "number",
|
||||
"unit": "련",
|
||||
"default": null,
|
||||
"required": false
|
||||
"default": 2,
|
||||
"required": false,
|
||||
"default_basis": "세월교 배관 수량 기본 2련 — 사용자 확정값(PLAN 65-4)"
|
||||
},
|
||||
{
|
||||
"key": "ford_width_m",
|
||||
@@ -3381,10 +3391,48 @@
|
||||
"inlet.structure=없음": "없음",
|
||||
"inlet.structure=날개벽": "날개벽",
|
||||
"inlet.structure=흙막이": "흙막이",
|
||||
"inlet.form": "형태",
|
||||
"inlet.form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"inlet.form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
"inlet.form=콘크리트": "콘크리트",
|
||||
"inlet.form=돌망태": "돌망태",
|
||||
"inlet.form=통나무·목재틀": "통나무/목재틀",
|
||||
"inlet.form=바자": "바자",
|
||||
"inlet.length": "길이 (m)",
|
||||
"inlet.height": "높이 (m)",
|
||||
"inlet.before": "기준측점 전 (m)",
|
||||
"inlet.after": "기준측점 후 (m)",
|
||||
"outlet.structure": "구조",
|
||||
"outlet.structure=없음": "없음",
|
||||
"outlet.structure=날개벽": "날개벽",
|
||||
"outlet.structure=흙막이": "흙막이"
|
||||
"outlet.structure=흙막이": "흙막이",
|
||||
"outlet.form": "형태",
|
||||
"outlet.form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"outlet.form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
"outlet.form=콘크리트": "콘크리트",
|
||||
"outlet.form=돌망태": "돌망태",
|
||||
"outlet.form=통나무·목재틀": "통나무/목재틀",
|
||||
"outlet.form=바자": "바자",
|
||||
"outlet.length": "길이 (m)",
|
||||
"outlet.height": "높이 (m)",
|
||||
"outlet.before": "기준측점 전 (m)",
|
||||
"outlet.after": "기준측점 후 (m)",
|
||||
"soil_add": "흙막이",
|
||||
"soil_add.form": "형태",
|
||||
"soil_add.form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"soil_add.form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
"soil_add.form=콘크리트": "콘크리트",
|
||||
"soil_add.form=돌망태": "돌망태",
|
||||
"soil_add.form=통나무·목재틀": "통나무/목재틀",
|
||||
"soil_add.form=바자": "바자",
|
||||
"soil_add.length": "길이 (m)",
|
||||
"soil_add.height": "높이 (m)",
|
||||
"soil_add.before": "기준측점 전 (m)",
|
||||
"soil_add.after": "기준측점 후 (m)",
|
||||
"chute_add": "도수로/돌붙임",
|
||||
"chute_add.structure": "구조",
|
||||
"chute_add.structure=도수로": "도수로",
|
||||
"chute_add.structure=돌붙임": "돌붙임"
|
||||
}
|
||||
},
|
||||
"ford_pavement": {
|
||||
@@ -3405,17 +3453,55 @@
|
||||
"ford.diameter=1000": "1000",
|
||||
"ford.diameter=1200": "1200",
|
||||
"ford.diameter=1500": "1500",
|
||||
"ford.count": "수량 (련)",
|
||||
"ford.width": "월류 폭 (m)",
|
||||
"ford.height": "월류 높이 (m)",
|
||||
"ford.count": "수량 (련)",
|
||||
"inlet.structure": "구조",
|
||||
"inlet.structure=없음": "없음",
|
||||
"inlet.structure=날개벽": "날개벽",
|
||||
"inlet.structure=흙막이": "흙막이",
|
||||
"inlet.form": "형태",
|
||||
"inlet.form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"inlet.form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
"inlet.form=콘크리트": "콘크리트",
|
||||
"inlet.form=돌망태": "돌망태",
|
||||
"inlet.form=통나무·목재틀": "통나무/목재틀",
|
||||
"inlet.form=바자": "바자",
|
||||
"inlet.length": "길이 (m)",
|
||||
"inlet.height": "높이 (m)",
|
||||
"inlet.before": "기준측점 전 (m)",
|
||||
"inlet.after": "기준측점 후 (m)",
|
||||
"outlet.structure": "구조",
|
||||
"outlet.structure=없음": "없음",
|
||||
"outlet.structure=날개벽": "날개벽",
|
||||
"outlet.structure=흙막이": "흙막이"
|
||||
"outlet.structure=흙막이": "흙막이",
|
||||
"outlet.form": "형태",
|
||||
"outlet.form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"outlet.form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
"outlet.form=콘크리트": "콘크리트",
|
||||
"outlet.form=돌망태": "돌망태",
|
||||
"outlet.form=통나무·목재틀": "통나무/목재틀",
|
||||
"outlet.form=바자": "바자",
|
||||
"outlet.length": "길이 (m)",
|
||||
"outlet.height": "높이 (m)",
|
||||
"outlet.before": "기준측점 전 (m)",
|
||||
"outlet.after": "기준측점 후 (m)",
|
||||
"soil_add": "흙막이",
|
||||
"soil_add.form": "형태",
|
||||
"soil_add.form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"soil_add.form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
"soil_add.form=콘크리트": "콘크리트",
|
||||
"soil_add.form=돌망태": "돌망태",
|
||||
"soil_add.form=통나무·목재틀": "통나무/목재틀",
|
||||
"soil_add.form=바자": "바자",
|
||||
"soil_add.length": "길이 (m)",
|
||||
"soil_add.height": "높이 (m)",
|
||||
"soil_add.before": "기준측점 전 (m)",
|
||||
"soil_add.after": "기준측점 후 (m)",
|
||||
"chute_add": "도수로/돌붙임",
|
||||
"chute_add.structure": "구조",
|
||||
"chute_add.structure=도수로": "도수로",
|
||||
"chute_add.structure=돌붙임": "돌붙임"
|
||||
}
|
||||
},
|
||||
"revetment": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/* 박스암거 명세 단위검증 헬퍼(PLAN 52-2) — `design_structure_spec.ts` · `design_structure_box.ts` 를
|
||||
* 그 자리에서 트랜스파일해 Node 로 돌림(꼴만 import). 결과 = JSON 한 벌.
|
||||
* 그 자리에서 트랜스파일해 Node 로 돌림(같은 폴더 명세 import 는 따라 옮김). 결과 = JSON 한 벌.
|
||||
* 인자 `names` 면 박스암거 이름 표(정의 `종류.box_culvert.이름`)만 냄.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
@@ -13,7 +13,9 @@ const load = (file) => {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, require);
|
||||
// 같은 폴더 명세(흙막이 양식 · 65-2)는 그 자리에서 옮김
|
||||
const near = (m) => (m.startsWith("./") ? load(`${m.slice(2)}.ts`) : require(m));
|
||||
new Function("exports", "module", "require", js)(box.exports, box, near);
|
||||
return box.exports;
|
||||
};
|
||||
const s = load("design_structure_spec.ts");
|
||||
|
||||
@@ -130,6 +130,7 @@ const STUBS = {
|
||||
// 관 보호공 [연동] · [경사] 줄(54-30) — 여기서는 자리만
|
||||
"./B05_Profile_UI_Drainage_Revet_Choice": {
|
||||
revetChoiceRow: () => ({ paint: () => new FakeNode("div") }),
|
||||
guardChoiceFields: () => () => [],
|
||||
},
|
||||
"../../B05_Profile/B05_Profile_UI_Structures_Summary_Row": {
|
||||
buildSummaryRow: () => {
|
||||
|
||||
@@ -75,13 +75,17 @@ def test_집계표_채움에_컨테이너_줄(world):
|
||||
|
||||
def test_서버_컨테이너_표는_배관_명세와_짝():
|
||||
with open(
|
||||
os.path.join(ROOT, "A00_Common/design/design_structure_pipe.ts"), encoding="utf-8"
|
||||
os.path.join(ROOT, "A00_Common/design/design_structure_guard.ts"), encoding="utf-8"
|
||||
) as f:
|
||||
text = f.read()
|
||||
found = re.findall(
|
||||
r'title: "([^"]+)",\s*required: false,\s*prefix: "(\w+)",\s*link: "(\w+)"', text
|
||||
)
|
||||
assert [(p, link, t) for t, p, link in found] == list(SPEC_CONTAINERS["pipe"])
|
||||
# 박스 · 세월교 = 같은 흙막이 양식(65-2)
|
||||
assert (
|
||||
SPEC_CONTAINERS["box_culvert"] == SPEC_CONTAINERS["ford_bridge"] == SPEC_CONTAINERS["pipe"]
|
||||
)
|
||||
|
||||
|
||||
def test_54_11_흙막이는_선_단_수까지만():
|
||||
|
||||
@@ -70,22 +70,23 @@ def test_컨테이너_경사각도_필수_도수로돌붙임_선택(out):
|
||||
|
||||
|
||||
def test_저장_값은_도_와_인스턴스_키(out):
|
||||
assert out["state"] == {"slope": [{"angle": "12.5"}], "chute": [{"structure": "돌붙임"}]}
|
||||
slope = {
|
||||
"angle": "12.5",
|
||||
"width": "6",
|
||||
"height": "0.3",
|
||||
} # 월류 폭 · 높이 = 옛 키 그대로 읽음(65-5)
|
||||
assert out["state"] == {"slope": [slope], "chute": [{"structure": "돌붙임"}]}
|
||||
assert out["written"] == {
|
||||
"ford_slope_deg": 15,
|
||||
"ford_width_m": 6,
|
||||
"ford_height_m": 0.3,
|
||||
"ford_chute_1_structure": "돌붙임",
|
||||
"ford_chute_2_structure": "도수로",
|
||||
}
|
||||
|
||||
|
||||
def test_옛_칸은_지난_정의_값으로_남음(out):
|
||||
assert out["retired"] == [
|
||||
"ford_width_m",
|
||||
"ford_height_m",
|
||||
"ford_slope_pct",
|
||||
"thickness_cm",
|
||||
"length_m",
|
||||
]
|
||||
assert out["retired"] == ["ford_slope_pct", "thickness_cm", "length_m"]
|
||||
# 폼이 모르는 옛 칸은 그대로 · 폼이 아는 머리(빠진 인스턴스)는 폼 값으로 갈아 끼움
|
||||
assert out["saved"] == {
|
||||
"ford_width_m": 6,
|
||||
@@ -95,7 +96,12 @@ def test_옛_칸은_지난_정의_값으로_남음(out):
|
||||
"length_m": 7,
|
||||
**out["written"],
|
||||
}
|
||||
assert out["formKeys"] == out["keys"]["keys"] == ["ford_slope_deg"]
|
||||
# 월류 폭 · 높이 = 세월교와 같은 키(65-5)
|
||||
assert (
|
||||
out["formKeys"]
|
||||
== out["keys"]["keys"]
|
||||
== ["ford_slope_deg", "ford_width_m", "ford_height_m"]
|
||||
)
|
||||
assert out["heads"] == out["keys"]["prefixes"] == ["ford_chute_"]
|
||||
|
||||
|
||||
|
||||
@@ -39,17 +39,20 @@ def test_컨테이너_셋_모두_필수(out):
|
||||
["ford", "세월교", True],
|
||||
["inlet", "유입 구조", True],
|
||||
["outlet", "유출 구조", True],
|
||||
["soil_add", "흙막이", False],
|
||||
["chute_add", "도수로/돌붙임", False],
|
||||
]
|
||||
# 수량(련) = 관경 바로 뒤(65-4)
|
||||
assert out["items"][0] == [
|
||||
["diameter", "select", "pipe_diameter_mm"],
|
||||
["count", "number", "pipe_count"],
|
||||
["width", "number", "ford_width_m"],
|
||||
["height", "number", "ford_height_m"],
|
||||
["count", "number", "pipe_count"],
|
||||
]
|
||||
assert out["choices"] == [
|
||||
[["800", "1000", "1200", "1500"]],
|
||||
[["없음", "날개벽", "흙막이"]],
|
||||
[["없음", "날개벽", "흙막이"]],
|
||||
assert [c[0] for c in out["choices"][:3]] == [
|
||||
["800", "1000", "1200", "1500"],
|
||||
["없음", "날개벽", "흙막이"],
|
||||
["없음", "날개벽", "흙막이"],
|
||||
]
|
||||
|
||||
|
||||
@@ -73,7 +76,7 @@ def test_흙막이_날개벽_고르면_옛_설치_칸도_맞춰_씀(out):
|
||||
|
||||
|
||||
def test_잇기_키_컨테이너마다(out):
|
||||
assert out["links"] == ["gford", "gfordin", "gfordout"]
|
||||
assert out["links"] == ["gford", "gfordin", "gfordout", "gsoiladd1", "gchuteadd1"]
|
||||
|
||||
|
||||
def _manning_depth(q, b):
|
||||
|
||||
@@ -36,8 +36,10 @@ def test_컨테이너_셋_모두_필수(out):
|
||||
["body", "본체", True],
|
||||
["inlet", "유입 구조", True],
|
||||
["outlet", "유출 구조", True],
|
||||
["soil_add", "흙막이", False],
|
||||
["chute_add", "도수로/돌붙임", False],
|
||||
]
|
||||
assert out["links"] == ["gboxbody", "gboxin", "gboxout"]
|
||||
assert out["links"] == ["gboxbody", "gboxin", "gboxout", "gsoiladd1", "gchuteadd1"]
|
||||
|
||||
|
||||
def test_선택지_표_02_그대로(out):
|
||||
|
||||
@@ -56,9 +56,12 @@ def test_상자_칸을_고치면_그_부분을_고름() -> None:
|
||||
|
||||
|
||||
def test_흙막이_상자는_사면안정_흙막이와_같은_칸() -> None:
|
||||
pipe = _src("A00_Common/design/design_structure_pipe.ts")
|
||||
soil = pipe[pipe.index('id: "soil_add"') :]
|
||||
assert soil[: soil.index("},")].count("items: guardItems(),") == 1
|
||||
# 흙막이 양식 한 벌(65-2) — 배관 · 박스 · 세월교가 같은 상자를 씀
|
||||
guard = _src("A00_Common/design/design_structure_guard.ts")
|
||||
soil = guard[guard.index('id: "soil_add"') :]
|
||||
assert soil[: soil.index("};")].count("items: guardItems(),") == 1
|
||||
for spec in ("pipe", "box", "ford"):
|
||||
assert "SOIL_ADD" in _src(f"A00_Common/design/design_structure_{spec}.ts")
|
||||
|
||||
|
||||
def test_관_보호공_연동_경사_단추() -> None:
|
||||
|
||||
Reference in New Issue
Block a user