무슨 일이 있었나 랩탑 줄의 병합 `20ba886c`(Merge origin/main_desktop_1·main_laptop_1·sub_desktop_1 into sub_laptop_1)가 우리 파일 22개를 떨구고 35개 파일의 내용을 옛것으로 되돌림. 손으로 지운 커밋은 없고 **병합 자체가 떨군 것**임. 그것이 `origin/dev`·`main_laptop_1`·`sub_laptop_1`· `CODEX` 까지 퍼졌고(데스크탑 둘만 무사), 이 창의 병합 `d92c1f2b` 로 들어옴. 잃었던 것 - 공용 — `common_util_provenance.py` · `ui_template_provenance.ts` - B08 — 근거 사전 · 좌측 패널 상자 모듈 · 토량환산계수 칸 - B09 — 근거 사전 셋 - B05 — 계획노선 편집 모듈 아홉 · 지형 라우터 · B04 지도 모듈 - 시험 셋과, 35개 파일 안의 최근 작업(환산계수 고르기 · 근거 호버 배선 등) 어떻게 되살렸나 `611a2b40`(병합 직전, 전부 온전)에서 `git show <커밋>:<경로>` 로 내용만 꺼내 되돌림. 이력은 안 건드림. ⚠ HEAD 에만 있던 「추가 816줄」은 랩탑의 새 작업이 아니라 **되살아난 옛 코드**였음(B05 편집은 모듈로 쪼개기 전 덩어리 · B08 라우터는 환산계수 고르기 전 옛 상수판). 되돌릴 시점 이후의 **진짜 새 커밋은 둘뿐**이라 그 둘만 패치로 다시 얹음 — `9f827bf6`(리로드 빌드 고리 끊기, 데스크탑 보조) · `b9bca6b3`(B06 조정창 1px, 랩탑). 위키 여덟은 코덱스 몫이라 손대지 않음. 자체검증 — 양쪽 작업이 다 살아 있음을 짚어 확인: `main.py` 의 「개발 서버는 살려 둔다」 · `B05_Profile_Engine_Grade.py` 의 `plan_curve_length_limit_m` · `B08_..._EarthworkGrid.ts` 의 `attachProvenance`. `tsc --noEmit` 통과 · `pytest -q` **1317 passed, 28 skipped** (되살리기 전에는 시험 둘이 수집 단계에서 깨져 있었음). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RANEBHns1S4tkmsYwewtk
87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
"""유토 **배분(평형선·운반거리·장비)** 만 내주는 창구.
|
|
|
|
왜 따로 있나(2026-09-06 사용자 확정) — 배분 산식은 노하우가 몰린 자리라 브라우저 번들에
|
|
남기지 않는다. 화면은 누가토량까지만 스스로 내고(`common_util_mass_haul.ts`), 편집이
|
|
멈추면 그 결과를 여기로 보내 배분을 받아 쥔다. 그래서 유토곡선 패널을 펼치는 순간이
|
|
즉시가 된다(미리 받아 뒀으므로).
|
|
|
|
**계산은 한 벌이다** — 화면이 쓰던 TS 를 Node 진입점(`B06_Section_Server_Calc_Node.ts`)으로
|
|
그대로 돌린다(CLAUDE.md 5장). 파이썬으로 옮기면 저장 정본과 화면 값이 갈린다.
|
|
|
|
정본을 만들지 않는다 — 이 응답은 **표시 전용**이다. 저장 정본은 [저장]·[확정] 뒤
|
|
`recompute_server_side` 가 따로 낸다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Body
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from B06_Section.B06_Section_Server_Calc_Prebuild import (
|
|
BUNDLE,
|
|
_mass_haul_context,
|
|
conversion_factors_for,
|
|
haul_inputs_for,
|
|
)
|
|
from common_util.common_util_node_bundle import run_bundle_json
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/projects", tags=["B06 Profile Cross"])
|
|
|
|
_NPM_SCRIPT = "build:server-calc"
|
|
# 측점 수 상한 — 정상 노선은 수백 곳이다. 그보다 크면 비정상 요청으로 본다.
|
|
_MAX_POINTS = 5000
|
|
|
|
|
|
@router.post("/{project_id}/sections/{route_id}/haul-plan", response_model=None)
|
|
async def compute_haul_plan(
|
|
project_id: UUID,
|
|
route_id: int,
|
|
payload: dict[str, Any] = Body(...),
|
|
) -> JSONResponse:
|
|
"""브라우저가 낸 누가토량 결과를 받아 **배분만** 돌려준다.
|
|
|
|
입력은 `common_util_mass_haul.computeMassHaul` 의 결과 한 벌이다(`points` 포함).
|
|
출력은 `{"haul_plan": {...} | null}` — 화면이 그대로 그린다.
|
|
"""
|
|
result = payload.get("result")
|
|
points = result.get("points") if isinstance(result, dict) else None
|
|
if not isinstance(points, list) or not points:
|
|
return JSONResponse(
|
|
status_code=400,
|
|
content={"status": "error", "message": "누가토량 결과가 비어 있습니다."},
|
|
)
|
|
if len(points) > _MAX_POINTS:
|
|
return JSONResponse(
|
|
status_code=400,
|
|
content={"status": "error", "message": "측점 수가 너무 많습니다."},
|
|
)
|
|
# 구조물 몫(공제·잔토)을 **넘겨야** 사토가 줄고 는다 — 인자 없이 부르면 늘 `None` 이라
|
|
# 통로만 있고 값이 안 흐른다(2026-09-09 실측으로 드러난 자리).
|
|
haul_inputs = await haul_inputs_for(project_id)
|
|
# 곡선이 쓰는 계수도 프로젝트가 고른 값으로 — 토적표·운반표와 같은 값이어야 한다.
|
|
factors = await conversion_factors_for(project_id)
|
|
try:
|
|
output = await asyncio.to_thread(
|
|
run_bundle_json,
|
|
BUNDLE,
|
|
_NPM_SCRIPT,
|
|
{
|
|
"haul_plan_for": result,
|
|
"context": _mass_haul_context(haul_inputs, factors),
|
|
},
|
|
)
|
|
except Exception:
|
|
logger.exception("유토 배분 계산 실패: project_id=%s route_id=%s", project_id, route_id)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"status": "error", "message": "유토 배분 계산에 실패했습니다."},
|
|
)
|
|
plan = output.get("haul_plan") if isinstance(output, dict) else None
|
|
return JSONResponse(content={"status": "success", "haul_plan": plan})
|