From 98e7d4aee2673aa80e83b4a8d3672fef3eefc3bd Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 18 Oct 2026 21:26:22 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(454=20B05=20=EB=B0=A9=ED=96=A5=ED=82=A4?= =?UTF-8?q?=20=EC=B8=A1=EC=A0=90=20=EA=B1=B8=EC=9D=8C):=20=EA=B3=A1?= =?UTF-8?q?=EC=84=A0=20=EB=A7=9E=EC=B6=A4=20=ED=9B=84=EB=B3=B4=20=EC=B8=A1?= =?UTF-8?q?=EC=A0=90=20=EB=91=98=20=C2=B7=20=EB=92=A4=20=EA=B3=A1=EC=84=A0?= =?UTF-8?q?=20=EC=95=9E=EB=B3=B4=EA=B8=B0=20(433-21=20=E2=91=A1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 곡선마다 내림 · 올림 측점 후보 · 뒤 곡선이 측점 위거나 맞춰지는 쪽 고름 - 무작위 노선 30 개 20 m 걸음 측점 밖 막힘 141 → 25 · 놓은 뒤 측점 밖 43 → 0 - 시험 test_454_arrow_station_step 추가 Co-Authored-By: Claude Opus 5.5 --- .../logic/B05_Profile_RouteEdit_StationFit.ts | 76 +++++++++++---- .../tester/helper_454_arrow_station_step.cjs | 96 +++++++++++++++++++ .../tester/test_454_arrow_station_step.py | 35 +++++++ 3 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 resources/tester/helper_454_arrow_station_step.cjs create mode 100644 resources/tester/test_454_arrow_station_step.py diff --git a/B05_Profile/logic/B05_Profile_RouteEdit_StationFit.ts b/B05_Profile/logic/B05_Profile_RouteEdit_StationFit.ts index f54583bba..5351b86cd 100644 --- a/B05_Profile/logic/B05_Profile_RouteEdit_StationFit.ts +++ b/B05_Profile/logic/B05_Profile_RouteEdit_StationFit.ts @@ -4,7 +4,7 @@ * * 추천 노선 맞춤(`B05_Profile_Engine_RouteInitial_Station.py` `_try` · `_solve`)을 줄인 꼴: * · `from`(`fitStart` = 고친 자리 앞)부터 끝까지 차례로 — 고정 아닌 교각점(IP)을 들어오는 접선 따라 ±간격 안에서 - * 밀어 MC(BC · EC 누가거리 가운데)를 가까운 일반측점(앞 MC 측점 + 간격 이상 · No.1 이상)에 둠. + * 밀어 MC(BC · EC 누가거리 가운데)를 일반측점(앞 MC 측점 + 간격 이상 · No.1 이상)에 둠 — 내림 · 올림 후보 중 뒤 곡선도 서는 쪽(454). * · 곡선 하한을 새로 깨거나(`shortfallCrossed`) 앞 곡선 MC 를 흔들면 버림 → 그 곡선은 그대로. * · 반지름 · 켬끔은 안 바꿈 — IP 자리만. 합치기 · 곡선 끄기 · 되돌아가기 없음. * · 간격 = 스냅 간격(390-2 · 0.1 · 1 · 10 m · 측점 No.) · 허용 오차도 간격 따라(`mcTolM`). @@ -139,7 +139,9 @@ function solve( return null; } -/** `from` 꺾임점부터 끝까지 곡선 MC 를 일반측점에 — 새 IP 목록(못 놓은 곡선 IP 는 그대로). */ +/** `from` 꺾임점부터 끝까지 곡선 MC 를 일반측점에 — 새 IP 목록(못 놓은 곡선 IP 는 그대로). + * 곡선마다 후보 측점 둘(내림 · 올림 · 가까운 차례) · 뒤 곡선 하나를 미리 맞춰 보고 둘 다 서는 쪽(433-21 ②) + * — 가까운 쪽만 고르면 뒤 곡선이 「앞 측점 + 1」에 몰려 R 이 눌리고 앞 MC 가 흔들려 버려짐. */ export function fitCurveStations( cols: Columns & Pick, limits: FitLimits, @@ -150,28 +152,64 @@ export function fitCurveStations( if (!(interval > 0) || planned.length < 3) return planned; let built = build(cols, planned, limits); const floor = curveShortfalls(built.nodes, limits.limitRadiusM, limits.limitArcM); + const tol = mcTolM(interval); + type Fit = { planned: Vertex[]; built: EditedPolyline }; + /** 곡선을 놓을 측점 — 내림 · 올림 중 앞 측점 + 1 이상 · 가까운 차례. */ + const near = (mc: number, last: number): number[] => { + const lo = Math.floor(mc / interval); + return [lo, lo + 1] + .filter((n) => n > last) + .sort((a, b) => Math.abs(a * interval - mc) - Math.abs(b * interval - mc)); + }; + const onAt = (mc: number, last: number) => + near(mc, last).find((n) => Math.abs(mc - n * interval) <= tol); + /** 곡선 `k` 를 측점 `n` 에 — 하한을 새로 깨거나 앞 곡선(`previous`) MC 를 흔들면 null. */ + const attempt = (at: Fit, k: number, mc: number, n: number, previous: number): Fit | null => { + const fitted = solve(cols, limits, at.planned, k, n * interval, mc); + if ( + !fitted || + shortfallCrossed( + floor, + curveShortfalls(fitted.built.nodes, limits.limitRadiusM, limits.limitArcM), + ) + ) + return null; + if (previous < 0) return fitted; + const moved = Math.abs( + (mcOf(fitted.built, previous) ?? Infinity) - (mcOf(at.built, previous) ?? 0), + ); + return moved <= tol / 5 ? fitted : null; + }; + /** `k` 뒤 첫 곡선이 측점 위이거나 맞춰지나(앞보기 한 걸음). */ + const nextSettles = (at: Fit, k: number, last: number): boolean => { + for (let j = k + 1; j < at.planned.length - 1; j += 1) { + const mc = mcOf(at.built, j); + if (mc === null) continue; + if (onAt(mc, last) !== undefined) return true; + return !cols.apexLock[j] && near(mc, last).some((n) => attempt(at, j, mc, n, k) !== null); + } + return true; + }; let lastStation = 0; // 앞 곡선이 놓인 측점 번호(없으면 0 = 시점) let previous = -1; // 앞 곡선 꺾임점 for (let k = 1; k < planned.length - 1; k += 1) { const mc = mcOf(built, k); if (mc === null) continue; - const near = Math.max(lastStation + 1, Math.round(mc / interval)); - const onStation = Math.abs(mc - near * interval) <= mcTolM(interval); - const fitted = - onStation || k < from || cols.apexLock[k] || Math.abs(near * interval - mc) > interval / 2 - ? null - : solve(cols, limits, planned, k, near * interval, mc); - const keep = - fitted && - !shortfallCrossed( - floor, - curveShortfalls(fitted.built.nodes, limits.limitRadiusM, limits.limitArcM), - ) && - (previous < 0 || - Math.abs((mcOf(fitted.built, previous) ?? Infinity) - (mcOf(built, previous) ?? 0)) <= - mcTolM(interval) / 5); - if (fitted && keep) ({ planned, built } = fitted); - if (onStation || keep) lastStation = near; + const on = onAt(mc, lastStation); + if (on !== undefined) lastStation = on; + else if (k >= from && !cols.apexLock[k]) { + let pick: { fit: Fit; n: number } | null = null; + for (const n of near(mc, lastStation)) { + const fit = attempt({ planned, built }, k, mc, n, previous); + if (!fit) continue; + pick ??= { fit, n }; + if (nextSettles(fit, k, n)) { + pick = { fit, n }; + break; + } + } + if (pick) (({ planned, built } = pick.fit), (lastStation = pick.n)); + } previous = k; } return planned; diff --git a/resources/tester/helper_454_arrow_station_step.cjs b/resources/tester/helper_454_arrow_station_step.cjs new file mode 100644 index 000000000..fd8ac2627 --- /dev/null +++ b/resources/tester/helper_454_arrow_station_step.cjs @@ -0,0 +1,96 @@ +/* 454(433-21 ②) 방향키 측점 No. 걸음 — 무작위 짧은 다리 노선(14 IP · 고정 R) 교점마다 네 방향 한 걸음 + * 누르고 측점 밖 막힘 수를 JSON 한 줄로(`test_454_arrow_station_step.py` 가 판정). + * 누름 판정은 편집기 방향키 길과 같음 — 하한 깸 = 막힘 · 미리보기(`fitDrag`) 측점 밖이 늘면 막힘. + */ +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(); + +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) => + 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 LOGIC = path.join(ROOT, "B05_Profile", "logic"); +const fit = loadTs(path.join(LOGIC, "B05_Profile_RouteEdit_StationFit.ts")); +const edits = loadTs(path.join(LOGIC, "B05_Profile_UI_RouteEdit_Edits.ts")); + +const INTERVAL = 20; +const LIMITS = { minRadiusM: 30, limitRadiusM: 12, limitArcM: 0, intervalM: INTERVAL }; +const columns = (p) => ({ + planned: p, + curveOn: p.map(() => true), + curveRadius: p.map(() => null), + curveLock: p.map(() => null), + curveArc: p.map(() => null), + apexLock: p.map((_, i) => i === 0 || i === p.length - 1), +}); +const copy = (c) => ({ + ...c, + planned: c.planned.map(([x, y]) => [x, y]), + curveRadius: [...c.curveRadius], +}); +const off = (b) => fit.offStationCount(b.vertices, b.curves, INTERVAL); +const floor = (b) => edits.curveShortfalls(b.nodes, LIMITS.limitRadiusM, LIMITS.limitArcM); + +function route(seed) { + const rnd = () => (seed = (seed * 16807) % 2147483647) / 2147483647; + const line = [[0, 0]]; + let a = 0; + for (let i = 0; i < 14; i += 1) { + a += (rnd() - 0.5) * 1.6; + const length = 35 + rnd() * 60; + const p = line[line.length - 1]; + line.push([p[0] + Math.cos(a) * length, p[1] + Math.sin(a) * length]); + } + return line; +} + +const out = { press: 0, floorBlock: 0, offBlock: 0, offStart: 0, offAfterRelease: 0 }; +for (let seed = 1; seed <= 10; seed += 1) { + const hist = columns(fit.fitCurveStations(columns(route(seed)), LIMITS, 1)); + hist.curveRadius = edits + .rebuildLine(copy(hist), LIMITS) + .nodes.map((n) => (n.radius_m === null ? null : Math.round(n.radius_m * 100) / 100)); + out.offStart += off(edits.rebuildLine(copy(hist), LIMITS)); + for (let k = 1; k < hist.planned.length - 1; k += 1) + for (const [dx, dy] of [ + [1, 0], + [-1, 0], + [0, 1], + [0, -1], + ]) { + const st = copy(hist); + const b0 = edits.rebuildLine(st, LIMITS); + const node = st.planned[k]; + st.planned[k] = [node[0] + dx * INTERVAL, node[1] + dy * INTERVAL]; + out.press += 1; + if (edits.shortfallCrossed(floor(b0), floor(edits.rebuildLine(st, LIMITS)))) { + out.floorBlock += 1; + continue; + } + const pv = { ...st, planned: fit.fitDrag(st, hist.planned, k, LIMITS) }; + if (off(edits.rebuildLine(copy(pv), LIMITS)) > off(b0)) { + out.offBlock += 1; + continue; + } + const rel = { ...pv, planned: fit.fitCurveStations(pv, LIMITS, fit.fitStart(hist, pv)) }; + if (off(edits.rebuildLine(copy(rel), LIMITS))) out.offAfterRelease += 1; + } +} +process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_454_arrow_station_step.py b/resources/tester/test_454_arrow_station_step.py new file mode 100644 index 000000000..8dfd47aa3 --- /dev/null +++ b/resources/tester/test_454_arrow_station_step.py @@ -0,0 +1,35 @@ +"""454(433-21 ②) 방향키 측점 No. 20 m 걸음 막힘 — 곡선 맞춤이 후보 측점 둘(내림 · 올림) + 뒤 곡선 +앞보기로 고름. 무작위 노선 열 개 · 교점마다 네 방향 = 520 누름(`helper_454_arrow_station_step.cjs`). +고치기 전 측점 밖 막힘 48 · 처음 맞춤 측점 밖 2 · 놓은 뒤 측점 밖 43 → 6 · 0 · 0. + + ./venv/Scripts/python.exe -m pytest resources/tester/test_454_arrow_station_step.py -q +""" + +import json +import subprocess +from pathlib import Path + +import pytest + +HELPER = Path(__file__).with_name("helper_454_arrow_station_step.cjs") + + +@pytest.fixture(scope="module") +def got() -> dict: + run = subprocess.run( # noqa: S603 — 고정 실행 파일 + ["node", str(HELPER)], capture_output=True, text=True, encoding="utf-8", timeout=120 + ) + assert run.returncode == 0, run.stderr + return json.loads(run.stdout) + + +def test_누름_수(got) -> None: + assert got["press"] == 520 + + +def test_측점_밖_막힘_줄어듦(got) -> None: + assert got["offBlock"] <= 10 + + +def test_처음_맞춤_놓은_뒤_측점_밖_없음(got) -> None: + assert got["offStart"] == 0 and got["offAfterRelease"] == 0 From 1458010944a30c606f467eeeb09a74181d706af0 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 18 Oct 2026 21:33:18 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor(454=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EC=A4=84=20=EC=9C=A0=ED=86=A0=20=EB=8B=A8=EA=B3=84):=20?= =?UTF-8?q?=EC=95=88=20=EC=93=B0=EB=8A=94=20masshaul=20=C2=B7=20haul=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=20=EA=B1=B7=EC=9D=8C=20(433-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 계산 줄 masshaul(유토곡선 계열) · haul(흙 배분 POST) 단계 지움 — 읽는 화면 없음 · 조작마다 헛 셈 · 헛 POST - 유토곡선 판 계산 · createHaulPlanPrefetch · postHaulPlan 남김 · 표준단면 도착 때 cross 다시 먹이기 남김 - 탐침 시험: 끝 누가토량은 공용 계산 헬퍼(helper_454_final_cumulative) · 뒤 단계 늦은 응답 = corridor · 묶음 흙 배분 POST 0 단언 Co-Authored-By: Claude Opus 5.5 --- A00_Common/design/design_pipeline_page.ts | 24 +++------ A00_Common/design/design_profile.ts | 2 +- A00_Common/design/logic/design_pipeline.ts | 7 +-- .../design/logic/design_pipeline_types.ts | 8 +-- B05_Profile/B05_Profile_Api_HaulPlan.ts | 22 +------- B05_Profile/B05_Profile_UI_Profile_Preview.ts | 2 +- .../logic/B05_Profile_MassHaul_Stage.ts | 53 ------------------- resources/tester/helper_344_3_entry.ts | 4 +- .../tester/helper_344_3_station_keys.mjs | 35 ------------ .../tester/helper_344_4_design_pipeline.cjs | 24 +++++---- .../helper_346_2_pipeline_cross_page.cjs | 10 ++-- .../tester/helper_349_0g_draft_b06_loop.cjs | 3 +- .../tester/helper_349_0j_draft_vs_saved.cjs | 3 +- resources/tester/helper_349_pipeline_once.cjs | 5 +- .../tester/helper_454_final_cumulative.cjs | 23 ++++++++ resources/tester/test_344_3_station_keys.py | 9 +--- .../tester/test_344_4_design_pipeline.py | 13 ++--- .../tester/test_346_2_pipeline_cross_page.py | 9 ++-- .../tester/test_354_2_masshaul_cross_only.py | 5 +- 19 files changed, 74 insertions(+), 187 deletions(-) delete mode 100644 B05_Profile/logic/B05_Profile_MassHaul_Stage.ts create mode 100644 resources/tester/helper_454_final_cumulative.cjs diff --git a/A00_Common/design/design_pipeline_page.ts b/A00_Common/design/design_pipeline_page.ts index 04d89ca2d..3f0ae51e6 100644 --- a/A00_Common/design/design_pipeline_page.ts +++ b/A00_Common/design/design_pipeline_page.ts @@ -6,9 +6,8 @@ * route · ground `B05_Profile/logic/B05_Profile_Pipeline_RouteGround.ts`(sub8) * follow · planline · profile `B05_Profile/logic/B05_Profile_Pipeline_Grade.ts` * stations `design_pipeline_stations.ts` · footprint `B05_Profile_Footprint_Stage.ts`(sub6) - * cross · masshaul · haul `B06_Section_Cross_Stage` · `logic/B05_Profile_MassHaul_Stage` · - * `B05_Profile_Api_HaulPlan`(sub2) · drainage 배수유역 판 `pipelineStage`(sub3) · corridor 3D 「낡음」. - * haul · corridor 는 노선 초안 동안만(저장 노선은 유토곡선 판 · 횡단 다시 셈이 맡음). + * cross `B06_Section_Cross_Stage` · drainage 배수유역 판 `pipelineStage`(sub3) · corridor 3D 「낡음」. + * corridor 는 노선 초안 동안만 · 유토곡선 · 흙 배분은 유토곡선 판이 셈(454). * * 입력 — 노선 = 편집기 다리(`RouteDraftBridge` · 캐시 칸 route-draft) · 계획고 = 종단도 편집 * (`pipelineGrade` · 노선 초안 동안은 alignment-draft 노선 `DRAFT_ROUTE_ID`) · 횡단 = 저장 상세의 측점 @@ -65,7 +64,6 @@ import { fetchRoutePlan, type RoutePlanResponse, } from "../../B05_Profile/B05_Profile_Api_Replan"; -import { createHaulStage } from "../../B05_Profile/B05_Profile_Api_HaulPlan"; import { fetchStructureTypes } from "../../B05_Profile/B05_Profile_Api_Structures"; import { forgetPageData, pagePipes, pageStructures, trackPageBusy } from "./design_page_data"; import type { PagePipelineOptions, ProfileLink } from "./design_pipeline_page_types"; @@ -98,10 +96,6 @@ import { SECTION_STATION_INTERVAL_M, } from "../../B05_Profile/logic/B05_Profile_Sections_Stations"; import { footprintStage } from "../../B05_Profile/logic/B05_Profile_Footprint_Stage"; -import { - createMassHaulStage, - type MassHaulStageContext, -} from "../../B05_Profile/logic/B05_Profile_MassHaul_Stage"; import type { PlanPipe, PlanStructure, @@ -122,7 +116,7 @@ interface PagePipeline { bridge: RouteDraftBridge; grade(edits: AlignmentEdits): void; restart(): void; - earthwork(context: MassHaulStageContext | null): void; + earthwork(): void; cross(): void; reconcile(): Promise; ownsCross: boolean; @@ -154,9 +148,8 @@ export const pipelineGrade = (projectId: string, edits: AlignmentEdits): void => /** 저장 정본이 바뀜(새로 받은 상세 · 새 노선) — 처음부터 다시. 갈아끼운 상세 · 같은 상세 다시 그림은 안 셈. */ export const restartPagePipeline = (projectId: string): void => pages.get(projectId)?.restart(); -/** 유토곡선 설정(토량환산계수 · 자연방토 경사) — masshaul 단계 재료 · 표준단면이 막 왔으면 횡단 입력도. */ -export const pipelineEarthwork = (projectId: string, context: MassHaulStageContext | null): void => - pages.get(projectId)?.earthwork(context); +/** 유토곡선 설정(표준단면 포함)이 막 옴 — 횡단 입력을 다시 먹임. */ +export const pipelineEarthwork = (projectId: string): void => pages.get(projectId)?.earthwork(); /** 횡단 고침(지반 유형 · 측구 · 암 · 소단 · 표준 — B06 · 365-2) — 세션 횡단 입력을 다시 모아 cross 부터. */ export const pipelineCross = (projectId: string): void => pages.get(projectId)?.cross(); @@ -195,7 +188,6 @@ function createPagePipeline( let enabled: PlanType[] = []; let savedStructures: PlanStructure[] = []; let savedPipes: PlanPipe[] = []; - let earthwork: MassHaulStageContext | null = null; /** 저장 노선 측점(열쇠) · 마지막으로 모은 횡단 입력(시설 제원 — 새 노선 상세가 씀). */ let savedStations: PipelineStation[] = []; let crossSource: PipelineCrossSource | null = null; @@ -280,7 +272,6 @@ function createPagePipeline( }; const groundDeps = groundStageDeps(projectId); const crossStage = createCrossStage({ grid: groundDeps.grid }); // 미교차 넓힘 = 같은 지형 격자(349-0j) - const haulStage = createHaulStage(projectId, () => savedSource().routeId ?? undefined); const followStage = createFollowStage({ savedLine, saved: () => { @@ -379,8 +370,6 @@ function createPagePipeline( }); }, footprint: footprintStage, - masshaul: createMassHaulStage(() => earthwork), - haul: (ctx) => (ctx.inputs.route ? haulStage(ctx) : (ctx.prev ?? null)), drainage: link.side?.pipelineStage, corridor: (ctx) => { if (ctx.inputs.route) options.onCorridorStale?.(); @@ -678,8 +667,7 @@ function createPagePipeline( // 갈아끼움 · 같은 상세 다시 그림은 안 셈 · 보기가 심은 측점은 묶음이 입력으로 셈(386-3) if (canonChanged()) void loadPlan().then(start); }, - earthwork(context) { - earthwork = context; + earthwork() { const cross = crossNow()?.input ?? null; if (started && cross) void feed("cross", cross); }, diff --git a/A00_Common/design/design_profile.ts b/A00_Common/design/design_profile.ts index 8cbb093ff..ce8acc6f7 100644 --- a/A00_Common/design/design_profile.ts +++ b/A00_Common/design/design_profile.ts @@ -522,7 +522,7 @@ export function createDesignProfile(ctx: DesignProfileContext) { */ setEarthworkContext(next: RouteMassHaulContext | null) { massHaul.setContext(next); - pagePipeline.pipelineEarthwork(projectId, next); + pagePipeline.pipelineEarthwork(projectId); drawSoon(); }, setSelectedStation(stationId: string | null) { diff --git a/A00_Common/design/logic/design_pipeline.ts b/A00_Common/design/logic/design_pipeline.ts index 970dc5384..ce23b5d06 100644 --- a/A00_Common/design/logic/design_pipeline.ts +++ b/A00_Common/design/logic/design_pipeline.ts @@ -39,10 +39,9 @@ export const BROWSER_STAGES: StageName[] = [ "profile", "cross", "footprint", - "masshaul", ]; /** 뒤 — 브라우저 단계가 끝나면 함께 띄우고 기다리지 않음. */ -export const AFTER_STAGES: StageName[] = ["haul", "drainage", "corridor"]; +export const AFTER_STAGES: StageName[] = ["drainage", "corridor"]; type Dep = StageName | `input.${InputKey}`; @@ -56,15 +55,13 @@ export const STAGE_DEPS: Record = { profile: ["planline", "input.alignment"], cross: ["profile", "ground", "input.cross", "input.pipes", "input.structures"], footprint: ["cross", "ground"], - masshaul: ["cross"], - haul: ["masshaul"], drainage: ["route", "input.pipes"], corridor: ["profile", "cross"], }; /** follow 가 고칠 수 있는 입력 — 노선 · 횡단은 안 됨(고리). */ const FOLLOW_KEYS: ReadonlyArray = ["pipes", "structures", "alignment"]; -/** 이 단계 뒤 한 번 쉼 — 종단이 먼저 그려지고 횡단 · 유토곡선이 그다음. */ +/** 이 단계 뒤 한 번 쉼 — 종단이 먼저 그려지고 횡단이 그다음. */ const YIELD_AFTER = new Set(["profile"]); export const RUN_LOG_SIZE = 20; diff --git a/A00_Common/design/logic/design_pipeline_types.ts b/A00_Common/design/logic/design_pipeline_types.ts index 217735186..003289de6 100644 --- a/A00_Common/design/logic/design_pipeline_types.ts +++ b/A00_Common/design/logic/design_pipeline_types.ts @@ -9,9 +9,9 @@ * plusStations(추가 측점 · 429 ⑨) * ↓ update(출처, 값, 범위) — 조작마다 차례 번호(generation) +1 · 옛 계산 · 옛 요청 버림 * 브라우저 즉시(차례대로 · 단계 사이마다 번호 확인 · 단계마다 결과 바로 내보냄) - * route → follow → stations → ground → planline → profile → cross → footprint → masshaul + * route → follow → stations → ground → planline → profile → cross → footprint * 뒤(기다리지 않음 · 새 조작이 오면 AbortSignal 로 끊김 · 옛 번호 결과 버림) - * haul(흙 배분 · 서버 Node) · drainage(배수유역 「낡음」 표시) · corridor(3D 「낡음」 표시) + * drainage(배수유역 「낡음」 표시) · corridor(3D 「낡음」 표시) * * 규칙: * · 단계는 자기 출력만 돌려줌 — 입력 · 다른 단계 출력을 고치지 않음(ctx 의 값은 읽기만). @@ -31,8 +31,6 @@ * | profile | sub1 | planline · input.alignment | ProfileAlignment | * | cross | sub2 | profile · ground · input.cross · pipes · structures | CrossOutput | * | footprint | sub6 | cross · ground | PlanFootprint | - * | masshaul | sub2 | cross | unknown(sub2 모양) | - * | haul | sub2 | masshaul | unknown(서버 응답) | * | drainage | sub3 | route · input.pipes | 「낡음」 표시(출력 안 씀) | * | corridor | sub5 | profile · cross | 「낡음」 표시(출력 안 씀) | * @@ -238,8 +236,6 @@ export interface PipelineOutputs { profile: ProfileAlignment; cross: CrossOutput; footprint: PlanFootprint; - masshaul: unknown; - haul: unknown; drainage: null; corridor: null; } diff --git a/B05_Profile/B05_Profile_Api_HaulPlan.ts b/B05_Profile/B05_Profile_Api_HaulPlan.ts index acfa2e943..78fe2810d 100644 --- a/B05_Profile/B05_Profile_Api_HaulPlan.ts +++ b/B05_Profile/B05_Profile_Api_HaulPlan.ts @@ -12,8 +12,6 @@ import { API_BASE_URL } from "@config/config_frontend"; import type { HaulPlan } from "@util/common_util_mass_haul_balance"; -import type { StageContext } from "../A00_Common/design/logic/design_pipeline_types"; -import type { MassHaulStageOutput } from "./logic/B05_Profile_MassHaul_Stage"; /** 서버가 돌려주는 배분 한 벌 — **화면이 쓰는 꼴 그대로**라 그리기 코드가 손대지 않는다. * `import type` 이라 배분 모듈이 번들에 실리지 않는다(빌드에서 지워진다). */ @@ -26,7 +24,7 @@ const TIMEOUT_MS = 20000; /** 열쇠별로 받아 둔 배분 — 되돌리기 · 보기 오감처럼 같은 누가토량이 다시 오면 묻지 않음(342-1). */ const KEPT_PLANS = 8; -/** 받은 배분 — `프로젝트:노선|열쇠` → 배분 · 오래된 것부터 버림 · 선반입 · 묶음 단계가 함께 씀. */ +/** 받은 배분 — `프로젝트:노선|열쇠` → 배분 · 오래된 것부터 버림 · 선반입이 씀. */ const kept = new Map(); /** @@ -86,24 +84,6 @@ async function postHaulPlan( } } -/** - * 계산 묶음 `haul` 단계(PLAN 346-2 · 계약 `design_pipeline_types.ts`) — masshaul 의 횡단 기준 결과를 서버 - * Node 로 배분 · 같은 열쇠면 안 물음 · 새 조작이 오면 `ctx.signal` 로 끊김(옛 번호 결과는 묶음이 버림). - * 못 받으면 앞 배분(`ctx.prev`) 그대로. - */ -export function createHaulStage(projectId: string, routeId: () => number | undefined) { - return async (ctx: StageContext<"haul">): Promise => { - const result = (ctx.out.masshaul as MassHaulStageOutput | null)?.haulInput; - const route = routeId(); - if (!result || !route) return (ctx.prev as HaulPlanPayload) ?? null; - const key = keyOf(result); - const at = keptKey(projectId, route, key); - if (kept.has(at)) return kept.get(at) ?? null; - const plan = await postHaulPlan(projectId, route, result, key, ctx.signal); - return plan === undefined ? ((ctx.prev as HaulPlanPayload) ?? null) : plan; - }; -} - export interface HaulPlanPrefetch { /** 새 누가토량 결과가 나왔음을 알린다 — 잠잠해지면 서버에 물어본다. */ schedule: (result: unknown) => void; diff --git a/B05_Profile/B05_Profile_UI_Profile_Preview.ts b/B05_Profile/B05_Profile_UI_Profile_Preview.ts index 06b7a969c..3adf9a94d 100644 --- a/B05_Profile/B05_Profile_UI_Profile_Preview.ts +++ b/B05_Profile/B05_Profile_UI_Profile_Preview.ts @@ -17,7 +17,7 @@ import { useDesignStore } from "../A00_Common/design/design_store"; import type { DesignPipeline, PipelineEvent } from "../A00_Common/design/logic/design_pipeline"; /** cross 를 지났다고 볼 단계(cross 가 같은 입력이라 건너뛰어도 뒤 단계가 알림). */ -const AFTER_CROSS = new Set(["cross", "footprint", "masshaul"]); +const AFTER_CROSS = new Set(["cross", "footprint"]); /** 묶음 차례가 cross 를 못 낸 채 끝나도(단계 실패) 「다시 계산 중」을 푸는 때(ms). */ const DRAFT_WAIT_MS = 3000; diff --git a/B05_Profile/logic/B05_Profile_MassHaul_Stage.ts b/B05_Profile/logic/B05_Profile_MassHaul_Stage.ts deleted file mode 100644 index 68f01ce28..000000000 --- a/B05_Profile/logic/B05_Profile_MassHaul_Stage.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* ============================================================================= - * B05_Profile_MassHaul_Stage.ts - * 계산 묶음 `masshaul` 단계(PLAN 346-2 · 계약 `design_pipeline_types.ts`) — cross(측점 열쇠마다 면적) · - * profile → 유토곡선 계열(`computeMassHaulSeries` · 화면 유토곡선 · B06 과 같은 계산). - * - * 출력 = `MassHaulStageOutput`(계열 + 흙 배분에 보낼 횡단 기준 결과) · 순수 계산(DOM · fetch · 세션 없음). - * 토량환산계수 · 자연 사토 기울기는 부르는 쪽이 넘김(`context`) — 없으면 null. - * ========================================================================== */ - -import { computeMassHaulSeries, type MassHaulSeries } from "@util/common_util_mass_haul"; -import type { EarthworkConversion, MassHaulSection } from "@util/common_util_mass_haul_types"; -import type { StageContext } from "../../A00_Common/design/logic/design_pipeline_types"; - -export interface MassHaulStageContext { - conversion: EarthworkConversion; - naturalSpoilMinSlope?: number; -} - -export interface MassHaulStageOutput { - series: MassHaulSeries[]; - /** 흙 배분(haul 단계)에 보낼 결과 — 횡단 기준(없으면 첫 계열) · 계열이 없으면 null. */ - haulInput: MassHaulSeries["result"] | null; -} - -/** `masshaul` 단계 만들기 — `context()` 는 부를 때마다 읽음(설정이 바뀌면 다음 계산부터). */ -export function createMassHaulStage(context: () => MassHaulStageContext | null) { - return (ctx: StageContext<"masshaul">): MassHaulStageOutput | null => { - const cross = ctx.out.cross; - const settings = context(); - if (!cross || !settings) return null; - const sections: MassHaulSection[] = Object.values(cross.byKey) - .sort((a, b) => a.chainageM - b.chainageM) - .map((entry) => ({ - station_id: entry.key, - chainage_m: entry.chainageM, - design: entry.detail as MassHaulSection["design"], - })); - const samples = ctx.out.profile?.samples ?? []; - const longitudinal = { - length_m: - samples[samples.length - 1]?.chainage_m ?? sections[sections.length - 1]?.chainage_m ?? 0, - design_profiles: samples.length ? [{ samples }] : [], - }; - const series = computeMassHaulSeries( - longitudinal, - sections, - settings.conversion, - settings.naturalSpoilMinSlope, - ); - const picked = series.find((entry) => entry.basis === "cross") ?? series[0]; - return { series, haulInput: picked?.result ?? null }; - }; -} diff --git a/resources/tester/helper_344_3_entry.ts b/resources/tester/helper_344_3_entry.ts index 6368371f3..684e63d3d 100644 --- a/resources/tester/helper_344_3_entry.ts +++ b/resources/tester/helper_344_3_entry.ts @@ -1,5 +1,5 @@ /* 344-3 · 346-2 2파 시험 묶음 진입점 — 진짜 모듈을 vite ssr 로 한 벌 묶어 Node 헬퍼가 부름. - * 측점 열쇠(옛 초안 옮김 · 노선 밖 고침) · cross · masshaul · haul 단계를 화면 · 서버 없이 잰다. + * 측점 열쇠(옛 초안 옮김 · 노선 밖 고침) · cross 단계를 화면 · 서버 없이 잰다. */ export { carryStationDrafts, @@ -20,8 +20,6 @@ export { readRockBoundarySession, } from "../../B06_Section/B06_Section_UI_Page_Persist"; export { createCrossStage, groundSamples } from "../../B06_Section/logic/B06_Section_Cross_Stage"; -export { createMassHaulStage } from "../../B05_Profile/logic/B05_Profile_MassHaul_Stage"; -export { createHaulStage } from "../../B05_Profile/B05_Profile_Api_HaulPlan"; export { buildAlignment, emptyEdits, diff --git a/resources/tester/helper_344_3_station_keys.mjs b/resources/tester/helper_344_3_station_keys.mjs index 1a5aebfb3..99ff8dd34 100644 --- a/resources/tester/helper_344_3_station_keys.mjs +++ b/resources/tester/helper_344_3_station_keys.mjs @@ -202,39 +202,4 @@ const edited = m.buildAlignment(base, { station_offsets: { "40.000": 0.4 }, curv stage(ctxOf(touched, { sources: ["grade"], fromM: 20, toM: 60, keys: [] }, third, edited)); out.crossGrade = counts(); -/* 6. masshaul 단계 — 계열 · 흙 배분 입력 */ -const conversion = { - soil: { loose: 1.25, compacted: 0.9 }, - ripping_rock: { loose: 1.5, compacted: 1.2 }, - blasting_rock: { loose: 1.65, compacted: 1.35 }, -}; -const massStage = m.createMassHaulStage(() => ({ conversion })); -const mass = massStage({ ...ctxOf(crossInput, null, null), out: { profile, cross: first } }); -out.massSeries = mass.series.map((s) => s.basis); -out.massHaulInput = Array.isArray(mass.haulInput?.points); - -/* 7. haul 단계 — 같은 열쇠 안 물음 · 끊김 = 앞 배분 그대로 */ -const posts = []; -globalThis.fetch = async (_url, init) => { - if (init.signal?.aborted) throw new Error("aborted"); - posts.push(1); - return { ok: true, json: async () => ({ haul_plan: { n: posts.length } }) }; -}; -const haul = m.createHaulStage(P, () => R); -const haulCtx = (masshaul, prev, signal = new AbortController().signal) => ({ - ...ctxOf(crossInput, null, prev), - signal, - out: { masshaul }, -}); -const plan1 = await haul(haulCtx(mass, null)); -const plan2 = await haul(haulCtx(mass, plan1)); -const other = massStage({ - ...ctxOf(crossInput, null, null), - out: { profile: edited, cross: third }, -}); -const aborted = new AbortController(); -aborted.abort(); -const plan3 = await haul(haulCtx(other, plan1, aborted.signal)); -out.haul = { posts: posts.length, plan1, plan2, plan3 }; - process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/helper_344_4_design_pipeline.cjs b/resources/tester/helper_344_4_design_pipeline.cjs index 1550ac8b2..19e39415b 100644 --- a/resources/tester/helper_344_4_design_pipeline.cjs +++ b/resources/tester/helper_344_4_design_pipeline.cjs @@ -133,7 +133,7 @@ async function run() { }; } - // ② 늦은 응답 버림 — 브라우저 단계(ground 늦음) · 뒤 단계(haul 늦음 · 끊김) + // ② 늦은 응답 버림 — 브라우저 단계(ground 늦음) · 뒤 단계(corridor 늦음 · 끊김) { const log = []; let releaseGround = null; @@ -161,25 +161,27 @@ async function run() { commits, }; - // 뒤 단계 — haul 1 이 늦게 옴 · 그 사이 새 조작 → 끊김 · 버림 - let releaseHaul = []; - const hold2 = { haul: () => new Promise((resolve) => releaseHaul.push(resolve)) }; + // 뒤 단계 — corridor 1 이 늦게 옴 · 그 사이 새 조작 → 끊김 · 버림 + let releaseAfter = []; + const hold2 = { corridor: () => new Promise((resolve) => releaseAfter.push(resolve)) }; const log2 = []; const pipe2 = pipeMod.createDesignPipeline({ stages: fakeStages(log2, hold2) }); const commits2 = []; - pipe2.subscribe((e) => e.kind === "stage" && e.stage === "haul" && commits2.push(e.generation)); + pipe2.subscribe( + (e) => e.kind === "stage" && e.stage === "corridor" && commits2.push(e.generation), + ); await pipe2.start(BASE); - const haul1 = log2.find((e) => e.name === "haul"); + const after1 = log2.find((e) => e.name === "corridor"); await pipe2.update("cross", { x: 1 }, { fromM: 0, toM: 0 }); - const haul1Aborted = haul1.aborted(); - releaseHaul[0](); + const after1Aborted = after1.aborted(); + releaseAfter[0](); await tick(1); - releaseHaul[1](); + releaseAfter[1](); await pipe2.settled(); out.lateAfter = { - haul1Aborted, + after1Aborted, commits: commits2, - haulGen: pipe2.output("haul").gen, + afterGen: pipe2.output("corridor").gen, generation: pipe2.generation, }; } diff --git a/resources/tester/helper_346_2_pipeline_cross_page.cjs b/resources/tester/helper_346_2_pipeline_cross_page.cjs index 9699139ba..86360a208 100644 --- a/resources/tester/helper_346_2_pipeline_cross_page.cjs +++ b/resources/tester/helper_346_2_pipeline_cross_page.cjs @@ -1,4 +1,4 @@ -/* PLAN 346-1 · 346-2 붙임 단위검증 헬퍼 — 페이지 묶음에 cross · masshaul · haul · drainage · corridor 를 +/* PLAN 346-1 · 346-2 붙임 단위검증 헬퍼 — 페이지 묶음에 cross · drainage · corridor 를 * 붙인 것. 진짜 TS(`design_pipeline_page` · `_cross` · 단계 파일 전부)를 그 자리에서 트랜스파일해 Node 로 * 돌림. 서버 창구(노선 · 구조물 · 관 목록 · 지형 격자 · 표준단면 · 흙 배분 fetch)만 가짜. * 입력 argv[2] JSON · argv[3] 격자 이진. 결과 = 표준출력 JSON. 짝 = `test_346_2_pipeline_cross_page.py`. */ @@ -6,6 +6,7 @@ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const CATALOG = require("./helper_322_catalog_module.cjs"); +const finalCumulative = require("./helper_454_final_cumulative.cjs"); const ROOT = path.join(__dirname, "..", ".."); const job = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); @@ -29,7 +30,7 @@ globalThis.window = { clearTimeout, }; -// 흙 배분 서버 — 늦게 답함 · 끊기면 버림 +// 흙 배분 서버 — 늦게 답함 · 끊기면 버림 · 묶음은 안 부름(454 masshaul · haul 단계 걷음) const haul = { calls: 0, aborted: 0, answered: 0 }; globalThis.fetch = (url, init) => new Promise((resolve, reject) => { @@ -205,7 +206,7 @@ async function run() { stationIntervalM: job.interval, onCorridorStale: () => (corridor += 1), }); - pageMod.pipelineEarthwork(P, { conversion: job.conversion }); + pageMod.pipelineEarthwork(P); const pipe = pageMod.pagePipeline(P); const commits = []; pipe.subscribe((e) => e.kind === "stage" && commits.push(`${e.stage}@${e.generation}`)); @@ -242,7 +243,6 @@ async function run() { const ground = pipe.output("ground"); const profile = pipe.output("profile"); const shown = pageMod.pipelineDetail(P); - const mass = pipe.output("masshaul"); const moved = { run: { outcome: runB.outcome, @@ -264,7 +264,7 @@ async function run() { cut: pipe.output("cross").byKey[s.key]?.cutAreaM2 ?? null, fill: pipe.output("cross").byKey[s.key]?.fillAreaM2 ?? null, })), - finalCumulative: mass?.haulInput?.final_cumulative_m3 ?? null, + finalCumulative: finalCumulative(load, pipe, job.conversion), footprint: pipe.output("footprint")?.records.length ?? 0, detail: shown && { sections: shown.cross_sections.length, diff --git a/resources/tester/helper_349_0g_draft_b06_loop.cjs b/resources/tester/helper_349_0g_draft_b06_loop.cjs index c5f3a20be..9276b3628 100644 --- a/resources/tester/helper_349_0g_draft_b06_loop.cjs +++ b/resources/tester/helper_349_0g_draft_b06_loop.cjs @@ -6,6 +6,7 @@ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const finalCumulative = require("./helper_454_final_cumulative.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); const ROOT = path.join(__dirname, "..", ".."); @@ -167,7 +168,7 @@ async function run() { drawn = next; void Promise.resolve().then(renderB06); }); - const final = () => pipe.output("masshaul")?.haulInput?.final_cumulative_m3 ?? null; + const final = () => finalCumulative(load, pipe, job.conversion); const cut = () => Object.values(pipe.output("cross")?.byKey ?? {}).reduce((s, c) => s + (c.cutAreaM2 ?? 0), 0); diff --git a/resources/tester/helper_349_0j_draft_vs_saved.cjs b/resources/tester/helper_349_0j_draft_vs_saved.cjs index 7880ccf0f..cea4134d1 100644 --- a/resources/tester/helper_349_0j_draft_vs_saved.cjs +++ b/resources/tester/helper_349_0j_draft_vs_saved.cjs @@ -5,6 +5,7 @@ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const finalCumulative = require("./helper_454_final_cumulative.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); const ROOT = path.join(__dirname, "..", ".."); @@ -249,7 +250,7 @@ async function draftRun(P, grid) { totalMs: run.totalMs, line: pipe.output("route").line, curves: pipe.output("route").curves, - final: pipe.output("masshaul")?.haulInput?.final_cumulative_m3 ?? null, + final: finalCumulative(load, pipe, job.conversion), stations, copied, }; diff --git a/resources/tester/helper_349_pipeline_once.cjs b/resources/tester/helper_349_pipeline_once.cjs index 868ab78be..8d7cf79ff 100644 --- a/resources/tester/helper_349_pipeline_once.cjs +++ b/resources/tester/helper_349_pipeline_once.cjs @@ -6,6 +6,7 @@ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const finalCumulative = require("./helper_454_final_cumulative.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); const ROOT = path.join(__dirname, "..", ".."); @@ -198,7 +199,7 @@ async function run() { stations: chainagesOf(stations), saved: job.detail.cross_sections.map((s) => s.chainage_m), pipeChainages: (pipe.input("pipes") ?? []).map((p) => p.chainage_m), - final: pipe.output("masshaul")?.haulInput?.final_cumulative_m3 ?? null, + final: finalCumulative(load, pipe, job.conversion), footprint: pipe.output("footprint")?.records.length ?? 0, }; @@ -239,7 +240,7 @@ async function run() { const again = await openOnce("proj-349b"); const reopen = { runs: runs().slice(again.before).length, - final: again.pipe.output("masshaul")?.haulInput?.final_cumulative_m3 ?? null, + final: finalCumulative(load, again.pipe, job.conversion), stations: chainagesOf(again.pipe.output("ground")?.stations), }; again.stop(); diff --git a/resources/tester/helper_454_final_cumulative.cjs b/resources/tester/helper_454_final_cumulative.cjs new file mode 100644 index 000000000..b17efe05d --- /dev/null +++ b/resources/tester/helper_454_final_cumulative.cjs @@ -0,0 +1,23 @@ +/* 454 — 옛 masshaul 단계 탐침 자리: 묶음 cross · profile 출력으로 누가토량 끝값(m3). + * 유토곡선 판(`B05_Profile_UI_Profile_MassHaul`)과 같은 공용 계산(`computeMassHaulSeries`) · 횡단 기준. + * `load` = 부르는 헬퍼의 TS 로더(`load("common_util", "…ts")`). */ +module.exports = function finalCumulative(load, pipe, conversion) { + const cross = pipe.output("cross"); + const samples = pipe.output("profile")?.samples ?? []; + if (!cross) return null; + const { computeMassHaulSeries } = load("common_util", "common_util_mass_haul.ts"); + const sections = Object.values(cross.byKey) + .sort((a, b) => a.chainageM - b.chainageM) + .map((e) => ({ station_id: e.key, chainage_m: e.chainageM, design: e.detail })); + const series = computeMassHaulSeries( + { + length_m: + samples[samples.length - 1]?.chainage_m ?? sections[sections.length - 1]?.chainage_m ?? 0, + design_profiles: samples.length ? [{ samples }] : [], + }, + sections, + conversion, + ); + const picked = series.find((s) => s.basis === "cross") ?? series[0]; + return picked?.result?.final_cumulative_m3 ?? null; +}; diff --git a/resources/tester/test_344_3_station_keys.py b/resources/tester/test_344_3_station_keys.py index 06adb6ef7..37430c880 100644 --- a/resources/tester/test_344_3_station_keys.py +++ b/resources/tester/test_344_3_station_keys.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""344-3 · 346-2 2파 — 세션 횡단 초안 측점 열쇠 · cross · masshaul · haul 단계. +"""344-3 · 346-2 2파 — 세션 횡단 초안 측점 열쇠 · cross 단계(masshaul · haul 단계는 454 에서 걷음). 못박는 것 - 옛 초안(누가거리 열쇠)은 읽을 때 측점 열쇠로 옮김 · 지금 측점에 없는 것은 지우지 않고 남김. @@ -8,7 +8,6 @@ - 노선이 바뀜 = 측점 열쇠로 넘김(관 · 구조물은 옮긴 자리로) · 넘김 = 카드 선택 넷 · 암 경계 · 노견 확장(417) · 단면 유형 · 측구 방향 · 포장은 안 넘김 · 사라진 측점 = 「노선 밖 고침 N 건」. - cross 단계: 같은 입력 다시 셈 0 · 측점 하나 고침 = 1 · 범위 밖 재사용. - - masshaul 단계 = 계열 + 흙 배분 입력 · haul 단계 = 같은 열쇠 안 물음 · 끊김 = 앞 배분 그대로. Node 헬퍼 `helper_344_3_station_keys.mjs` 가 vite ssr 로 묶은 진짜 모듈(`helper_344_3_entry.ts`)을 부름. @@ -116,9 +115,3 @@ def test_cross_단계_국부_연산(out): assert out["crossOne"] == {"computed": 1, "reused": 5} assert out["crossOneChanged"] == "soil" assert out["crossGrade"]["computed"] <= 3 and out["crossGrade"]["reused"] >= 3 - - -def test_masshaul_haul_단계(out): - assert out["massSeries"] == ["cross", "longitudinal"] - assert out["massHaulInput"] is True - assert out["haul"] == {"posts": 1, "plan1": {"n": 1}, "plan2": {"n": 1}, "plan3": {"n": 1}} diff --git a/resources/tester/test_344_4_design_pipeline.py b/resources/tester/test_344_4_design_pipeline.py index 3010614c3..a39490257 100644 --- a/resources/tester/test_344_4_design_pipeline.py +++ b/resources/tester/test_344_4_design_pipeline.py @@ -31,9 +31,8 @@ BROWSER = [ "profile", "cross", "footprint", - "masshaul", ] -AFTER = ["haul", "drainage", "corridor"] +AFTER = ["drainage", "corridor"] @pytest.fixture(scope="module") @@ -65,7 +64,7 @@ def test_같은_입력_다시_안_셈(r): b = r["basic"] assert b["same"] == {"result": None, "generation": 1} # 같은 값 = 차례 번호도 안 오름 # 횡단만 바뀌면 노선 ~ 종단 · 배수유역은 건너뜀 - assert b["crossRan"] == ["cross", "footprint", "masshaul", "haul", "corridor"] + assert b["crossRan"] == ["cross", "footprint", "corridor"] assert b["crossSkipped"] == [ "route", "follow", @@ -76,7 +75,7 @@ def test_같은_입력_다시_안_셈(r): "drainage", ] # 계획고만 바뀌면 측점 · 지반 · 첫 계획선 · 배수유역 건너뜀 - assert b["gradeRan"] == ["profile", "cross", "footprint", "masshaul", "haul", "corridor"] + assert b["gradeRan"] == ["profile", "cross", "footprint", "corridor"] # 노선이 바뀌면 전부 assert b["routeRan"] == BROWSER + AFTER @@ -101,8 +100,8 @@ def test_늦은_응답_버림_브라우저_단계(r): def test_늦은_응답_버림_뒤_단계_끊김(r): late = r["lateAfter"] - assert late["haul1Aborted"] is True # 새 조작 = 옛 요청 끊음 - assert late["commits"] == [2] and late["haulGen"] == 2 and late["generation"] == 2 + assert late["after1Aborted"] is True # 새 조작 = 옛 요청 끊음 + assert late["commits"] == [2] and late["afterGen"] == 2 and late["generation"] == 2 def test_못_끝낸_단계는_범위를_합쳐_셈(r): @@ -133,8 +132,6 @@ def test_단계_시간_기록(r): "profile", "cross", "footprint", - "masshaul", - "haul", "corridor", ] assert t["last"]["totalIsNumber"] is True diff --git a/resources/tester/test_346_2_pipeline_cross_page.py b/resources/tester/test_346_2_pipeline_cross_page.py index b6645a1e6..fb66966d3 100644 --- a/resources/tester/test_346_2_pipeline_cross_page.py +++ b/resources/tester/test_346_2_pipeline_cross_page.py @@ -201,7 +201,7 @@ def r(request) -> dict: def test_열_때_한_번_발자국_저장_횡단_입력(r): o = r["open"] assert o["outcome"] == "done" - assert {"cross", "footprint", "masshaul"} <= set(o["stages"]) + assert {"cross", "footprint"} <= set(o["stages"]) assert o["crossCount"] == o["stations"] and o["footprint"] > 2 assert o["carried"] == o["stations"] # 저장 측점마다 입력이 측점 열쇠로 assert o["corridor"] == 0 and o["haulCalls"] == 0 and o["detail"] is None @@ -214,7 +214,7 @@ def test_노선_옮김_한_번에_유토곡선까지_시간(r): assert run["outcome"] == "done" and run["sources"] == ["route"] for stage in ("route", "stations", "ground", "planline", "profile", "cross", "footprint"): assert stage in run["stages"], stage - assert "masshaul" in run["stages"] + assert "masshaul" not in run["stages"] and "haul" not in run["stages"] # 454 걷음 assert ( sum(run["stages"][s] for s in run["stages"] if s not in ("haul", "drainage", "corridor")) < BUDGET_MS @@ -271,9 +271,8 @@ def test_누가토량_서버_엔진과_같은_자리수(r): def test_늦은_응답_버림(r): late = r["late"] assert late["genB"] > late["genA"] - assert late["haul"]["aborted"] >= 1 # 앞 차례 흙 배분 요청 끊김 - assert f"haul@{late['genA']}" not in late["haulCommits"] # 옛 차례 배분 안 들어옴 - assert late["haulCommits"][-1] == f"haul@{late['genB']}" + # 454 — 묶음은 흙 배분을 안 물음(유토곡선 판 선반입만) · 늦은 응답 버림 = 344-4 corridor + assert late["haul"]["calls"] == 0 and late["haulCommits"] == [] def test_저장_관_목록이_좌표로_따라감(r): diff --git a/resources/tester/test_354_2_masshaul_cross_only.py b/resources/tester/test_354_2_masshaul_cross_only.py index b6ad7f172..3f178e535 100644 --- a/resources/tester/test_354_2_masshaul_cross_only.py +++ b/resources/tester/test_354_2_masshaul_cross_only.py @@ -3,7 +3,7 @@ - 그리기 = 횡단 기준만(`["cross"]`) · 범례는 낸 계열마다라 종단 단추가 안 섬. - 저장된 범례가 종단(`longitudinal`)이면 횡단으로 맞춤 · 분배 켬은 그대로. -- 계산 묶음 단계(MassHaul_Stage)는 두 기준 그대로 · 공용 계산 코드 남김. +- 공용 계산 코드는 두 기준 그대로 남김(계산 묶음 masshaul 단계는 454 에서 걷음). `helper_354_2_masshaul_cross_only.cjs` 가 진짜 TS 를 잘라 돌림. """ @@ -52,8 +52,7 @@ def test_그리기는_횡단만_부름(): def test_묶음_단계와_공용_코드는_그대로(): - stage = (ROOT / "B05_Profile/logic/B05_Profile_MassHaul_Stage.ts").read_text(encoding="utf-8") - assert '["cross"]' not in stage + assert not (ROOT / "B05_Profile/logic/B05_Profile_MassHaul_Stage.ts").exists() # 454 걷음 util = (ROOT / "common_util/common_util_mass_haul.ts").read_text(encoding="utf-8") assert 'const SERIES_BASES: MassHaulBasis[] = ["cross", "longitudinal"];' in util assert "longitudinalAreaSamples(longitudinal, sections)" in util