feat(B08): 구조물 원단위 줄에 구간 표기 — 「H=2.5 · L=10.0m · NO.4 ~ NO.4+10」

계획서 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) <noreply@anthropic.com>
This commit is contained in:
2026-09-09 23:36:17 +09:00
co-authored by Claude Opus 5
parent 5d4473b40f
commit 909e4e8ed2
3 changed files with 52 additions and 3 deletions
@@ -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;
+18 -1
View File
@@ -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"));
@@ -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