From ca37861159583305231d363ad7fd9d1d4960acec Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 10 Oct 2026 15:40:33 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06):=20=ED=9A=A1=EB=8B=A8=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=A9=ED=96=A5=ED=82=A4=EA=B0=80=20=EA=B4=80?= =?UTF-8?q?=EC=9D=84=20=EB=AA=B0=EB=9E=98=20=EC=98=AE=EA=B8=B0=EB=8D=98=20?= =?UTF-8?q?=ED=9D=A0=20=C2=B7=20=EC=B8=A1=EC=A0=90=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=AC=BC=20=ED=8C=90=EC=A0=95=20=ED=8F=AD=20?= =?UTF-8?q?0.006=20=C2=B7=20=EB=85=B8=EC=84=A0=20=EB=B0=96=20=EB=BA=8C(219?= =?UTF-8?q?-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 까닭: 관 · 도수로/돌붙임 띠 고름은 조정창이 없어 방향키를 받는 쪽이 없음 → 종단도 창 듣개가 받아 고른 관 측점 0.1 m 이동 → [저장] 때 pipe_points +0.1(192 · 213 숨은 측점 씨앗) - 종단도 좌우 이동 = 마지막 누름이 종단도 안일 때만(arrowsBelongToProfile 한 벌 · 새 창 종단도 누름도 closest 로 가림) - 관 · 도수로 띠 고름 동안 방향키 삼킴(swallowsArrows · 종단도를 누르면 종단도 몫) - 측점 없는 구조물 판정 폭 = SAME_STATION_SNAP_M(0.006) · 노선 밖(0 미만 · 끝 넘음) 뺌 - 새 시험 test_219_1_arrow_owner · test_b06_missing_stations 폭 0.006 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU --- .../B05_Profile_UI_Profile_Panel_Tools.ts | 3 + B06_Section/B06_Section_Router_Stations.py | 13 +- .../B06_Section_UI_Cross_Panel_Base.ts | 12 +- .../B06_Section_UI_Cross_Structure_Panel.ts | 14 +- resources/tester/helper_219_1_arrow_owner.cjs | 177 ++++++++++++++++++ resources/tester/test_219_1_arrow_owner.py | 129 +++++++++++++ resources/tester/test_b06_missing_stations.py | 4 +- 7 files changed, 345 insertions(+), 7 deletions(-) create mode 100644 resources/tester/helper_219_1_arrow_owner.cjs create mode 100644 resources/tester/test_219_1_arrow_owner.py diff --git a/B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts b/B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts index d50323666..33bfb4a79 100644 --- a/B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts +++ b/B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts @@ -26,6 +26,7 @@ import { tiltStraightRun, } from "./B05_Profile_UI_Profile_Straighten"; import type { IrregularStation } from "./B05_Profile_UI_IrregularStations"; +import { arrowsBelongToProfile } from "../B06_Section/B06_Section_UI_Cross_Panel_Base"; /** 방향키 한 번에 움직이는 양(m) — 계획고·누가거리 모두 같다(사용자 확정). */ const KEY_STEP_M = 0.1; @@ -200,6 +201,8 @@ export function createPanelTools(ctx: PanelToolsContext): PanelTools { ctx.applyEdits(adjustStation(base, ctx.edits(), chainage, delta)); return true; } + // 좌우 이동 = 마지막 누름이 종단도 안일 때만 — 횡단 카드 · 3D 를 누른 뒤 방향키가 관을 몰래 옮겼음(219-1) + if (!arrowsBelongToProfile()) return false; // 옮기는 동안에는 처음 잡은 측점을 계속 쓴다 — 관 측점 id 가 누가거리로 만들어져 // (`pipe-85.59`) 한 번 옮기면 선택이 풀리고 두 번째 키부터 먹지 않았다(2026-09-04 실측). const station = holdStation ?? selectedIrregular(); diff --git a/B06_Section/B06_Section_Router_Stations.py b/B06_Section/B06_Section_Router_Stations.py index df1c4eb4e..3c8172d2c 100644 --- a/B06_Section/B06_Section_Router_Stations.py +++ b/B06_Section/B06_Section_Router_Stations.py @@ -20,6 +20,7 @@ from __future__ import annotations import asyncio import logging +import math from pathlib import Path from typing import Any from uuid import UUID @@ -28,6 +29,7 @@ from fastapi import APIRouter from fastapi.responses import JSONResponse from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path +from B05_Profile.B05_Profile_Engine_Grade_Alignment import SAME_STATION_SNAP_M from B05_Profile.B05_Profile_Repository import ( get_latest_route, get_surface_crs_epsg, @@ -41,10 +43,9 @@ logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/projects", tags=["B06 Section"]) -#: 「그 자리에 측점이 있다」고 볼 거리(m) — 측점 생성의 스냅 폭과 같음 -#: (`B05_Profile_Engine_Sections_Core` · 같은 횡단 이름이 되는 0.005m 안만 한 측점). +#: 「그 자리에 측점이 있다」고 볼 거리(m) — 같은 측점 폭(213-1 · 반올림 차이 0.006m) 한 벌(219-1). #: 넓으면 조금 옮긴 관(769.28 → 769.38)을 「측점 있음」으로 세어 측점이 옛 자리에 남음(192-1). -STATION_MATCH_TOLERANCE_M = 0.005 +STATION_MATCH_TOLERANCE_M = SAME_STATION_SNAP_M SNAPSHOT_MISSING = ( "지표 샘플링 조건을 찾을 수 없어 측점을 만들 수 없음 — 1단계(지표 확정)를 마친 뒤" @@ -96,9 +97,15 @@ def _missing_marks(project_root: Path, route_data_path: str) -> list[dict[str, A if not extras: return [] existing = _station_chainages(project_root) + # 노선 밖(0 미만 · 끝 넘음) = 측점을 못 세움 — 판정에서 뺌(219-1 · 길이 모르면 끝은 안 봄) + end_m = sum(math.dist(a[:2], b[:2]) for a, b in zip(polyline, polyline[1:])) missing = [] for chainage, label in extras: value = float(chainage) + if value < -STATION_MATCH_TOLERANCE_M or ( + end_m > 0 and value > end_m + STATION_MATCH_TOLERANCE_M + ): + continue if any(abs(value - other) <= STATION_MATCH_TOLERANCE_M for other in existing): continue missing.append({"chainage_m": round(value, 3), "label": label}) diff --git a/B06_Section/B06_Section_UI_Cross_Panel_Base.ts b/B06_Section/B06_Section_UI_Cross_Panel_Base.ts index 08da4506a..6833d8a20 100644 --- a/B06_Section/B06_Section_UI_Cross_Panel_Base.ts +++ b/B06_Section/B06_Section_UI_Cross_Panel_Base.ts @@ -80,7 +80,9 @@ if (typeof document !== "undefined") document.addEventListener( "pointerdown", (event) => { - lastPointer = event.target instanceof Element ? event.target : null; + // 새 창 종단도가 넘긴 누름은 다른 창 요소라 instanceof Element 가 거짓 — closest 로 가림(219-1) + const target = event.target as Element | null; + lastPointer = typeof target?.closest === "function" ? target : null; }, true, ); @@ -90,6 +92,14 @@ export function arrowsBelongToProfile(): boolean { return !!lastPointer?.closest(".b05-route-profile"); } +/** 조정창 없는 고름(관 · 도수로/돌붙임 띠)이 방향키를 삼키나 — 그 동안 방향키는 아무것도 안 옮김. + * 안 삼키면 종단도 듣개가 받아 관 기준측점을 0.1 m 씩 몰래 옮겼음(219-1). 종단도를 눌렀으면 종단도 몫. */ +export function swallowsArrows(side: string | undefined, key: string): boolean { + return ( + (side === "pipe" || side === "chute") && key.startsWith("Arrow") && !arrowsBelongToProfile() + ); +} + /** 고른 구조물의 도형 — 벽 · 집수정 · 측벽은 `culvert` 계열, BOX 끝은 판정면이 켜진다. */ const ACTIVE_SHAPES = '[class*="culvert"].is-active, .b06-chart__box-hit.is-active'; diff --git a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts index b5549919b..0dec26028 100644 --- a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts +++ b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts @@ -21,6 +21,7 @@ import type { SpanValues } from "./B06_Section_UI_Cross_Culvert_Wire"; import { L } from "./B06_Section_UI_Section_Common"; import { arrowsBelongToProfile, + swallowsArrows, bindDragHandle, dragReadout, makeButton, @@ -437,6 +438,14 @@ export function buildStructurePanel( document.removeEventListener("keydown", onArrowKey); return; } + // 관 · 도수로 띠 고름(조정창 없음) = 방향키 삼킴 · 보기가 보일 때만(219-1) + if ( + swallowsArrows(root.dataset.side, event.key) && + root.parentElement?.getClientRects().length + ) { + event.preventDefault(); + return; + } const slot = ARROW_SLOT[event.key]; // 보기가 숨었으면(한 페이지 두 보기 · 계획서 24-3a) 다른 보기의 방향키를 먹지 않는다. if (!slot || root.classList.contains("is-hidden") || !root.getClientRects().length) return; @@ -503,7 +512,10 @@ export function buildStructurePanel( root.dataset.side = pick === "pipe" || pick === "chute" ? pick : ""; document.removeEventListener("keydown", onArrowKey); drag.detach(); - if (!key) return; + if (!key) { + if (!opts?.dock && root.dataset.side) document.addEventListener("keydown", onArrowKey); + return; + } if (!opts?.dock) { document.addEventListener("keydown", onArrowKey); drag.attach(); diff --git a/resources/tester/helper_219_1_arrow_owner.cjs b/resources/tester/helper_219_1_arrow_owner.cjs new file mode 100644 index 000000000..9c2b2dc24 --- /dev/null +++ b/resources/tester/helper_219_1_arrow_owner.cjs @@ -0,0 +1,177 @@ +/* PLAN 219-1 단위검증 헬퍼 — 횡단 보기 방향키가 관을 몰래 옮기던 흠. + * 실제 TS(`B05_Profile_UI_Profile_Panel_Tools` · `B06_Section_UI_Cross_Panel_Base`)를 그 자리에서 + * 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌. 짝 = `test_219_1_arrow_owner.py`. + * 창 · 문서 듣개는 흉내(누름 = 문서 잡기 단계 · 키 = 문서 듣개(횡단 조정창) 뒤 창 듣개(종단도) 차례). + */ +const fs = require("fs"); +const path = require("path"); +const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); + +const ROOT = path.join(__dirname, "..", ".."); +const cache = new Map(); + +const listeners = { window: {}, document: {} }; +const on = (where) => (type, fn) => (listeners[where][type] ??= new Set()).add(fn); +const off = (where) => (type, fn) => listeners[where][type]?.delete(fn); +const session = new Map(); +globalThis.sessionStorage = { + getItem: (k) => session.get(k) ?? null, + setItem: (k, v) => session.set(k, String(v)), + removeItem: (k) => session.delete(k), +}; +globalThis.window = { + addEventListener: on("window"), + removeEventListener: off("window"), + setTimeout: () => 0, + clearTimeout: () => undefined, + setInterval: () => 0, + clearInterval: () => undefined, +}; +globalThis.document = { addEventListener: on("document"), removeEventListener: off("document") }; + +// 화면 계산 · 이력 · 도구줄은 이 흠과 무관 — 쓰는 꼴만 흉내 +const calls = { move: [], edits: 0 }; +const STUBS = { + "./B05_Profile_UI_Profile_Alignment": { adjustStation: () => (calls.edits++, {}) }, + "./B05_Profile_UI_Profile_Edit": { HOLD_DELAY_MS: 500, HOLD_INTERVAL_MS: 100 }, + "./B05_Profile_UI_Profile_History": { + createProfileHistory: () => ({ + record: () => undefined, + pause: () => undefined, + resume: () => undefined, + undo: () => undefined, + redo: () => undefined, + canUndo: () => false, + canRedo: () => false, + }), + }, + "./B05_Profile_UI_Profile_Tools": { + createProfileTools: () => ({ mode: () => "none", clear: () => undefined }), + }, + "./B05_Profile_UI_Profile_Straighten": {}, +}; + +function loadTs(file) { + const full = path.resolve(file); + if (cache.has(full)) return cache.get(full); + const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, + }).outputText; + const box = { exports: {} }; + cache.set(full, box.exports); + const localRequire = (request) => + STUBS[request] ?? + (request.startsWith(".") + ? loadTs(path.join(path.dirname(full), `${request}.ts`)) + : require(request)); + new Function("exports", "module", "require", js)(box.exports, box, localRequire); + cache.set(full, box.exports); + return box.exports; +} + +const tools = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Profile_Panel_Tools.ts")); +const base = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Panel_Base.ts")); + +// 엄상준 263.21 관 — 횡단 보기에서 그 관 카드가 골라져 종단도도 그 관 측점을 고른 상태 +const pipe = { id: "p1", chainage_m: 263.21, origin: "pipe" }; +tools.createPanelTools({ + root: { isConnected: true }, + base: () => ({}), + alignment: () => null, + edits: () => ({}), + applyEdits: () => undefined, + selectedStationId: () => "irregular:p1", + irregularStations: () => [pipe], + stationIdOf: (s) => `irregular:${s.id}`, + moveStation: (s, to) => calls.move.push([s.chainage_m, to]), + drainage: { pipeChainages: () => [], setPipeChainages: () => undefined }, + restore: () => undefined, + restoreSaved: () => undefined, + canRestoreSaved: () => false, + refresh: () => undefined, +}); + +/** 누른 자리 — 종단도 안이면 closest 가 그 판을 줌(새 창 요소처럼 Element 가 아닌 것도). */ +const spot = (inProfile) => ({ + closest: (sel) => (inProfile && sel === ".b05-route-profile" ? {} : null), +}); +const press = (target) => listeners.document.pointerdown?.forEach((fn) => fn({ target })); + +/** 횡단 조정창 흉내 — 관 · 도수로 띠를 고른 동안 `swallowsArrows` 면 삼킴(Cross_Structure_Panel 배선과 같음). */ +let side = ""; +const crossPanel = (event) => { + if (base.swallowsArrows(side, event.key)) event.preventDefault(); +}; +listeners.document.keydown = new Set([crossPanel]); + +function key(name) { + const event = { + key: name, + target: null, + repeat: false, + defaultPrevented: false, + preventDefault() { + this.defaultPrevented = true; + }, + }; + listeners.document.keydown.forEach((fn) => fn(event)); + listeners.window.keydown?.forEach((fn) => fn(event)); + listeners.window.keyup?.forEach((fn) => fn({ key: name })); + return event.defaultPrevented; +} + +function run(steps) { + calls.move = []; + calls.edits = 0; + steps(); + return { move: calls.move, edits: calls.edits }; +} + +const out = { + // 횡단 카드를 누른 뒤(조정창 없음) ← → = 안 옮김 — 옛 흠: 263.21 → 263.31 + cardThenArrows: run(() => { + press(spot(false)); + key("ArrowRight"); + key("ArrowLeft"); + }), + // 종단도를 누른 뒤 ← → = 지금처럼 0.1 m 이동 + profileThenArrows: run(() => { + press(spot(true)); + key("ArrowRight"); + }), + // 새 창 종단도(다른 창 요소)도 종단도 안 + popoutThenArrows: run(() => { + press(Object.create(null, { closest: { value: spot(true).closest } })); + key("ArrowLeft"); + }), + // 횡단에서 도수로 띠 고름 · 카드 쪽 누름 = 방향키 넷 다 아무것도 안 옮김(계획고도) + chute: run(() => { + side = "chute"; + press(spot(false)); + ["ArrowRight", "ArrowLeft", "ArrowUp", "ArrowDown"].forEach(key); + }), + pipeSide: run(() => { + side = "pipe"; + press(spot(false)); + key("ArrowRight"); + key("ArrowUp"); + }), + // 도수로 고른 채 종단도를 누르면 방향키는 종단도 몫 + chuteThenProfile: run(() => { + side = "chute"; + press(spot(true)); + key("ArrowRight"); + }), + // 조정창 있는 벽 고름 · 고름 없음 = 삼키지 않음(조정창 · 종단도 제 길) + swallow: { + wall: base.swallowsArrows("inlet", "ArrowRight"), + none: base.swallowsArrows("", "ArrowRight"), + letter: base.swallowsArrows("chute", "a"), + }, +}; +side = ""; +out.cardUpArrow = run(() => { + press(spot(false)); + key("ArrowUp"); // 고름 없는 카드 ▲ = 계획고(24-3b 그대로) +}); +process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_219_1_arrow_owner.py b/resources/tester/test_219_1_arrow_owner.py new file mode 100644 index 000000000..f9884020d --- /dev/null +++ b/resources/tester/test_219_1_arrow_owner.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +"""PLAN 219-1 — 횡단 보기 방향키가 관을 몰래 옮기던 흠 · 측점 없는 구조물 판정 폭. + +까닭: 횡단 보기에서 관 · 도수로/돌붙임 띠를 고르면 조정창이 없어 방향키를 받는 쪽이 없고, +종단도 창 듣개(`_Profile_Panel_Tools`)가 받아 고른 관 측점을 0.1 m 씩 옮김 → [저장] 때 +pipe_points +0.1(엄상준 263.21 → 263.31 · 226fc805 769.28 → 769.38) → 192 · 213 숨은 측점 씨앗. +고침: ① 좌우 이동은 마지막 누름이 종단도 안일 때만(`arrowsBelongToProfile` 한 벌) ② 관 · 도수로 +띠 고름 동안 방향키 삼킴(`swallowsArrows`) ③ 판정 폭 = 같은 측점 폭 0.006 · 노선 밖 뺌. +TS 는 `helper_219_1_arrow_owner.cjs` 가 Node 로 돌린다. +""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.abspath(os.path.join(HERE, "..", "..")) +if ROOT not in sys.path: + sys.path.insert(0, ROOT) + +import B05_Profile.B05_Profile_Engine_Sections as sections # noqa: E402 +from B05_Profile.B05_Profile_Engine_Grade_Alignment import SAME_STATION_SNAP_M # noqa: E402 +from B06_Section.B06_Section_Router_Stations import ( # noqa: E402 + STATION_MATCH_TOLERANCE_M, + _missing_marks, +) + +needs_node = pytest.mark.skipif( + not os.path.isdir(os.path.join(ROOT, "config", "node_modules")), + reason="프론트엔드 의존성 미설치", +) + + +@pytest.fixture(scope="module") +def out() -> dict: + proc = subprocess.run( # noqa: S603 — 고정 실행 파일 + ["node", os.path.join(HERE, "helper_219_1_arrow_owner.cjs")], + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + assert proc.returncode == 0, proc.stderr + return json.loads(proc.stdout) + + +def _read(*parts: str) -> str: + with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle: + return handle.read() + + +# ── ① · ② 방향키 소속 ───────────────────────────────────────────────── +@needs_node +def test_횡단_카드를_누른_뒤_좌우키는_관을_안_옮김(out) -> None: + assert out["cardThenArrows"] == {"move": [], "edits": 0} + + +@needs_node +def test_종단도를_누른_뒤_좌우키는_지금처럼_이동(out) -> None: + assert out["profileThenArrows"]["move"] == [[263.21, 263.31]] + assert out["popoutThenArrows"]["move"] == [[263.21, 263.11]] # 새 창 종단도도 + assert out["chuteThenProfile"]["move"] == [[263.21, 263.31]] + + +@needs_node +def test_도수로_관_고름_동안_방향키는_아무것도_안_옮김(out) -> None: + assert out["chute"] == {"move": [], "edits": 0} # 계획고(▲▼)도 + assert out["pipeSide"] == {"move": [], "edits": 0} + assert out["swallow"] == {"wall": False, "none": False, "letter": False} + assert out["cardUpArrow"] == {"move": [], "edits": 1} # 고름 없는 카드 ▲ = 계획고 그대로 + + +def test_배선() -> None: + tools = _read("B05_Profile", "B05_Profile_UI_Profile_Panel_Tools.ts") + assert "if (!arrowsBelongToProfile()) return false;" in tools + assert 'from "../B06_Section/B06_Section_UI_Cross_Panel_Base"' in tools + panel = _read("B06_Section", "B06_Section_UI_Cross_Structure_Panel.ts") + assert "swallowsArrows(root.dataset.side, event.key)" in panel + assert ( + 'if (!opts?.dock && root.dataset.side) document.addEventListener("keydown", onArrowKey);' + in panel + ) + base = _read("B06_Section", "B06_Section_UI_Cross_Panel_Base.ts") + assert 'typeof target?.closest === "function"' in base + + +# ── ③ 측점 없는 구조물 판정 ─────────────────────────────────────────── +def test_판정_폭은_같은_측점_폭_한_벌() -> None: + assert STATION_MATCH_TOLERANCE_M == SAME_STATION_SNAP_M == 0.006 + + +def test_판정_폭과_노선_밖(tmp_path: Path, monkeypatch) -> None: + target = tmp_path / "B06_Section" / "longitudinal" / "longitudinal.json" + target.parent.mkdir(parents=True) + target.write_text( + json.dumps( + {"stations": [{"chainage_m": 0.0}, {"chainage_m": 50.0}, {"chainage_m": 100.0}]} + ), + encoding="utf-8", + ) + # 노선 = 수평 길이 100m + monkeypatch.setattr( + sections, "_load_route_polyline", lambda *_: [[0, 0, 5], [60, 0, 9], [100, 0, 7]] + ) + monkeypatch.setattr(sections, "_load_pipe_points", lambda *_: []) + extras = [ + (-0.5, "시작 앞"), + (50.0055, "반올림 차이"), # 옛 0.005 폭은 「측점 없음」으로 셌음 + (70.0, "관"), + (100.004, "끝 반올림"), + (100.3, "끝 넘음"), + ] + monkeypatch.setattr(sections, "resolve_extra_stations", lambda *_: extras) + assert _missing_marks(tmp_path, "route.csv") == [{"chainage_m": 70.0, "label": "관"}] + + +def test_노선_길이를_모르면_끝은_안_봄(tmp_path: Path, monkeypatch) -> None: + monkeypatch.setattr(sections, "_load_route_polyline", lambda *_: []) + monkeypatch.setattr(sections, "_load_pipe_points", lambda *_: []) + monkeypatch.setattr( + sections, "resolve_extra_stations", lambda *_: [(500.0, "관"), (-1.0, "앞")] + ) + assert _missing_marks(tmp_path, "route.csv") == [{"chainage_m": 500.0, "label": "관"}] diff --git a/resources/tester/test_b06_missing_stations.py b/resources/tester/test_b06_missing_stations.py index 81a01534b..48d0ac4f0 100644 --- a/resources/tester/test_b06_missing_stations.py +++ b/resources/tester/test_b06_missing_stations.py @@ -48,8 +48,8 @@ def test_측점이_없으면_빈_목록(tmp_path: Path) -> None: def test_같은_자리_판정은_측점_스냅_폭() -> None: - """측점 생성 스냅 폭(0.005m · 같은 횡단 이름)과 같음 — 0.5m 면 0.1m 옮긴 관을 놓침(192-1).""" - assert STATION_MATCH_TOLERANCE_M == 0.005 + """같은 측점 폭(213-1 · 0.006m) 한 벌(219-1) — 0.5m 면 0.1m 옮긴 관을 놓침(192-1).""" + assert STATION_MATCH_TOLERANCE_M == 0.006 def test_샘플링_조건이_없으면_None(tmp_path: Path) -> None: