Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -67,6 +67,8 @@ function writePending(sessionKey: string | null, pending: PendingMap): void {
|
||||
* 세션에 쌓인 구간값을 `pipe_points.json`에 한 번에 쓴다. 예약분이 없으면 아무것도
|
||||
* 하지 않는다. 성공하면 세션을 비우고, 실패하면 **그대로 두어** 다음 [저장]에서
|
||||
* 다시 시도한다 — 조용히 잃으면 화면과 저장분이 갈린다.
|
||||
* 지금 관에 못 맞춘 값(옮긴 · 지운 관 옛 자리)은 버림 — 남기면 그 자리에 온 다른 관에 얹힘(444 ·
|
||||
* 세월교 · BOX 폼 값은 관 초안 한 벌로 나가므로 잃지 않음).
|
||||
* 옛 「관 이동 예약」(culvertmove)은 읽지 않음 — 관 위치는 B05·B06 이 같은 관 목록 스냅샷으로만
|
||||
* 옮김. 옛 세션 잔값을 저장 때 적용하면 관이 이웃 측점으로 튀어 시설 속성이 넘어감.
|
||||
*/
|
||||
|
||||
@@ -492,7 +492,8 @@ function moveIdleStates(moves: ReadonlyMap<string, string>, owned: ReadonlySet<s
|
||||
const { projectId, routeId } = current;
|
||||
const move = (cells: Record<string, unknown>) =>
|
||||
Object.fromEntries(renameChainageKeys(new Map(Object.entries(cells)), moves));
|
||||
for (const state of Object.keys(ROUTE_CARRY_STATES)) {
|
||||
// 관 구간값 예약(culvertopt · 설계 칸 아님 = 옮김 목록 짝 밖)도 관 새 자리로(444)
|
||||
for (const state of [...Object.keys(ROUTE_CARRY_STATES), "culvertopt"]) {
|
||||
if ((KEYED_STATES as readonly string[]).includes(state)) continue;
|
||||
const name = state as StateName;
|
||||
const key = stateKey(name, projectId, routeId);
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* (`_UI_Page_Station_Controls.ts`)에서 700줄 제한으로 분리했다(2026-08-25).
|
||||
*
|
||||
* 데이터 흐름은 집수정과 같다: 세션 사본 + 캐시 `design.ford_adjust`에 함께 실어
|
||||
* 3D(코리도)가 같은 값으로 다시 그리게 한다. 관경·수량은 B05 정본(`pipe_points`)이
|
||||
* 원천이라 배수관 구간값과 같은 저장기로 되돌려 쓴다.
|
||||
* 3D(코리도)가 같은 값으로 다시 그리게 한다. 관경·수량·날개벽은 폼이 관 초안(가게 pipes)
|
||||
* 한 벌로 냄 — 여기는 캐시만 고침(444 · 조정 예약 culvertopt 에 또 담으면 옮긴 관 옛 자리에 남음).
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
@@ -106,8 +106,6 @@ export interface FordControlDeps {
|
||||
sectionAt: (chainageM: number) => CrossSection | undefined;
|
||||
patchCachedDesign: (chainageM: number, patch: Partial<CrossDesign>) => void;
|
||||
refreshCard: (chainageM: number) => void;
|
||||
/** 관경·수량·날개벽 제원을 B05 정본으로 되돌려 쓴다(묶어서 늦게 저장). */
|
||||
queuePipeOptions: (chainageM: number, patch: Record<string, number | string>) => void;
|
||||
round1: (value: number) => number;
|
||||
clampMove: (value: number) => number;
|
||||
/** 같은 측점 벽 고름을 풂 — 측벽 · 흙막이 동시 고름 금지(107-1). */
|
||||
@@ -203,7 +201,6 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
||||
spec.overflow_depth_m = Math.max(patch.ford_height_m, 0);
|
||||
}
|
||||
dropDefaulted(spec, patch);
|
||||
deps.queuePipeOptions(chainageM, patch);
|
||||
deps.refreshCard(chainageM);
|
||||
},
|
||||
setWing: (chainageM, role, patch) => {
|
||||
@@ -218,7 +215,6 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
||||
wing.slab_extend_m = wingSlabExtendM(wing.installed, wing.length_m, wing.angle_deg);
|
||||
}
|
||||
dropDefaulted(spec, wingOptions(role, patch));
|
||||
deps.queuePipeOptions(chainageM, wingOptions(role, patch));
|
||||
deps.refreshCard(chainageM);
|
||||
},
|
||||
selectedFor: (chainageM) => fordSelections.get(chainageM.toFixed(2)) ?? null,
|
||||
@@ -400,7 +396,6 @@ export function createBoxControls(deps: FordControlDeps): {
|
||||
if (patch.body_height_m) spec.inner_height_m = patch.body_height_m;
|
||||
}
|
||||
dropDefaulted(spec, patch);
|
||||
deps.queuePipeOptions(chainageM, patch as Record<string, number | string>);
|
||||
deps.refreshCard(chainageM);
|
||||
},
|
||||
setWing: (chainageM, role, patch) => {
|
||||
@@ -416,7 +411,6 @@ export function createBoxControls(deps: FordControlDeps): {
|
||||
: 0;
|
||||
}
|
||||
dropDefaulted(spec, wingOptions(role, patch));
|
||||
deps.queuePipeOptions(chainageM, wingOptions(role, patch));
|
||||
deps.refreshCard(chainageM);
|
||||
},
|
||||
selectedFor: (chainageM) => selections.get(chainageM.toFixed(2)) ?? null,
|
||||
|
||||
@@ -580,7 +580,6 @@ export function createStationControls(deps: StationControlDeps): StationControls
|
||||
sectionAt,
|
||||
patchCachedDesign,
|
||||
refreshCard: deps.refreshCard,
|
||||
queuePipeOptions: culvertOptions.queue,
|
||||
round1,
|
||||
clampMove,
|
||||
releaseWall: (chainageM) => {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/* 444 헬퍼 — 관 구간값 예약(culvertopt)이 관 옮김을 따라가고 · 못 맞춘 값은 버리고 · 두 번째 [저장]이 안 덮음.
|
||||
* node helper_444_culvertopt.mjs <묶음.mjs>
|
||||
* 묶음 = 444 진입점(`helper_444_entry.ts` · 346-2 묶음 + 내보내기 · vite ssr 진짜 모듈) · 표준출력 JSON 한 벌.
|
||||
* 짝 = test_444_culvertopt_single_draft.py · 틀 = helper_415_swap.mjs(415-3).
|
||||
*/
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const store = () => {
|
||||
const m = new Map();
|
||||
return {
|
||||
getItem: (k) => (m.has(k) ? m.get(k) : null),
|
||||
setItem: (k, v) => m.set(k, String(v)),
|
||||
removeItem: (k) => m.delete(k),
|
||||
key: (i) => [...m.keys()][i] ?? null,
|
||||
get length() {
|
||||
return m.size;
|
||||
},
|
||||
clear: () => m.clear(),
|
||||
};
|
||||
};
|
||||
globalThis.window = globalThis;
|
||||
globalThis.sessionStorage = store();
|
||||
globalThis.localStorage = store();
|
||||
const any = new Proxy(function () {}, {
|
||||
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any),
|
||||
apply: () => any,
|
||||
construct: () => any,
|
||||
});
|
||||
globalThis.document = any;
|
||||
globalThis.requestAnimationFrame = (run) => setTimeout(run, 0);
|
||||
globalThis.__toasts = [];
|
||||
|
||||
/* 관 정본(서버) — GET 은 지금 관 · PUT 은 받은 몸통으로 바꿈 */
|
||||
let saved = [];
|
||||
const puts = [];
|
||||
globalThis.fetch = async (url, init = {}) => {
|
||||
if (String(url).includes("/drainage/pipe-points") && init.method === "PUT") {
|
||||
const points = JSON.parse(init.body).points;
|
||||
puts.push(points);
|
||||
saved = points.map((point) => ({ ...point, lonlat: [0, 0] }));
|
||||
}
|
||||
return { ok: true, status: 200, json: async () => ({ pipe_points: saved, basins: [] }) };
|
||||
};
|
||||
|
||||
const m = await import(pathToFileURL(process.argv[2]).href);
|
||||
const P = "p444";
|
||||
const R = 3;
|
||||
const out = {};
|
||||
|
||||
const section = (chainage_m, kind, extra = {}) => ({
|
||||
station_id: `station_${String(Math.round(chainage_m * 1000)).padStart(12, "0")}`,
|
||||
chainage_m,
|
||||
label: "",
|
||||
kind,
|
||||
center_z: 100,
|
||||
azimuth_deg: 90,
|
||||
center_x: chainage_m,
|
||||
center_y: 0,
|
||||
frame: { left_xy: [0, 1] },
|
||||
samples: [{ offset_m: 0, elevation_m: 100, valid: true }],
|
||||
design: { design_elevation_m: 101 },
|
||||
...extra,
|
||||
});
|
||||
const detail = () => ({
|
||||
longitudinal: {
|
||||
length_m: 100,
|
||||
stations: [],
|
||||
profile_alignment: { policy: { station_interval_m: 20 } },
|
||||
},
|
||||
cross_sections: [
|
||||
section(0, "bp"),
|
||||
section(20, "regular"),
|
||||
section(40, "regular"),
|
||||
section(45.054, "irregular", { structure: "세월교", culvert: {} }),
|
||||
section(60, "regular"),
|
||||
section(80, "regular"),
|
||||
section(100, "ep"),
|
||||
],
|
||||
});
|
||||
const station = (key, chainageM, kind, id = null) => ({
|
||||
key,
|
||||
chainageM,
|
||||
x: chainageM,
|
||||
y: 0,
|
||||
azimuthDeg: 90,
|
||||
left: [0, 1],
|
||||
kind,
|
||||
id,
|
||||
ground: { centerZ: 99, left: [{ offsetM: 0.5, z: 99.1 }], right: [{ offsetM: 0.5, z: 98.9 }] },
|
||||
planRadiusM: null,
|
||||
curveOuterSide: null,
|
||||
curveWideningM: null,
|
||||
});
|
||||
const list = (pipeAt) =>
|
||||
[
|
||||
station("end:BP", 0, "end"),
|
||||
station("grid:1", 20, "grid"),
|
||||
station("grid:2", 40, "grid"),
|
||||
station("pipe:pipe-s1", pipeAt, "pipe", "pipe-s1"),
|
||||
station("grid:3", 60, "grid"),
|
||||
station("grid:4", 80, "grid"),
|
||||
station("end:EP", 100, "end"),
|
||||
].sort((a, b) => a.chainageM - b.chainageM);
|
||||
const key = `aislo:draft:culvertopt:${P}:${R}`;
|
||||
|
||||
/* 1. 관 5 m 옮김 — 옛 세션 구간값 예약도 관 새 자리로(조정창 맵 없이 · 415-3 과 같은 길) */
|
||||
m.writeState("section-detail", detail(), P, R);
|
||||
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 45.054 }] }, P);
|
||||
await m.loadSectionDetail(P, R);
|
||||
m.writeState("culvertopt", { 45.05: { ford_width_m: 4 } }, P, R);
|
||||
const stop = m.followSwaps(
|
||||
P,
|
||||
() => R,
|
||||
{ visible: () => true },
|
||||
async () => undefined,
|
||||
);
|
||||
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 50 }] }, P);
|
||||
m.useDesignStore(P, null).set("stations", list(50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
out.moved = m.readState("culvertopt", P, R);
|
||||
stop();
|
||||
|
||||
/* 2. [저장] 두 번 — 첫 번째는 관 새 자리에 얹고 세션 비움 · 두 번째는 안 보냄(값 그대로) */
|
||||
saved = [
|
||||
{ id: "p5000", chainage_m: 50, source: "stream", facility: "ford", options: { ford_width_m: 3 } },
|
||||
];
|
||||
await m.flushCulvertOptions(P, key);
|
||||
out.firstSave = {
|
||||
puts: puts.length,
|
||||
width: saved[0].options.ford_width_m,
|
||||
left: sessionStorage.getItem(key),
|
||||
};
|
||||
await m.flushCulvertOptions(P, key);
|
||||
out.secondSave = { puts: puts.length, width: saved[0].options.ford_width_m };
|
||||
|
||||
/* 3. 못 맞춘 값(지운 · 옮긴 관 옛 자리) — 다른 관에 안 얹고 버림 */
|
||||
m.writeState("culvertopt", { "30.00": { ford_width_m: 9 } }, P, R);
|
||||
await m.flushCulvertOptions(P, key);
|
||||
out.unmatched = {
|
||||
puts: puts.length,
|
||||
width: saved[0].options.ford_width_m,
|
||||
left: sessionStorage.getItem(key),
|
||||
};
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,3 @@
|
||||
/* 444 시험 묶음 진입점 — 346-2 갈아끼움 묶음 + 관 구간값 예약 내보내기(진짜 모듈 · vite ssr). */
|
||||
export * from "./helper_346_2_swap_entry";
|
||||
export { flushCulvertOptions } from "../../B06_Section/B06_Section_Api_Culvert_Options";
|
||||
@@ -0,0 +1,88 @@
|
||||
"""444 관 구간값(H4a · H4b) — 세월교 · BOX 폼 값은 관 초안 한 벌 · 옛 예약은 관을 따라가고 못 맞추면 버림.
|
||||
|
||||
- 폼이 낸 세월교 · BOX 값(관경 · 수량 · 월류 폭 · 날개벽)은 관 초안(가게 pipes)으로만 나감 — 조정 예약
|
||||
(culvertopt)에 또 담지 않음(test_b06_adjust_single_form 규칙 · 관 id 로 옮김 · 노선 바뀜을 따라감).
|
||||
- 옛 세션 예약(culvertopt)은 관 옮김(측점 갈아끼움)을 따라 새 자리로 · [저장] 때 못 맞춘 값은 버림(지운 관
|
||||
자리에 온 다른 관에 안 얹힘) · 두 번째 [저장]은 다시 안 보냄(값 그대로).
|
||||
|
||||
짝: `helper_444_culvertopt.mjs`(444 진입점 묶음 · 진짜 상세 가게 · 내보내기).
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
if str(HERE) not in sys.path:
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
import test_365_page_pipeline as t365 # noqa: E402
|
||||
|
||||
pytestmark = t365.pytestmark
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out(tmp_path_factory) -> dict:
|
||||
work = tmp_path_factory.mktemp("b444")
|
||||
build = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
[
|
||||
"node",
|
||||
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
||||
"build",
|
||||
"--configLoader",
|
||||
"runner",
|
||||
"--ssr",
|
||||
"../resources/tester/helper_444_entry.ts",
|
||||
"--outDir",
|
||||
str(work),
|
||||
"--logLevel",
|
||||
"error",
|
||||
],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=300,
|
||||
)
|
||||
assert build.returncode == 0, build.stderr
|
||||
bundle = work / "bundle.mjs"
|
||||
shutil.copy(work / "helper_444_entry.js", bundle)
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HERE / "helper_444_culvertopt.mjs"), str(bundle)],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_세월교_BOX_폼_값은_조정_예약에_안_쌓음() -> None:
|
||||
ford = (ROOT / "B06_Section" / "B06_Section_UI_Page_Ford_Controls.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
controls = (ROOT / "B06_Section" / "B06_Section_UI_Page_Station_Controls.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "queuePipeOptions" not in ford
|
||||
assert "queuePipeOptions" not in controls
|
||||
|
||||
|
||||
def test_관_옮김_예약도_새_자리로(out):
|
||||
assert out["moved"] == {"50.00": {"ford_width_m": 4}}
|
||||
|
||||
|
||||
def test_두_번째_저장은_값_그대로(out):
|
||||
assert out["firstSave"] == {"puts": 1, "width": 4, "left": None}
|
||||
assert out["secondSave"] == {"puts": 1, "width": 4}
|
||||
|
||||
|
||||
def test_못_맞춘_값은_다른_관에_안_얹고_버림(out):
|
||||
assert out["unmatched"] == {"puts": 1, "width": 4, "left": None}
|
||||
Reference in New Issue
Block a user