fix(B06): 횡단 보기 방향키가 관을 몰래 옮기던 흠 · 측점 없는 구조물 판정 폭 0.006 · 노선 밖 뺌(219-1)
- 까닭: 관 · 도수로/돌붙임 띠 고름은 조정창이 없어 방향키를 받는 쪽이 없음 → 종단도 창 듣개가 받아 고른 관 측점 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
@@ -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": "관"}]
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user