Files
Aislo/resources/tester/test_244_2_b08_milestone_diagram.py
T
eomsangdonandClaude Opus 5.5 2584325b16 feat(B08): 거리이정표 다이어그램 — 기준점 연결 점 · 주소 줄바꿈 · 선 글 두 줄 · 거리 비례 선 길이(254-2)
- 연결 점 = 거래처 · 기준점 상자 왼쪽 위 안쪽 · 기준점 = 시점 · 다른 기준점에 놓음(돌기면 취소 + 알림)
- 기준점 상자 주소 · 주소 줄바꿈(최대 3줄) + 상자 높이 = 내용 · 항목 글 13px
- 선 글 = 구간마다 두 줄(도로 구분 / km) · 처음 자리 선 길이 = 100 + 90·log10(1 + km)(100 ~ 320)
- 밀어 냄 = 놓인 상자 · 놓인 선 둘 다 피함
- 시험 = test_254_2 새로 · 244 · 250 기대값(두 줄 · 짧아진 선) · 244_9 node 입력 = stdin

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-11 14:07:14 +09:00

166 lines
7.2 KiB
Python

"""244-2 B08 거리이정표 다이어그램 — 방위 자리(8방 · 경위도 방위각 · 빈 방위 차례 ·
같은 방위 나란히) · 선 글(도로 구분 km) · 상자 누름 · [+] 항목 추가 길.
자리 계산 = `B08_Quantity_Milestone_Layout.ts` 를 node 로 돌림(화면 = 북 위 · SVG y 아래 = 남).
"""
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 B08_Quantity.B08_Quantity_Milestone_Calc import migrate_milestone # noqa: E402
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
def _layout(data, site=None, mid_km=0.75):
data = migrate_milestone(data) # 254 옛 꼴(base · via_base) → bases · to(옛 기준점 = b1)
src = (ROOT / "B08_Quantity" / "B08_Quantity_Milestone_Layout.ts").read_text(encoding="utf-8")
body = (
f"\nconst d = {json.dumps(data, ensure_ascii=False)};"
f"\nconsole.log(JSON.stringify(layoutMilestone(d, {json.dumps(site)}, {mid_km})));"
)
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-e", IMPORTS.sub("", src) + body],
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def _route(rid, name, dir=None, via=True, legs=None, lon=None, lat=None):
return {"id": rid, "name": name, "address": "", "via_base": via, "dir": dir, "lon": lon,
"lat": lat, "legs": legs or [],
"rows": [{"item": "", "dest": "start", "note": ""}]} # fmt: skip
def _data(routes, base=None):
return {
"site": {"mid_station_m": None, "mid_road": "비포장"},
"base": base or {"enabled": False, "name": "", "address": "", "dir": None, "legs": []},
"routes": routes,
}
def _center(layout, bid):
box = next(b for b in layout["boxes"] if b["id"] == bid)
return box["x"] + box["w"] / 2, box["y"] + box["h"] / 2
def test_공사지_컨테이너_시점_중점_선():
lay = _layout(_data([]))
kinds = {b["id"]: b["kind"] for b in lay["boxes"]}
assert kinds == {"site": "site", "start": "start", "mid": "mid"}
sx, _ = _center(lay, "start")
mx, _ = _center(lay, "mid")
assert sx < mx # 시점 왼 · 중점 오른 · 한 컨테이너 안
site = next(b for b in lay["boxes"] if b["id"] == "site")
for bid in ("start", "mid"):
b = next(x for x in lay["boxes"] if x["id"] == bid)
assert site["x"] <= b["x"] and b["x"] + b["w"] <= site["x"] + site["w"]
assert lay["lines"] == [
{**lay["lines"][0], "from": "start", "to": "mid", "labels": ["비포장\n0.75km"]} # 254 두 줄
]
def test_8방_자리_기준점_없음_공사지_기준():
lay = _layout(
_data([_route("e", "동쪽", "E"), _route("n", "북쪽", "N"), _route("sw", "남서", "SW")])
)
cx, cy = _center(lay, "site")
ex, ey = _center(lay, "e")
nx, ny = _center(lay, "n")
wx, wy = _center(lay, "sw")
# 254 선 길이 = km 비례(빈 km = 최소 100) — 공사지 테두리(가로 190 · 세로 53)에서 잼
assert ex > cx + 200 and abs(ey - cy) < 1
assert ny < cy - 150 and abs(nx - cx) < 1
assert wx < cx and wy > cy
line = next(ln for ln in lay["lines"] if ln["from"] == "e")
assert line["to"] == "start" # 기준점 없으면 시점으로
def test_기준점_기준_방위_선_글():
base = {"enabled": True, "name": "병곡리 968-31", "address": "", "dir": "W",
"legs": [{"road": "1차포장", "km": 0.6}, {"road": "비포장", "km": 0.8}]} # fmt: skip
lay = _layout(
_data(
[
_route("g", "거창군청", "N", legs=[{"road": "2차포장", "km": 23.8}]),
_route("k", "김천역", "E", via=False),
],
base,
), # fmt: skip
)
cx, cy = _center(lay, "site")
bx, by = _center(lay, "b1")
assert bx < cx - 200 and abs(by - cy) < 1 # 기준점 = 공사지 서쪽
gx, gy = _center(lay, "g")
assert abs(gx - bx) < 1 and gy < by - 200 # 거창군청 = 기준점 북쪽
kx, _ = _center(lay, "k")
assert kx > cx + 200 # 기준점 안 거침 = 공사지 동쪽
labels = {(ln["from"], ln["to"]): ln["labels"] for ln in lay["lines"]}
assert labels[("g", "b1")] == ["2차포장\n23.8km"] # 254 두 줄(도로 구분 / km)
assert labels[("b1", "start")] == ["1차포장\n0.6km", "비포장\n0.8km"]
assert labels[("k", "start")] == []
assert next(b for b in lay["boxes"] if b["id"] == "b1")["label"] == "병곡리 968-31"
def test_경위도_있으면_방위각이_8방보다_앞섬():
site = {"start_lonlat": [127.8, 35.7], "mid_lonlat": None}
lay = _layout(_data([_route("e", "동쪽 좌표", "N", lon=127.9, lat=35.7)]), site)
cx, cy = _center(lay, "site")
ex, ey = _center(lay, "e")
assert ex > cx + 200 and abs(ey - cy) < 1 # 고른 방위 N 보다 좌표(동) 먼저
def test_빈_방위_차례_같은_방위_나란히():
lay = _layout(
_data(
[_route("a", "가", "E"), _route("b", "나", "E"), _route("c", "다"), _route("d", "라")]
)
)
(ax, ay), (bx, by) = _center(lay, "a"), _center(lay, "b")
assert abs(ax - bx) < 1 and abs(ay - by) >= 60 # 같은 방위 = 옆으로 나란히
cx, cy = _center(lay, "c")
dx, dy = _center(lay, "d")
sx, sy = _center(lay, "site")
assert cy < sy - 150 and abs(cx - sx) < 1 # 고르지 않음 = 빈 방위 첫째(북)
assert (dx, dy) != (cx, cy) and dx < sx # 다음 빈 방위(북서)
boxes = [b for b in lay["boxes"] if b["kind"] == "route"]
for i, p in enumerate(boxes): # 업체 상자끼리 안 겹침
for q in boxes[i + 1 :]:
assert (
p["x"] + p["w"] <= q["x"] or q["x"] + q["w"] <= p["x"]
or p["y"] + p["h"] <= q["y"] or q["y"] + q["h"] <= p["y"]
) # fmt: skip
def test_화면_상자_누름_더하기_창():
ui = (ROOT / "B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts").read_text(encoding="utf-8")
# 244-4 — 상자 누름 = 모달(탭 이동 아님)
assert (
"openTab" not in ui
and "route: (id) => openPlaceModal(projectId, { add: false, id })," in ui
)
# 155 공사지 · 기준점 [+] 없앰 → 다이어그램 [요소 추가]
assert "plus(box" not in ui and 'label: "요소 추가"' in ui
assert "openPlaceModal(projectId, { add: true }, addRoute)" in ui
modal = (ROOT / "B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts").read_text(encoding="utf-8")
assert "openModal({" in modal
# 149 상자 하나 = 항목 하나 — [+ 줄] · 같은 이름 합침 없음
assert "same.rows.push" not in modal and '"+ 줄"' not in modal
page = (ROOT / "B08_Quantity/B08_Quantity_UI_Milestone.ts").read_text(encoding="utf-8")
assert "createDiagram(projectId, (init) => addRoute(projectId, init))" in page
assert "children: [diagram.root, none, table.root]" in page and "diagram.refresh();" in page
for name in ("Milestone_Layout.ts", "UI_Milestone_Diagram.ts"):
assert (
len((ROOT / f"B08_Quantity/B08_Quantity_{name}").read_text("utf-8").splitlines()) <= 700
)