- 까닭: 폭 → 높이 계산은 설계유량(담당 유역)이 있어야 돎 — 유량 없는 지점은 아무 일도 안 일어나고 안내도 없었음 · 높이를 비우면 안 채움 - overflowSync(design_structure_ford_calc) — 폭 바꿈 = 필요 수심 · 높이 비움 · 그 아래 = 필요 수심 · 상자 끝 안내 「필요 수심 n 이상」 · 「설계유량 없음 — 수기 입력」 · 유량이 오면 안내 다시 그림 - 세월교 · 물넘이 폼이 같은 것을 씀 · Facility.ts 줄 줄임(697 → 686) - 시험 test_287 화면 길(폼 onChange 훅) — 유량 있음 · 없음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
117 lines
5.9 KiB
TypeScript
117 lines
5.9 KiB
TypeScript
/* =============================================================================
|
||
* design_structure_ford_calc.ts
|
||
* 세월교 명세 계산 — 월류 폭 ↔ 월류 높이 연동(PLAN 52-4).
|
||
*
|
||
* 높이 = 담당 유역 설계유량을 그 월류 폭으로 넘기는 필요 수심(Manning 개수로 · `fordSection` 그대로).
|
||
* 근거: Manning 공식 `resources/knowledge/technical_info/01_임도/03_계산정보/통수단면.md:23~33`
|
||
* (KDS 44 40 00 2.7.1~2.7.2) · 조도계수 0.017 = 같은 파일 :59(콘크리트 수로 보통 · 울진 물넘이) ·
|
||
* 경사 10 % = 실무 수리계산서 역산값(`config_frontend.FORD_SLOPE` · 지식DB 수치 없음).
|
||
* 폭을 바꾸면 높이 칸을 이 값으로 채움 · 사용자는 올려 적을 수 있고 이 값 아래로는 안 받음.
|
||
* 높이는 저장되는 입력값 — 서버는 받은 높이로 노면을 내림(`B06_Section_Engine_Culvert._ford_set`) · 재계산 안 함.
|
||
* 설계유량이 없으면 null = 손 입력.
|
||
* 짝: 자동 배치 새 세월교 빈 높이 = 서버 `B04_PreProcess_Router_Basins.ford_height_m`(같은 풀이 · 65-4) ·
|
||
* 거울 시험 test_65_ford_defaults.
|
||
* 짝: B08 수리집수면적표 세월교 · 물넘이 월류 높이 = 서버 `B08_Quantity_Engine_Hydro.ford_depth`
|
||
* (같은 풀이 · n · S 를 받음) · 거울 시험 test_73_11_b08_hydro_box_ford.
|
||
* ========================================================================== */
|
||
|
||
import { FORD_PAVEMENT_DEFAULT_WIDTH_M } from "@config/config_frontend";
|
||
import { fordSection } from "../../B05_Profile/B05_Profile_UI_Drainage_Facility_Fields";
|
||
import type { SpecForm } from "./design_structure_form";
|
||
import { CHUTE_ITEMS } from "./design_structure_guard";
|
||
import type { SpecContainer, SpecItem } from "./design_structure_spec";
|
||
|
||
/** 월류 폭(m) → 필요 월류 높이(m) · 0.01 m 올림. 유량 · 폭이 없으면 null. */
|
||
export function fordHeightM(designFlowM3s: number | null, widthM: number): number | null {
|
||
const section = fordSection(designFlowM3s, widthM);
|
||
return section ? Math.ceil(section.depthM * 100 - 1e-9) / 100 : null;
|
||
}
|
||
|
||
/**
|
||
* 월류 폭 ↔ 높이 연동 — 세월교 · 물넘이 포장 한 벌(52-4 · 65-4 · 65-5 · 287-3).
|
||
* · 폭을 바꾸면 높이 = 필요 수심 · 높이를 비우거나 그 아래로 넣으면 필요 수심으로.
|
||
* · 새 시설(`arm(true)`) 빈 높이 = 유량이 오면 필요 수심(사용자가 넣은 것처럼 — [추가] · [수정]이 읽음).
|
||
* · 상자 끝 안내 — 「필요 수심 n 이상」 · 「설계유량 없음 — 수기 입력」(유량이 없으면 손 입력).
|
||
* `box` = 폭 · 높이 칸이 든 상자 id(세월교 `ford` · 물넘이 `slope`) · `flow` = 담당 유역 설계유량.
|
||
*/
|
||
export function overflowSync(
|
||
form: () => Pick<SpecForm, "state" | "setValue" | "refresh">,
|
||
box: string,
|
||
flow: () => number | null,
|
||
emit: () => void,
|
||
) {
|
||
let pending = false;
|
||
const values = () => form().state()[box]?.[0] ?? {};
|
||
const width = () => Number(values().width ?? "");
|
||
const need = (): number | null => fordHeightM(flow(), width());
|
||
return {
|
||
arm: (fresh: boolean): void => {
|
||
pending = fresh;
|
||
},
|
||
onChange: (c: SpecContainer, item: SpecItem): void => {
|
||
if (c.id === box && item.id === "height") pending = 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: (): void => {
|
||
const want = pending && !values().height ? need() : null;
|
||
if (want === null) return form().refresh();
|
||
form().setValue(box, "height", want.toFixed(2));
|
||
pending = false;
|
||
},
|
||
/** 상자 끝 안내 글(폼 `fields` 훅). */
|
||
fields: (c: SpecContainer): HTMLElement[] => {
|
||
if (c.id !== box) return [];
|
||
const hint = document.createElement("p");
|
||
hint.className = "b05-structure__flow-hint";
|
||
hint.textContent = fordHeightHint(flow(), width());
|
||
return [hint];
|
||
},
|
||
};
|
||
}
|
||
|
||
/** 높이 빈 칸 안내. */
|
||
export function fordHeightHint(designFlowM3s: number | null, widthM: number): string {
|
||
const need = fordHeightM(designFlowM3s, widthM);
|
||
if (need !== null) return `필요 수심 ${need.toFixed(2)} 이상`;
|
||
return designFlowM3s ? "월류 폭을 넣으면 셈" : "설계유량 없음 — 수기 입력";
|
||
}
|
||
|
||
/** 도수로 · 돌붙임 면적(㎡ · 287-1) = 폭 × 길이 · 0.01 반올림. */
|
||
export function chuteAreaM2(widthM: number, lengthM: number): number {
|
||
return Math.round(widthM * lengthM * 100) / 100;
|
||
}
|
||
|
||
/**
|
||
* 물넘이 포장 도수로/돌붙임 길이 ↔ 면적(287-1) — 길이를 넣으면 면적 = 폭 × 길이(폭 = 띠가 도로 방향으로
|
||
* 걸치는 월류 폭 · 비면 물넘이 기본 폭) · 길이를 비우면 면적 = 칸 기본값 · 월류 폭을 바꾸면 길이가 있을 때만
|
||
* 다시 셈. 길이는 그림(사면 따라 그 길이) · 면적은 수량(B08 돌붙임)이 씀.
|
||
*/
|
||
export function syncChuteArea(
|
||
form: Pick<SpecForm, "state" | "setValue">,
|
||
c: SpecContainer,
|
||
item: SpecItem,
|
||
): void {
|
||
const byLength = c.id === "chute" && item.id === "length_m";
|
||
if (!byLength && !(c.id === "slope" && item.id === "width")) return;
|
||
const chute = form.state().chute?.[0];
|
||
if (!chute) return;
|
||
const length = Number(chute.length_m ?? "");
|
||
if (!(chute.length_m ?? "").trim() || !(length > 0)) {
|
||
if (byLength)
|
||
form.setValue("chute", "area", CHUTE_ITEMS.find((i) => i.id === "area")?.default ?? "");
|
||
return;
|
||
}
|
||
const width = Number(form.state().slope?.[0]?.width ?? "");
|
||
form.setValue(
|
||
"chute",
|
||
"area",
|
||
String(chuteAreaM2(width > 0 ? width : FORD_PAVEMENT_DEFAULT_WIDTH_M, length)),
|
||
);
|
||
}
|