From 909e4e8ed2def35fdbcf01d97b9fb854d9cce51a Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 9 Sep 2026 23:36:17 +0900 Subject: [PATCH] =?UTF-8?q?feat(B08):=20=EA=B5=AC=EC=A1=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9B=90=EB=8B=A8=EC=9C=84=20=EC=A4=84=EC=97=90=20=EA=B5=AC?= =?UTF-8?q?=EA=B0=84=20=ED=91=9C=EA=B8=B0=20=E2=80=94=20=E3=80=8CH=3D2.5?= =?UTF-8?q?=20=C2=B7=20L=3D10.0m=20=C2=B7=20NO.4=20~=20NO.4+10=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 계획서 V-8. 엔진이 일부러 화면 몫으로 남긴 자리(`_Handoff_Rows_Prep.py:182` 「표기는 만들지 않는다 — 측점 간격을 아는 화면 몫이다」)를 채움. - 토적표가 쓰던 `stationLabel` 을 **내보내 같이 씀** — 두 벌로 만들면 같은 측점이 화면 두 곳에서 다르게 적힘. - 구간은 `start_m`·`end_m` 을 그대로 씀. `length_m` 이 그 구간의 합이라 두 값이 갈릴 자리가 없음. - 값이 없으면 표기를 안 붙임(지어내지 않음). 화면 실측 — 「돌쌓기(찰) H=2.5 · L=10.0m · NO.4 ~ NO.4+10」(정본 80~90m)로 뜸. 시험 둘 추가(표기 함수 한 벌 · 구간은 구조물 값에서), 전체 1220 통과·실패 0. Co-Authored-By: Claude Opus 5 (1M context) --- B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts | 6 ++-- B08_Quantity/B08_Quantity_UI_MaterialGrid.ts | 19 +++++++++++- resources/tester/test_b08_station_label.py | 30 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 resources/tester/test_b08_station_label.py diff --git a/B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts b/B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts index 95c9548b..4f7cfc32 100644 --- a/B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts +++ b/B08_Quantity/B08_Quantity_UI_EarthworkGrid.ts @@ -216,8 +216,10 @@ const PAIR_LABELS = ["단면적", "입 적"]; const flatColumns = (): Column[] => GROUPS.flatMap((g) => g.sub.flatMap((s) => s.cols)); -/** 측점 표기 — `20` → `NO.1`, `25` → `NO.1+5`. 실무 토적표가 이 모양이다. */ -function stationLabel(chainage: number, interval = 20): string { +/** 측점 표기 — `20` → `NO.1`, `25` → `NO.1+5`. 실무 토적표가 이 모양이다. + * 구조물 원단위 표도 같은 표기를 쓰므로 **한 벌로 두고 내보낸다**(2026-09-09) — + * 두 벌이면 같은 측점이 화면 두 곳에서 다르게 적힌다. */ +export function stationLabel(chainage: number, interval = 20): string { const no = Math.floor(chainage / interval); const plus = chainage - no * interval; const rounded = Math.round(plus * 100) / 100; diff --git a/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts b/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts index a288291d..98d8dfcc 100644 --- a/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts +++ b/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts @@ -11,6 +11,8 @@ * ⚠ 반올림은 여기서만 한다(PLAN 8-16 표기 자리 ≠ 계산 자리). 서버가 준 값은 전정밀이다. * ========================================================================== */ +import { stationLabel } from "./B08_Quantity_UI_EarthworkGrid"; + export interface MaterialRow { name: string; unit: string; @@ -45,6 +47,10 @@ export interface UnitQuantityStructure { name: string; length_m: number; height_m: number; + /** 구조물이 덮는 구간(m) — 표기(`NO.4 ~ NO.4+10`)는 화면 몫이다 + * (`B08_Quantity_Engine_Handoff_Rows_Prep.py:182` 「표기는 만들지 않는다」). */ + start_m?: number | null; + end_m?: number | null; notes: string[]; components: { name: string; @@ -97,6 +103,12 @@ export interface MaterialResponse { }[]; } +/** 구조물이 덮는 구간을 실무 표기로 — 값이 없으면 빈 문자열(지어내지 않는다). */ +function stationRange(startM?: number | null, endM?: number | null): string { + if (typeof startM !== "number" || typeof endM !== "number") return ""; + return startM === endM ? stationLabel(startM) : `${stationLabel(startM)} ~ ${stationLabel(endM)}`; +} + /** 거푸집·동바리 안내에 쓰는 값. */ export interface FormworkInfo { formwork_notes?: string[]; @@ -418,7 +430,12 @@ export function renderUnitQuantityGrid(response: MaterialResponse): HTMLElement const body = document.createElement("tbody"); for (const structure of unit.structures) { - const spec = `H=${num(structure.height_m, 1)} · L=${num(structure.length_m, 1)}m`; + // 규격 + **구간 표기** — 구간은 길이와 같은 값에서 나오므로 둘이 갈릴 수 없다 + // (`length_m` 은 겹침을 지운 구간 목록의 합, 2026-09-09 확인). + const span = stationRange(structure.start_m, structure.end_m); + const spec = + `H=${num(structure.height_m, 1)} · L=${num(structure.length_m, 1)}m` + + (span ? ` · ${span}` : ""); if (!structure.components.length) { const tr = document.createElement("tr"); tr.append(textCell(structure.name, "b08-grid__station")); diff --git a/resources/tester/test_b08_station_label.py b/resources/tester/test_b08_station_label.py new file mode 100644 index 00000000..efbc1348 --- /dev/null +++ b/resources/tester/test_b08_station_label.py @@ -0,0 +1,30 @@ +"""측점 표기는 **한 벌**이다 — 토적표와 구조물 원단위가 같은 함수를 쓴다 (2026-09-09). + +구조물 줄에 구간 표기(`NO.4 ~ NO.4+10`)를 붙였다(계획서 V-8). 표기를 각자 만들면 같은 +측점이 화면 두 곳에서 다르게 적히므로 `stationLabel` 하나를 내보내 쓴다. + +⚠ 길이와 갈리지 않는 것이 요점이다 — 구간은 `start_m`·`end_m` 에서 나오고 `length_m` 은 + 그 구간(겹침을 지운 목록)의 합이라 두 값이 어긋날 자리가 없다. +""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +EARTHWORK = ROOT / "B08_Quantity" / "B08_Quantity_UI_EarthworkGrid.ts" +MATERIAL = ROOT / "B08_Quantity" / "B08_Quantity_UI_MaterialGrid.ts" + + +def test_표기_함수가_한_벌이다() -> None: + assert "export function stationLabel" in EARTHWORK.read_text(encoding="utf-8") + material = MATERIAL.read_text(encoding="utf-8") + assert 'import { stationLabel } from "./B08_Quantity_UI_EarthworkGrid"' in material + assert "function stationLabel" not in material, "표기 함수를 두 벌로 만들었다" + + +def test_구간은_구조물_값에서_나온다() -> None: + """지어낸 측점이 아니라 `start_m`·`end_m` 을 그대로 쓴다.""" + material = MATERIAL.read_text(encoding="utf-8") + assert "stationRange(structure.start_m, structure.end_m)" in material + assert "start_m?: number | null" in material