/* ============================================================================= * B06_Section_Server_Calc_Node.ts * 브라우저에서만 돌던 횡단 계산을 **서버가 한 번 돌리는** 진입점 — 구조물 폐회로 * 절·성토 면적 + 그것을 쌓아 만든 유토곡선. * * 왜 있나(2026-09-06, CLAUDE.md 5장 「계산 자리」) — 이 두 값은 지금까지 브라우저에서만 * 나왔다. 사용자가 B06 을 한 번도 안 열면 값이 없고, 저장·확정 뒤 서버가 다시 계산하면 * 구조물을 모르는 표준값으로 되돌아갔다. 코리도(`B05_Profile_Corridor_Node.ts`)와 같은 * 방식으로 **브라우저가 쓰는 코드를 서버가 그대로 실행**한다 — 계산을 두 벌로 짜지 않는다. * * 순서가 중요하다: 면적 보정을 **먼저** 얹고 그 위에서 유토곡선을 쌓는다. 화면도 같은 * 순서다(카드를 그리며 면적을 고친 뒤 유토곡선을 낸다). * * 실행: node <번들> <입력.json> <출력.json> * 입력 { detail: 종횡단 상세(API와 같은 꼴), context: { earthwork_conversion, * natural_spoil_min_ground_slope, haul_equipment_limits } } * 출력 { areas: [{ chainage_m, cut_area_m2, … }], mass_haul: {…} | null } * — areas 는 **구조물 트림이 있는 측점만**. 나머지는 표준 계산값이 이미 맞다. * 끝 코드: 0 성공 / 2 인자 오류 * ========================================================================== */ import { readFileSync, writeFileSync } from "node:fs"; import { computeMassHaul, massHaulPayload } from "@util/common_util_mass_haul"; import { computeHaulPlan, haulPlanPayload } from "@util/common_util_mass_haul_balance"; import type { CrossSection, SectionDetailResponse } from "./B06_Section_Api_Fetch"; import { applyStructureAreaRows, structureAreaRows } from "./B06_Section_Structure_Layouts"; interface ServerCalcInput { detail?: SectionDetailResponse; /** * 유토 **배분만** 낼 때 쓰는 입력 — 브라우저가 누가토량(`computeMassHaul`)까지 내고 * 그 결과를 보내면 여기서 배분·운반거리만 얹어 돌려준다(2026-09-06). * 배분 코드를 브라우저 번들에서 빼기 위한 길이라, 이 갈래는 `detail` 을 안 받는다. */ haul_plan_for?: Parameters[0]; context?: { earthwork_conversion?: Parameters[1]; natural_spoil_min_ground_slope?: number | null; haul_equipment_limits?: Parameters[1]; /** 채집석 공제(㎥, 양수) — B08 이 낸다. `null`/없음은 「아직 안 옴」이다. */ collected_stone_deduction_m3?: number | null; /** 구조물 터파기 잔토(㎥, 양수) — B08 이 낸다. 사토에 **더한다**. */ structure_spoil_m3?: number | null; /** 측점별 잔토 — 오면 **그 자리**에 얹는다(운반거리가 맞다). */ structure_spoil_points?: Array<{ chainage_m: number; spoil_m3: number; /** 그 터파기의 토질 — B08 이 이미 판정한 값(품셈 9-13). `null` 이면 「지반 모름」. */ ground_type?: string | null; ground_label?: string | null; ground?: string | null; }> | null; }; } const [inputPath, outputPath] = process.argv.slice(2); if (!inputPath || !outputPath) { console.error("사용법: node <번들> <입력.json> <출력.json>"); process.exit(2); } const input = JSON.parse(readFileSync(inputPath, "utf8")) as ServerCalcInput; // 배분만 내는 갈래 — 화면이 편집을 멈추면 조용히 물어보는 자리(유토곡선 배경 선반입). if (input.haul_plan_for) { // **화면이 쓰는 꼴 그대로** 내보낸다(직렬화 형태 `haulPlanPayload` 가 아니다) — 그래야 // 그리기 코드가 손대지 않고 그대로 받는다. 전부 숫자·문자열이라 JSON 으로 오간다. const plan = computeHaulPlan(input.haul_plan_for, input.context?.haul_equipment_limits, { collected_stone_deduction_m3: input.context?.collected_stone_deduction_m3 ?? null, structure_spoil_m3: input.context?.structure_spoil_m3 ?? null, structure_spoil_points: input.context?.structure_spoil_points ?? null, }); writeFileSync(outputPath, JSON.stringify({ haul_plan: plan ?? null })); process.exit(0); } const sections: CrossSection[] = input.detail?.cross_sections ?? []; const areas = structureAreaRows(sections); // 보정값을 **자리에서** 얹는다 — 유토곡선이 고쳐진 면적 위에서 쌓이게 한다. applyStructureAreaRows(sections, areas); // 유토곡선 — balloon 위치는 사용자 화면값이라 서버가 만들지 않는다(파이썬이 보존). const conversion = input.context?.earthwork_conversion; const result = conversion ? computeMassHaul( sections, conversion, input.context?.natural_spoil_min_ground_slope ?? undefined, ) : null; // 배분은 **서버만** 만든다 — 그래야 그 코드가 브라우저 번들에서 빠진다(2026-09-06). const plan = result ? computeHaulPlan(result, input.context?.haul_equipment_limits, { collected_stone_deduction_m3: input.context?.collected_stone_deduction_m3 ?? null, structure_spoil_m3: input.context?.structure_spoil_m3 ?? null, structure_spoil_points: input.context?.structure_spoil_points ?? null, }) : null; const massHaul = result ? massHaulPayload(result, plan ? { haul_plan: haulPlanPayload(plan) } : null) : null; writeFileSync(outputPath, JSON.stringify({ areas, mass_haul: massHaul }));