From a1f4949672c8340aafac9c13c889b120bee9966d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 7 Oct 2026 07:54:19 +0900 Subject: [PATCH] =?UTF-8?q?feat(B08):=20=EC=88=98=EB=A6=AC=EC=A7=91?= =?UTF-8?q?=EC=88=98=EB=A9=B4=EC=A0=81=ED=91=9C=20=EB=B0=95=EC=8A=A4?= =?UTF-8?q?=EC=95=94=EA=B1=B0=20=C2=B7=20=EC=84=B8=EC=9B=94=EA=B5=90=20?= =?UTF-8?q?=EC=A7=80=EC=A0=90=20=3D=20=EB=B9=88=20=EC=9E=A5(=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EB=B0=A9=EB=B2=95=20=EC=A0=95=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=A4=91=20=C2=B7=20PLAN=2073-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 관 지점 시설 속성(box_culvert · ford_bridge)으로 보조 탭 줄에 「측점 종류」 표시 · 그 탭 = 공용 카드 빈 장(측점 · 종류 · 안내) - 관 지점 계산 장은 그대로 · 물넘이포장 · 기슭막이는 지금처럼 관 계산 장 - [저장]에서 빈 장 지점은 빠짐 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4 --- B08_Quantity/B08_Quantity_Hydro_Defaults.py | 18 +++++++ B08_Quantity/B08_Quantity_Router_Hydro.py | 8 +-- B08_Quantity/B08_Quantity_UI_Hydro.ts | 53 +++++++++++++++++-- .../tester/test_73_1_b08_hydro_pending.py | 37 +++++++++++++ 4 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 resources/tester/test_73_1_b08_hydro_pending.py diff --git a/B08_Quantity/B08_Quantity_Hydro_Defaults.py b/B08_Quantity/B08_Quantity_Hydro_Defaults.py index 5bef667eb..ea30b98e8 100644 --- a/B08_Quantity/B08_Quantity_Hydro_Defaults.py +++ b/B08_Quantity/B08_Quantity_Hydro_Defaults.py @@ -37,6 +37,24 @@ DEFAULTS: dict[str, Any] = { } +#: 계산 방법 정하는 중인 시설(73-1) — 관 계산 대신 빈 장 · 값은 관 지점 시설 속성(pipe_points) +PENDING_FACILITIES = {"box_culvert": "박스암거", "ford_bridge": "세월교"} + + +def station_rows(points: list[Any]) -> list[dict[str, Any]]: + """관 지점 → 보조 탭 줄(측점 차례) · 박스암거 · 세월교는 pending(빈 장).""" + rows = [ + { + "chainage_m": p.chainage_m, + "facility": p.facility, + "kind": PENDING_FACILITIES.get(p.facility, ""), + "pending": p.facility in PENDING_FACILITIES, + } + for p in points + ] + return sorted(rows, key=lambda s: s["chainage_m"]) + + def options() -> dict[str, Any]: """화면 선택지 — 기본값 · 할증 · 단면 가정 · 관 종류 n.""" return { diff --git a/B08_Quantity/B08_Quantity_Router_Hydro.py b/B08_Quantity/B08_Quantity_Router_Hydro.py index a3cc5c206..1b823eaaa 100644 --- a/B08_Quantity/B08_Quantity_Router_Hydro.py +++ b/B08_Quantity/B08_Quantity_Router_Hydro.py @@ -21,7 +21,7 @@ from pydantic import BaseModel, Field from B02_ProjRegister.B02_ProjRegister_Repository import get_project_by_id from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path from B08_Quantity.B08_Quantity_Engine_Hydro import compute -from B08_Quantity.B08_Quantity_Hydro_Defaults import FACTORS, PIPE_KINDS, options +from B08_Quantity.B08_Quantity_Hydro_Defaults import FACTORS, PIPE_KINDS, options, station_rows from B08_Quantity.B08_Quantity_Hydro_Site import read_saved, site_info, site_r24, write_saved from common_util.common_util_drainage_pipes import pipe_points_path_in, read_pipe_points_file from common_util.common_util_storage import resolve_stored_project_path @@ -69,11 +69,7 @@ async def _project_root(project_id: UUID) -> Path: @router.get("/{project_id}/quantity/hydro") async def hydro_sheet(project_id: UUID) -> dict[str, Any]: root = await _project_root(project_id) - points = read_pipe_points_file(pipe_points_path_in(root)) - stations = sorted( - ({"chainage_m": p.chainage_m, "facility": p.facility} for p in points), - key=lambda s: s["chainage_m"], - ) + stations = station_rows(read_pipe_points_file(pipe_points_path_in(root))) project = await get_project_by_id(str(project_id)) or {} return { "stations": stations, diff --git a/B08_Quantity/B08_Quantity_UI_Hydro.ts b/B08_Quantity/B08_Quantity_UI_Hydro.ts index 15880b1bf..ffda4cd5e 100644 --- a/B08_Quantity/B08_Quantity_UI_Hydro.ts +++ b/B08_Quantity/B08_Quantity_UI_Hydro.ts @@ -3,6 +3,7 @@ * 4탭 수리집수면적표 — 배수지점 측점마다 보조 탭 · 한 장 = 홍수량 계산서(PLAN 62-2 · 69-4~6). * * 측점 목록만 저장된 관 지점(pipe_points)에서 · 입력은 이 탭 자기 값(B05/B06 값과 안 이음) + * 박스암거 · 세월교 지점 = 빈 장(계산 방법 정하는 중 · 73-1) · [저장]에서 빠짐 * 처음 값 = 표준 값 · 측점 고유 값은 빈칸 · 지역 · R24 = 공사지 자료(서버) * 조작 = 캐시(sessionStorage) · [저장] = B08 자기 파일(PUT …/quantity/hydro) · 뒤 캐시 비움 * 계산 = 서버 단독(POST …/quantity/hydro/calc · 최신 응답만 채택) @@ -12,6 +13,7 @@ import "./B08_Quantity_UI_Hydro.css"; import { API_BASE_URL } from "@config/config_frontend"; import { createButton, + createCard, hideLoadingOverlay, showLoadingOverlay, showToast, @@ -29,6 +31,10 @@ import { interface HydroStation { chainage_m: number; facility: string; + /** 박스암거 · 세월교 등 계산 방법 정하는 중인 시설 이름(관이면 "") */ + kind: string; + /** true = 관 계산 대신 빈 장(73-1) */ + pending: boolean; } type Drafts = Record>; @@ -57,6 +63,39 @@ function note(text: string): HTMLElement { const keyOf = (station: HydroStation): string => station.chainage_m.toFixed(3); +/** 박스암거 · 세월교 지점 빈 장(73-1) — 계산 방법이 정해지면 이 자리에 계산서가 들어감. */ +function pendingSheet(stationText: string, kind: string): HTMLElement { + const row = (label: string, value: string): HTMLElement => { + const line = document.createElement("div"); + line.className = "b08-hydro__row"; + const name = document.createElement("span"); + name.className = "b08-hydro__label"; + name.textContent = label; + const text = document.createElement("span"); + text.className = "b08-hydro__value"; + text.textContent = value; + line.append(name, text); + return line; + }; + const card = createCard({ + title: `홍수량 계산 — ${kind}`, + body: [ + row("측점", stationText), + row("종류", kind), + note("계산 방법 정하는 중 — 관과 다른 계산(사각 단면 · 월류)을 정한 뒤 이 장에 넣음."), + ], + }); + card.classList.add("b08-hydro__card"); + const sheet = document.createElement("div"); + sheet.className = "b08-hydro__sheet b08-hydro__sheet--pending"; + sheet.dataset.b08HydroPending = kind; + const body = document.createElement("div"); + body.className = "b08-hydro__body"; // 관 장과 같은 격자 — 카드 폭이 같음 + body.append(card); + sheet.append(body); + return sheet; +} + export async function renderHydro(host: HTMLElement, projectId: string): Promise { host.replaceChildren(note("홍수량 계산서를 불러오는 중…")); let sheet: HydroPayload; @@ -107,8 +146,9 @@ export async function renderHydro(host: HTMLElement, projectId: string): Promise const station = sheet.stations[index]; tabs.replaceChildren( ...sheet.stations.map((s, i) => { + const label = stationLabel(s.chainage_m, interval); const button = createButton({ - label: stationLabel(s.chainage_m, interval), + label: s.pending ? `${label} ${s.kind}` : label, variant: i === index ? "filled" : "ghost", size: "sm", onClick: () => i !== index && open(i), @@ -116,10 +156,15 @@ export async function renderHydro(host: HTMLElement, projectId: string): Promise button.setAttribute("role", "tab"); button.setAttribute("aria-selected", String(i === index)); button.dataset.b08HydroStation = String(s.chainage_m); - button.title = s.facility; + button.title = s.kind || s.facility; return button; }), ); + if (station.pending) { + seq++; // 앞 장의 늦은 계산 응답을 버림 + page.replaceChildren(pendingSheet(stationLabel(station.chainage_m, interval), station.kind)); + return; + } const draw = (inputs: HydroInputs): void => { const view = buildHydroSheet( stationLabel(station.chainage_m, interval), @@ -157,7 +202,9 @@ export async function renderHydro(host: HTMLElement, projectId: string): Promise /** [저장] — 측점 전부의 지금 입력을 B08 자기 파일로 · 성공하면 캐시를 비우고 저장본으로 다시 엶 */ const saveAll = async (): Promise => { - const stations = Object.fromEntries(sheet.stations.map((s) => [keyOf(s), inputsOf(s)])); + const stations = Object.fromEntries( + sheet.stations.filter((s) => !s.pending).map((s) => [keyOf(s), inputsOf(s)]), + ); showLoadingOverlay(); try { const body = await request<{ saved: Drafts }>(`/projects/${projectId}/quantity/hydro`, { diff --git a/resources/tester/test_73_1_b08_hydro_pending.py b/resources/tester/test_73_1_b08_hydro_pending.py new file mode 100644 index 000000000..16f9d1978 --- /dev/null +++ b/resources/tester/test_73_1_b08_hydro_pending.py @@ -0,0 +1,37 @@ +"""B08 수리집수면적표 — 박스암거 · 세월교 지점은 빈 장(계산 방법 정하는 중 · PLAN 73-1).""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from B08_Quantity.B08_Quantity_Hydro_Defaults import station_rows # noqa: E402 +from common_util.common_util_drainage_pipes import PipePoint # noqa: E402 + + +def _point(chainage: float, facility: str) -> PipePoint: + point = PipePoint.__new__(PipePoint) + point.chainage_m = chainage + point.facility = facility + return point + + +def test_박스_세월교만_빈_장_탭_수는_그_구조물_수() -> None: + points = [ + _point(173.0, "ford_bridge"), + _point(85.0, "pipe"), + _point(300.0, "box_culvert"), + _point(352.0, "ford_bridge"), + _point(400.0, "ford_pavement"), + ] + rows = station_rows(points) + assert [r["chainage_m"] for r in rows] == [85.0, 173.0, 300.0, 352.0, 400.0] + pending = [r for r in rows if r["pending"]] + assert len(pending) == 3 # 세월교 2 · 박스 1 + assert {r["kind"] for r in pending} == {"세월교", "박스암거"} + # 관 · 물넘이포장은 지금 관 계산 장 그대로 + assert [r["facility"] for r in rows if not r["pending"]] == ["pipe", "ford_pavement"] + assert all(r["kind"] == "" for r in rows if not r["pending"])