- 새 파일 = B08_Quantity_Summary_Table · Material_Tables · Earthwork_Tables · Hydro_Table · Milestone_Table - 화면 UI 파일 = 불러오기 · 그리기만 · 옛 B08 시험 = 옮긴 글 자리 따라 읽는 파일 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
189 lines
8.1 KiB
TypeScript
189 lines
8.1 KiB
TypeScript
/* =============================================================================
|
|
* B08_Quantity_Hydro_Table.ts
|
|
* 수리집수면적표(PLAN 62-2 · 69-4) 자료 읽기 · 측점 입력 겹치기 · 인쇄 표 — 화면 없음(PLAN 302-3).
|
|
* 화면(`B08_Quantity_UI_Hydro`) · 인쇄 미리보기(`B09_Draft_Print_Quantity`)가 같은 입력 · 같은 서버 계산을 씀.
|
|
*
|
|
* 값 차례 = 기본 < 장 기본 < 연결 < 저장 < 초안(캐시 `b08-hydro`) · 연결값과 같은 칸은 null 로 저장(연동 유지)
|
|
* 계산 = 서버 단독(POST …/quantity/hydro/calc)
|
|
* `hydroTable` = 계산서 한 장(화면 두 카드 칸 차례 그대로)을 구분 · 항목 · 값 표로 — 인쇄 전용
|
|
* ========================================================================== */
|
|
|
|
import type { DataTableOptions } from "@ui/ui_template_data_table";
|
|
import { readState } from "../A00_Common/b_page_state";
|
|
import { request } from "./B08_Quantity_Material_Tables";
|
|
import { stationInterval } from "./B08_Quantity_Summary_Table";
|
|
import type {
|
|
HydroInputs,
|
|
HydroLink,
|
|
HydroOptions,
|
|
HydroResult,
|
|
} from "./B08_Quantity_UI_Hydro_Sheet";
|
|
|
|
export interface HydroStation {
|
|
chainage_m: number;
|
|
facility: string;
|
|
/** 박스암거 · 세월교 · 물넘이포장(관이면 "") */
|
|
kind: string;
|
|
/** 계산서 장 — 시설이 정함(서버 station_rows) */
|
|
sheet: HydroInputs["sheet"];
|
|
/** B05 연결값(값이 있는 칸만) · 비교용 B05 Qd · T — 서버 station_rows */
|
|
linked: HydroLink["values"] | null;
|
|
b05: HydroLink["b05"];
|
|
}
|
|
|
|
export type Drafts = Record<string, Partial<HydroInputs>>;
|
|
export type HydroPayload = { stations: HydroStation[]; saved: Drafts } & HydroOptions;
|
|
|
|
export const keyOf = (station: HydroStation): string => station.chainage_m.toFixed(3);
|
|
|
|
const linkedOf = (station: HydroStation): Record<string, unknown> => station.linked ?? {};
|
|
|
|
/** 저장본 · 초안 한 겹 — 연결값이 있는 칸의 null 은 건너뜀(연결값을 덮지 않게) */
|
|
const layer = (station: HydroStation, values: Partial<HydroInputs> = {}) =>
|
|
Object.fromEntries(
|
|
Object.entries(values).filter(([k, v]) => v != null || !(k in linkedOf(station))),
|
|
);
|
|
|
|
/** 측점 입력 — 기본 < 장 기본 < 연결 < 저장 < 초안 */
|
|
export const hydroInputs = (
|
|
sheet: HydroPayload,
|
|
station: HydroStation,
|
|
drafts: Drafts,
|
|
): HydroInputs =>
|
|
({
|
|
...sheet.defaults,
|
|
...(station.sheet === "pipe" ? {} : sheet.sheet_defaults[station.sheet]),
|
|
...station.linked,
|
|
// 초안은 측점 입력 통째 — 있으면 저장본 대신(초안의 null = 연결값으로 되돌림)
|
|
...layer(station, drafts[keyOf(station)] ?? sheet.saved[keyOf(station)]),
|
|
sheet: station.sheet,
|
|
}) as HydroInputs;
|
|
|
|
/** 연결값과 같은 칸은 null — 저장 · 초안이 연동을 유지(B05 값이 바뀌면 따라옴) · 다르면 손 값 */
|
|
export const unlinkedInputs = (station: HydroStation, inputs: HydroInputs): HydroInputs =>
|
|
Object.fromEntries(
|
|
Object.entries(inputs).map(([k, v]) => [k, linkedOf(station)[k] === v ? null : v]),
|
|
) as unknown as HydroInputs;
|
|
|
|
export async function loadHydro(
|
|
projectId: string,
|
|
): Promise<{ sheet: HydroPayload; interval: number; drafts: Drafts }> {
|
|
const [sheet, interval] = await Promise.all([
|
|
request<HydroPayload>(`/projects/${projectId}/quantity/hydro`),
|
|
stationInterval(projectId).catch(() => 20),
|
|
]);
|
|
return { sheet, interval, drafts: readState<Drafts>("b08-hydro", projectId) ?? {} };
|
|
}
|
|
|
|
export const hydroCalc = (projectId: string, inputs: HydroInputs): Promise<HydroResult> =>
|
|
request<HydroResult>(`/projects/${projectId}/quantity/hydro/calc`, {
|
|
method: "POST",
|
|
body: JSON.stringify(inputs),
|
|
});
|
|
|
|
const DASH = "-";
|
|
const fmt = (value: number | null | undefined, digits: number, unit = ""): string =>
|
|
value == null || !Number.isFinite(value) ? DASH : `${value.toFixed(digits)}${unit}`;
|
|
const raw = (value: number | string | null | undefined, unit = ""): string =>
|
|
value == null || value === "" ? DASH : `${value}${unit}`;
|
|
|
|
/** 계산서 한 장 → 표(구분 · 항목 · 값) — 칸 이름 · 자리수 = 화면 계산서(`…_UI_Hydro_Sheet`) */
|
|
export function hydroTable(
|
|
stationText: string,
|
|
kind: string,
|
|
inputs: HydroInputs,
|
|
r: HydroResult | null,
|
|
options: HydroOptions,
|
|
): DataTableOptions {
|
|
const site = options.site;
|
|
const flood = "1. 홍수량 계산";
|
|
const sheetDefaults = inputs.sheet === "pipe" ? {} : options.sheet_defaults[inputs.sheet];
|
|
const n =
|
|
inputs.n ?? (inputs.sheet === "pipe" ? options.pipe_kinds[inputs.pipe_kind] : sheetDefaults.n);
|
|
const slope = [
|
|
"하상경사 S = 1 /",
|
|
`${raw(inputs.slope_denominator)} (${fmt(r?.slope_bed == null ? null : r.slope_bed * 100, 2, " %")})`,
|
|
];
|
|
const section: Array<[string, string]> =
|
|
inputs.sheet === "box"
|
|
? [
|
|
["내공 폭 B (m)", raw(inputs.box_width_m)],
|
|
["내공 높이 H (m)", raw(inputs.box_height_m)],
|
|
["수심비 h / H", raw(inputs.depth_ratio)],
|
|
["수심 h (m)", fmt(r?.depth, 2)],
|
|
["단면적", fmt(r?.section_area, 3, " ㎡")],
|
|
["윤변", fmt(r?.perimeter, 3, " m")],
|
|
["경심", fmt(r?.radius, 3)],
|
|
slope as [string, string],
|
|
["조도계수 n", raw(n)],
|
|
]
|
|
: inputs.sheet === "ford"
|
|
? [
|
|
["월류 폭 B (m)", raw(inputs.overflow_width_m)],
|
|
slope as [string, string],
|
|
["조도계수 n", raw(n)],
|
|
["월류 높이 h (m)", fmt(r?.depth, 2)],
|
|
["단면적", fmt(r?.section_area, 3, " ㎡")],
|
|
["윤변", fmt(r?.perimeter, 3, " m")],
|
|
["경심", fmt(r?.radius, 3)],
|
|
]
|
|
: [
|
|
["관 종류", inputs.pipe_kind],
|
|
["단면 가정", options.sections[inputs.section] ?? inputs.section],
|
|
["단면 D (mm)", raw(inputs.diameter_mm)],
|
|
["단면적", fmt(r?.section_area, 3, " ㎡")],
|
|
["윤변", fmt(r?.perimeter, 3, " m")],
|
|
["경심", fmt(r?.radius, 3)],
|
|
slope as [string, string],
|
|
["조도계수 n", raw(n)],
|
|
];
|
|
const judged = r != null && r.ok != null;
|
|
const check =
|
|
inputs.sheet === "box"
|
|
? `2. 단면 검토 — ${kind}`
|
|
: inputs.sheet === "ford"
|
|
? `2. 월류 높이 — ${kind}`
|
|
: "2. 단면 검토";
|
|
const rows: Array<[string, string, string]> = [
|
|
[flood, `Qd : 설계 홍수량 (㎥/s) = Q × ${inputs.factor.toFixed(1)}`, fmt(r?.design_flow, 2)],
|
|
[flood, "Q : 홍수량 (㎥/s)", fmt(r?.flow, 2)],
|
|
[flood, "f : 유출계수", raw(inputs.runoff)],
|
|
[flood, "rt : 평균 강우강도 (mm/hr)", fmt(r?.intensity, 2)],
|
|
[flood, "A : 유역면적 (㎢)", `${raw(inputs.area_km2)} (${fmt(r?.area_ha, 1, " ha")})`],
|
|
[flood, "T : 홍수 도달시간 (hr)", fmt(r?.tc_hr, 3)],
|
|
[flood, "L : 산정에서 하천시점까지 거리 (m)", raw(inputs.length_m)],
|
|
[flood, "H : 산정에서 하천시점까지 표고차 (m)", raw(inputs.height_m)],
|
|
[flood, "s : 지표면 경사 (H / L)", fmt(r?.slope_ground, 3)],
|
|
[flood, "최소 도달시간 (분)", raw(inputs.tc_min_minutes)],
|
|
[flood, "지역", site.region.name || raw(inputs.region)],
|
|
[flood, "재현기간", `${inputs.return_period} 년`],
|
|
[
|
|
flood,
|
|
"R24 : 확률강우량 (mm/day)",
|
|
fmt(r?.r24_mm ?? site.r24[String(inputs.return_period)], 1),
|
|
],
|
|
[check, "총 홍수량 Qd (㎥/s)", fmt(r?.design_flow, 2)],
|
|
...section.map(([label, value]) => [check, label, value] as [string, string, string]),
|
|
[check, "유속 (m/s)", fmt(r?.velocity, 3)],
|
|
[check, "유출량 (㎥/s)", fmt(r?.capacity, 3)],
|
|
[
|
|
check,
|
|
"판정",
|
|
judged
|
|
? `${fmt(r.capacity, 3)} ㎥/s ${r.ok ? "≥" : "<"} Qd ${fmt(r.design_flow, 2)} ㎥/s — ${r.ok ? "O K" : "N G"}`
|
|
: DASH,
|
|
],
|
|
[check, "유출율 (%)", fmt(r?.ratio_pct, 0)],
|
|
];
|
|
return {
|
|
columns: [
|
|
{ key: "group", label: "구분", width: 120 },
|
|
{ key: "item", label: "항목", width: 260 },
|
|
{ key: "value", label: "값", width: 240 },
|
|
],
|
|
rows: rows.map(([group, item, value]) => ({ group, item, value })),
|
|
mergeSame: ["group"],
|
|
caption: `홍수량 계산서 ${stationText}${kind ? ` ${kind}` : ""}`,
|
|
};
|
|
}
|