Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_2

# Conflicts:
#	B04_PreProcess/B04_PreProcess_Router_Basins.py
#	B08_Quantity/B08_Quantity_Router_Summary.py
This commit is contained in:
2026-10-09 15:54:20 +09:00
25 changed files with 762 additions and 122 deletions
+23 -4
View File
@@ -17,6 +17,7 @@ import {
API_SURFACE_BUILD_TIMEOUT_MS,
API_TIMEOUT_MS,
} from "@config/config_frontend";
import { showToast } from "@ui/ui_template_elements";
import type { GroupItems, SummaryItem } from "../common_util/common_util_summary_item";
/** 지표면 분석 실행 요청 (SurfaceAnalyzeRequest) */
@@ -557,6 +558,22 @@ export interface DetailBasinResponse {
arrow_spacing_m: number;
/** 유역 안쪽 상류 세류망 — 하이라이트 토글용. */
upstream_lonlat: Array<Array<[number, number]>>;
/** [저장] 때 못 만든 구조물 조합 사본 폴더(142-2 · `*` = 통째 실패). */
snapshot_failed?: string[];
}
let lastNote = { text: "", at: 0 };
/** 못 만든 구조물 조합 사본(저장 응답 `snapshot_failed` · 142-2) — [저장] 알림 한 줄 ·
* 관 · 구조물 저장이 같은 맞춤을 두 번 알리면 한 번만. */
export function noteSnapshotFailed(failed: readonly string[] | undefined): void {
if (!failed?.length) return;
const text = failed.includes("*")
? "구조물 조합 사본 맞춤에 실패했습니다 — 다시 [저장]해 주세요."
: `구조물 조합 사본 ${failed.length}곳을 못 만들었습니다(원본 조합 없음) — 조합을 다시 골라 [저장]해 주세요.`;
const now = Date.now();
if (text === lastNote.text && now - lastNote.at < 5000) return;
lastNote = { text, at: now };
showToast(text, "error");
}
/** 저장된 관 매설 지점과 그 세부유역. 저장분이 없으면 백엔드가 자동 생성해 돌려준다. */
@@ -593,8 +610,10 @@ export async function saveDetailPipePoints(
projectId: string,
points: DetailPipeInput[],
): Promise<DetailBasinResponse> {
return requestJson<DetailBasinResponse>(`/projects/${projectId}/drainage/pipe-points`, {
method: "PUT",
body: JSON.stringify({ points }),
});
const saved = await requestJson<DetailBasinResponse>(
`/projects/${projectId}/drainage/pipe-points`,
{ method: "PUT", body: JSON.stringify({ points }) },
);
noteSnapshotFailed(saved.snapshot_failed);
return saved;
}
+10 -4
View File
@@ -20,12 +20,13 @@ import math
from typing import Any
from uuid import UUID
from fastapi import APIRouter
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Export import drainage_dir
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
from B06_Section.B06_Section_Engine_Culvert import pipe_wall_min_height_m
from common_util.common_util_auth import get_optional_session
from common_util.common_util_drainage_context import DrainageContext, load_drainage_context
from common_util.common_util_drainage_detail import DrainageDetail, build_detail
from common_util.common_util_drainage_pipes import (
@@ -422,7 +423,9 @@ async def delete_pipe_points(project_id: UUID) -> dict[str, Any] | JSONResponse:
@router.put("/{project_id}/drainage/pipe-points", response_model=None)
async def put_pipe_points(
project_id: UUID, payload: dict[str, Any] | None = None
project_id: UUID,
payload: dict[str, Any] | None = None,
session: dict[str, Any] | None = Depends(get_optional_session), # 가져온이(142-2)
) -> dict[str, Any] | JSONResponse:
"""관 지점을 정본으로 확정하고 세부유역 산출물까지 함께 남긴다(모델 확정 시점)."""
resolved = await _resolve(project_id, payload, use_stored=True)
@@ -443,9 +446,12 @@ async def put_pipe_points(
)
# 지정한 집계 항목을 프로젝트에 복사 · 안 쓰는 복사본 지움(PLAN 21-3 · 실패해도 저장 유지)
from common_util.common_util_storage import resolve_stored_project_path
from M02_MasterTemplete.M02_Item_Snapshot import safe_sync
from M02_MasterTemplete.M02_Item_Snapshot import importer, safe_sync
await asyncio.to_thread(safe_sync, resolve_stored_project_path(context.stored_path))
failed = await asyncio.to_thread(
safe_sync, resolve_stored_project_path(context.stored_path), importer(session)
)
result = _payload(project_id, context, detail, points, saved=True)
result["saved_count"] = saved
result["snapshot_failed"] = failed # 못 만든 구조물 조합 복사본(142-2) — [저장] 알림
return result