From 131271e9ae93e8e031951961bf72250328e3e43d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 6 Oct 2026 15:35:05 +0900 Subject: [PATCH] =?UTF-8?q?fix(structure):=20=EC=A2=85=EB=8B=A8=20?= =?UTF-8?q?=EC=95=8C=EC=95=BD=20=EC=A0=95=EB=B3=B4=20=EC=B0=BD=202?= =?UTF-8?q?=ED=96=89=20=E2=80=94=201=ED=96=89=20=EC=9D=B4=EB=A6=84=20+=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=20=EC=B8=A1=EC=A0=90=20=C2=B7=202=ED=96=89?= =?UTF-8?q?=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20=EC=B2=AB=20=EA=B0=92?= =?UTF-8?q?=EB=A7=8C=20=E3=80=8C=20=C2=B7=20=E3=80=8D=20=EC=9D=B4=EC=9D=8C?= =?UTF-8?q?=20=C2=B7=20=EC=A0=9C=EB=AA=A9=20=C2=B7=20=EB=B9=88=20=EA=B0=92?= =?UTF-8?q?=20=EB=BA=8C(54-46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP --- .../B05_Profile_UI_Structures_Balloon.ts | 50 +++++++--------- .../B05_Profile_UI_Structures_Marks.ts | 2 +- resources/tester/test_54_14_balloon_lines.py | 59 +++++++------------ 3 files changed, 42 insertions(+), 69 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Structures_Balloon.ts b/B05_Profile/B05_Profile_UI_Structures_Balloon.ts index 8023aecb8..9c49247d6 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Balloon.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Balloon.ts @@ -2,12 +2,12 @@ * B05_Profile_UI_Structures_Balloon.ts * 종단 구조물 알약 정보 라벨 줄(54-14) — 화면 요소 없는 순수 규칙. B05 · B06 알약 레인이 같이 씀. * - * · 줄 = 종류 이름(기타 = 손 이름) · 기준 측점(구간 = 시작 ~ 끝) · 컨테이너마다 첫 옵션 값 한 줄 - * (「관: 관경 1000」) · 컨테이너가 없으면 앞 두 줄만 · 메모 · 그 밖 옵션은 안 씀 + * · 2행 — 1행 = 종류 이름(기타 = 손 이름) + 기준 측점(구간 = 시작 ~ 끝) · 2행 = 컨테이너마다 첫 옵션 + * 값만 「 · 」 로 이음(제목 · 옵션 이름 없음 · 빈 값은 건너뜀) · 값이 하나도 없으면 1행만 · 메모 · 그 밖 옵션은 안 씀 * · 컨테이너 = 좌측 패널 폼과 같은 상자 — 명세 종류(`STRUCTURE_SPECS`)는 명세 컨테이너(선택 컨테이너는 * 인스턴스마다) · 그 밖 종류는 정의 옵션 그룹(`StructureType.groups`) * · 첫 옵션 = 지금 보이는 첫 입력 칸(자동 칸 · 갈래가 없어 가린 칸 · 끈 칸은 건너뜀) - * · 값 `auto` = 「(자동)」 · 빈 값 = 「—」(54-39 · 기본값으로 안 바꿈 · 기본값은 새 구조물 칸에 실제 값) + * · 값 `auto` = 「(자동)」 · 빈 값은 건너뜀(기본값으로 안 바꿈 · 기본값은 새 구조물 칸에 실제 값) * ========================================================================== */ import type { StructureInstance, StructureType } from "./B05_Profile_Api_Structures"; @@ -18,8 +18,6 @@ import { branchOf, choiceLabel, choicesOf, - containerTitle, - itemLabel, readState, type Instance, type SpecContainer, @@ -28,12 +26,12 @@ import { } from "../A00_Common/design/design_structure_spec"; const AUTO_MARK = "(자동)"; -const NONE = "—"; +const SEP = " · "; /** 선택 컨테이너 개수를 밖에서 정함(배관 흙막이 = 유출 단 수 · 폼 `hooks.count` 와 같은 값). */ export type ContainerCount = (containerId: string) => number | null | undefined; -/** 라벨 줄 — [이름, 기준 측점, ...컨테이너 줄]. */ +/** 라벨 줄 — [이름 기준 측점, 컨테이너 값 이음?]. */ export function balloonLines( structure: StructureInstance, type: StructureType | undefined, @@ -41,13 +39,14 @@ export function balloonLines( count?: ContainerCount, ): string[] { const spec = STRUCTURE_SPECS[structure.type_id]; - const rest = spec - ? specLines(spec, structure, type, count) - : groupLines(structure.options ?? {}, type); - return [head.name, head.position, ...rest]; + const values = ( + spec ? specLines(spec, structure, type, count) : groupLines(structure.options ?? {}, type) + ).filter(Boolean); + const first = `${head.name} ${head.position}`; + return values.length ? [first, values.join(SEP)] : [first]; } -/** 명세 컨테이너 줄 — 폼(`Drainage_Facility.setFacility` · `paintStructureSpec`)과 같은 값 · 이름. */ +/** 명세 컨테이너 값 — 폼(`Drainage_Facility.setFacility` · `paintStructureSpec`)과 같은 값 · 이름(빈 값 = ""). */ function specLines( spec: StructureSpec, structure: StructureInstance, @@ -61,19 +60,14 @@ function specLines( names = pipeNames; } const state = formState(spec, structure.type_id, structure.options ?? {}); - const groups = Object.fromEntries((type?.groups ?? []).map((g) => [g.id, g.name])); - return spec.containers.flatMap((c) => { - const title = containerTitle(c, names, groups[c.link]); - return instancesOf(c, state[c.id] ?? [], count).map((values, index) => { + return spec.containers.flatMap((c) => + instancesOf(c, state[c.id] ?? [], count).map((values) => { const item = c.items.find( (entry) => entry.kind !== "auto" && (!entry.by || branchOf(entry, values)), ); - const head = c.required ? title : `${title} ${index + 1}`; - return item - ? `${head}: ${itemLabel(c, item, names)} ${specValue(c, item, values, names)}` - : `${head}: ${NONE}`; - }); - }); + return item ? specValue(c, item, values, names) : ""; + }), + ); } /** 저장 값 → 폼 칸 값 — 폼(`Drainage_Facility.setFacility`)과 같은 손질(BOX 옛 설치 · 본체 규격 · @@ -116,10 +110,10 @@ function specValue( }; const raw = (values[item.id] ?? "").trim(); if (raw === "auto") return AUTO_MARK; - return raw ? shown(raw) : NONE; + return raw ? shown(raw) : ""; } -/** 정의 옵션 그룹 줄 — 그룹마다 첫 옵션(정의에 있고 켜진 칸). */ +/** 정의 옵션 그룹 값 — 그룹마다 첫 옵션(정의에 있고 켜진 칸 · 빈 값 = ""). */ function groupLines( options: Record, type: StructureType | undefined, @@ -129,11 +123,9 @@ function groupLines( const option = group.options .map((key) => byKey.get(key)) .find((entry) => entry && entry.visible !== false && entry.enabled !== false); - if (!option) return `${group.name}: ${NONE}`; - const unit = option.unit ?? ""; + if (!option) return ""; const raw = options[option.key]; - const value = - raw === "auto" ? AUTO_MARK : raw !== undefined && raw !== "" ? `${raw}${unit}` : NONE; - return `${group.name}: ${option.label} ${value}`; + if (raw === "auto") return AUTO_MARK; + return raw !== undefined && raw !== "" ? `${raw}${option.unit ?? ""}` : ""; }); } diff --git a/B05_Profile/B05_Profile_UI_Structures_Marks.ts b/B05_Profile/B05_Profile_UI_Structures_Marks.ts index e05b782d7..0fc00d26a 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Marks.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Marks.ts @@ -263,7 +263,7 @@ export function buildStructureLane(options: StructureMarksOptions): HTMLElement * 알약 **위**로 띄운다 — 아래로 펴면 도면 테이블에 가려 안 보인다(2026-08-18 * 사용자 지시). 2행 구조에서 1행(윗줄)은 좌측 위, 2행(아랫줄)은 우측 위로 * 전개해 서로 겹치지 않게 하고, 솔로 알약은 위 중앙이다. - * 줄 = 이름 · 기준 측점 · 컨테이너마다 첫 옵션 값(`balloonLines` · 54-14) · 그래프 바닥 ▼ 단추를 가리지 + * 줄 = 2행 — 이름 + 기준 측점 · 컨테이너마다 첫 옵션 값 이음(`balloonLines` · 54-14 · 54-46) · 그래프 바닥 ▼ 단추를 가리지 * 않게 단추 높이만큼 더 위 · 위가 잘리면 알약 아래로. */ function buildBalloon( lines: string[], diff --git a/resources/tester/test_54_14_balloon_lines.py b/resources/tester/test_54_14_balloon_lines.py index aa9b92539..24361d7dc 100644 --- a/resources/tester/test_54_14_balloon_lines.py +++ b/resources/tester/test_54_14_balloon_lines.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """54-14 종단 구조물 알약 정보 라벨 — 중요한 것만. -• 줄 = 이름 · 기준 측점 · 컨테이너마다 첫 옵션 값 한 줄(컨테이너가 없으면 앞 두 줄만) +• 2행 — 1행 = 이름 + 기준 측점 · 2행 = 컨테이너마다 첫 옵션 값만 「 · 」 로 이음(54-46 · 제목 없음) • 컨테이너 = 좌측 패널 폼과 같은 상자(명세 컨테이너 · 선택 컨테이너는 인스턴스마다 · 정의 옵션 그룹) -• 빈 값 = 등록부 기본값 「(기본)」 · 값 auto = 「(자동)」 · 그것도 없으면 「—」 · 메모 · 그 밖 옵션은 안 씀 +• 빈 값은 건너뜀 · 값 auto = 「(자동)」 · 값이 하나도 없으면 1행만 · 메모 · 그 밖 옵션은 안 씀 • 라벨은 ▲▼ 단추층 위 · 단추 높이만큼 위로 비킴 · B05 · B06 은 같은 레인 — 실제 화면은 ORCA 로 따로 본다. 화면 그릇 없이 Node 헬퍼가 TS 를 트랜스파일해 돌린다(`helper_54_14_balloon_lines.cjs`). """ @@ -33,56 +33,37 @@ def out(): return json.loads(proc.stdout) -def test_배관_컨테이너마다_첫_값_한_줄(out): - assert out["pipeFull"] == [ - "이름", - "5+0", - "관: 관경 1200", - "관 보호공(유입): 구조 집수정", - "관 보호공(유출): 구조 흙막이", - "흙막이 1: 형태 돌망태", # 구조 칸 없음(54-30) - "도수로/돌붙임 1: 구조 도수로", - ] +def test_배관_컨테이너마다_첫_값만_한_줄로_이음(out): + # 흙막이 1 은 구조 칸 없음 — 형태(54-30) + assert out["pipeFull"] == ["이름 5+0", "1200 · 집수정 · 흙막이 · 돌망태 · 도수로"] -def test_빈_값은_등록부_기본값에_기본_표시(out): - assert out["pipeBlank"] == [ - "이름", - "5+0", - "관: 관경 1000 (기본)", - "관 보호공(유입): 구조 흙막이 (기본)", - "관 보호공(유출): 구조 —", - ] +def test_빈_값은_건너뜀(out): + assert out["pipeBlank"] == ["이름 5+0"] def test_흙막이_상자_수는_밖에서_정한_단_수(out): - assert out["pipeCount"][-2:] == ["흙막이 1: 형태 —", "흙막이 2: 형태 —"] - assert len(out["pipeCount"]) == 2 + 3 + 2 + # 저장 1 벌(형태 빈 값) · 모자란 둘째도 빈 값 — 둘 다 건너뜀 + assert out["pipeCount"] == ["이름 5+0"] def test_박스_본체_규격은_폭_높이로_되찾음(out): - assert out["boxPreset"] == [ - "이름", - "5+0", - "본체: 본체 규격 3.0 × 3.0", - "유입 구조: 구조 날개벽", - "유출 구조: 구조 —", - ] - assert out["boxBlank"][2] == "본체: 본체 규격 2.0 × 2.0 (기본)" + assert out["boxPreset"] == ["이름 5+0", "3.0 × 3.0 · 날개벽"] + assert out["boxBlank"] == ["이름 5+0"] def test_정의_옵션_그룹은_그룹마다_첫_켜진_옵션(out): - assert out["underdrain"] == ["이름", "5+0", "맹암거: 높이 0.6m"] - assert out["underdrainBlank"] == ["이름", "5+0", "맹암거: 높이 —"] - # 끈 칸(높이)은 건너뜀 · 빈 값은 기본값 - assert out["wall"] == ["이름", "5+0", "상세 내용: 길이 10m (기본)"] - assert out["wallAuto"] == ["이름", "5+0", "상세 내용: 길이 (자동)"] + assert out["underdrain"] == ["이름 5+0", "0.6m"] + assert out["underdrainBlank"] == ["이름 5+0"] + # 끈 칸(높이)은 건너뜀 + assert out["wall"] == ["이름 5+0"] + assert out["wallAuto"] == ["이름 5+0", "(자동)"] -def test_컨테이너가_없으면_이름과_기준_측점만(out): - assert out["mirror"] == ["이름", "5+0"] - assert out["etcEmpty"] == ["이름", "5+0"] - assert out["etcNote"] == ["이름", "5+0", "상세 내용 1: 비고 시험 비고"] +def test_컨테이너가_없으면_1행만(out): + assert out["mirror"] == ["이름 5+0"] + assert out["etcEmpty"] == ["이름 5+0"] + assert out["etcNote"] == ["이름 5+0", "시험 비고"] def test_라벨은_단추층_위로_비켜_뜬다():