feat(B05,B06): 유토 배분을 서버로 옮기고 브라우저는 미리 받아 쓰게 함

배분·운반거리 산식(_balance 523 + _settle 235 = 758줄)을 브라우저 번들에서 빼기 위한
배선. 계산은 여전히 한 벌 — 서버가 같은 TS 를 Node 로 돈다(CLAUDE.md 5장).

- POST /projects/{id}/sections/{route_id}/haul-plan 신설
  (B06_Section_Router_HaulPlan.py). 브라우저가 낸 누가토량 결과를 받아 배분만 돌려줌.
  화면이 쓰는 꼴 그대로 내보내 그리기 코드를 안 건드림. 표시 전용 — 정본은 저장 때 따로.
- B05_Profile_Api_HaulPlan.ts 신설 — 편집이 멈추면(400ms) 조용히 받아 두는 선반입기.
  늦게 온 응답은 버림(최신 요청만 채택). 못 받아도 곡선 자체는 그대로 보임.
- common_util_mass_haul.massHaulPayload 가 배분을 값으로 물던 것을 끊음(extra 인자).
  이 커플링 때문에 화면에서 안 불러도 번들에 남았음.
- 저장 경로는 곡선도 배분도 안 만듦 — 서버가 정본을 내므로 balloon 위치만 보냄.
- 죽은 파일 B06_Section_UI_Section_View_MassHaul.ts(236줄) 삭제.

자체검증(공용 브라우저, 용화 route 149) — 엔드포인트 200 · 196ms.
서버 배분과 브라우저 computeHaulPlan 결과가 JSON 문자열까지 동일(blocks 1 · steps 2 ·
spoil/borrow/hauled/transferred/fill_total 전부 일치). 시험 400 통과·17 건너뜀.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-06 22:47:22 +09:00
co-authored by Claude Opus 5
parent 1abb456ef9
commit c0bfcba34b
9 changed files with 228 additions and 276 deletions
+6 -5
View File
@@ -40,8 +40,6 @@ import type {
MassHaulLongitudinal,
MassHaulSection,
} from "./common_util_mass_haul_types";
import type { HaulPlan } from "./common_util_mass_haul_balance";
import { haulPlanPayload } from "./common_util_mass_haul_balance";
/** 지반유형별 토량(㎥). 도면 표기 EA(토사)/RR(리핑암)/BR(발파암)에 대응한다. */
export interface GroundVolumes {
@@ -464,18 +462,21 @@ export function computeMassHaulSeries(
/**
* 확정 시 DB(`longitudinal_sections.data.mass_haul`)에 넣을 직렬화 형태로 정리한다.
* 토량 분배(평형선)까지 냈으면 `haul_plan`으로 함께 실어 B08 수량·B09 견적이 되받게 한다.
*
* **토량 분배(`haul_plan`)는 여기서 만들지 않는다** — 그 코드를 브라우저 번들에서 빼려고
* 서버(Node 진입점)가 얹는다(2026-09-06 사용자 확정). 이 함수는 누가토량만 다룬다.
* 이미 만들어 둔 배분 조각이 있으면 `extra` 로 넘겨 그대로 실린다.
*/
export function massHaulPayload(
result: MassHaulResult,
haulPlan?: HaulPlan | null,
extra?: Record<string, unknown> | null,
balloonOffsets?: Record<string, [number, number]>,
): Record<string, unknown> {
const round = (value: number): number => Math.round(value * 100) / 100;
return {
basis: "compacted",
conversion: result.conversion,
...(haulPlan ? { haul_plan: haulPlanPayload(haulPlan) } : {}),
...(extra ?? {}),
// 사용자가 끌어 옮긴 balloon 위치 — 비어 있어도 보낸다(초기화가 저장에 반영돼야 한다).
...(balloonOffsets ? { balloon_offsets: balloonOffsets } : {}),
cut_natural_m3: {