diff --git a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts index f7349e98a..68c8575e1 100644 --- a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts +++ b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts @@ -6,7 +6,7 @@ * 이 프로그램은 파일 입력 시 기본 설정값으로 B06 몫까지 미리 계산해 둔다 — 종횡단 상세 * 조회(`fetchSectionDetail`)가 지정 설계 없는 측점에 기본 설계를 즉석 계산해 얹으므로 * (`_attach_default_designs`), B05 진입 시점에 이미 **횡단 단면적 기반 정식 곡선**을 그릴 - * 수 있다. 그래서 B06과 똑같이 `computeMassHaulSeries`(횡단 기준 + 종단 개략 비교)를 쓰고, + * 수 있다. 그래서 B06과 똑같이 `computeMassHaulSeries`(횡단 기준만 · 종단 기준 숨김 354-2)를 쓰고, * 범례·토량 분배·balloon·요약도 전부 공용 모듈 그대로 쓴다. * * 표시 토글 sessionStorage 키도 B06과 **공유**한다 — 두 화면은 같은 그림의 두 창이라 @@ -173,8 +173,11 @@ function readVisible(): Set { if (!raw) return new Set(DEFAULT_VISIBLE); const parsed = JSON.parse(raw) as unknown; // 기준 키는 라디오라 항상 하나로 눌러 맞춘다(B06 읽기와 같은 규칙). + // 종단 기준은 안 그림(354-2) — 저장된 범례가 종단이면 횡단으로 맞춘다. return Array.isArray(parsed) - ? normalizeVisibleBasis(new Set(parsed.map(String))) + ? normalizeVisibleBasis( + new Set(parsed.map((key) => (key === "longitudinal" ? "cross" : String(key)))), + ) : new Set(DEFAULT_VISIBLE); } catch { return new Set(DEFAULT_VISIBLE); @@ -370,7 +373,7 @@ export function createRouteMassHaulPanel( overlay.hidden = !open; syncHandlePosition(); // 접혀 있어도 상단줄에는 값이 서야 한다 — 이때는 **횡단 기준만** 적분한다. - // 펼친 경우에는 아래에서 두 기준을 내므로 그 결과로 총괄값을 낸다(계산 한 번). + // 펼친 경우에는 아래에서 낸 계열로 총괄값을 낸다(계산 한 번). if (!open) { reportSummary(params); return; @@ -400,7 +403,8 @@ export function createRouteMassHaulPanel( .map((chainage) => `${chainage.toFixed(2)} m`) .join(" · ")}${stale.length > 5 ? " …" : ""}` : "다시 계산 중…"; - // B06과 같은 계산 — 횡단 기준(정식)과 종단 기준(개략 비교)을 함께 낸다. + // B06과 같은 계산 — 횡단 기준(정식)만 낸다. 종단 기준은 안 셈 · 안 그림 · 범례 단추 없음 + // (354-2 · 계산 코드는 공용에 남김). // 결과를 아껴 두지 **않는다** — 횡단 설계가 제자리에서 갈리므로 객체 동일성으로는 // 바뀐 것을 못 잰다(2026-09-04 캐시를 넣었다가 곡선이 안 따라와 되돌림). const series: MassHaulSeries[] = computeMassHaulSeries( @@ -408,6 +412,7 @@ export function createRouteMassHaulPanel( params.crossSections, context.conversion, context.naturalSpoilMinSlope, + ["cross"], ); // 총괄값은 방금 낸 계열에서 꺼낸다 — 접힘 경로처럼 따로 적분하지 않는다. emitSummary(series); diff --git a/resources/tester/helper_354_2_masshaul_cross_only.cjs b/resources/tester/helper_354_2_masshaul_cross_only.cjs new file mode 100644 index 000000000..8a688c9a6 --- /dev/null +++ b/resources/tester/helper_354_2_masshaul_cross_only.cjs @@ -0,0 +1,57 @@ +/* 354-2 종단 기준 숨김 — 진짜 `readVisible`(B05 유토곡선) · `normalizeVisibleBasis` · + * `computeMassHaulSeries`(공용)를 잘라 돌려 JSON 으로 냄. + * · 저장된 범례 = 종단 → 횡단으로 맞춤 · 분배 켬은 그대로 + * · 기준 ["cross"] = 횡단 계열 하나 · 기본(묶음 단계) = 두 기준 그대로 */ +const fs = require("fs"); +const path = require("path"); +const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); + +const ROOT = path.join(__dirname, "..", ".."); +const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), "utf8"); +const transpile = (code) => + ts.transpileModule(code, { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, + }).outputText; +const block = (src, head) => { + const at = src.indexOf(head); + if (at < 0) throw new Error(`없음: ${head}`); + return src.slice(at, src.indexOf("\n}", at) + 2).replace(/^export /, ""); +}; + +const util = read("common_util", "common_util_mass_haul.ts"); +const mass = read("B05_Profile", "B05_Profile_UI_Profile_MassHaul.ts"); +let stored = null; +const src = [ + 'const SERIES_BASES = ["cross", "longitudinal"];', + 'const DEFAULT_VISIBLE = ["cross"];', + block(util, "export function normalizeVisibleBasis("), + block(mass, "function readVisible(): Set {"), + block(util, "export function computeMassHaulSeries("), +].join("\n"); +const api = new Function( + "readStateRaw", + "designedSections", + "crossAreaSamples", + "longitudinalAreaSamples", + "integrate", + `${transpile(src)}; return { readVisible, computeMassHaulSeries };`, +)( + () => stored, + (sections) => sections, + () => [{ basis: "cross" }], + () => [{ basis: "longitudinal" }], + (samples) => ({ from: samples[0].basis }), +); + +const visible = (raw) => ((stored = raw), [...api.readVisible()].sort()); +process.stdout.write( + JSON.stringify({ + longitudinal: visible('["longitudinal"]'), + longitudinalBalance: visible('["longitudinal","balance"]'), + cross: visible('["cross"]'), + empty: visible(null), + broken: visible("{"), + crossOnly: api.computeMassHaulSeries({}, [], {}, undefined, ["cross"]).map((s) => s.key), + both: api.computeMassHaulSeries({}, [], {}).map((s) => s.key), + }), +); diff --git a/resources/tester/test_354_2_masshaul_cross_only.py b/resources/tester/test_354_2_masshaul_cross_only.py new file mode 100644 index 000000000..fe84be732 --- /dev/null +++ b/resources/tester/test_354_2_masshaul_cross_only.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +"""354-2 유토곡선 종단 기준 숨김 — 안 셈 · 안 그림 · 범례 단추 없음(코드 남김). + +- 그리기 = 횡단 기준만(`["cross"]`) · 범례는 낸 계열마다라 종단 단추가 안 섬. +- 저장된 범례가 종단(`longitudinal`)이면 횡단으로 맞춤 · 분배 켬은 그대로. +- 계산 묶음 단계(MassHaul_Stage)는 두 기준 그대로 · 공용 계산 코드 남김. + +`helper_354_2_masshaul_cross_only.cjs` 가 진짜 TS 를 잘라 돌림. +""" + +import json +import os +import re +import subprocess +from pathlib import Path + +import pytest + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = Path(HERE).parents[1] + + +@pytest.fixture(scope="module") +def r(): + proc = subprocess.run( + ["node", os.path.join(HERE, "helper_354_2_masshaul_cross_only.cjs")], + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + assert proc.returncode == 0, proc.stderr + return json.loads(proc.stdout) + + +def test_저장된_종단_범례는_횡단으로(r): + assert r["longitudinal"] == ["cross"] + assert r["longitudinalBalance"] == ["balance", "cross"] + assert r["cross"] == ["cross"] + assert r["empty"] == ["cross"] and r["broken"] == ["cross"] + + +def test_기준_횡단만_계산(r): + assert r["crossOnly"] == ["cross"] + assert r["both"] == ["cross", "longitudinal"] # 공용 기본값 = 묶음 단계 그대로 + + +def test_그리기는_횡단만_부름(): + mass = (ROOT / "B05_Profile/B05_Profile_UI_Profile_MassHaul.ts").read_text(encoding="utf-8") + calls = re.findall(r"computeMassHaulSeries\(([^;]*?)\)", mass, re.S) + assert calls and all('["cross"]' in call for call in calls) + + +def test_묶음_단계와_공용_코드는_그대로(): + stage = (ROOT / "B05_Profile/logic/B05_Profile_MassHaul_Stage.ts").read_text(encoding="utf-8") + assert '["cross"]' not in stage + util = (ROOT / "common_util/common_util_mass_haul.ts").read_text(encoding="utf-8") + assert 'const SERIES_BASES: MassHaulBasis[] = ["cross", "longitudinal"];' in util + assert "longitudinalAreaSamples(longitudinal, sections)" in util