/* ============================================================================= * 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>; export type HydroPayload = { stations: HydroStation[]; saved: Drafts } & HydroOptions; export const keyOf = (station: HydroStation): string => station.chainage_m.toFixed(3); const linkedOf = (station: HydroStation): Record => station.linked ?? {}; /** 저장본 · 초안 한 겹 — 연결값이 있는 칸의 null 은 건너뜀(연결값을 덮지 않게) */ const layer = (station: HydroStation, values: Partial = {}) => 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(`/projects/${projectId}/quantity/hydro`), stationInterval(projectId).catch(() => 20), ]); return { sheet, interval, drafts: readState("b08-hydro", projectId) ?? {} }; } export const hydroCalc = (projectId: string, inputs: HydroInputs): Promise => request(`/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}` : ""}`, }; }