fix(B05·B06 한 페이지): 평면 바꾼 저장 때 B06 조절 · 사면 쉬프트 · 소단 깊이 새 노선 측점으로 · 평면 초안 횡단 입력에 사면 쉬프트 · 조절 · 소단 깊이 (417-1 M2 · M3 · L4)
- 측점 열쇠 이월에 사면 쉬프트 · 소단 깊이 더함(CARRY_WHOLE) - 측점 옮김 목록 ROUTE_CARRY_STATES — 옹벽 · 세월 · BOX · 집수 · 덧 옹벽 · 경간 · 잇기 · 측점 폭 · 메모 · 상세 표를 노선 저장 때 새 노선 측점으로(구조물 조절 = 관 · 구조물 열쇠만) · 옮긴 값을 새 상세 설계 칸에 얹어 같은 [저장] 횡단 패치로 - 서버 이월 열쇠 짝 ROUTE_CARRY_KEYS — 노선 저장 이월이 측점마다 넘김 · 기본 설계가 사면 쉬프트로 셈 - 평면 초안 횡단 입력에 사면 쉬프트 · 저장분 사면 쉬프트 · 소단 깊이 · 초안 상세에 조절 얹음 - 시험 test_417_route_carry(거울 시험 포함) · 344-3 · 346-3 기대값 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
This commit is contained in:
@@ -6,10 +6,11 @@
|
||||
* 묶음 측점과 같은 열쇠 규칙(관 = 저장 관 id).
|
||||
*
|
||||
* 넘기는 칸 = 서버 노선 저장(`CARRY_INPUT_KEYS`)과 같은 일곱(지반 유형 · 측구 종류 · 측구 선택 · 2단 경사 ·
|
||||
* 암 경계 · 암 경사 · 소단) + 얹힌 시설 맥락(포장 · 물넘이 기울임 · 세월교 노면 하강). 단면 유형 · 측구
|
||||
* 방향은 새 지형이 정함(`logic/B05_Profile_Pipeline_Detail.ts` `crossWithUphill`) · 노견 확장 · 소단
|
||||
* 깊이는 안 넘김. 값 = 상세에 얹힌 설계 위에 세션 고침(측점 열쇠 칸 · 지반 유형 · 측구 · 2단 경사 · 암 경계 ·
|
||||
* 암 경사 · 소단 깊이) — 노선 초안 동안 B06 고침은 저장 상세에 안 얹혀도 입력에 들어감(365-2 · 365-5). 부를 때마다 모음.
|
||||
* 암 경계 · 암 경사 · 소단) + 사면 쉬프트(노견 확장) · 소단 깊이(417 · 서버 `ROUTE_CARRY_KEYS`) + 얹힌 시설
|
||||
* 맥락(포장 · 물넘이 기울임 · 세월교 노면 하강). 단면 유형 · 측구 방향은 새 지형이 정함
|
||||
* (`logic/B05_Profile_Pipeline_Detail.ts` `crossWithUphill`). 값 = 상세에 얹힌 설계 위에 세션 고침(측점 열쇠 칸 ·
|
||||
* 지반 유형 · 측구 · 2단 경사 · 암 경계 · 암 경사 · 사면 쉬프트 · 소단 깊이) — 노선 초안 동안 B06 고침은 저장 상세에
|
||||
* 안 얹혀도 입력에 들어감(365-2 · 365-5 · 417). 부를 때마다 모음.
|
||||
* ========================================================================== */
|
||||
|
||||
import { readState } from "../b_page_state";
|
||||
@@ -85,12 +86,28 @@ function carried(section: CrossSection, saved?: CrossSection["design"]): Record<
|
||||
tilted: Boolean(section.ford_pavement || section.underdrain),
|
||||
tiltPct: section.ford_pavement?.slope_pct ?? null, // 맹암거 = 표준 경사(371-3)
|
||||
surfaceDropM: drop > 0 ? drop : 0,
|
||||
// 저장분 사면 쉬프트 · 소단 깊이(417 L4) — 세션 고침이 없어도 저장 뒤 그대로
|
||||
...sides(design, "shoulder_extend", "shoulderExtend"),
|
||||
...sides(design, "berm_depth", "bermDepth"),
|
||||
};
|
||||
}
|
||||
|
||||
/** 저장분 쪽마다 값(`{field}_{left|right}_m`) → 입력 칸(`{name}LeftM` · `{name}RightM`) — 있는 쪽만. */
|
||||
function sides(design: Record<string, unknown>, field: string, name: string) {
|
||||
const out: Record<string, number> = {};
|
||||
for (const [side, tail] of [
|
||||
["left", "LeftM"],
|
||||
["right", "RightM"],
|
||||
] as const) {
|
||||
const value = finite(design[`${field}_${side}_m`]);
|
||||
if (value !== null) out[`${name}${tail}`] = value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 세션 칸 하나를 측점 열쇠마다 — 옛 열쇠(누가거리 · `at:`)는 읽는 쪽이 옮긴 뒤에 들어옴(빼고 봄). */
|
||||
function sessionCells(
|
||||
name: "crossdesign" | "rockb" | "cutslope" | "bermdepth",
|
||||
name: "crossdesign" | "rockb" | "cutslope" | "bermdepth" | "shoulderext",
|
||||
projectId: string,
|
||||
routeId: number,
|
||||
): Array<[string, unknown]> {
|
||||
@@ -117,12 +134,16 @@ function sessionCarried(projectId: string, routeId: number): CrossInput["byKey"]
|
||||
const ratio = finite(value);
|
||||
if (ratio !== null) (out[key] ??= {}).cutSlopeRatio = ratio > 0 ? ratio : null;
|
||||
}
|
||||
// 소단 쪽 깊이(194-1) — 소단 제원은 구조물 구간이 정함(`crossWithBerm` · 365-5)
|
||||
for (const [key, value] of sessionCells("bermdepth", projectId, routeId)) {
|
||||
const sides = (value ?? {}) as Record<string, unknown>;
|
||||
if (finite(sides.left) !== null) (out[key] ??= {}).bermDepthLeftM = sides.left;
|
||||
if (finite(sides.right) !== null) (out[key] ??= {}).bermDepthRightM = sides.right;
|
||||
}
|
||||
// 소단 쪽 깊이(194-1) — 소단 제원은 구조물 구간이 정함(`crossWithBerm` · 365-5) · 사면 쉬프트(139-6 · 417 M3)
|
||||
for (const [state, name] of [
|
||||
["bermdepth", "bermDepth"],
|
||||
["shoulderext", "shoulderExtend"],
|
||||
] as const)
|
||||
for (const [key, value] of sessionCells(state, projectId, routeId)) {
|
||||
const sides = (value ?? {}) as Record<string, unknown>;
|
||||
if (finite(sides.left) !== null) (out[key] ??= {})[`${name}LeftM`] = sides.left;
|
||||
if (finite(sides.right) !== null) (out[key] ??= {})[`${name}RightM`] = sides.right;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
applyStructureAreaRows,
|
||||
structureAreaRows,
|
||||
} from "../../B06_Section/B06_Section_Structure_Layouts";
|
||||
import { withDraftAdjusts } from "../../B06_Section/B06_Section_Section_Adjusts";
|
||||
import {
|
||||
holdSectionDetail,
|
||||
restoreSectionDetail,
|
||||
@@ -231,8 +232,13 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo
|
||||
return detailCross(detail, stations);
|
||||
},
|
||||
plantedBase, // 저장 노선 종단 기준 = 입력이 심는 측점(386-3 · 보기 심기 무관)
|
||||
structureAreas: ({ cross_sections: rows }) =>
|
||||
applyStructureAreaRows(rows, structureAreaRows(rows)),
|
||||
structureAreas: (detail) => {
|
||||
// 초안 상세에도 저장 안 한 조절(옹벽 · 세월 · BOX · 집수 · 417 M3) — 면적 얹기 앞
|
||||
const routeId = routeIdOf(shared);
|
||||
if (routeId !== null) withDraftAdjusts(detail, projectId, routeId);
|
||||
const rows = detail.cross_sections;
|
||||
applyStructureAreaRows(rows, structureAreaRows(rows));
|
||||
},
|
||||
// 초안 시작 — 초안 전 상태를 떠 둠 · 버리면 그대로(측점이 달라도 · 349-0o) · 상세가 없으면 묶음 측점으로 갈아끼움
|
||||
holdDraft: (routeId, saved) => {
|
||||
const hold = saved && holdSectionDetail(projectId, routeId, saved);
|
||||
|
||||
@@ -248,6 +248,7 @@ async def _carry_cross(
|
||||
) -> int:
|
||||
"""측점 열쇠로 옛 횡단 입력을 새 측점에 얹고 전 측점을 새 계획고로 셈 · 버린 수 돌려줌."""
|
||||
from B05_Profile.B05_Profile_Structures_Repository import load_structures
|
||||
from B06_Section.B06_Section_Engine_Design import ROUTE_CARRY_KEYS
|
||||
from B06_Section.B06_Section_Repository_Bulk import merge_cross_section_designs
|
||||
from B06_Section.B06_Section_Router_Confirm import _rowless_station_chainages
|
||||
from B06_Section.B06_Section_Router_Design import (
|
||||
@@ -268,7 +269,7 @@ async def _carry_cross(
|
||||
if isinstance(row.get("design"), dict)
|
||||
}
|
||||
inputs, adjusts, dropped = carry_cross_inputs(
|
||||
old_keys, new_keys, designs, CARRY_INPUT_KEYS, STRUCTURE_ADJUST_KEYS
|
||||
old_keys, new_keys, designs, (*CARRY_INPUT_KEYS, *ROUTE_CARRY_KEYS), STRUCTURE_ADJUST_KEYS
|
||||
)
|
||||
chainages = await asyncio.to_thread(_rowless_station_chainages, project_root, relative, [])
|
||||
entries = await asyncio.to_thread(
|
||||
|
||||
@@ -11,7 +11,8 @@ DB · 파일 없이 값만 받아 값을 돌려준다(시험이 그대로 부름
|
||||
- 측점 열쇠 = `common_util_station_key.station_key` 한 벌(sub2 짝) — 일반 `grid:k` ·
|
||||
관 `pipe:<id>` · 구조물 `structure:<id>` · 시종점 `end:BP` · `end:EP` ·
|
||||
id 없으면 `at:<누가거리 0.01 m>`.
|
||||
- 횡단 이월 = 같은 열쇠 측점에 `CARRY_INPUT_KEYS` 일곱 · 관 · 구조물 열쇠면
|
||||
- 횡단 이월 = 같은 열쇠 측점에 `CARRY_INPUT_KEYS` 일곱 + `ROUTE_CARRY_KEYS`(사면 쉬프트 ·
|
||||
소단 깊이 · 측점 폭 · 메모 · 상세 표 · 417) · 관 · 구조물 열쇠면
|
||||
`STRUCTURE_ADJUST_KEYS` 도(구조물 조정은 구조물 따라) · 열쇠가 사라진 측점 수 =
|
||||
「노선 밖 고침」.
|
||||
"""
|
||||
|
||||
@@ -21,7 +21,12 @@ import {
|
||||
loadSectionDetail,
|
||||
saveCachedCrossPatches,
|
||||
} from "../B06_Section/B06_Section_Section_Store";
|
||||
import { carryStationDrafts, useStationKeys } from "../B06_Section/B06_Section_Station_Keys";
|
||||
import {
|
||||
carryChainageStates,
|
||||
carryStationDrafts,
|
||||
useStationKeys,
|
||||
} from "../B06_Section/B06_Section_Station_Keys";
|
||||
import { withCarriedEdits, withDraftAdjusts } from "../B06_Section/B06_Section_Section_Adjusts";
|
||||
import { clearRouteLatestCache } from "./B05_Profile_Api_Fetch";
|
||||
import {
|
||||
replanRoute,
|
||||
@@ -135,8 +140,8 @@ export function routeSaveSummary(saved: RouteReplanResponse, outside = 0): strin
|
||||
}
|
||||
|
||||
/**
|
||||
* 노선 저장 뒤 세션 횡단 초안(옛 노선 측점 열쇠) → 새 노선으로 넘기고 그 고침을 저장 — 초안을 통째로
|
||||
* 지우지 않는다. 사라진 측점 고침은 버림(수를 돌려줌 · 서버 `cross_dropped` 와 한 줄로).
|
||||
* 노선 저장 뒤 세션 횡단 초안(옛 노선 측점 열쇠 · 누가거리 열쇠 조절 · 417) → 새 노선으로 넘기고 그 고침을 저장 —
|
||||
* 초안을 통째로 지우지 않는다. 사라진 측점 고침은 버림(수를 돌려줌 · 서버 `cross_dropped` 와 한 줄로).
|
||||
*/
|
||||
export async function carryAfterRouteSave(
|
||||
projectId: string,
|
||||
@@ -146,7 +151,12 @@ export async function carryAfterRouteSave(
|
||||
invalidateSectionDetail(projectId, toRouteId);
|
||||
const detail = await loadSectionDetail(projectId, toRouteId);
|
||||
useStationKeys(projectId, toRouteId, detail);
|
||||
const outside = carryStationDrafts(projectId, fromRouteId, toRouteId, { quiet: true });
|
||||
const outside =
|
||||
carryStationDrafts(projectId, fromRouteId, toRouteId, { quiet: true }) +
|
||||
carryChainageStates(projectId, fromRouteId, toRouteId);
|
||||
// 옮긴 고침을 새 상세 설계 칸에 — 같은 [저장]의 횡단 패치가 실음(사면 쉬프트 · 조절 · 소단 깊이 · 417)
|
||||
withDraftAdjusts(detail, projectId, toRouteId);
|
||||
withCarriedEdits(detail, projectId, toRouteId);
|
||||
await saveCachedCrossPatches(projectId, toRouteId);
|
||||
return outside;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,22 @@ def stored_shoulder_extend(stored: dict[str, Any] | None) -> dict[str, float]:
|
||||
return out
|
||||
|
||||
|
||||
# 노선 저장(B05 `_carry_cross`)이 측점 열쇠마다 더 넘기는 사용자 손질(417) — 사면 쉬프트 ·
|
||||
# 소단 깊이(기하 입력) · 측점 폭 · 메모 · 상세 표. 구조물 조절 = `STRUCTURE_ADJUST_KEYS`(관 · 구조물
|
||||
# 열쇠만). 브라우저 짝 = `B06_Section_Station_Keys.ts` `ROUTE_CARRY_STATES`
|
||||
# (거울 시험 test_417_route_carry_mirror).
|
||||
ROUTE_CARRY_KEYS = (
|
||||
"shoulder_extend_left_m",
|
||||
"shoulder_extend_right_m",
|
||||
"berm_depth_left_m",
|
||||
"berm_depth_right_m",
|
||||
"display_half_width_m",
|
||||
"structure_note",
|
||||
"structure_note_cad",
|
||||
"detail_table",
|
||||
)
|
||||
|
||||
|
||||
def stored_tilt(stored: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""저장분의 구조물 자리 기울기(287-4 · 288-1) — `compute_cross_design(**...)` 로 넘긴다."""
|
||||
value = (stored or {}).get("tilt_pct")
|
||||
|
||||
@@ -14,6 +14,7 @@ from B05_Profile.B05_Profile_Structures_Repository import load_structures
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||||
from B06_Section.B06_Section_Engine_Culvert import load_culvert_sets
|
||||
from B06_Section.B06_Section_Engine_Design import (
|
||||
ROUTE_CARRY_KEYS,
|
||||
compute_cross_design,
|
||||
curve_widening_args,
|
||||
stored_shoulder_extend,
|
||||
@@ -630,8 +631,9 @@ def compute_default_designs(
|
||||
없는 B06 미리 뜨기 자리(273-2) · 상단측도 그 횡단 값(종단 정본에 측점이 없음).
|
||||
|
||||
standard가 오면(확정 요청의 패널 편집값) 그 값으로 표준단면 기하를 계산한다.
|
||||
`carried`(누가거리 round 3 → `CARRY_INPUT_KEYS` 값)가 오면 그 측점은 기본값 대신 그 입력으로
|
||||
계산한다 — 노선 [확인] 체인의 옛 설계 이월(PLAN 74-3). 결과 칸은 늘 새 계획고로 다시 낸다.
|
||||
`carried`(누가거리 round 3 → `CARRY_INPUT_KEYS` · `ROUTE_CARRY_KEYS` 값)가 오면 그 측점은
|
||||
기본값 대신 그 입력으로 계산한다 — 노선 [확인] 체인의 옛 설계 이월(PLAN 74-3 · 417). 결과 칸은
|
||||
늘 새 계획고로 다시 낸다.
|
||||
계획고 부재 등으로 계산 불가한 측점은 조용히 건너뛴다(확정을 막지 않기 위함).
|
||||
"""
|
||||
root = project_root.resolve()
|
||||
@@ -683,9 +685,10 @@ def compute_default_designs(
|
||||
surface_drop_m=ford_drop_at(chainage_m, ford_drops),
|
||||
berm=stored_berm(user),
|
||||
**curve_widening_args(cross),
|
||||
**stored_shoulder_extend(user), # 노선 저장이 넘긴 사면 쉬프트(417)
|
||||
**tilt_args(chainage_m, tilts),
|
||||
)
|
||||
for key in ("berm", "cut_slope_ratio_user"):
|
||||
for key in ("berm", "cut_slope_ratio_user", *ROUTE_CARRY_KEYS):
|
||||
if user.get(key) is not None:
|
||||
design[key] = user[key]
|
||||
design["status"] = "provisional"
|
||||
|
||||
@@ -22,6 +22,7 @@ import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
|
||||
import { applySpanPatch, WALL_MIN_PURE_HEIGHT_M } from "./logic/B06_Section_UI_Cross_Culvert_Const";
|
||||
import { tierSpanOf } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import { computeStoredLayouts } from "./B06_Section_Structure_Layouts";
|
||||
import { readBermDepths, readShoulderExtends } from "./B06_Section_Cross_Design_Session";
|
||||
|
||||
type Cells = Record<string, unknown>;
|
||||
type Obj = Record<string, unknown>;
|
||||
@@ -91,6 +92,38 @@ export function withDraftAdjusts(
|
||||
}
|
||||
}
|
||||
|
||||
/** 노선 저장 뒤 새 노선으로 옮긴 세션 고침(417) — 사면 쉬프트 · 소단 깊이 · 측점 폭 · 메모 · 상세 표를 설계 칸에 얹음 ·
|
||||
* 같은 [저장]의 횡단 패치(`crossPatchesFromCache`)가 실음. 조절 4축 · 형식은 `withDraftAdjusts`. */
|
||||
export function withCarriedEdits(
|
||||
detail: SectionDetailResponse,
|
||||
projectId: string,
|
||||
routeId: number,
|
||||
): void {
|
||||
const read = (name: Parameters<typeof readState>[0]): Cells =>
|
||||
readState<Cells>(name, projectId, routeId) ?? {};
|
||||
const cells: Array<[string, Cells]> = [
|
||||
["display_half_width_m", read("crossw")],
|
||||
["structure_note", read("structure-notes")],
|
||||
["structure_note_cad", read("structure-note-cads")],
|
||||
["detail_table", read("detail-table")],
|
||||
];
|
||||
const sides: Array<[string, ReturnType<typeof readShoulderExtends>]> = [
|
||||
["shoulder_extend", readShoulderExtends(projectId, routeId)],
|
||||
["berm_depth", readBermDepths(projectId, routeId)],
|
||||
];
|
||||
for (const section of detail.cross_sections) {
|
||||
const design = section.design as (CrossDesign & Obj) | null | undefined;
|
||||
if (!design) continue;
|
||||
const at = section.chainage_m.toFixed(2);
|
||||
for (const [field, values] of cells) if (values[at] !== undefined) design[field] = values[at];
|
||||
for (const [field, values] of sides) {
|
||||
const own = values.get(Number(at));
|
||||
for (const side of ["left", "right"] as const)
|
||||
if (own?.[side] !== undefined) design[`${field}_${side}_m`] = own[side];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 관 옵션 조정값 몫 얹기(387-4b) — 관 초안 ?? 저장 관(페이지 자료 줄). 가게 · 자료 줄은 부를 때 늦게 불러옴 —
|
||||
* 이 파일을 끄는 시험 · 묶음이 그 줄기(fetch · 가게)를 안 끌게. 못 받으면 그대로.
|
||||
* `reshape` = 관 제원도 형상 얹기 규칙으로 다시(다시 셈 창구 앞 · B06 폼 되보내기가 옵션 값을 제원에 그대로
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
* · 칸마다 열쇠 → 쓴 때 누가거리 `@at` 을 함께 담음 — 구조물 id 를 아직 모를 때(첫 열기)도 그 자리 측점이면 읽음.
|
||||
* · 옛 초안(누가거리 열쇠 `"85.05"`) · `at:` 열쇠는 읽을 때 지금 측점 열쇠로 옮겨 다시 씀.
|
||||
* · 노선이 바뀜 = `carryStationDrafts` — 넘기는 칸 = 서버 `CARRY_INPUT_KEYS` 일곱(지반 유형 · 측구 종류 ·
|
||||
* 측구 선택 · 2단 경사 · 암 경계 · 암 경사 · 소단 구간은 구간 그대로) · 단면 유형 · 측구 방향은 새 지형이
|
||||
* 정함 · 사라진 측점의 고침은 버리고 「노선 밖 고침 N 건」 한 줄.
|
||||
* 측구 선택 · 2단 경사 · 암 경계 · 암 경사 · 소단 구간은 구간 그대로) + 사면 쉬프트 · 소단 깊이(417 · 서버
|
||||
* `ROUTE_CARRY_KEYS`) · 단면 유형 · 측구 방향은 새 지형이 정함 · 사라진 측점의 고침은 버리고 「노선 밖 고침 N 건」.
|
||||
* · 노선 저장 = `carryChainageStates` — 누가거리 열쇠 칸(옹벽 · 세월 · BOX · 집수 조절 · 측점 폭 · 메모 · 상세 표 ·
|
||||
* `ROUTE_CARRY_STATES`)도 측점 열쇠로 새 노선 측점에 옮김(구조물 조절 = 관 · 구조물 열쇠만 · 서버와 같음).
|
||||
* ========================================================================== */
|
||||
|
||||
import { readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { readState, writeState, type StateName } from "../A00_Common/b_page_state";
|
||||
import { pipeIdAt, pipeIdNear } from "../A00_Common/design/design_pipe_ids";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import {
|
||||
@@ -40,8 +42,41 @@ export const KEYED_STATES: readonly KeyedState[] = [
|
||||
|
||||
/** 노선이 바뀔 때 넘기는 카드 선택 — 서버 `CARRY_INPUT_KEYS` 중 선택 칸(나머지 셋 = rockb · cutslope · 소단 구간). */
|
||||
const CARRY_CHOICE_KEYS = ["ground_type", "ditch_type", "ditch_choice", "two_stage_slope"] as const;
|
||||
/** 노선이 바뀔 때 통째로 넘기는 칸 · 버리는 칸(노견 확장 · 소단 깊이 = 그 측점 기하 손질). */
|
||||
const CARRY_WHOLE: readonly KeyedState[] = ["rockb", "cutslope"];
|
||||
/** 노선이 바뀔 때 통째로 넘기는 칸 — 사면 쉬프트(노견 확장) · 소단 깊이도(417 · 서버 `ROUTE_CARRY_KEYS`). */
|
||||
const CARRY_WHOLE: readonly KeyedState[] = ["rockb", "cutslope", "shoulderext", "bermdepth"];
|
||||
|
||||
/**
|
||||
* 노선 저장 때 새 노선 측점으로 옮기는 **누가거리 열쇠** 칸(측점 옮김 목록 · 417) → 담긴 설계 칸.
|
||||
* ⚠ 짝 — 설계 칸 합 = 서버 `ROUTE_CARRY_KEYS`(측점 열쇠마다) + `STRUCTURE_ADJUST_KEYS`(관 · 구조물 열쇠만) ·
|
||||
* 거울 시험 `resources/tester/test_417_route_carry_mirror.py`.
|
||||
*/
|
||||
export const ROUTE_CARRY_STATES = {
|
||||
revetx: ["revet_adjust"],
|
||||
basinadjust: ["basin_adjust"],
|
||||
inletstruct: ["inlet_structure"],
|
||||
extrawall: ["extra_wall_counts"],
|
||||
extraspan: ["extra_spans"],
|
||||
revetlink: ["revet_link_detached", "revet_follow_grade"],
|
||||
fordadjust: ["ford_adjust"],
|
||||
boxadjust: ["box_adjust"],
|
||||
crossw: ["display_half_width_m"],
|
||||
"structure-notes": ["structure_note"],
|
||||
"structure-note-cads": ["structure_note_cad"],
|
||||
"detail-table": ["detail_table"],
|
||||
shoulderext: ["shoulder_extend_left_m", "shoulder_extend_right_m"],
|
||||
bermdepth: ["berm_depth_left_m", "berm_depth_right_m"],
|
||||
} as const;
|
||||
/** 구조물 조절 칸 — 관 · 구조물 열쇠 측점만 따라감(서버 `carry_cross_inputs` 와 같음). */
|
||||
const STRUCTURE_STATES = new Set<string>([
|
||||
"revetx",
|
||||
"basinadjust",
|
||||
"inletstruct",
|
||||
"extrawall",
|
||||
"extraspan",
|
||||
"revetlink",
|
||||
"fordadjust",
|
||||
"boxadjust",
|
||||
]);
|
||||
|
||||
/** 같은 측점으로 보는 거리 — 옛 초안 열쇠(0.01 m) · 서버 측점(0.001 m) 차이를 받음. */
|
||||
const NEAR_M = 0.01;
|
||||
@@ -344,3 +379,58 @@ function carriedValue(name: KeyedState, value: unknown): unknown {
|
||||
);
|
||||
return Object.keys(picked).length ? picked : null;
|
||||
}
|
||||
|
||||
/** 누가거리 열쇠 머리(`b` · 0.01 m 글 · `:역할`) — 조정창 맵 열쇠 꼴(`renameChainageKeys` 와 같음). */
|
||||
const CHAINAGE_HEAD = /^(b?)(-?\d+(?:\.\d+)?)(:.*)?$/;
|
||||
|
||||
/**
|
||||
* 노선 저장 뒤(새 노선 측점 열쇠를 세운 뒤) 누가거리 열쇠 칸(`ROUTE_CARRY_STATES` 중 측점 열쇠 칸 빼고)을 옛 노선
|
||||
* → 새 노선 측점으로 옮김 — 옛 누가거리 → 옛 측점 열쇠 → 새 누가거리. 구조물 조절은 관 · 구조물 열쇠만 ·
|
||||
* 못 옮긴 고침은 버림. 돌려주는 값 = 버린 측점 수(「노선 밖 고침」 · 측점 단위).
|
||||
*/
|
||||
export function carryChainageStates(
|
||||
projectId: string,
|
||||
fromRouteId: number,
|
||||
toRouteId: number,
|
||||
): number {
|
||||
const outside = new Set<string>();
|
||||
const moveAt = (state: string, text: string): string | null => {
|
||||
const stable = keyAt(projectId, fromRouteId, Number(text));
|
||||
const structural = stable.startsWith("pipe:") || stable.startsWith("structure:");
|
||||
const toAt =
|
||||
stable.startsWith("at:") || (STRUCTURE_STATES.has(state) && !structural)
|
||||
? null
|
||||
: chainageOfKey(projectId, toRouteId, stable);
|
||||
if (toAt === null) outside.add(stable);
|
||||
return toAt === null ? null : chainageKey(toAt);
|
||||
};
|
||||
const rekey = (state: string, cells: Record<string, unknown>): Record<string, unknown> => {
|
||||
const next: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(cells)) {
|
||||
const head = CHAINAGE_HEAD.exec(key);
|
||||
const to = head && moveAt(state, head[2]);
|
||||
if (head && to !== null) next[`${head[1]}${to}${head[3] ?? ""}`] = value;
|
||||
}
|
||||
return next;
|
||||
};
|
||||
for (const state of Object.keys(ROUTE_CARRY_STATES)) {
|
||||
if ((KEYED_STATES as readonly string[]).includes(state)) continue; // 측점 열쇠 칸 = carryStationDrafts
|
||||
const name = state as StateName;
|
||||
const raw = readState<Record<string, unknown>>(name, projectId, fromRouteId);
|
||||
if (!raw || typeof raw !== "object") continue;
|
||||
// 연동 = { detached: {누가거리: …}, grade: {…} } 두 겹
|
||||
const next =
|
||||
state === "revetlink"
|
||||
? Object.fromEntries(
|
||||
Object.entries(raw).map(([part, cells]) => [
|
||||
part,
|
||||
cells && typeof cells === "object"
|
||||
? rekey(state, cells as Record<string, unknown>)
|
||||
: cells,
|
||||
]),
|
||||
)
|
||||
: rekey(state, raw);
|
||||
writeState(name, next, projectId, toRouteId);
|
||||
}
|
||||
return outside.size;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ m.useStationKeys(P, R, oldRoute);
|
||||
out.hintKeyNow = m.keyAt(P, R, 52.5);
|
||||
out.hintRead = m.crossDesignChoices(P, R).has(52.5);
|
||||
|
||||
/* 4. 노선이 바뀜 — 측점 열쇠로 넘김 · 넘김 = 카드 선택 넷 · 암 경계 · 노견 확장 안 넘김 · 사라진 측점 = 노선 밖 */
|
||||
/* 4. 노선이 바뀜 — 측점 열쇠로 넘김 · 넘김 = 카드 선택 넷 · 암 경계 · 노견 확장(417) · 사라진 측점 = 노선 밖 */
|
||||
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 47 }] }, P); // 관이 47 m 로 옮김
|
||||
m.useStructureIds(P, [{ structure_id: "st-1", chainage_m: 55 }]);
|
||||
m.useStationKeys(
|
||||
|
||||
@@ -47,6 +47,12 @@ const STUBS = {
|
||||
calls.push(`carry:${from}->${to}${opts?.quiet ? ":quiet" : ""}`),
|
||||
2
|
||||
),
|
||||
// 417 — 누가거리 열쇠 조절 옮김
|
||||
carryChainageStates: (_p, from, to) => (calls.push(`carryAt:${from}->${to}`), 1),
|
||||
},
|
||||
"../B06_Section/B06_Section_Section_Adjusts": {
|
||||
withDraftAdjusts: (_d, _p, routeId) => calls.push(`adjusts:${routeId}`),
|
||||
withCarriedEdits: (_d, _p, routeId) => calls.push(`edits:${routeId}`),
|
||||
},
|
||||
"./B05_Profile_Api_Fetch": { clearRouteLatestCache: () => calls.push("latest:clear") },
|
||||
"./B05_Profile_Api_Replan": {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/* 417 시험 묶음 진입점 — 진짜 모듈을 vite ssr 로 한 벌 묶어 Node 헬퍼(helper_417_route_carry.mjs)가 부름.
|
||||
* 노선 바뀐 [저장] 때 B06 조절 · 사면 쉬프트 · 소단 깊이 옮김 · 평면 초안 횡단 입력.
|
||||
*/
|
||||
export {
|
||||
carryChainageStates,
|
||||
carryStationDrafts,
|
||||
useStationKeys,
|
||||
} from "../../B06_Section/B06_Section_Station_Keys";
|
||||
export {
|
||||
writeBermDepth,
|
||||
writeShoulderExtend,
|
||||
} from "../../B06_Section/B06_Section_Cross_Design_Session";
|
||||
export { withCarriedEdits, withDraftAdjusts } from "../../B06_Section/B06_Section_Section_Adjusts";
|
||||
export { pipelineCrossSource } from "../../A00_Common/design/design_pipeline_cross";
|
||||
export { readState, writeState } from "../../A00_Common/b_page_state";
|
||||
@@ -0,0 +1,126 @@
|
||||
/* 417 헬퍼 — vite ssr 로 묶은 진짜 모듈(helper_417_entry.ts)을 부르고 결과를 JSON 한 벌로.
|
||||
* node helper_417_route_carry.mjs <묶음.mjs> <입력.json>
|
||||
* 입력 = { standard: 파이썬 config 표준단면 }.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
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);
|
||||
|
||||
const [bundlePath, inputPath] = process.argv.slice(2);
|
||||
const m = await import(pathToFileURL(bundlePath).href);
|
||||
const input = JSON.parse(readFileSync(inputPath, "utf8"));
|
||||
const P = "p417";
|
||||
const R = 7;
|
||||
const R2 = 8;
|
||||
const raw = (name, route) => m.readState(name, P, route) ?? {};
|
||||
const keysOf = (name, route) =>
|
||||
Object.keys(raw(name, route))
|
||||
.filter((k) => k !== "@at")
|
||||
.sort();
|
||||
const section = (chainage_m, kind, extra = {}) => ({
|
||||
station_id: `s${chainage_m}`,
|
||||
chainage_m,
|
||||
kind,
|
||||
label: String(chainage_m),
|
||||
samples: [],
|
||||
design: {},
|
||||
...extra,
|
||||
});
|
||||
const detailOf = (sections) => ({
|
||||
longitudinal: {
|
||||
stations: sections,
|
||||
profile_alignment: { policy: { station_interval_m: 20 } },
|
||||
},
|
||||
cross_sections: sections,
|
||||
});
|
||||
const out = {};
|
||||
|
||||
/* 1. 옛 노선(관 45 m) — B06 조절 · 사면 쉬프트 · 소단 깊이 · 측점 폭 · 상세 표 · 연동을 세션에 */
|
||||
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 45 }] }, P);
|
||||
const pipe = { structure: "파형강관 D800", culvert: {} };
|
||||
m.useStationKeys(
|
||||
P,
|
||||
R,
|
||||
detailOf([
|
||||
section(0, "bp"),
|
||||
section(20, "regular"),
|
||||
section(40, "regular"),
|
||||
section(45, "irregular", pipe),
|
||||
section(60, "ep"),
|
||||
]),
|
||||
);
|
||||
m.writeShoulderExtend(P, R, 40, "left", 0.5);
|
||||
m.writeBermDepth(P, R, 40, "right", 1.2);
|
||||
const wall = { x: 0.3, d: null, h: 1.5, m: null };
|
||||
m.writeState("revetx", { "45.00:outlet": wall, "40.00:inlet": wall }, P, R);
|
||||
m.writeState("boxadjust", { "45.00": { left: { x: 0.2 } } }, P, R);
|
||||
m.writeState("crossw", { "40.00": 6 }, P, R);
|
||||
m.writeState("detail-table", { "45.00": { rows: 2 } }, P, R);
|
||||
m.writeState("revetlink", { detached: { "45.00": true }, grade: {} }, P, R);
|
||||
|
||||
/* 2. 노선 바뀐 [저장] — 새 노선(관 47 m 로 따라감 · 끝 58 m) 측점 열쇠로 옮김 */
|
||||
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 47 }] }, P);
|
||||
const next = detailOf([
|
||||
section(0, "bp"),
|
||||
section(20, "regular", { design: { shoulder_extend_right_m: 0.7, berm_depth_left_m: 0.4 } }),
|
||||
section(40, "regular"),
|
||||
section(47, "irregular", pipe),
|
||||
section(58, "ep"),
|
||||
]);
|
||||
m.useStationKeys(P, R2, next);
|
||||
out.keyedOutside = m.carryStationDrafts(P, R, R2, { quiet: true });
|
||||
out.chainageOutside = m.carryChainageStates(P, R, R2);
|
||||
out.shoulderKeys = keysOf("shoulderext", R2);
|
||||
out.bermKeys = keysOf("bermdepth", R2);
|
||||
out.revet = raw("revetx", R2);
|
||||
out.box = raw("boxadjust", R2);
|
||||
out.width = raw("crossw", R2);
|
||||
out.table = raw("detail-table", R2);
|
||||
out.link = raw("revetlink", R2);
|
||||
|
||||
/* 3. 새 상세 설계 칸에 얹음 — 같은 [저장]의 횡단 패치가 실을 값 */
|
||||
m.withDraftAdjusts(next, P, R2);
|
||||
m.withCarriedEdits(next, P, R2);
|
||||
const at = (c) => next.cross_sections.find((s) => s.chainage_m === c).design;
|
||||
out.design40 = at(40);
|
||||
out.design47 = at(47);
|
||||
|
||||
/* 4. 평면 초안 횡단 입력 — 세션 사면 쉬프트 · 소단 깊이 + 저장분(세션 없음)도 */
|
||||
m.writeState("std-cross", input.standard, P);
|
||||
const stations = next.cross_sections.map((s, i) => ({
|
||||
key: ["end:BP", "grid:1", "grid:2", "pipe:pipe-s1", "end:EP"][i],
|
||||
chainageM: s.chainage_m,
|
||||
kind: i === 3 ? "pipe" : "grid",
|
||||
}));
|
||||
const source = m.pipelineCrossSource(P, next, stations, R2);
|
||||
const pick = (entry) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(entry ?? {}).filter(([k]) => /^(shoulderExtend|bermDepth)/.test(k)),
|
||||
);
|
||||
out.input40 = pick(source?.input.byKey["grid:2"]);
|
||||
out.input20 = pick(source?.input.byKey["grid:1"]);
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -6,7 +6,7 @@
|
||||
- 쓰기 = 측점 열쇠(grid · pipe · structure) · 부르는 쪽은 누가거리로 읽음 · 암 경계 저장소도 같음.
|
||||
- 구조물 id 를 아직 모를 때(첫 열기)도 쓴 때 자리로 그 측점 고침을 읽음.
|
||||
- 노선이 바뀜 = 측점 열쇠로 넘김(관 · 구조물은 옮긴 자리로) · 넘김 = 카드 선택 넷 · 암 경계 ·
|
||||
단면 유형 · 측구 방향 · 포장 · 노견 확장은 안 넘김 · 사라진 측점 = 「노선 밖 고침 N 건」.
|
||||
노견 확장(417) · 단면 유형 · 측구 방향 · 포장은 안 넘김 · 사라진 측점 = 「노선 밖 고침 N 건」.
|
||||
- cross 단계: 같은 입력 다시 셈 0 · 측점 하나 고침 = 1 · 범위 밖 재사용.
|
||||
- masshaul 단계 = 계열 + 흙 배분 입력 · haul 단계 = 같은 열쇠 안 물음 · 끊김 = 앞 배분 그대로.
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_노선_바뀜_측점_열쇠로_넘김(out):
|
||||
"two_stage_slope": True,
|
||||
}
|
||||
assert out["carriedRock"] == {"20.00": -0.8}
|
||||
assert out["carriedShoulder"] == []
|
||||
assert out["carriedShoulder"] == ["grid:2"] # 사면 쉬프트도 넘김(417)
|
||||
|
||||
|
||||
def test_cross_단계_국부_연산(out):
|
||||
|
||||
@@ -80,9 +80,12 @@ def test_초안_넘김_차례(out):
|
||||
"load:9",
|
||||
"keys:9:9",
|
||||
"carry:7->9:quiet",
|
||||
"carryAt:7->9", # 417 — 조절 · 측점 폭 · 상세 표도 새 노선 측점으로
|
||||
"adjusts:9",
|
||||
"edits:9", # 옮긴 고침을 새 상세 설계 칸에 → 같은 [저장] 횡단 패치
|
||||
"crossSave:9",
|
||||
]
|
||||
assert out["outside"] == 2
|
||||
assert out["outside"] == 3 # 측점 열쇠 2 + 누가거리 열쇠 1
|
||||
|
||||
|
||||
def test_추천_적용은_as_initial_저장_뒤_다시_세움(out):
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""417 — 노선 바뀐 [저장] 때 B06 조절 · 사면 쉬프트 · 소단 깊이 옮김(M2) · 평면 초안 횡단 입력
|
||||
(M3 · L4).
|
||||
|
||||
못박는 것
|
||||
- 브라우저: 측점 열쇠 칸(사면 쉬프트 · 소단 깊이) · 누가거리 열쇠 칸(옹벽 · BOX · 측점 폭 ·
|
||||
상세 표 · 연동)이 새 노선 측점으로 옮겨 감 · 구조물 조절은 관 · 구조물 열쇠만(일반 측점 옹벽
|
||||
조절은 버림).
|
||||
- 옮긴 값이 새 상세 설계 칸에 얹힘(같은 [저장] 횡단 패치가 실음).
|
||||
- 평면 초안 횡단 입력 = 세션 사면 쉬프트 · 소단 깊이 + 저장분(세션 없음)도.
|
||||
- 서버: `carry_cross_inputs` 가 `ROUTE_CARRY_KEYS` 를 측점 열쇠마다 넘김 · 구조물 조절은 관
|
||||
열쇠만 · `compute_default_designs` 가 넘긴 사면 쉬프트로 설계선을 셈.
|
||||
- 거울: 브라우저 `ROUTE_CARRY_STATES` 설계 칸 = 서버 `ROUTE_CARRY_KEYS` +
|
||||
`STRUCTURE_ADJUST_KEYS` = `USER_TOUCHED_KEYS` − 소단 제원 · 암 경사(둘은 `CARRY_INPUT_KEYS`).
|
||||
|
||||
Node 헬퍼 `helper_417_route_carry.mjs` 가 vite ssr 로 묶은 진짜 모듈(`helper_417_entry.ts`)을 부름.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Sections import find_cross_file # noqa: E402
|
||||
from B05_Profile.B05_Profile_Route_Save_Parts import carry_cross_inputs # noqa: E402
|
||||
from B06_Section.B06_Section_Engine_Design import ROUTE_CARRY_KEYS # noqa: E402
|
||||
from B06_Section.B06_Section_Router_Design import ( # noqa: E402
|
||||
CARRY_INPUT_KEYS,
|
||||
STRUCTURE_ADJUST_KEYS,
|
||||
USER_TOUCHED_KEYS,
|
||||
compute_default_designs,
|
||||
)
|
||||
from config.config_system import STANDARD_CROSS_SECTION # noqa: E402
|
||||
|
||||
TS_KEYS = ROOT / "B06_Section" / "B06_Section_Station_Keys.ts"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out(tmp_path_factory):
|
||||
work = tmp_path_factory.mktemp("b417")
|
||||
build = subprocess.run(
|
||||
[
|
||||
"node",
|
||||
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
||||
"build",
|
||||
"--configLoader",
|
||||
"runner",
|
||||
"--ssr",
|
||||
"../resources/tester/helper_417_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_417_entry.js", bundle)
|
||||
source = work / "input.json"
|
||||
source.write_text(json.dumps({"standard": STANDARD_CROSS_SECTION}, default=str), "utf-8")
|
||||
proc = subprocess.run(
|
||||
["node", str(HERE / "helper_417_route_carry.mjs"), str(bundle), str(source)],
|
||||
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_사면_쉬프트_소단_깊이_새_노선으로(out):
|
||||
assert out["shoulderKeys"] == ["grid:2"]
|
||||
assert out["bermKeys"] == ["grid:2"]
|
||||
|
||||
|
||||
def test_조절은_관_따라_옮김_일반_측점_옹벽은_버림(out):
|
||||
assert out["revet"] == {"47.00:outlet": {"x": 0.3, "d": None, "h": 1.5, "m": None}}
|
||||
assert out["box"] == {"47.00": {"left": {"x": 0.2}}}
|
||||
assert out["link"] == {"detached": {"47.00": True}, "grade": {}}
|
||||
assert out["width"] == {"40.00": 6}
|
||||
assert out["table"] == {"47.00": {"rows": 2}}
|
||||
assert out["chainageOutside"] == 1 # 40 m 옹벽 조절(일반 측점)
|
||||
|
||||
|
||||
def test_새_상세_설계_칸에_얹힘(out):
|
||||
d40, d47 = out["design40"], out["design47"]
|
||||
assert d40["shoulder_extend_left_m"] == 0.5 and d40["berm_depth_right_m"] == 1.2
|
||||
assert d40["display_half_width_m"] == 6 and "revet_adjust" not in d40
|
||||
assert d47["revet_adjust"]["outlet"]["h"] == 1.5
|
||||
assert d47["box_adjust"]["left"]["x"] == 0.2
|
||||
assert d47["revet_link_detached"] is True and d47["detail_table"] == {"rows": 2}
|
||||
|
||||
|
||||
def test_평면_초안_횡단_입력에_사면_쉬프트_소단_깊이(out):
|
||||
assert out["input40"] == {"shoulderExtendLeftM": 0.5, "bermDepthRightM": 1.2}
|
||||
# 세션 없음 = 저장분 그대로(저장 뒤 사라지지 않음 · L4)
|
||||
assert out["input20"] == {"shoulderExtendRightM": 0.7, "bermDepthLeftM": 0.4}
|
||||
|
||||
|
||||
def test_서버_이월_측점_손질_넘김_구조물_조절은_관만():
|
||||
old = {"grid:2": 40.0, "pipe:p1": 45.0, "grid:3": 60.0}
|
||||
new = {"grid:2": 40.0, "pipe:p1": 47.0}
|
||||
designs = {
|
||||
40.0: {"shoulder_extend_left_m": 0.5, "structure_note": "메모", "revet_adjust": {"h": 1}},
|
||||
45.0: {"berm_depth_right_m": 1.2, "revet_adjust": {"h": 2}, "detail_table": {"r": 1}},
|
||||
60.0: {"shoulder_extend_right_m": 0.3},
|
||||
}
|
||||
inputs, adjusts, dropped = carry_cross_inputs(
|
||||
old, new, designs, (*CARRY_INPUT_KEYS, *ROUTE_CARRY_KEYS), STRUCTURE_ADJUST_KEYS
|
||||
)
|
||||
assert inputs == {
|
||||
40.0: {"shoulder_extend_left_m": 0.5, "structure_note": "메모"},
|
||||
47.0: {"berm_depth_right_m": 1.2, "detail_table": {"r": 1}},
|
||||
}
|
||||
assert adjusts == {47.0: {"revet_adjust": {"h": 2}}}
|
||||
assert dropped == 1
|
||||
|
||||
|
||||
def test_서버_기본_설계가_넘긴_사면_쉬프트로_셈(tmp_path):
|
||||
route = tmp_path / "routes" / "r1"
|
||||
(route / "longitudinal").mkdir(parents=True)
|
||||
(route / "cross_sections").mkdir()
|
||||
profile = [
|
||||
{"chainage_m": 0.0, "elevation_m": 100.0},
|
||||
{"chainage_m": 40.0, "elevation_m": 100.0},
|
||||
]
|
||||
relative = "routes/r1/longitudinal/longitudinal.json"
|
||||
(tmp_path / relative).write_text(
|
||||
json.dumps({"stations": [{"chainage_m": 20.0}], "design_profiles": [{"samples": profile}]}),
|
||||
"utf-8",
|
||||
)
|
||||
samples = [
|
||||
{"offset_m": float(o), "elevation_m": 100.0 + 0.3 * o, "valid": True}
|
||||
for o in range(-15, 16)
|
||||
]
|
||||
cross = {"chainage_m": 20.0, "samples": samples, "uphill_side": "left"}
|
||||
find_cross_file(route / "cross_sections", 20.0).write_text(json.dumps(cross), "utf-8")
|
||||
plain = compute_default_designs(tmp_path, relative, [20.0])
|
||||
carried = {20.0: {"shoulder_extend_right_m": 0.8, "structure_note": "메모"}}
|
||||
shifted = compute_default_designs(tmp_path, relative, [20.0], carried=carried)
|
||||
assert plain and shifted
|
||||
design = shifted[0][1]
|
||||
assert design["shoulder_extend_right_m"] == pytest.approx(0.8)
|
||||
assert design["structure_note"] == "메모"
|
||||
assert "shoulder_extend_right_m" not in plain[0][1]
|
||||
assert design["design_line"] != plain[0][1]["design_line"] # 설계선이 쉬프트로 바뀜
|
||||
|
||||
|
||||
def test_거울_브라우저_옮김_목록_서버_이월_열쇠():
|
||||
text = TS_KEYS.read_text(encoding="utf-8")
|
||||
block = re.search(r"ROUTE_CARRY_STATES = \{(.*?)\} as const", text, re.S).group(1)
|
||||
rows = re.findall(r":\s*\[([^\]]*)\]", block) # 칸 이름 → [설계 칸 …]
|
||||
ts_keys = {key for row in rows for key in re.findall(r'"([a-z_]+)"', row)}
|
||||
assert ts_keys == set(ROUTE_CARRY_KEYS) | set(STRUCTURE_ADJUST_KEYS)
|
||||
assert ts_keys == set(USER_TOUCHED_KEYS) - {"berm", "cut_slope_ratio_user"}
|
||||
assert {"berm", "cut_slope_ratio_user"} <= set(CARRY_INPUT_KEYS)
|
||||
Reference in New Issue
Block a user