- UI_Map: 바탕(위성 · 등고선) + 겹(그리기 · 거리재기) · 도구줄 부품(되돌리기 · 다시하기 · 3D · 좌 · 우 회전 · 거리재기) · B05 createMapRotation · 왼쪽 끌기 이동 끔(가운데 단추 · 휠 방향 = 설정) - 새 UI_Draw: 시점에서 점 찍어 직선 · 미리보기 길이 · 기울기 · 제약 칸(자유 · 길이 고정 · 기울기 고정 · 둘 다 · 20 m · 9 %) · 종점 붙기 · 갈래 · 구역 마크 · Enter · Esc · Backspace - 새 UI_Measure: 두 점 길이 · 기울기 · 높이 차(지형 heightAt) - logic/Plan: 선 · 갈래 · 조각 꼴 다루기로 다시 씀(옛 갈래 넷 · 이은 누가 걷음) - UI_Page · Api: 지형 범위 카카오 지도 미리보기 → [이 범위 받기] · [범위 밖 더 받기] · GET · PUT plan/network · [초기화] · 조각 연도 고름 · 캐시 판 2 - UI_3D: 지형 + 그은 선(선 고름) · 예상 코리도 · 체크 칸 걷음 - 지움: UI_Cross · UI_Edit · logic/Expect · logic/Edit · test_544_1(예상 횡단 · 예상 코리도 셈이 화면에서 걷혀 지킬 대상 없음) - 시험: 530_3 = 손 긋기 꼴 다루기 + 서버 derive 대조로 다시 씀 · 553_4 D03 부품 단언 새 화면으로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
147 lines
5.6 KiB
Python
147 lines
5.6 KiB
Python
"""D03 손 긋기 꼴 다루기(PLAN 562-3 · 563-1) — TS `logic/D03_PlanAnalysis_Plan.ts`.
|
|
|
|
조각이 선을 빈틈없이 덮음 · 마크 놓기 · 지우기 · 갈래 차례 밀기 · 갈래 붙은 점 못 뺌 · 서버 꼴 읽기 ·
|
|
화면이 보내는 꼴(선 · 마크 · 조각)을 서버 셈(`D03_PlanAnalysis_Network.derive`)이 그대로 받음.
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_530_3_plan_logic.py -q
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from D03_PlanAnalysis import D03_PlanAnalysis_Network as server # noqa: E402
|
|
|
|
TS_FILE = ROOT / "D03_PlanAnalysis" / "logic" / "D03_PlanAnalysis_Plan.ts"
|
|
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
|
|
|
_RUNNER = """
|
|
import { writeFileSync } from "node:fs";
|
|
import * as plan from "./D03_PlanAnalysis/logic/D03_PlanAnalysis_Plan.js";
|
|
const Y = 2026;
|
|
let n = plan.emptyNetwork([0, 0]);
|
|
n = plan.appendVertex(n, 0, [100, 0]);
|
|
n = plan.appendVertex(n, 0, [100, 100]);
|
|
n = plan.normalizePieces(n, Y);
|
|
const one = n;
|
|
n = plan.normalizePieces(plan.splitPiece(n, "main", 50), Y);
|
|
const split = n;
|
|
const tooClose = plan.splitPiece(n, "main", 50.5);
|
|
n = plan.normalizePieces(plan.setPieceYear(n, 1, 2027), Y);
|
|
n = plan.addBranch(n, 0, 1);
|
|
n = plan.appendVertex(n, 1, [160, 0]);
|
|
n = plan.normalizePieces(n, Y);
|
|
const branched = n;
|
|
const inserted = plan.insertVertex(n, 0, 0, [40, 0]);
|
|
const blocked = plan.dropLastVertex(inserted, 0);
|
|
const dropped = plan.dropLastVertex(n, 1);
|
|
const guard = plan.dropLastVertex(plan.dropLastVertex(n, 0), 0);
|
|
const removed = plan.normalizePieces(plan.removeMark(n, "main", 50.4, 1), Y);
|
|
const grown = plan.normalizePieces(plan.appendVertex(n, 0, [100, 150]), Y);
|
|
const read = plan.fromServer(
|
|
n.lines,
|
|
[{ line: "main", at_m: 50 }, { line: "main", at_m: 150 }],
|
|
[{ id: "p9", line: "main", from_m: 0, to_m: 50, year: 2028 }],
|
|
Y,
|
|
);
|
|
writeFileSync(process.argv[2], JSON.stringify({
|
|
one, split, tooClose, branched, inserted, blocked, dropped, guard, removed, grown, read,
|
|
marks: plan.marksOf(branched),
|
|
reached: plan.reachedEnd(n, [100, 100]),
|
|
}));
|
|
"""
|
|
|
|
|
|
def _run_ts(tmp_path: Path) -> dict:
|
|
out = tmp_path / "js"
|
|
subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
[
|
|
"node",
|
|
str(TSC),
|
|
str(TS_FILE),
|
|
"--outDir",
|
|
str(out),
|
|
"--rootDir",
|
|
str(ROOT),
|
|
"--module",
|
|
"esnext",
|
|
"--target",
|
|
"es2022",
|
|
"--moduleResolution",
|
|
"bundler",
|
|
"--ignoreConfig",
|
|
],
|
|
cwd=str(ROOT),
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
for emitted in out.rglob("*.js"):
|
|
emitted.write_text(
|
|
re.sub(r'(from\s+"\.\.?/[^"]+)"', r'\1.js"', emitted.read_text(encoding="utf-8")),
|
|
encoding="utf-8",
|
|
)
|
|
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
|
result = tmp_path / "out.json"
|
|
subprocess.run( # noqa: S603
|
|
["node", str(out / "runner.mjs"), str(result)],
|
|
cwd=str(ROOT),
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
return json.loads(result.read_text(encoding="utf-8"))
|
|
|
|
|
|
def _spans(network: dict, line: str = "main") -> list[tuple[float, float, int]]:
|
|
return [(p["from_m"], p["to_m"], p["year"]) for p in network["pieces"] if p["line"] == line]
|
|
|
|
|
|
def test_pieces_cover_and_split(tmp_path: Path) -> None:
|
|
got = _run_ts(tmp_path)
|
|
assert _spans(got["one"]) == [(0, 200, 2026)]
|
|
assert _spans(got["split"]) == [(0, 50, 2026), (50, 200, 2026)]
|
|
assert [p["id"] for p in got["split"]["pieces"]] == ["p1", "p2"] # 앞 조각이 id 를 지님
|
|
assert got["tooClose"] == got["split"] # 마크 1 m 안 = 안 놓음
|
|
assert _spans(got["removed"]) == [(0, 200, 2026)] # 마크 지움 = 앞 조각 연도
|
|
assert _spans(got["grown"]) == [(0, 50, 2026), (50, 250, 2027)] # 선이 늘면 끝 조각이 늚
|
|
assert got["reached"] is True
|
|
|
|
|
|
def test_branch_order_and_guard(tmp_path: Path) -> None:
|
|
got = _run_ts(tmp_path)
|
|
branch = got["branched"]["lines"][1]
|
|
assert branch["branch"] == {"line": "main", "vertex": 1} and branch["vertices"][0] == [100, 0]
|
|
assert _spans(got["branched"], branch["id"]) == [(0, 60, 2026)]
|
|
# 본선 앞 구간에 꺾임점을 끼우면 갈래가 붙은 차례가 한 칸 밀림
|
|
assert got["inserted"]["lines"][0]["vertices"][1] == [40, 0]
|
|
assert got["inserted"]["lines"][1]["branch"]["vertex"] == 2
|
|
assert got["blocked"]["lines"][0]["vertices"][-1] == [100, 0] # 끝 점은 갈래 없음 → 빠짐
|
|
assert "갈래가 붙은 점" in got["guard"] # 갈래가 붙은 점은 못 뺌
|
|
assert got["dropped"]["lines"][1]["vertices"] == [[100, 0]]
|
|
|
|
|
|
def test_from_server_fills_marks(tmp_path: Path) -> None:
|
|
got = _run_ts(tmp_path)
|
|
# 조각 없는 구역(마크만) = 처음 연도 · 새 id
|
|
assert _spans(got["read"]) == [(0, 50, 2028), (50, 150, 2026), (150, 200, 2026)]
|
|
assert got["read"]["pieces"][0]["id"] == "p9"
|
|
assert len({p["id"] for p in got["read"]["pieces"]}) == 4 # 갈래 조각 포함 겹침 없음
|
|
|
|
|
|
def test_server_accepts_screen_body(tmp_path: Path) -> None:
|
|
got = _run_ts(tmp_path)
|
|
body = {**got["branched"], "marks": got["marks"], "epsg": 5186}
|
|
derived = server.derive(body, 2026)
|
|
assert [(p["line"], p["from_m"], p["to_m"], p["year"]) for p in derived["pieces"]] == [
|
|
("main", 0.0, 50.0, 2026),
|
|
("main", 50.0, 200.0, 2027),
|
|
("branch-1", 0.0, 60.0, 2026),
|
|
]
|