Files
Aislo/A00_Common/design/design_structure_ford_calc.ts
T
eomsangdonandClaude Opus 5.5 6c86612f65 fix(b05,b06): 월류 폭 → 높이 — 세월교 · 물넘이 한 벌 overflowSync · 안내 글 · 높이 비우면 다시 채움 (287-3)
- 까닭: 폭 → 높이 계산은 설계유량(담당 유역)이 있어야 돎 — 유량 없는 지점은 아무 일도 안 일어나고 안내도 없었음 · 높이를 비우면 안 채움
- 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
2026-10-12 21:24:35 +09:00

117 lines
5.9 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_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)),
);
}