Files
Aislo/A00_Common/design/design_structure_ford_calc.ts
T
eomsangdonandClaude Opus 5.5 209383ed17 feat(b05,b06,b07): 도수로/돌붙임 띠 = 낮은 쪽 끝단에서 · 원지반 닿으면 끝 · 길이 = 그 길이 · 모든 시설 같은 규칙과 길이 칸 (294-4)
- 공용 chuteRun(Wall_Hatch) — 길이 비움 = 원지반 닿으면 끝 · 넣음 = 사면 따라 그 길이 · 사면보다 길면 원지반 따라 이어 그 길이 · 끝단이 이미 원지반이면 안 그림
- 물넘이 — 띠 시작 = 늘인 노면 끝(294-3) · 아니면 유출 쪽 노견 끝 · 원지반 닿으면 끝(양측 절토 = 띠 없음)
- 배관 · 박스 · 세월교 — 띠 계산이 원지반(layout.ground)을 받아 같은 규칙 · 3D 띠도 같음 · 코리도 해시에 길이
- 명세 CHUTE_ITEMS 맨 아래 「길이 (m)」(chute_add_{n}_length_m · 그림 전용) · 물넘이는 공용 칸 그대로
- 면적 자동 = 물넘이 · 세월교만 월류 폭 × 길이(배관 · 박스 = 폭 정의 없어 손 입력 그대로 · 브레인 정함)
- 길이는 집계 열 · B08 수량(면적만)에 안 실림 · 마스터 정의 이름 표는 안 건드림(이름 표 대조 시험에서 이 칸만 밖)
- 시험 test_294_3 · 286_1 · 52 · 65 · 152_3 기대값

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
2026-10-12 21:58:16 +09:00

120 lines
6.1 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 · 294-4) — 길이를 넣으면 면적 = 폭 × 길이(폭 = 띠가
* 도로 방향으로 걸치는 월류 폭 · 비면 물넘이 기본 폭 · 배관 · 박스는 폭 정의 없어 자동 안 함) · 길이를 비우면 면적 = 칸 기본값 · 월류 폭을 바꾸면 길이가 있을 때만
* 다시 셈. 길이는 그림(사면 따라 그 길이) · 면적은 수량(B08 돌붙임)이 씀.
*/
export function syncChuteArea(
form: Pick<SpecForm, "state" | "setValue">,
c: SpecContainer,
item: SpecItem,
/** 도수로 상자 · 월류 폭 상자 — 물넘이 `chute` · `slope` · 세월교 `chute_add` · `ford`(294-4). */
chuteBox = "chute",
widthBox = "slope",
): void {
const byLength = c.id === chuteBox && item.id === "length_m";
if (!byLength && !(c.id === widthBox && item.id === "width")) return;
const chute = form.state()[chuteBox]?.[0];
if (!chute) return;
const length = Number(chute.length_m ?? "");
if (!(chute.length_m ?? "").trim() || !(length > 0)) {
if (byLength)
form.setValue(chuteBox, "area", CHUTE_ITEMS.find((i) => i.id === "area")?.default ?? "");
return;
}
const width = Number(form.state()[widthBox]?.[0]?.width ?? "");
form.setValue(
chuteBox,
"area",
String(chuteAreaM2(width > 0 ? width : FORD_PAVEMENT_DEFAULT_WIDTH_M, length)),
);
}