feat(b08): 구조물 집계표 커플링 = 좌측 패널 1본 길이로 바로 다시 셈 · 응답에 관마다 길이 pipes(122-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
머리 글 = M02 정의 이름(`구조물정의.json` 종류.pipe.이름 — 사용자가 M02 에서 고침) ·
|
||||
열마다 `combos` = 그 값을 낸 컨테이너에 이은 조합 id(`{층}|{주인}|{키}` · 95-3 모달 · 맨 윗 머리).
|
||||
설계 = B08 토공과 같은 길(확정 노선 먼저) · 관 · 컨테이너 줄 = M02 채움 `collect_items` 재사용.
|
||||
`pipes` = 관마다 길이(커플링 열 · 측점) — 좌측 패널 1본 길이를 바꾸면 화면이 같은 식으로 바로 다시 셈
|
||||
(짝 = `couplings` ↔ `B08_Quantity_Options_Calc.ts` `couplings` · `recountCouplings` · PLAN 122).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -181,6 +183,7 @@ def pipe_summary(
|
||||
entry["base"] = item
|
||||
|
||||
sheet = _Sheet()
|
||||
pipes: list[dict[str, Any]] = []
|
||||
no_length = no_basin = 0
|
||||
soil_name = names.get(f"outlet.structure={SOIL}", SOIL)
|
||||
for owner, entry in by_owner.items():
|
||||
@@ -213,6 +216,7 @@ def pipe_summary(
|
||||
couplings(length, one_m),
|
||||
pipe_combos,
|
||||
)
|
||||
pipes.append({"chainage_m": at, "column": f"pipe:cp|{diameter}", "length_m": length})
|
||||
shape = fill._nearest(basins, at)
|
||||
no_basin += shape is None
|
||||
if shape:
|
||||
@@ -288,7 +292,7 @@ def pipe_summary(
|
||||
notes.append(f"집수정 모양 기록 없음 {no_basin}곳 — B06 횡단 [저장] 뒤 채워짐")
|
||||
if one_m is None:
|
||||
notes.append(f"1본 길이 변수({ONE_LENGTH_VAR}) 빈칸 — 커플링 빈칸")
|
||||
return {**out, "notes": notes, "one_length_m": one_m}
|
||||
return {**out, "notes": notes, "one_length_m": one_m, "pipes": pipes}
|
||||
|
||||
|
||||
def _summary(root: Path, designs: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* 배관(횡단배수관) 열 · 칸 = GET …/quantity/summary(서버 단독 · PLAN 98) — 머리 구조물 · 형식 · 규격
|
||||
* 3단 + 단위 줄 · 「관 공」 맨 윗 머리 = 관에 이은 조합 경로(있으면) · 다른 구조물 열은 조합 그대로
|
||||
* 줄을 누르면 그 측점의 구조물도 모달(PLAN 95-3 · `B08_Quantity_UI_Drawing` 은 누를 때 `import()`)
|
||||
* 커플링 열 = 좌측 패널 1본 길이로 바로 다시 셈(관마다 길이 `pipes` · `recountCouplings` · PLAN 122)
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -32,6 +33,8 @@ import {
|
||||
type PlacedItem,
|
||||
type StationCombo,
|
||||
} from "./B08_Quantity_Summary_Data";
|
||||
import { recountCouplings, type PipeLength } from "./B08_Quantity_Options_Calc";
|
||||
import { liveOnePipe, onOptionsChange } from "./B08_Quantity_UI_Options";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
@@ -65,6 +68,8 @@ interface PipeSummary {
|
||||
rows: Array<{ chainage_m: number; values: Record<string, number> }>;
|
||||
total: Record<string, number>;
|
||||
notes: string[];
|
||||
one_length_m: number | null;
|
||||
pipes: PipeLength[];
|
||||
}
|
||||
|
||||
async function fetchPipeSummary(projectId: string): Promise<PipeSummary> {
|
||||
@@ -204,20 +209,29 @@ export async function renderStructureSummary(host: HTMLElement, projectId: strin
|
||||
...placed.filter((point) => (point.facility ?? "pipe") !== "pipe"),
|
||||
];
|
||||
const columns = summaryColumns(others, describe);
|
||||
const summary = typeof pipe === "string" ? null : pipe;
|
||||
const saved = typeof pipe === "string" ? null : pipe;
|
||||
const live = (): PipeSummary | null =>
|
||||
saved && recountCouplings(saved, liveOnePipe(projectId, saved.one_length_m));
|
||||
const summary = live();
|
||||
const parts: HTMLElement[] = [];
|
||||
if (typeof pipe === "string") parts.push(note(`${L("B08_Quantity_Summary_Failed")} — ${pipe}`));
|
||||
for (const text of summary?.notes ?? []) parts.push(note(text));
|
||||
if (!columns.length && !summary?.columns.length)
|
||||
parts.push(note(L("B08_Quantity_Summary_NoCombo")));
|
||||
parts.push(
|
||||
const draw = (): HTMLElement =>
|
||||
stations.length
|
||||
? buildTable(stations, columns, summary, comboPath, interval, (station, label) =>
|
||||
? buildTable(stations, columns, live(), comboPath, interval, (station, label) =>
|
||||
openSheets(projectId, label, combosAt(things, station), m02, describe),
|
||||
)
|
||||
: note(L("B08_Quantity_Summary_NoStation")),
|
||||
);
|
||||
: note(L("B08_Quantity_Summary_NoStation"));
|
||||
let table = draw();
|
||||
parts.push(table);
|
||||
host.replaceChildren(...parts);
|
||||
onOptionsChange(host, () => {
|
||||
const next = draw();
|
||||
table.replaceWith(next);
|
||||
table = next;
|
||||
});
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error);
|
||||
host.replaceChildren(note(`${L("B08_Quantity_Summary_Failed")} — ${reason}`));
|
||||
|
||||
@@ -210,6 +210,23 @@ def test_1본_길이는_프로젝트_층_변수(root, tmp_path, monkeypatch):
|
||||
assert summary.one_pipe_length(root) is None
|
||||
|
||||
|
||||
def test_관마다_길이_칸_화면이_1본_바꿔_다시_셈(root):
|
||||
# 122 — 응답 `pipes` = 관마다 길이 · 커플링 열(화면 recountCouplings 가 같은 식으로 셈)
|
||||
six, eight = _run(root, 6.0), _run(root, 8.0)
|
||||
assert six["pipes"] == eight["pipes"]
|
||||
assert [(round(p["chainage_m"]), p["column"], p["length_m"]) for p in six["pipes"]] == [
|
||||
(1433, "pipe:cp|1200", 22),
|
||||
(1567, "pipe:cp|800", 18),
|
||||
(1680, "pipe:cp|800", 16),
|
||||
(1790, "pipe:cp|800", None),
|
||||
]
|
||||
for out, one in ((six, 6.0), (eight, 8.0)):
|
||||
total = sum(
|
||||
summary.couplings(p["length_m"], one) or 0 for p in out["pipes"] if "800" in p["column"]
|
||||
)
|
||||
assert out["total"]["pipe:cp|800"] == total
|
||||
|
||||
|
||||
def test_라우터_등록():
|
||||
text = (Path(__file__).resolve().parents[2] / "main.py").read_text(encoding="utf-8")
|
||||
assert "app.include_router(b08_summary_router, dependencies=protected_with_company)" in text
|
||||
|
||||
Reference in New Issue
Block a user