Merge remote-tracking branch 'origin/dev' into sub_laptop_5

This commit is contained in:
2026-10-21 17:39:39 +09:00
19 changed files with 168 additions and 74 deletions
+29 -5
View File
@@ -22,13 +22,20 @@ import {
effectiveStandardCross,
readRockBoundaryDefault,
} from "../../B06_Section/B06_Section_UI_Standard_Panel";
import type {
CrossInput,
SeatedFields,
SectionSpecs,
import {
crossAtSeat,
type CrossInput,
type SeatedFields,
type SectionSpecs,
} from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
import { SEAT_HINT } from "../../B06_Section/logic/B06_Section_Route_Carry";
import type { PipelineStation } from "./logic/design_pipeline_types";
import {
SOURCE_INPUT,
type ChangeSource,
type PipelineInputs,
type PipelineStation,
} from "./logic/design_pipeline_types";
import type { DesignPipeline } from "./logic/design_pipeline";
import type { StationSeat } from "../../common_util/common_util_station_key";
export interface PipelineCrossSource {
@@ -252,3 +259,20 @@ export function pipelineCrossSource(
specs,
};
}
/** 맞춤이 다시 먹일 입력 — 지금 입력이 묶음 입력 · 넘긴 입력(follow 전) 둘 다와 다른 것. cross 는 지금 측점 자리로
* 자리 칸을 가른 뒤 견줌(`crossAtSeat` — 노선 초안 넘김이 새 자리 자리 칸만 걷은 것 = 같은 값 · 508-1c). */
export function staleInputs(
now: PipelineInputs,
given: Partial<PipelineInputs>,
pipe: DesignPipeline,
): Array<[ChangeSource, unknown]> {
const stations = pipe.output("ground")?.stations ?? [];
const seen = (key: keyof PipelineInputs, value: unknown): string =>
JSON.stringify(key === "cross" ? crossAtSeat(value as CrossInput | null, stations) : value);
return (Object.entries(SOURCE_INPUT) as Array<[ChangeSource, keyof PipelineInputs]>)
.filter(([, key]) =>
[pipe.input(key), given[key]].every((was) => seen(key, was) !== seen(key, now[key])),
)
.map(([source, key]) => [source, now[key]]);
}
+2 -4
View File
@@ -70,6 +70,7 @@ import { fetchStructureTypes } from "../../B05_Profile/B05_Profile_Api_Structure
import { forgetPageData, pagePipes, pageStructures, trackPageBusy } from "./design_page_data";
import type { PagePipelineOptions, ProfileLink } from "./design_pipeline_page_types";
import { createPageSaved } from "./design_pipeline_page_saved";
import { staleInputs } from "./design_pipeline_cross";
import {
draftDetailOf,
heldSectionDetail,
@@ -563,10 +564,7 @@ function createPagePipeline(
await pipe.settled();
if (disposed || pageSaved.canonRouteId() !== savedSource().routeId) return round; // 정본 바뀜 = restart 몫
if (facilities.saving()) return (facilities.afterSave(), round); // 저장 도중 = 옛 저장분 · 끝나면 다시
const now = inputsNow();
const stale = (Object.entries(SOURCE_INPUT) as Array<[ChangeSource, keyof PipelineInputs]>)
.filter(([, key]) => !same(now[key], pipe.input(key)) && !same(now[key], given[key]))
.map(([source, key]): [ChangeSource, unknown] => [source, now[key]]);
const stale = staleInputs(inputsNow(), given, pipe); // cross = 지금 측점 자리로 견줌(508-1c)
if (!stale.length) return round;
if (round >= RECONCILE_ROUNDS) {
options.onUnstable?.();
@@ -37,3 +37,8 @@
2. b05 b06의 자동관표시는 추천 노천의 미리뵉 모달창에 띄운다는 말인지? 추천 다시셈이라는게 전처리에 저장된값을 다시걔산한다는 말임?
3. b07의 횡단도가 도각이나 칸 밖으로나가는건 사용자가 조절해야하는 부분임.
4. 계산자리의 제어페이지를 만드는 이유는 프론트와 백엔드에서 일원화된 동작을 하게하고 테스트하면서 사용자편의성을 보고 선택하기 위함임. 이해함?
## 요구 315 (10-21 · 사용자)
1. 2번 필요없음.
2. 4번 45개를 전체다 선택 가능하게 하려면? 어떻게 해야함? 일단 방법만 공유
+14 -3
View File
@@ -33,7 +33,12 @@ const target = (inStation = false, inCard = false) => ({
/* ① 종단 그래프 빈 곳 누름 */
const render = read("B05_Profile", "B05_Profile_UI_Profile_Render.ts");
const chartClick = block(render, ` chartWrap.addEventListener("click", (event) => {`, "\n });");
// 500-2 잡는 단계(capture) 셋째 인자 — 본문은 `true,` 로 닫음
const chartClick = block(
render,
' chartWrap.addEventListener(\n "click",',
"\n true,\n );",
);
const calls = [];
let tool = false;
const ctx = {
@@ -52,7 +57,7 @@ const chartWrap = {
getBoundingClientRect: () => ({ left: 0 }),
scrollLeft: 0,
};
// 368-3 원지반선 근처 누름 = 작은 창 — 여기 빈 곳은 원지반선에서 멂(groundPickAt = null)
// 368-3 원지반선 근처 누름 = 작은 창 — 여기 빈 곳은 원지반선에서 멂(crossPickFromEvent = null · 500-2)
const groundCalls = [];
run(
chartClick,
@@ -64,9 +69,12 @@ run(
"longitudinal",
"width",
"layout",
"groundPickAt",
"crossPickFromEvent",
"openCrossModal",
"graphData",
"body",
"graphLongitudinal",
"stationIntervalM",
],
[
ctx,
@@ -79,6 +87,9 @@ run(
() => null,
() => groundCalls.push("modal"),
{ samples: [] },
{},
{ stations: [] },
20,
],
);
handler({ target: target(), clientX: 42 });
+6 -3
View File
@@ -183,14 +183,17 @@ const profileMod = load("A00_Common", "design", "design_profile.ts");
const tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
const runs = () => globalThis.__aisloPipeline?.runs ?? [];
const PID = "proj-349l";
/** 묶음 · 상세 가게 · 다시 셈이 조용해질 때까지. */
async function quiet() {
let seen = -1;
for (let i = 0; i < 400; i += 1) {
await tick(30);
const last = runs()[runs().length - 1];
const count = runs().length + refreshCalls * 1000 + draws.length * 1e6;
if (count === seen && (!last || last.outcome !== "running")) return;
// 버림 그리기 멈춤(`pipelineHolding`)이 풀리고 그 다음 틀 그림까지(472-5 덧 — 묶음이 조용해진 뒤 풂)
const held = pageMod.pipelineHolding(PID);
const count = runs().length + refreshCalls * 1000 + draws.length * 1e6 + (held ? 0.5 : 0);
if (count === seen && !held && (!last || last.outcome !== "running")) return;
seen = count;
}
throw new Error("묶음이 조용해지지 않음");
@@ -248,7 +251,7 @@ function settleDesigns(detail) {
}
async function run() {
const P = "proj-349l";
const P = PID;
const R = 7;
window.localStorage.setItem("frd_current_project_id", P);
opened = await sectionStore.loadSectionDetail(P, R);
+5 -3
View File
@@ -4,6 +4,7 @@
* 횡단 보기 진입 다시 셈 판정(`B06_Section_UI_Page_Design_Sync`). 화면 부품 · 서버 창구만 가짜 · 다시 셈 창구는
* 부른 수를 세고 면적을 두 배로(서버와 다른 값 흉내) · 낡음 판정은 진짜 화면 선 기준 그대로.
* 되돌리기 · 다시 하기 = 화면 꼴(사진첩이 세션 노선 초안 칸을 먼저 씀 · 알림 두 번 · 349-0m).
* 잴 때 = 묶음 조용 + 초안 버림 그리기 멈춤이 풀린 뒤(472-5 덧 · 풀기 전엔 마지막 그림이 초안 상세).
* 입력 argv[2] JSON · argv[3] 격자 이진. 결과 = 표준출력 JSON. 짝 = `test_349_0o_redo_same.py`. */
const fs = require("fs");
const path = require("path");
@@ -205,16 +206,18 @@ const stationKeys = load("B06_Section", "B06_Section_Station_Keys.ts");
// 진짜 다시 셈 창구(묶음 새 노선 상세를 안 고치는지만 봄 · 다른 곳은 위 가짜를 부름)
const realRefresh = loadTs(path.join(ROOT, "B06_Section", "B06_Section_Cross_Refresh.ts"));
const P = "proj-349o";
const tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
const runs = () => globalThis.__aisloPipeline?.runs ?? [];
/** 묶음 · 상세 가게 · 다시 셈이 조용해질 때까지. */
/** 묶음 · 상세 가게 · 다시 셈이 조용해질 때까지 — 초안 버림 그리기 멈춤(`pipelineHolding`)이 풀린 뒤까지(472-5 덧). */
async function quiet() {
let seen = -1;
for (let i = 0; i < 400; i += 1) {
await tick(30);
const last = runs()[runs().length - 1];
const count = runs().length + refreshCalls * 1000 + draws.length * 1e6;
if (count === seen && (!last || last.outcome !== "running")) return;
if (count === seen && (!last || last.outcome !== "running") && !pageMod.pipelineHolding(P))
return;
seen = count;
}
throw new Error("묶음이 조용해지지 않음");
@@ -272,7 +275,6 @@ function settleDesigns(detail) {
}
async function run() {
const P = "proj-349o";
const R = 7;
window.localStorage.setItem("frd_current_project_id", P);
opened = await sectionStore.loadSectionDetail(P, R);
+4 -2
View File
@@ -247,14 +247,16 @@ const realRefresh = loadTs(path.join(ROOT, "B06_Section", "B06_Section_Cross_Ref
const tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
const runs = () => globalThis.__aisloPipeline?.runs ?? [];
/** 묶음 · 상세 가게 · 다시 셈이 조용해질 때까지. */
/** 묶음 · 상세 가게 · 다시 셈이 조용해질 때까지 — 초안 버림의 그리기 멈춤이 풀릴 때까지도(472-5 덧 ·
* 풂 = 묶음 조용 50 ms 뒤 한 번 그림 · 30 ms 두 번만 보면 풀기 전에 끝나 다음 조작 창에 그림이 섞였음). */
async function quiet() {
let seen = -1;
for (let i = 0; i < 400; i += 1) {
await tick(30);
const last = runs()[runs().length - 1];
const count = runs().length + refreshCalls * 1000 + draws.length * 1e6;
if (count === seen && (!last || last.outcome !== "running")) return;
const held = pageMod.pipelineHolding("proj-349q");
if (count === seen && (!last || last.outcome !== "running") && !held) return;
seen = count;
}
throw new Error("묶음이 조용해지지 않음");
+6 -3
View File
@@ -204,14 +204,17 @@ const realRefresh = loadTs(path.join(ROOT, "B06_Section", "B06_Section_Cross_Ref
const tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
const runs = () => globalThis.__aisloPipeline?.runs ?? [];
const PID = "proj-349s";
/** 묶음 · 상세 가게 · 다시 셈이 조용해질 때까지. */
async function quiet() {
let seen = -1;
for (let i = 0; i < 400; i += 1) {
await tick(30);
const last = runs()[runs().length - 1];
const count = runs().length + refreshCalls * 1000 + draws.length * 1e6;
if (count === seen && (!last || last.outcome !== "running")) return;
// 버림 그리기 멈춤(`pipelineHolding`)이 풀리고 그 다음 틀 그림까지(472-5 덧 — 묶음이 조용해진 뒤 풂)
const held = pageMod.pipelineHolding(PID);
const count = runs().length + refreshCalls * 1000 + draws.length * 1e6 + (held ? 0.5 : 0);
if (count === seen && !held && (!last || last.outcome !== "running")) return;
seen = count;
}
throw new Error("묶음이 조용해지지 않음");
@@ -269,7 +272,7 @@ function settleDesigns(detail) {
}
async function run() {
const P = "proj-349s";
const P = PID;
const R = 7;
window.localStorage.setItem("frd_current_project_id", P);
opened = await sectionStore.loadSectionDetail(P, R);
@@ -119,10 +119,15 @@ async function quiet() {
throw new Error("묶음이 조용해지지 않음");
}
const n = job.nodes.length;
// 저장 곡선 그대로 연 편집기 칸(`flattenServerPlan` 꼴) — 꼭짓점 = 곡선 꼭지 · R = 저장 R(0.01 m) · 곡선 없는 자리 = 끔
// (전부 켬 · R 비움이면 안 끈 자리 곡선도 기본 R 로 다시 깔려 노선이 통째로 바뀜)
const savedCurve = job.nodes.map((node) =>
job.curves.find((c) => Math.hypot(c.apex[0] - node[0], c.apex[1] - node[1]) < 1e-3),
);
const draftOf = (nodes) => ({
nodes,
curveOn: Array(n).fill(true),
curveRadius: Array(n).fill(null),
curveOn: savedCurve.map((curve) => curve !== undefined),
curveRadius: savedCurve.map((curve) => (curve ? Math.round(curve.radius_m * 100) / 100 : null)),
curveLock: Array(n).fill(null),
curveArc: Array(n).fill(null),
stationSnap: job.stationSnap,
@@ -106,6 +106,8 @@ const pageMod = load("A00_Common", "design", "design_pipeline_page.ts");
const storeMod = load("A00_Common", "design", "design_store.ts");
const alignment = load("B05_Profile", "logic", "B05_Profile_UI_Profile_Alignment.ts");
const page = load("A00_Common", "b_page_state.ts");
// 상세 가게 칸(472-5) — 실제는 상세 가게가 받을 때 채움 · 여기선 종단도가 내주는 저장 상세 그대로(갈아끼움 제외)
const slots = load("B06_Section", "B06_Section_Section_Canon.ts");
const tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
const runs = () => globalThis.__aisloPipeline?.runs ?? [];
@@ -139,6 +141,7 @@ async function openOnce(P) {
const savedAlignment = job.detail.longitudinal.profile_alignment;
const savedBase = alignment.toAlignmentBase(savedAlignment);
let detail = job.detail;
slots.keepSavedDetail(P, 7, job.detail);
const swaps = [];
const grades = [];
const link = {
@@ -9,8 +9,6 @@ from __future__ import annotations
import os
import re
import pytest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
@@ -55,10 +53,3 @@ def find_project(*rels: str) -> Path:
and (project / "B06_Section" / "cross_sections").is_dir()
]
return min(found, key=lambda project: _touched(project, rels), default=NONE)
#: 검증 프로젝트로 처음 돌려 본 때(507-4) 이미 빨강 — 대부분 프로젝트와 무관(여섯 프로젝트 같음) · 옛 기대가 낡았는지 제품 흠인지 가려질 때까지(0q 메아리 = 돌 때마다 흔들림)
DRIFT = pytest.mark.xfail(
strict=False,
reason="507-4: 검증 프로젝트로 돌리니 빨강 · 흔들림 — 기대 낡음 또는 제품 흠(브레인 가림 전)",
)
@@ -3,13 +3,17 @@
- 시스템 관리자 = 「계산 자리」 카드 · 창(밝음 · 어두움 한 장씩 · tmp/shots_504_2)
- 회사 관리자 · 일반 = 카드 없음 · PUT 403
- G1 체크 끔 → [저장] → B05 열기(검증_sub2) → 지반 표본이 서버 길(`/route/elevations`) · 판 기본값으로 되돌림
- 시험 브라우저 = 표식 문자열(`MARK`) · 끝나면 그 표식 시스템 로그 행만 지움(508-1a)
- 포트 = OWNERS.md 이 폴더 줄 — 다른 폴더 사본이 남의 서버 판을 안 고침
./venv/Scripts/python.exe -m pytest resources/tester/test_309_2_calc_sites_browser.py -q
"""
from __future__ import annotations
import asyncio
import json
import re
import subprocess
import urllib.request
from pathlib import Path
@@ -17,12 +21,26 @@ from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
BASE = "http://localhost:5177"
API = "http://localhost:8004" # 5177 이 프록시하는 이 폴더 백엔드(OWNERS.md) — 다른 폴더로 가면 그 폴더 마스터를 씀
def _own_ports() -> list[str]:
"""OWNERS.md 표에서 이 폴더 줄의 (API, vite) 포트(508-1a)."""
for line in (ROOT / "OWNERS.md").read_text(encoding="utf-8-sig").splitlines():
cells = [cell.strip().strip("`") for cell in line.strip().strip("|").split("|")]
if len(cells) >= 5 and Path(cells[2]).resolve() == ROOT.resolve():
return re.findall(r"\d{4,5}", cells[4])
return []
_PORTS = _own_ports()
API, BASE = (
[f"http://localhost:{port}" for port in _PORTS] if len(_PORTS) == 2 else ["", ""]
) # 이 폴더 백엔드 · 그 백엔드를 프록시하는 vite
BOARD = ROOT / "resources" / "master_template" / "calc_sites.json"
PROJECT = "cb15892a-5788-4a26-9316-20a15421f68b" # 검증_sub2(OWNERS.md)
SHOTS = ROOT / "tmp" / "shots_504_2"
CARD = "계산 자리"
MARK = "AisloTest-309-2" # 시험 브라우저 문자열 — 시스템 로그 user_agent 칸으로 시험 행을 가림
def _started(base: str) -> str | None:
@@ -33,10 +51,10 @@ def _started(base: str) -> str | None:
return None
_UP = _started(BASE)
# 5177 이 이 폴더 8004 가 아닌 서버(예 8000 = 메인 폴더)로 가면 PUT 이 남의 판을 고침 → 돌리지 않음
_UP = _started(BASE) if BASE else None
# vite 가 이 폴더 백엔드가 아닌 서버(예 8000 = 메인 폴더)로 가면 PUT 이 남의 판을 고침 → 돌리지 않음
pytestmark = pytest.mark.skipif(
_UP is None or _UP != _started(API), reason="5177 서버 없음 · 5177 이 8004 로 안 감"
_UP is None or _UP != _started(API), reason="이 폴더 서버 없음 · vite 가 이 폴더 백엔드로 안 감"
)
@@ -47,6 +65,22 @@ def _cred(who: str) -> dict:
return {k: accounts[who][k] for k in ("email", "password")}
def _drop_test_logs() -> None:
"""시험이 남긴 시스템 로그(로그인 · CALC_SITE_CHANGE) = 표식 브라우저 문자열 행만 지움."""
from config.config_db import close_db_pool, init_db_pool
async def run():
pool = await init_db_pool()
try:
async with pool.acquire() as connection, connection.cursor() as cursor:
await cursor.execute("DELETE FROM system_audit_logs WHERE user_agent = %s", (MARK,))
await connection.commit()
finally:
await close_db_pool()
asyncio.run(run())
@pytest.fixture(scope="module")
def browser():
sync = pytest.importorskip("playwright.sync_api")
@@ -54,17 +88,20 @@ def browser():
["git", "show", "HEAD:resources/master_template/calc_sites.json"],
cwd=ROOT, capture_output=True, check=True,
).stdout # fmt: skip
with sync.sync_playwright() as play:
chrome = play.chromium.launch(channel="msedge", headless=True)
try:
yield chrome
finally: # 실패해도 판 = 커밋한 기본값 그대로
chrome.close()
BOARD.write_bytes(committed)
try:
with sync.sync_playwright() as play:
chrome = play.chromium.launch(channel="msedge", headless=True)
try:
yield chrome
finally:
chrome.close()
finally: # 실패해도 판 = 커밋한 기본값 그대로 · 로그 지움은 Playwright 고리 밖에서
BOARD.write_bytes(committed)
_drop_test_logs()
def _context(browser, who: str):
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
ctx = browser.new_context(viewport={"width": 1400, "height": 900}, user_agent=MARK)
r = ctx.request.post(f"{BASE}/api/auth/login/request", data=_cred(who), timeout=60000)
assert r.ok, f"로그인 실패 {r.status}" # 비밀번호는 안 찍음
return ctx
+14 -7
View File
@@ -4,6 +4,7 @@
· 꼭짓점 한 번 끌기 = 묶음 한 번 · 가게 관 · 구조물 측점 바뀜
· 사진첩이 찍기 전(setTimeout 0)에 씀(한 걸음)
· 새 노선에서 20 m 넘게 먼 구조물 수 알림
· 좌표가 그 누가거리를 낳는 구조물만(아니면 410 낡음 = 노선 위로 · 알림 0)
· 되돌리기(사진첩이 세션을 되씀) · 초안만 버림 = 초안 전 값(가게 비움 · 입력 = 저장 관)
· 계산 · 알림 안 입력 쓰기 0
짝: `helper_349_0d_follow_store.cjs`(실제 TS 를 Node 로 · 서버 창구만 가짜 · 검증_sub7 노선 · 관).
@@ -20,13 +21,14 @@ import tempfile
from pathlib import Path
import pytest
from shapely.geometry import LineString, Point
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
@@ -54,22 +56,29 @@ def _json(path: Path):
def _structure(sid: str, line: list[list[float]], ratio: float, off: float) -> dict:
"""노선 위(off 0) · 옆(off m) 점 구조물 — 저장 노선 누가거리 · 좌표."""
"""노선 위(off 0) · 옆(off m) 점 구조물 — 저장 노선 정수 m 누가거리 · 좌표.
좌표가 그 누가거리를 낳는 쪽 옆(410 낡음 판정 밖 · 굽은 노선은 한쪽 옆이
다른 구간에 더 가까움)."""
lengths = [math.dist(a, b) for a, b in zip(line, line[1:])]
at = sum(lengths) * ratio
at = float(round(sum(lengths) * ratio))
walked = 0.0
for (a, b), length in zip(zip(line, line[1:]), lengths):
if walked + length >= at:
t = (at - walked) / length
dx, dy = (b[0] - a[0]) / length, (b[1] - a[1]) / length
x, y = a[0] + dx * length * t - dy * off, a[1] + dy * length * t + dx * off
foot = (a[0] + dx * length * t, a[1] + dy * length * t)
break
walked += length
route = LineString(line)
side = lambda s: (foot[0] - dy * s, foot[1] + dx * s) # noqa: E731
x, y = next(
(side(s) for s in (off, -off) if round(route.project(Point(side(s)))) == at), side(off)
)
return {
"structure_id": sid,
"type_id": "erosion_check",
"placement": "point",
"chainage_m": round(at, 2),
"chainage_m": at,
"start_m": None,
"end_m": None,
"x": x,
@@ -134,7 +143,6 @@ def test_열기만_하면_가게_안_씀(r):
assert o["followed"] == []
@DRIFT
def test_꼭짓점_끌기_가게에_새_측점_한_번(r):
d = r["drag"]
assert d["runs"] == [["route"]], d["runs"]
@@ -147,7 +155,6 @@ def test_꼭짓점_끌기_가게에_새_측점_한_번(r):
assert d["blocked"] == 0
@DRIFT
def test_되돌리기_초안_전_값(r):
u = r["undo"]
assert u["runs"] == [["route"]], u["runs"]
+2 -2
View File
@@ -4,6 +4,7 @@
· 진짜 종단도 낡음 판정 · crossPreview 예약을 거침 — 되돌린 뒤 다시 셈 0 · 낡음 0
· 묶음 기록에 되돌림 길(restored · 까닭) · 되돌리기 = 묶음 한 번
· 되돌리기 = 화면 Ctrl+Z 꼴(사진첩이 세션 노선 초안 칸을 먼저 비움 · 알림 두 번 · 349-0m)
· 되돌린 뒤 값 = 버림 그리기 멈춤이 풀린 다음 틀 그림(묶음이 조용해진 뒤 한 번 · 472-5 덧)
짝: `helper_349_0l_undo_draw.cjs`(실제 TS 를 Node 로 · 화면 부품 · 서버 창구만 가짜).
"""
@@ -23,7 +24,7 @@ ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
@@ -130,7 +131,6 @@ def test_끌기_새_측점(r):
assert d["final"] != r["open"]["final"]
@DRIFT
def test_되돌리기_열기_값_다시_셈_0(r):
u, o = r["undo"], r["open"]
assert [(x["sources"], x["restored"], x["why"]) for x in u["runs"]] == [
+1 -4
View File
@@ -25,7 +25,7 @@ ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
@@ -149,7 +149,6 @@ def test_열기(r):
assert o["final"] is not None
@DRIFT
def test_끌기_새_노선(r):
d = r["first"]
assert [x["sources"] for x in d["runs"]] == [["route"]], d["runs"]
@@ -157,7 +156,6 @@ def test_끌기_새_노선(r):
assert d["final"] != r["open"]["final"]
@DRIFT
def test_되돌리기_열기_값_측점_다름은_기록(r):
o = r["open"]
for u in (r["back1"], r["back2"]):
@@ -189,7 +187,6 @@ def test_다시_하기_첫_끌기_값(r):
assert d["stations"] == f["stations"]
@DRIFT
def test_횡단_보기_다녀와도_열기_값(r):
o = r["open"]
for step in (r["cross"], r["back"]):
+1 -2
View File
@@ -24,7 +24,7 @@ ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
@@ -161,7 +161,6 @@ def test_되돌리기_열기_객체_그대로(r):
assert d["pipeline"] > d["saved"] and isinstance(d["pipelineM"], float)
@DRIFT
def test_메아리_차례_갈아끼움_없음_열기_값(r):
o, e = r["open"], r["echo"]
assert [x["outcome"] for x in e["runs"]], e["runs"] # 묶음이 실제로 돎
+2 -3
View File
@@ -3,7 +3,7 @@
· 꼭짓점 끌기 다섯 걸음 · 되돌리기 — 걸음마다 종단도 그리기 ≤ 2(종단 먼저 · 횡단 · 유토곡선 다음 틀)
(옛 길 = 갈아끼움 · 정책 · 이어 공사 기준 · 비정규 측점 · 새 상세 알림마다 바로 그림)
· runs[].work — 가게 · 새 상세 · 종단도 · 쉼 이름별 ms(알림 받는 쪽 · 그리기 몫)
· 되돌리기 = 열기 값 규칙 그대로(restored)
· 되돌리기 = 열기 값 규칙 그대로(restored) · 그리기 = 버림 그리기 멈춤이 풀린 다음 틀(472-5 덧)
짝: `helper_349_0s_draw_work.cjs`(실제 TS 를 Node 로 · 화면 부품 · 서버 창구만 가짜).
"""
@@ -23,7 +23,7 @@ ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
@@ -149,7 +149,6 @@ def test_그리기_몫_기록(r):
assert x["totalMs"] >= x["stagesMs"] * 0.99
@DRIFT
def test_되돌리기_그대로(r):
u = r["undo"]
assert [x["why"] for x in u["runs"]] == ["saved-diff"], u["runs"] # 세월교 하나 더(0o 꼴)
@@ -23,7 +23,7 @@ ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
from common_util.common_util_drainage_pipes import ( # noqa: E402
@@ -134,14 +134,25 @@ def r() -> dict:
timeout=300,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
out = json.loads(proc.stdout)
# 끈 꼭짓점(1) 다음 꺾임점 — 그 뒤는 선 모양 그대로
out["afterM"] = _near_m(line, job["nodes"][2])
return out
def _near_m(line: list[list[float]], point: list[float]) -> float:
"""저장 노선 위 그 점에 가장 가까운 꼭짓점의 누가거리."""
walked, best = 0.0, (math.inf, 0.0)
for i, xy in enumerate(line):
walked += math.dist(line[i - 1], xy) if i else 0.0
best = min(best, (math.dist(xy, point), walked))
return best[1]
def _moved(before: list, after: list) -> list[float]:
return [b[1] - a[1] for a, b in zip(before, after)]
@DRIFT
def test_앞쪽_꺾임점_끌기_측점_밀림(r):
d = r["drag"]
assert d["runs"] == [["route"]], d["runs"]
@@ -150,15 +161,14 @@ def test_앞쪽_꺾임점_끌기_측점_밀림(r):
assert grow > 5, grow # 앞쪽 길이 늚
pipes = _moved(r["draftPipes"], d["pipes"])
assert len(d["pipes"]) == len(r["draftPipes"])
# 끈 꼭짓점 뒤 관 = 길이 는 만큼(정수 m 맞춤 · 1 m 안) — 대부분 그 뒤
tail = [m for (_, c), m in zip(r["draftPipes"], pipes) if c > r["draftPipes"][2][1]]
# 끈 꼭짓점 다음 꺾임점 뒤 관 = 길이 는 만큼(정수 m 맞춤 · 1 m 안) — 관 수 · 자리는 프로젝트마다
tail = [m for (_, c), m in zip(r["draftPipes"], pipes) if c > r["afterM"]]
assert tail and all(abs(m - grow) <= 1.0 for m in tail), (grow, pipes)
s = _moved(r["draftStructures"], d["structures"])
assert all(abs(m - grow) <= 1.0 for m in s), (grow, s)
assert len(d["followed"]) == 1
@DRIFT
def test_되돌리기_초안_값(r):
for u in (r["undo"], r["undo2"]):
assert u["runs"] == [["route"]], u["runs"]
+1 -3
View File
@@ -23,7 +23,7 @@ ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
@@ -103,7 +103,6 @@ def _same(a: list[float], b: list[float]) -> bool:
return len(a) == len(b) and all(abs(x - y) < 1e-6 for x, y in zip(a, b))
@DRIFT
def test_열_때_한_번_저장_측점_그대로(r):
o = r["open"]
assert [x["outcome"] for x in o["runs"]] == ["done"], o["runs"]
@@ -121,7 +120,6 @@ def test_꼭짓점_끌기_한_번(r):
assert d["swaps"] == 1 # 새 노선 측점으로 갈아끼움은 한 번(고리 아님)
@DRIFT
def test_되돌리기_한_번_저장값으로(r):
u = r["undo"]
assert [x["sources"] for x in u["runs"]] == [["route"]], u["runs"]