/* ============================================================================= * A00_Common/design/logic/design_calc_node_cross.ts * 계산 Node 입구(`design_calc_node.ts`)의 횡단 몫(PLAN 511-B B-4) — 서버 GET · [저장] 이 부르는 횡단 셈을 * 브라우저와 같은 TS 로(파이썬 짝을 걷는 자리). * * facility.attach 측점 그림 자료(관 세트 · 벽 · 포장 · 맹암거 · 빈칸 경고 · `attachShapes` · F4 F5 F6 C6) * 입력 attach { sections: [{ chainage_m · center_x · center_y }] · pipes · structures · types } * 출력 { shapes: 측점마다 그림 자료 칸만 · misses: { outside · far } } * cross.enforce [저장] 횡단 한 바퀴(`enforceSavedCross` · 설계선 → 미교차 넓힘 → 포장 → 세월교 하강 · G2 C3) * 입력 cross_enforce = `SaveCrossInput` + grid(DTM 격자 이진 경로 · 넓힘 때만 읽음) * 출력 측점마다 { design · samples?(넓힘) · extended? } | null * cross.refresh 계산 자리 cross = server 의 저장 노선 횡단 다시 셈(`refreshDesigns` + 구조물 폐회로 면적) * 입력 cross_refresh { detail · inputs: `RefreshInputs` · grid(서버 창구가 채움) } * 출력 { design_profiles · sections: [{ design · samples?(넓힘) }] · rows: 구조물 면적 줄 } * — 등록부 밖(`PAGE_IDS` · 파이썬 `calc_runner.PAGE_IDS`) · 화면 cross 단계 길 * 구조물 표 = 서버 정의(`definition` · M02 반영) — 받으면 갈아 끼움(파이썬 `catalog_row` 와 같은 표). * ========================================================================== */ import { attachShapes, SHAPE_KEYS, type AttachType } from "@util/common_util_structure_attach"; import { useCatalogDefinition, type CatalogDefinition } from "@util/common_util_structure_catalog"; import type { PlanPipe, PlanStructure } from "@util/common_util_structure_station"; import { enforceSavedCross, type SaveCrossInput, } from "../../../B06_Section/logic/B06_Section_Cross_Save"; import { refreshDesigns, type RefreshInputs, } from "../../../B06_Section/logic/B06_Section_Cross_Refresh_Calc"; import type { SectionDetailResponse } from "../../../B06_Section/B06_Section_Api_Fetch"; import { structureAreaRows } from "../../../B06_Section/logic/B06_Section_Structure_Layouts"; import { gridAt } from "./design_calc_node_stages"; export interface CrossInputs { definition?: CatalogDefinition | null; attach?: { sections: Array<{ chainage_m: number; center_x?: number | null; center_y?: number | null }>; pipes: PlanPipe[]; structures: PlanStructure[]; types: AttachType[]; }; cross_enforce?: SaveCrossInput & { grid?: string | null }; cross_refresh?: { detail: SectionDetailResponse; inputs: RefreshInputs; grid?: string | null }; } /** 등록부 밖 화면 길 id(`available` 에 안 넣음 · 파이썬 `PAGE_IDS` 와 같음). */ export const PAGE_IDS = ["cross.refresh"]; let defined = ""; /** 서버 정의로 구조물 표를 갈아 끼움 — 같은 정의면 그대로(늘 떠 있는 Node · 요청마다 입구가 부름). */ export function useDefinition(definition: CatalogDefinition | null | undefined): void { if (!definition) return; const key = JSON.stringify(definition); if (key === defined) return; useCatalogDefinition(definition); defined = key; } const SHAPE_FIELDS = [...SHAPE_KEYS, "blank"]; export function crossRunners(inputs: CrossInputs): Record unknown> { return { "facility.attach": () => { const attach = inputs.attach; if (!attach) throw new Error("형상 얹기 입력(attach) 없음"); const sections = attach.sections.map((section) => ({ ...section })) as Array< Record >; const misses = attachShapes(sections, attach.pipes, attach.structures, attach.types); const shapes = sections.map((section) => Object.fromEntries( SHAPE_FIELDS.filter((key) => key in section).map((k) => [k, section[k]]), ), ); return { shapes, misses }; }, "cross.enforce": () => { const input = inputs.cross_enforce; if (!input) throw new Error("횡단 한 바퀴 입력(cross_enforce) 없음"); return enforceSavedCross(input, () => (input.grid ? gridAt(input.grid) : null)); }, "cross.refresh": () => { const job = inputs.cross_refresh; if (!job) throw new Error("횡단 다시 셈 입력(cross_refresh) 없음"); const { detail } = job; const before = detail.cross_sections.map((section) => section.samples); refreshDesigns(detail, job.inputs, job.grid ? gridAt(job.grid) : null); return { design_profiles: detail.longitudinal.design_profiles, sections: detail.cross_sections.map((section, index) => ({ design: section.design, ...(section.samples !== before[index] ? { samples: section.samples } : {}), })), rows: structureAreaRows(detail.cross_sections), }; }, }; }