refactor(B05·B06): 소단 = 옹벽처럼 측점에 제원 얹음 — 형상 얹기 한 벌 · 세션 사본 berm 걷음 (PLAN 577-2 · 요구 434 · 437)
- 얹기: attachShapes · attach_shapes 에 소단(section.berm · 덮는 소단 없음 = null) · SHAPE_KEYS 에 berm · TS attachBermSpecs ↔ 파이썬 attach_berm_specs · berm_spans_from 짝 - 읽기: 저장 노선 다시 셈(브라우저 · Node cross.refresh) = 얹힌 section.berm(칸 없음 = 저장분 design.berm) · 노선 초안 · 서버 Node 묶음 cross = crossWithShapes 의 얹힌 제원(bermAt · crossWithBerm 걷음) - 계단은 계산 안쪽 그대로 · [저장] 길(구조물 정본 → cross.berm_spans) 그대로 - 지움: 시설 줄 syncBerm · putBerm(C4.copy) · writeBermSpans · 세션 칸 berm · bermRequest · design_shared bermAt - 시험: test_272_berm_source 측점 얹기 거울로 · test_323_4 소단 얹음 · 477 · 511-A · 472 떠남 · 365 · 272 고름 헬퍼가 사본 대신 얹힌 소단 · design_pipeline_page 700 줄 → 699(주석 한 줄) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -235,9 +235,6 @@ export const STATE_REGISTRY = {
|
||||
"design-stations": { bucket: "result", scope: "project" },
|
||||
"design-profile": { bucket: "result", scope: "project" },
|
||||
"design-footprint": { bucket: "result", scope: "project" },
|
||||
/** 소단 사본 — 구조물(소단)에서 셈한 구간 목록(`design_facility_lane` 만 씀 · 고침은 `structures` 칸).
|
||||
* 다시 셈에 실어 보냄 · 안 실으면 계획선을 고치는 순간 계단이 사라짐(3-9 · 477-4). */
|
||||
berm: { bucket: "result", scope: "route" },
|
||||
} as const satisfies Record<string, StateEntry>;
|
||||
|
||||
export type StateName = keyof typeof STATE_REGISTRY;
|
||||
|
||||
@@ -142,18 +142,12 @@ export interface CalcRequest {
|
||||
pick: (results: Record<string, unknown>) => unknown;
|
||||
}
|
||||
|
||||
/** F7 구조물 면적 · C4 소단 구간 — 등록부 `node:` 입력 그대로. */
|
||||
/** F7 구조물 면적 — 등록부 `node:` 입력 그대로. */
|
||||
export const areasRequest = (rows: unknown[]): CalcRequest => ({
|
||||
run: ["structure.layouts"],
|
||||
inputs: { detail: { cross_sections: rows } },
|
||||
pick: (results) => (results["structure.layouts"] as { areas: unknown }).areas,
|
||||
});
|
||||
export const bermRequest = (structures: unknown[]): CalcRequest => ({
|
||||
run: ["cross.berm_spans"],
|
||||
inputs: { berm: { structures, chainages: [] } },
|
||||
pick: (results) => (results["cross.berm_spans"] as { spans: unknown }).spans,
|
||||
});
|
||||
|
||||
export const trialRequest = (id: string, args: unknown[]): CalcRequest => ({
|
||||
run: [id],
|
||||
inputs: { args: { [id]: args } },
|
||||
|
||||
@@ -8,20 +8,10 @@
|
||||
* 입력 = 관 알림(배수유역도 · B06 관 목록) · 구조물(보기 고침 · 저장분) · 종류 표 · 측점 간격 · 추가 측점(가게)
|
||||
* 관 id 장부 = 관 고정 id 가 쥔 세션 id 를 그 자리로(411-B) · 헛 id 정리(411-E) — 어느 보기에서 고쳐도
|
||||
* 계획고 편집 옮김(사라진 측점 → 새 측점 · 213-2) = 사용자 고침만 · 노선 따라감(`follow`)은 안 옮김(계산 묶음 몫 · 349-0d)
|
||||
* 소단 사본(세션 `berm`) = 구조물 소단 구간 — 쓰는 자리 여기 하나(B05 만 열어도 · 같으면 안 씀 · 쓰면 cross 한 번 · 467)
|
||||
* ========================================================================== */
|
||||
|
||||
import { useDesignStore } from "./design_store";
|
||||
import { pageRouteId } from "./design_page_route";
|
||||
import { readState } from "../b_page_state";
|
||||
import {
|
||||
bermSpansFromStructures,
|
||||
writeBermSpans,
|
||||
type BermSpan,
|
||||
} from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
import { pipeIdAt, placePipeIds, prunePipeIds, withPipeIds } from "./design_pipe_ids";
|
||||
import { calcOnServerNow } from "./design_calc_sites";
|
||||
import { bermRequest, calcChecked, calcOnServerOnly } from "./design_calc_server";
|
||||
import { plannedIrregular } from "./design_structure_status";
|
||||
import { planStations } from "../../common_util/common_util_structure_station";
|
||||
import { followPipeReports } from "../../B05_Profile/logic/B05_Profile_Pipeline_Follow";
|
||||
@@ -99,7 +89,7 @@ export function createFacilityLane(
|
||||
let types: StructureType[] = [];
|
||||
let stored: StructureInstance[] | null = null;
|
||||
let structures: StructureInstance[] = [];
|
||||
let structuresKnown = false; // 구조물을 받기 전 = 소단 사본 안 씀(빈 목록으로 저장분 소단을 지우지 않게)
|
||||
let structuresKnown = false; // 구조물을 받기 전 = 측점 계획 모름(빈 목록으로 저장 시설 측점을 지우지 않게)
|
||||
/** 마지막 관 알림 — 노선 따라감 · 관 초안이 바뀌면 이것을 초안 측점으로 다시 맞춤. */
|
||||
let reported: LanePipe[] | null = null;
|
||||
let pipes: LanePipe[] = [];
|
||||
@@ -152,36 +142,6 @@ export function createFacilityLane(
|
||||
|
||||
const marks = (): StructureInstance[] => [...structures, ...pipesToStructureMarks(pipes)];
|
||||
|
||||
/** 소단 사본 = 지금 구조물의 소단 구간(467 · 사본 없는 동안 다시 셈 = 저장분 `design.berm`) · 같으면 안 씀 ·
|
||||
* 쓰면 저장 노선 cross 한 번 더(같은 값 측점은 다시 셈 기억이 건너뜀 · 묶음은 늦게 불러옴 · 순환 없음). */
|
||||
function syncBerm(): void {
|
||||
const routeId = pageRouteId(projectId);
|
||||
if (!structuresKnown || routeId === null) return;
|
||||
// 계산 자리 quantity = server 면 잠잠 뒤 서버 값만(511-A) · 아니면 브라우저 셈
|
||||
if (calcOnServerNow("quantity")) {
|
||||
void calcOnServerOnly<BermSpan[]>("quantity", "C4.copy", bermRequest(structures)).then(
|
||||
(next) => next && putBerm(routeId, next),
|
||||
);
|
||||
return;
|
||||
}
|
||||
putBerm(
|
||||
routeId,
|
||||
calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [structures]),
|
||||
);
|
||||
}
|
||||
|
||||
function putBerm(routeId: number, next: BermSpan[]): void {
|
||||
const copy = readState<BermSpan[]>("berm", projectId, routeId);
|
||||
if (Array.isArray(copy) && JSON.stringify(copy) === JSON.stringify(next)) return;
|
||||
writeBermSpans(projectId, routeId, next);
|
||||
void Promise.all([import("./design_pipeline_cross"), import("./design_pipeline_page")])
|
||||
.then(([cross, page]) => {
|
||||
cross.forceCross();
|
||||
page.pipelineCross(projectId);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
/** 측점 계획 입력이 다 옴 — 모름(관 알림 전 · 구조물 받기 전)을 빈 목록으로 셈하면 종단도가 저장 시설 측점
|
||||
* (관 등)을 뺀 선으로 횡단을 다시 셈 · 유토 배지가 잠깐 틀림(473 · 요구 272). */
|
||||
const known = (): boolean => types.length > 0 && reported !== null && structuresKnown;
|
||||
@@ -234,14 +194,12 @@ export function createFacilityLane(
|
||||
}
|
||||
if (next.pipes) reported = [...next.pipes];
|
||||
refresh(carryEdits);
|
||||
if (next.structures) run(syncBerm);
|
||||
},
|
||||
follow() {
|
||||
const drafted = store.get("structures");
|
||||
structures = drafted ?? stored ?? [];
|
||||
structuresKnown ||= drafted !== null || stored !== null;
|
||||
refresh(false);
|
||||
run(syncBerm);
|
||||
},
|
||||
setIntervalM(next) {
|
||||
if (next > 0 && next !== intervalM) {
|
||||
|
||||
@@ -338,14 +338,13 @@ function createPagePipeline(
|
||||
groundPvi: groundPviAt, // 계산 자리 「서버」 = 서버 Node(511-B B-3)
|
||||
}),
|
||||
profile: profileAt,
|
||||
// 단면 유형 = 새 지형 상단측 · 소단 = 지금 구조물(365-5) · 저장 노선 = 다시 셈 창구 한 벌(365-4) · 엔진은
|
||||
// 창구 출력을 앞 결과로 안 이어 씀(초안 = 새로고침)
|
||||
// 단면 유형 = 상단측 · 소단 = 얹힌 제원(577-2) · 저장 노선 = 다시 셈 창구(365-4) · 엔진은 창구 출력 안 이어 씀
|
||||
cross: (ctx) => {
|
||||
const stations = ctx.out.ground?.stations ?? [];
|
||||
const status = statusOf(ctx.inputs.pipes, ctx.inputs.structures);
|
||||
const engine = () =>
|
||||
crossStage.stage({
|
||||
...contextCross(ctx, status, options.bermAt?.(ctx.inputs.structures) ?? null),
|
||||
...contextCross(ctx, status),
|
||||
prev: ctx.prev && savedOut.has(ctx.prev) ? null : ctx.prev,
|
||||
});
|
||||
// 계산 자리 cross = server — 엔진 길도 서버 Node 단계(같은 TS · 511-B B-4) · 못 받음 = 브라우저
|
||||
|
||||
@@ -14,7 +14,6 @@ import type {
|
||||
AlignmentBase,
|
||||
AlignmentEdits,
|
||||
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Alignment";
|
||||
import type { BermAt } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
import type { SectionDetailResponse } from "../../B06_Section/B06_Section_Api_Fetch";
|
||||
import type { DesignStoreValues } from "./design_store";
|
||||
|
||||
@@ -61,8 +60,6 @@ export interface PagePipelineOptions {
|
||||
stationKeys?: (routeId: number, detail: SectionDetailResponse) => string[];
|
||||
/** 고리가 `RECONCILE_ROUNDS` 바퀴 안에 안 맞음 — 「계산 불안정」 한 줄(365-2). */
|
||||
onUnstable?: () => void;
|
||||
/** 소단 구간(지금 구조물 목록 → 누가거리마다 제원) — 초안 cross 가 따라간 구조물로 소단을 셈(365-5). */
|
||||
bermAt?: (structures: unknown) => BermAt;
|
||||
/** 저장 노선 cross = 다시 셈 창구 한 벌(365-4 · 측점이 상세와 다르면 null = 엔진) · 주면 종단도는 따로 안 셈. */
|
||||
savedCross?: (
|
||||
d: SectionDetailResponse,
|
||||
|
||||
@@ -59,8 +59,7 @@ import {
|
||||
refreshCrossDesigns,
|
||||
type CrossRefreshInput,
|
||||
} from "../../B06_Section/B06_Section_Cross_Refresh";
|
||||
import { bermSpansFromStructures, bermSpecAt } from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
import { areasRequest, bermRequest, calcChecked } from "./design_calc_server";
|
||||
import { areasRequest, calcChecked } from "./design_calc_server";
|
||||
import { registeredKeys, stationIntervalOf } from "../../B06_Section/B06_Section_Station_Keys";
|
||||
import { detailCross } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
import type { CrossOutput, PipelineStation } from "./logic/design_pipeline_types";
|
||||
@@ -255,15 +254,6 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo
|
||||
each((hooks) => hooks.onFacilitiesFollowed?.(far));
|
||||
},
|
||||
onUnstable: () => showToast(L("Design_Pipeline_Unstable"), "warning"),
|
||||
// 초안 소단 = 지금 구조물(따라간 자리)의 소단 구간(365-5 · 저장 노선은 다시 셈 창구가 세션 사본으로)
|
||||
bermAt: (structures) => {
|
||||
const list = Array.isArray(structures) ? structures : [];
|
||||
// 계산 자리 quantity = server 면 잠잠 뒤 서버 대조만(초안 미리보기 · 511-A)
|
||||
const spans = calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [list], {
|
||||
request: bermRequest(list),
|
||||
});
|
||||
return (chainageM) => bermSpecAt(spans, chainageM);
|
||||
},
|
||||
// 저장 노선 횡단 = 다시 셈 창구 한 벌(365-4) — 카드 · 유토곡선 · [저장] 패치가 보는 상세 설계를 늘 고치고 그
|
||||
// 값을 묶음 출력으로(발자국 · 평면 횡단 판) · 측점이 상세와 다르면(갈아끼움 전) 묶음 엔진(439)
|
||||
savedCross: (detail, routeId, stations) =>
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { readFileSync, statSync } from "node:fs";
|
||||
import { bermSpansFromStructures, bermSpecAt } from "@util/common_util_berm_spans";
|
||||
import type { FollowingStructure } from "@util/common_util_route_follow";
|
||||
import { useCatalogDefinition, type CatalogDefinition } from "@util/common_util_structure_catalog";
|
||||
import type { PlanPipe, PlanStructure, PlanType } from "@util/common_util_structure_station";
|
||||
@@ -189,9 +188,7 @@ async function runStages(
|
||||
ctx.inputs.structures ?? savedStructures,
|
||||
types,
|
||||
);
|
||||
const list = Array.isArray(ctx.inputs.structures) ? ctx.inputs.structures : [];
|
||||
const spans = bermSpansFromStructures(list);
|
||||
return crossStage.stage(contextCross(ctx, status, (at) => bermSpecAt(spans, at)));
|
||||
return crossStage.stage(contextCross(ctx, status));
|
||||
},
|
||||
};
|
||||
if (stages.until === "follow") return followOnly(table, inputs);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from "../../../common_util/common_util_route_follow";
|
||||
import {
|
||||
crossWithContext,
|
||||
type BermAt,
|
||||
type CrossInput,
|
||||
type ShapeStatus,
|
||||
} from "../../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
@@ -45,11 +44,10 @@ export function placedFollowInputs(
|
||||
};
|
||||
}
|
||||
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림) · 단면 유형 기본값(상단측) · 소단(지금 구조물) 얹음(365-5 · 457). */
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림 · 소단 = 얹힌 제원 · 577-2) · 단면 유형 기본값(상단측) 얹음(365-5 · 457). */
|
||||
export const contextCross = (
|
||||
ctx: StageContext<"cross">,
|
||||
status: ShapeStatus | null,
|
||||
bermAt: BermAt | null,
|
||||
): StageContext<"cross"> => ({
|
||||
...ctx,
|
||||
inputs: {
|
||||
@@ -58,7 +56,6 @@ export const contextCross = (
|
||||
ctx.inputs.cross as CrossInput | null,
|
||||
ctx.out.ground?.stations ?? [],
|
||||
status,
|
||||
bermAt,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
* `resources/tester/test_346_2_pipeline_cross_page.py`.
|
||||
* crossWithUphill cross 단계 입력에 단면 유형 기본값(상단측)을 얹음 — 서버 노선 저장이 단면 유형 ·
|
||||
* 측구 방향을 새 지형으로 정하는 것과 같은 규칙(넘기는 칸 = 부르는 쪽 입력).
|
||||
* crossWithShapes 얹힌 시설 맥락(포장 · 기울임 · 하강) = 지금 현황을 새 측점에 다시 얹은 그림에서(457).
|
||||
* crossWithShapes 얹힌 시설 맥락(포장 · 기울임 · 하강 · 소단) = 지금 현황을 새 측점에 다시 얹은 그림에서(457 ·
|
||||
* 소단 = 얹힌 `berm` 제원 + 세션 쪽 깊이 · 577-2).
|
||||
* crossAtSeat 자리 칸(암 경계 · 2단 경사 · 손 상단측 · `seats`)은 측점이 그 자리 그대로일 때만 · 새 자리 =
|
||||
* 기본값(요구 275-3 · 판정 `sameSeat` · 서버 노선 저장 · Node 넘김과 같은 규칙).
|
||||
* draftSectionDetail ground · profile · cross → 상세(측점 · 지반 · 횡단 설계 · 그림 자료 · 카드 이름 = 지금
|
||||
@@ -113,38 +114,6 @@ export function crossWithUphill(
|
||||
return { ...input, byKey };
|
||||
}
|
||||
|
||||
/** 소단 한 측점 제원(세션 `berm` 꼴 · 구조물 목록에서 폄). */
|
||||
export type BermAt = (
|
||||
chainageM: number,
|
||||
) => { width_m: number; interval_m: number; slope_deg: number; depth_m?: number } | null;
|
||||
|
||||
/** 측점마다 소단 = 지금 구조물(초안 따라간 자리)의 소단 구간 · 쪽 깊이는 세션 고침(`bermDepthLeftM` ·
|
||||
* `bermDepthRightM` · 측점 열쇠) — 노선 초안 동안 소단 고침도 묶음으로(365-5). 구간 밖 측점은 소단 없음. */
|
||||
export function crossWithBerm(
|
||||
input: CrossInput | null,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
bermAt: BermAt | null,
|
||||
): CrossInput | null {
|
||||
if (!input || !bermAt) return input;
|
||||
const byKey: CrossInput["byKey"] = { ...input.byKey };
|
||||
for (const station of stations) {
|
||||
const { bermDepthLeftM, bermDepthRightM, ...rest } = input.byKey[station.key] ?? {};
|
||||
const at = bermAt(station.chainageM);
|
||||
byKey[station.key] = {
|
||||
...rest,
|
||||
berm: at && {
|
||||
widthM: at.width_m,
|
||||
intervalM: at.interval_m,
|
||||
slopeDeg: at.slope_deg,
|
||||
depthM: at.depth_m,
|
||||
depthLeftM: (bermDepthLeftM as number | undefined) ?? null,
|
||||
depthRightM: (bermDepthRightM as number | undefined) ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
return { ...input, byKey };
|
||||
}
|
||||
|
||||
const kindOf = (station: PipelineStation): SectionStation["kind"] =>
|
||||
station.key === "end:BP"
|
||||
? "bp"
|
||||
@@ -269,7 +238,8 @@ function shapeContext(section: CrossSection): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
/** 측점마다 시설 맥락(포장 · 물넘이 기울임 · 세월교 노면 하강) = 지금 현황을 새 측점에 다시 얹은 그림에서(457). */
|
||||
/** 측점마다 시설 맥락(포장 · 물넘이 기울임 · 세월교 노면 하강 · 소단) = 지금 현황을 새 측점에 다시 얹은 그림에서
|
||||
* (457) · 소단 쪽 깊이 = 세션 고침(`bermDepthLeftM` · `bermDepthRightM` · 측점 열쇠 · 365-5) · 구간 밖 = 소단 없음. */
|
||||
export function crossWithShapes(
|
||||
input: CrossInput | null,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
@@ -285,23 +255,31 @@ export function crossWithShapes(
|
||||
);
|
||||
const byKey: CrossInput["byKey"] = { ...input.byKey };
|
||||
stations.forEach((station, i) => {
|
||||
byKey[station.key] = { ...byKey[station.key], ...shapeContext(sections[i]) };
|
||||
const { bermDepthLeftM, bermDepthRightM, ...rest } = byKey[station.key] ?? {};
|
||||
const at = sections[i].berm;
|
||||
byKey[station.key] = {
|
||||
...rest,
|
||||
...shapeContext(sections[i]),
|
||||
berm: at && {
|
||||
widthM: at.width_m,
|
||||
intervalM: at.interval_m,
|
||||
slopeDeg: at.slope_deg,
|
||||
depthM: at.depth_m,
|
||||
depthLeftM: (bermDepthLeftM as number | undefined) ?? null,
|
||||
depthRightM: (bermDepthRightM as number | undefined) ?? null,
|
||||
},
|
||||
};
|
||||
});
|
||||
return { ...input, byKey };
|
||||
}
|
||||
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림) · 단면 유형 기본값(상단측) · 소단(지금 구조물) 얹음 — 페이지 묶음 cross. */
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림 · 소단 포함) · 단면 유형 기본값(상단측) 얹음 — 페이지 묶음 cross. */
|
||||
export const crossWithContext = (
|
||||
input: CrossInput | null,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
status: ShapeStatus | null,
|
||||
bermAt: BermAt | null,
|
||||
): CrossInput | null =>
|
||||
crossWithBerm(
|
||||
crossWithUphill(crossWithShapes(crossAtSeat(input, stations), stations, status), stations),
|
||||
stations,
|
||||
bermAt,
|
||||
);
|
||||
crossWithUphill(crossWithShapes(crossAtSeat(input, stations), stations, status), stations);
|
||||
|
||||
/** 묶음 출력 → 상세. 저장 상세의 종단 머리(이름)는 그대로 · 측점 · 지반 · 횡단 · 계획선 표본은 새 노선
|
||||
* (십자선 대체 높이도 초안 종단 · 413) · 그림 자료 · 카드 이름 = `status`(지금 현황)를 새 측점에 다시 얹음(457). */
|
||||
|
||||
@@ -62,6 +62,7 @@ import type { FordPavementSpec } from "./logic/B06_Section_Cross_Ford_Pavement";
|
||||
import type { RevetmentSpec } from "./logic/B06_Section_Cross_Revetment_Geom";
|
||||
import type { PavementEntry } from "@util/common_util_structure_pavement";
|
||||
import type { UnderdrainSpec } from "@util/common_util_structure_underdrain";
|
||||
import type { BermSessionSpec } from "@util/common_util_berm_spans";
|
||||
export type { FordPavementSpec, RevetmentSpec };
|
||||
|
||||
export interface SectionContextResponse {
|
||||
@@ -343,6 +344,8 @@ export interface CrossSection extends SectionStation {
|
||||
pavement?: PavementEntry[];
|
||||
/** 맹암거 — 기준 측점(288-1). 높이 · 경사 · 측구 없음 · 노면 기울임 · 돌 무늬 형상. */
|
||||
underdrain?: UnderdrainSpec;
|
||||
/** 소단 — 덮는 구간 제원(577-2 · null = 얹었는데 없음 · 칸 없음 = 아직 안 얹음 → 저장분 `design.berm`). */
|
||||
berm?: BermSessionSpec | null;
|
||||
/** 빈칸 경고(323-4 · 읽기 칸 · 저장 안 함) — 「종류 칸」(형상 필수 칸이 빈 관 · 구조물 · 측점 계획 자리). */
|
||||
blank?: string[];
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
import type { StandardCrossSectionSpec } from "@util/common_util_cross_design";
|
||||
import type { CrossSection, SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { readCutSlopeSession, readRockBoundarySession } from "./B06_Section_UI_Page_Persist";
|
||||
import type { BermSpan } from "./B06_Section_UI_Page_Persist";
|
||||
import { readState } from "../A00_Common/b_page_state";
|
||||
import { USER_TOUCHED_KEYS } from "./logic/B06_Section_Design_Keys";
|
||||
import { savedDesigns } from "./B06_Section_Section_Canon";
|
||||
import {
|
||||
@@ -228,17 +226,14 @@ function gatherRefresh(input: CrossRefreshInput): RefreshInputs | null {
|
||||
const standard = effectiveStandardCross(projectId) as StandardCrossSectionSpec | null;
|
||||
if (!standard) return null; // 컨텍스트를 아직 못 받음 — 받은 뒤 다시 셈
|
||||
const { routeId } = input;
|
||||
// 소단 사본 없음(시설 줄이 구조물을 받기 전) = 저장분 `design.berm`(파이썬 `load_berm_spans` 못 읽음 →
|
||||
// 저장분 · `stored_berm` 짝 · 467)
|
||||
const bermCopy = readState<BermSpan[]>("berm", projectId, routeId);
|
||||
return {
|
||||
alignment,
|
||||
standard,
|
||||
rockOffsets: [...readRockOffsets(projectId, routeId)],
|
||||
rockDefault: readRockBoundaryDefault(projectId),
|
||||
// 측점별 암 절토 경사 · 소단 · 카드 선택 · 노견 확장 = 세션에만 있는 값 — **계산 전에** 실음(2026-09-07 · 3-9)
|
||||
// 측점별 암 절토 경사 · 소단 깊이 · 카드 선택 · 노견 확장 = 세션에만 있는 값 — **계산 전에** 실음(2026-09-07 ·
|
||||
// 3-9) · 소단 제원 = 측점에 얹힌 `section.berm`(577-2)
|
||||
cutSlopes: [...readCutSlopeRatios(projectId, routeId)],
|
||||
bermSpans: Array.isArray(bermCopy) ? bermCopy : null,
|
||||
bermDepths: [...readBermDepths(projectId, routeId)],
|
||||
choices: [...crossDesignChoices(projectId, routeId)],
|
||||
shoulderExtends: [...readShoulderExtends(projectId, routeId)],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""형상 얹기 한 벌(323-4) — 측점(횡단 dict) + 현황(관 · 구조물) + 구조물 표 → 그 측점 그림 자료.
|
||||
|
||||
구조물 열쇠(`SHAPE_KEYS` · 빈칸 경고 `blank`)를 걷고 차례대로 다시 얹음 — 관 세트 → 벽 → 포장 → 맹암거 →
|
||||
빈칸 경고. 고르기 = 표 형상(322-1). 서버 상세 읽기(`_read_section_detail`) · 재생성 응답이 이것 하나를 씀.
|
||||
소단 → 빈칸 경고. 고르기 = 표 형상(322-1). 서버 상세 읽기(`_read_section_detail`) · 재생성 응답이 이것 하나를 씀.
|
||||
셈 = 늘 떠 있는 Node `facility.attach`(화면과 같은 TS · 511-B B-4) ·
|
||||
파이썬 몫(`_attach_python`)은 Node 를 못 부를 때만.
|
||||
TS 짝 = `common_util/common_util_structure_attach.ts` `attachShapes`(화면 = 초안 ?? 저장분 현황) ·
|
||||
@@ -18,7 +18,12 @@ from typing import Any
|
||||
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_Sets import attach_culvert_specs, culvert_sets_from
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import attach_wall_specs, wall_specs_from
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import (
|
||||
attach_berm_specs,
|
||||
attach_wall_specs,
|
||||
berm_spans_from,
|
||||
wall_specs_from,
|
||||
)
|
||||
from B06_Section.B06_Section_Engine_Tilt import attach_underdrain_specs, underdrains_from
|
||||
from common_util.common_util_structure_catalog import catalog_row, field_name, type_ids_with
|
||||
from common_util.common_util_structure_pavement import (
|
||||
@@ -35,7 +40,16 @@ from common_util.common_util_structure_station import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: 측점 그림 자료 열쇠 — TS `common_util_structure_shape_keys.SHAPE_KEYS` 짝.
|
||||
SHAPE_KEYS = ("culvert", "ford", "box", "ford_pavement", "revetment", "pavement", "underdrain")
|
||||
SHAPE_KEYS = (
|
||||
"culvert",
|
||||
"ford",
|
||||
"box",
|
||||
"ford_pavement",
|
||||
"revetment",
|
||||
"pavement",
|
||||
"underdrain",
|
||||
"berm",
|
||||
)
|
||||
|
||||
|
||||
def _blank_labels(type_id: str, options: dict | None, types: dict[str, Any]) -> list[str]:
|
||||
@@ -128,6 +142,7 @@ def _attach_python(
|
||||
pavement_specs_from([s.model_dump() for s in structures], type_ids_with(shape="pavement")),
|
||||
)
|
||||
attach_underdrain_specs(sections, underdrains_from(structures))
|
||||
attach_berm_specs(sections, berm_spans_from(structures))
|
||||
_attach_blanks(sections, pipes, structures)
|
||||
return misses
|
||||
|
||||
|
||||
@@ -110,11 +110,10 @@ def wall_specs_from(structures: list[Any]) -> list[dict[str, Any]]:
|
||||
return found
|
||||
|
||||
|
||||
def load_berm_spans(project_root: Path) -> list[tuple[float, float, dict[str, float]]] | None:
|
||||
"""구조물 정본의 소단 구간 `[(시작, 끝, 제원)]` — [저장] 서버 다시 셈의 소단 원천(194-1).
|
||||
def berm_spans_from(structures: list[Any]) -> list[tuple[float, float, dict[str, float]]]:
|
||||
"""구조물 목록 → 소단 구간 `[(시작, 끝, 제원)]`(형상 얹기 한 벌 577-2 · [저장] 194-1).
|
||||
|
||||
짝: TS `bermSpansFromStructures`(같은 규칙 — 폭 0 = 없음 · 깊이 빈 값 = 기본 1.0 · 0 미만 = 0).
|
||||
못 읽으면 None(부르는 쪽이 저장분 `design.berm` 으로 물러섬).
|
||||
"""
|
||||
from common_util.common_util_cross_berm import (
|
||||
BERM_DEFAULT_DEPTH_M,
|
||||
@@ -130,35 +129,41 @@ def load_berm_spans(project_root: Path) -> list[tuple[float, float, dict[str, fl
|
||||
return fallback
|
||||
return value if math.isfinite(value) and value >= 0 else fallback
|
||||
|
||||
spans: list[tuple[float, float, dict[str, float]]] = []
|
||||
for item in structures:
|
||||
if (catalog_row(item.type_id) or {}).get("shape") != "berm": # 표 형상(323-4)
|
||||
continue
|
||||
anchor = item.chainage_m if item.chainage_m is not None else item.start_m
|
||||
start = item.start_m if item.start_m is not None else anchor
|
||||
end = item.end_m if item.end_m is not None else anchor
|
||||
if start is None or end is None:
|
||||
continue
|
||||
options = item.options or {}
|
||||
width = number(options, "width_m", BERM_DEFAULT_WIDTH_M)
|
||||
if width <= 0:
|
||||
continue
|
||||
raw = options.get("depth_m")
|
||||
try:
|
||||
depth = BERM_DEFAULT_DEPTH_M if raw in (None, "") else max(float(raw), 0.0)
|
||||
except (TypeError, ValueError):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
if not math.isfinite(depth):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
spec = {
|
||||
"width_m": width,
|
||||
"interval_m": number(options, "interval_m", BERM_DEFAULT_INTERVAL_M),
|
||||
"slope_deg": number(options, "slope_deg", BERM_DEFAULT_SLOPE_DEG),
|
||||
"depth_m": depth,
|
||||
}
|
||||
spans.append((float(min(start, end)), float(max(start, end)), spec))
|
||||
return spans
|
||||
|
||||
|
||||
def load_berm_spans(project_root: Path) -> list[tuple[float, float, dict[str, float]]] | None:
|
||||
"""구조물 정본의 소단 구간 — [저장] 서버 다시 셈의 소단 원천(194-1) · 못 읽으면 None(부르는 쪽이
|
||||
저장분 `design.berm` 으로 물러섬)."""
|
||||
try:
|
||||
spans: list[tuple[float, float, dict[str, float]]] = []
|
||||
for item in load_structures(str(project_root))[1]:
|
||||
if (catalog_row(item.type_id) or {}).get("shape") != "berm": # 표 형상(323-4)
|
||||
continue
|
||||
anchor = item.chainage_m if item.chainage_m is not None else item.start_m
|
||||
start = item.start_m if item.start_m is not None else anchor
|
||||
end = item.end_m if item.end_m is not None else anchor
|
||||
if start is None or end is None:
|
||||
continue
|
||||
options = item.options or {}
|
||||
width = number(options, "width_m", BERM_DEFAULT_WIDTH_M)
|
||||
if width <= 0:
|
||||
continue
|
||||
raw = options.get("depth_m")
|
||||
try:
|
||||
depth = BERM_DEFAULT_DEPTH_M if raw in (None, "") else max(float(raw), 0.0)
|
||||
except (TypeError, ValueError):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
if not math.isfinite(depth):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
spec = {
|
||||
"width_m": width,
|
||||
"interval_m": number(options, "interval_m", BERM_DEFAULT_INTERVAL_M),
|
||||
"slope_deg": number(options, "slope_deg", BERM_DEFAULT_SLOPE_DEG),
|
||||
"depth_m": depth,
|
||||
}
|
||||
spans.append((float(min(start, end)), float(max(start, end)), spec))
|
||||
return spans
|
||||
return berm_spans_from(load_structures(str(project_root))[1])
|
||||
except Exception: # noqa: BLE001 — 정본을 못 읽으면 저장분으로 물러섬
|
||||
logger.exception("B06 소단 구간을 읽지 못했습니다 (저장분 소단을 씀)")
|
||||
return None
|
||||
@@ -174,6 +179,24 @@ def berm_span_at(
|
||||
return None
|
||||
|
||||
|
||||
def attach_berm_specs(
|
||||
cross_sections: list[dict[str, Any]], spans: list[tuple[float, float, dict[str, float]]]
|
||||
) -> int:
|
||||
"""측점마다 소단 제원을 얹음(TS `attachBermSpecs` 짝 · 577-2) — 덮는 소단 없음 = None
|
||||
(얹었음 표시). 얹은 측점 수."""
|
||||
attached = 0
|
||||
for section in cross_sections:
|
||||
chainage = section.get("chainage_m")
|
||||
if isinstance(chainage, bool) or not isinstance(chainage, (int, float)):
|
||||
continue
|
||||
if not math.isfinite(chainage):
|
||||
continue
|
||||
spec = berm_span_at(spans, float(chainage))
|
||||
section["berm"] = dict(spec) if spec else None
|
||||
attached += 1 if spec else 0
|
||||
return attached
|
||||
|
||||
|
||||
def attach_wall_specs(cross_sections: list[dict[str, Any]], walls: list[dict[str, Any]]) -> int:
|
||||
"""구간 안 측점에 벽 제원을 얹는다(TS `attachWallSpecs` 짝). 얹은 개수를 돌려준다."""
|
||||
if not walls:
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "./B06_Section_Api_Fetch";
|
||||
import type { DesignViewSave } from "../A00_Common/design/design_shared";
|
||||
import { buildCrossPatches, type CrossPatchSources } from "./logic/B06_Section_UI_Page_Patches";
|
||||
import { clearState, readByKey, writeByKey, writeState } from "../A00_Common/b_page_state";
|
||||
import { clearState, readByKey, writeByKey } from "../A00_Common/b_page_state";
|
||||
import { kindChangedChainages } from "./B06_Section_Api_Culvert_Options";
|
||||
import {
|
||||
crossDesignChoices,
|
||||
@@ -30,15 +30,8 @@ import type { StandardCrossSection } from "./B06_Section_Api_Fetch";
|
||||
import type { RockBoundaryControl } from "./B06_Section_UI_Section_View";
|
||||
import type { CutSlopeControl } from "./B06_Section_UI_Cross_CutSlope";
|
||||
import { balloonOffsetsPayload } from "@util/common_util_mass_haul_balance_view";
|
||||
// 소단 구간(C4) — common_util 로 옮김 · 부르는 곳 그대로(다시 내보냄).
|
||||
import type { BermSpan } from "@util/common_util_berm_spans";
|
||||
export {
|
||||
bermSpansFromStructures,
|
||||
bermSpecAt,
|
||||
isBermType,
|
||||
type BermSessionSpec,
|
||||
type BermSpan,
|
||||
} from "@util/common_util_berm_spans";
|
||||
// 소단 종류 판정 — common_util 로 옮김 · 부르는 곳 그대로(다시 내보냄).
|
||||
export { isBermType } from "@util/common_util_berm_spans";
|
||||
import { readKeyed, writeKeyed, type KeyedState } from "./B06_Section_Station_Keys";
|
||||
|
||||
/**
|
||||
@@ -124,11 +117,6 @@ function putCell(
|
||||
}
|
||||
}
|
||||
|
||||
/** 소단 사본(결과 통 · 구조물에서 셈 · 477-4) — 쓰는 곳은 페이지 시설 줄 하나. */
|
||||
export function writeBermSpans(projectId: string, routeId: number, spans: BermSpan[]): void {
|
||||
writeState("berm", spans, projectId, routeId);
|
||||
}
|
||||
|
||||
/** 암 경계선 오프셋 저장소 — 값(세션 칸)과 조정창 제어기를 함께 낸다. */
|
||||
export interface RockBoundaryStore {
|
||||
/** 지금 세션 칸 — 측점키(누가거리 2자리) → 오프셋(m). `buildCrossPatches` 재료(새 Map). */
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* 순수 — 화면 · fetch · 세션 없음(세션 읽기는 부르는 쪽).
|
||||
* ========================================================================== */
|
||||
|
||||
import { bermSpecAt, type BermSessionSpec, type BermSpan } from "@util/common_util_berm_spans";
|
||||
import type { BermSessionSpec } from "@util/common_util_berm_spans";
|
||||
import {
|
||||
BERM_DEFAULT_DEPTH_M,
|
||||
BERM_DEFAULT_INTERVAL_M,
|
||||
@@ -42,8 +42,6 @@ export interface RefreshInputs {
|
||||
rockDefault: number | null;
|
||||
/** 측점별 암 절토 경사(0 = 표준값). */
|
||||
cutSlopes: Array<[number, number]>;
|
||||
/** 소단 사본(세션 `berm` · 없으면 null = 저장분 `design.berm`). */
|
||||
bermSpans: BermSpan[] | null;
|
||||
bermDepths: Array<[number, SideValues]>;
|
||||
/** 카드 선택(지반 · 측구 · 2단 경사 …). */
|
||||
choices: Array<[number, CrossDesignChoice]>;
|
||||
@@ -141,13 +139,12 @@ export function refreshDesigns(
|
||||
// 측점별 암 절토 경사도 세션에만 있는 값이다 — **계산 전에** 실어야 설계선이 새 경사로
|
||||
// 그려진다. 계산 뒤에 값만 베껴 붙이면 그림은 옛 경사, 숫자만 새것이 된다(2026-09-07).
|
||||
const cutSlopeRatios = new Map(inputs.cutSlopes);
|
||||
// 소단도 같은 성격 — 세션에만 있는 값이라 여기서 실어 주지 않으면 계획선을 고치는
|
||||
// 순간 계단이 사라진다(계획서 3-9). 사본 없음(시설 줄이 구조물을 받기 전) = 저장분 `design.berm`
|
||||
// (파이썬 `load_berm_spans` 못 읽음 → 저장분 · `stored_berm` 짝 · 467).
|
||||
const bermSpans = inputs.bermSpans;
|
||||
// 소단도 같은 성격 — 계산 전에 실어야 계획선을 고치는 순간 계단이 안 사라진다(계획서 3-9). 제원 = 측점에
|
||||
// 얹힌 소단(형상 얹기 한 벌 · 577-2) · 아직 안 얹음(칸 없음) = 저장분 `design.berm`(`stored_berm` 짝 · 467)
|
||||
const bermDepthAt = bermDepthReader(new Map(inputs.bermDepths));
|
||||
const bermAt = (chainageM: number, design: Record<string, unknown>): BermSpec | null => {
|
||||
const spec = bermSpans ? bermSpecAt(bermSpans, chainageM) : storedBermSpec(design.berm);
|
||||
const bermAt = (section: CrossSection, design: Record<string, unknown>): BermSpec | null => {
|
||||
const chainageM = section.chainage_m;
|
||||
const spec = section.berm === undefined ? storedBermSpec(design.berm) : section.berm;
|
||||
return spec
|
||||
? {
|
||||
widthM: spec.width_m,
|
||||
@@ -226,7 +223,7 @@ export function refreshDesigns(
|
||||
tilted: Boolean(section.ford_pavement || section.underdrain),
|
||||
tiltPct: section.ford_pavement?.slope_pct ?? null, // 맹암거 = 표준 경사(371-3)
|
||||
standard,
|
||||
berm: bermAt(section.chainage_m, design),
|
||||
berm: bermAt(section, design),
|
||||
rockBoundaryOffsetM,
|
||||
cutSlopeRatio: cutSlopeAt(section.chainage_m, design),
|
||||
twoStageSlope: choice?.two_stage_slope ?? Boolean(design.two_stage_slope),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* =============================================================================
|
||||
* common_util_berm_spans.ts
|
||||
* 소단 구간 — 구조물 목록(표 형상 berm)에서 구간 · 측점마다 제원(PLAN 472-4 C4 · 화면 파일
|
||||
* `B06_Section_UI_Page_Persist.ts` 에서 옮김 · 그쪽이 다시 내보냄).
|
||||
* 브라우저(시설 줄 · 다시 셈)와 서버 Node 입구(`design_calc_node` · 등록부 `cross.berm_spans`)가 같은 셈.
|
||||
* 파이썬 짝 = `B06_Section_Engine_Structures_Wall.py` `load_berm_spans` · `berm_span_at`
|
||||
* (Node 를 못 부를 때만 · 거울 시험 `test_272_berm_source.py`).
|
||||
* 소단 구간 — 구조물 목록(표 형상 berm)에서 구간 · 측점마다 제원(PLAN 472-4 C4) · 측점에 얹기(577-2 ·
|
||||
* 벽처럼 형상 얹기 한 벌 `attachShapes` 가 `section.berm` 을 세움 · 계단은 계산 안쪽 그대로).
|
||||
* 브라우저(형상 얹기 · 다시 셈)와 서버 Node 입구(`design_calc_node` · 등록부 `cross.berm_spans` · `facility.attach`)가 같은 셈.
|
||||
* 파이썬 짝 = `B06_Section_Engine_Structures_Wall.py` `berm_spans_from` · `load_berm_spans` · `berm_span_at` ·
|
||||
* `attach_berm_specs`(Node 를 못 부를 때만 · 거울 시험 `test_272_berm_source.py` · `test_323_4_attach_mirror.py`).
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "./common_util_cross_berm";
|
||||
import { catalogRow } from "./common_util_structure_catalog";
|
||||
|
||||
/** 측점 하나의 소단 제원 — 서버 payload 와 같은 이름을 쓴다(그대로 실어 보낸다). */
|
||||
/** 측점 하나의 소단 제원(`section.berm`) — 서버 payload 와 같은 이름을 쓴다(그대로 실어 보낸다). */
|
||||
export interface BermSessionSpec {
|
||||
width_m: number;
|
||||
interval_m: number;
|
||||
@@ -42,10 +42,6 @@ export const isBermType = (typeId: string): boolean => catalogRow(typeId)?.shape
|
||||
/**
|
||||
* 구조물 목록에서 **소단 구간**을 뽑는다 — 사용자는 「구조물 배치」에서 놓는다
|
||||
* (2026-09-07 사용자 확정: 별도 폼이 아니라 다른 옹벽·기슭막이와 같은 자리).
|
||||
*
|
||||
* 세션 열쇠 `berm` 은 그 결과를 담는 **사본**이다. 재계산(브라우저·서버)이 측점마다 값을
|
||||
* 읽어야 하는데 구조물 목록은 비동기로 오므로, 목록이 바뀔 때마다 여기서 펴 두고 계산은
|
||||
* 그 사본만 본다.
|
||||
*/
|
||||
export function bermSpansFromStructures(
|
||||
structures: ReadonlyArray<{
|
||||
@@ -105,3 +101,19 @@ export function bermSpecAt(spans: BermSpan[], chainageM: number): BermSessionSpe
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
/** 측점마다 소단 제원을 얹음(파이썬 `attach_berm_specs` 짝 · 제자리) — 덮는 소단 없음 = null(얹었음 표시 ·
|
||||
* 칸 없음 = 아직 안 얹음 → 다시 셈이 저장분 `design.berm` 으로 물러섬). 얹은 측점 수. */
|
||||
export function attachBermSpecs(
|
||||
sections: Array<Record<string, unknown>>,
|
||||
spans: BermSpan[],
|
||||
): number {
|
||||
let attached = 0;
|
||||
for (const section of sections) {
|
||||
const chainage = section.chainage_m;
|
||||
if (typeof chainage !== "number" || !Number.isFinite(chainage)) continue;
|
||||
section.berm = bermSpecAt(spans, chainage);
|
||||
if (section.berm) attached += 1;
|
||||
}
|
||||
return attached;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 형상 얹기 한 벌(323-4) — 측점(횡단) + 현황(관 · 구조물) + 구조물 표 → 그 측점 그림 자료.
|
||||
*
|
||||
* 구조물 열쇠(`SHAPE_KEYS` · 빈칸 경고 `blank`)를 걷고 서버와 같은 차례로 다시 얹음 — 관 세트 → 벽 →
|
||||
* 포장 → 맹암거 → 빈칸 경고. 고르기 = 표 형상(322-1). 화면은 초안 ?? 저장분 현황으로 이것 하나를 씀
|
||||
* 포장 → 맹암거 → 소단 → 빈칸 경고. 고르기 = 표 형상(322-1). 화면은 초안 ?? 저장분 현황으로 이것 하나를 씀
|
||||
* (`B06_Section_Section_Drafts.withDraftShapes`) — 저장 전 그림 = 저장 뒤 그림.
|
||||
* 파이썬 짝 = `B06_Section/B06_Section_Engine_Attach.py` `attach_shapes`(서버 상세 읽기 · 재생성 응답) ·
|
||||
* 거울 시험 = `resources/tester/test_323_4_attach_mirror.py`.
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
underdrainSpecsFrom,
|
||||
type UnderdrainInput,
|
||||
} from "./common_util_structure_underdrain";
|
||||
import { attachBermSpecs, bermSpansFromStructures } from "./common_util_berm_spans";
|
||||
import { catalogRow, fieldName } from "./common_util_structure_catalog";
|
||||
import {
|
||||
blankKeys,
|
||||
@@ -163,6 +164,10 @@ export function attachShapes(
|
||||
),
|
||||
);
|
||||
attachUnderdrainSpecs(sections, underdrainSpecsFrom(structures as UnderdrainInput[]));
|
||||
attachBermSpecs(
|
||||
sections,
|
||||
bermSpansFromStructures(structures as Parameters<typeof bermSpansFromStructures>[0]),
|
||||
);
|
||||
attachBlanks(sections, pipes, structures, types);
|
||||
return misses;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ export const SHAPE_KEYS = [
|
||||
"revetment",
|
||||
"pavement",
|
||||
"underdrain",
|
||||
"berm",
|
||||
] as const;
|
||||
|
||||
@@ -35,8 +35,6 @@ const STUBS = {
|
||||
}),
|
||||
"ui_template_elements.ts": anyModule(),
|
||||
"B04_PreProcess_Api_Fetch.ts": anyModule(),
|
||||
// 소단 사본(467) — 끌어오는 줄이 import.meta 를 써 이 로더로 못 읽음 · 소단 몫은 test_272_berm_source(vite 묶음)
|
||||
"B06_Section_UI_Page_Persist.ts": anyModule({ bermSpansFromStructures: () => [] }),
|
||||
};
|
||||
|
||||
const resolve = (base) =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* 467 헬퍼 — 소단 원천 한 벌: 구조물 → 소단 구간(TS) · 저장분 소단 제원(TS) · 시설 줄이 세션 사본을 씀 ·
|
||||
* 다시 셈 = 사본 없으면 저장분 `design.berm`.
|
||||
/* 467 · 577-2 헬퍼 — 소단 원천 한 벌: 구조물 → 소단 구간(TS) · 측점 얹기(TS) · 저장분 소단 제원(TS) ·
|
||||
* 다시 셈 = 얹힌 `section.berm` · 칸 없으면 저장분 `design.berm`.
|
||||
* node helper_272_berm_source.mjs <묶음.mjs> <job.json>
|
||||
* job = { standard, rockDefault, structureCases, storedCases, ground } · 짝 = `test_272_berm_source.py`.
|
||||
* job = { standard, rockDefault, structureCases, storedCases, ground, chainages } · 짝 = `test_272_berm_source.py`.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -45,34 +45,12 @@ const out = {};
|
||||
out.spans = job.structureCases.map((list) => m.bermSpansFromStructures(list));
|
||||
out.stored = job.storedCases.map((spec) => m.storedBermSpec(spec));
|
||||
|
||||
/* 2. 시설 줄이 사본을 씀 — 구조물 받기 전 안 씀 · 같으면 안 씀 · 바뀌면 한 번 */
|
||||
{
|
||||
m.setPageRouteId(P, R);
|
||||
const key = m.stateKey("berm", P, R);
|
||||
const count = () => writes.filter((k) => k === key).length;
|
||||
const copy = () => m.readState("berm", P, R);
|
||||
const lane = m.createFacilityLane(P, () => null);
|
||||
const steps = {};
|
||||
lane.setTypes([]);
|
||||
lane.follow(); // 저장분 · 가게 구조물 없음 = 아직 모름
|
||||
steps.beforeKnown = { copy: copy(), writes: count() };
|
||||
const saved = job.structureCases[0];
|
||||
lane.setStored(saved);
|
||||
lane.set({ structures: saved }, false);
|
||||
steps.loaded = { copy: copy(), writes: count() };
|
||||
lane.set({ structures: saved.map((item) => ({ ...item })) });
|
||||
lane.set({ pipes: [] });
|
||||
steps.same = { writes: count() };
|
||||
const moved = saved.map((item) =>
|
||||
item.type_id === "berm" ? { ...item, start_m: item.start_m + 5, end_m: item.end_m + 5 } : item,
|
||||
);
|
||||
lane.set({ structures: moved });
|
||||
steps.moved = { copy: copy(), writes: count() };
|
||||
lane.set({ structures: [] });
|
||||
steps.removed = { copy: copy(), writes: count() };
|
||||
lane.dispose();
|
||||
out.lane = steps;
|
||||
}
|
||||
/* 2. 측점 얹기 — 구조물마다 측점 목록에 얹은 칸(덮는 소단 없음 = null · 누가거리 없음 = 칸 없음) */
|
||||
out.attach = job.structureCases.map((list) => {
|
||||
const sections = job.chainages.map((c) => ({ chainage_m: c }));
|
||||
m.attachBermSpecs(sections, m.bermSpansFromStructures(list));
|
||||
return sections;
|
||||
});
|
||||
|
||||
/* 2-2. 시설 줄 칠함 — 종류 표 받기 전 측점(빈 목록)을 종단도에 안 씀 · 받은 뒤 한 번 */
|
||||
{
|
||||
@@ -132,10 +110,9 @@ out.stored = job.storedCases.map((spec) => m.storedBermSpec(spec));
|
||||
],
|
||||
balance: { cut_area_m2: 0, fill_area_m2: 0, net_area_m2: 0 },
|
||||
};
|
||||
const run = async (copy) => {
|
||||
if (copy === undefined) sessionStorage.removeItem(m.stateKey("berm", P, R));
|
||||
else m.writeState("berm", copy, P, R);
|
||||
const run = async (berm) => {
|
||||
const detail = detailOf();
|
||||
if (berm !== undefined) detail.cross_sections[0].berm = berm;
|
||||
await m.refreshCrossDesigns({
|
||||
projectId: P,
|
||||
routeId: R,
|
||||
@@ -147,9 +124,9 @@ out.stored = job.storedCases.map((spec) => m.storedBermSpec(spec));
|
||||
return { fill: design.fill_area_m2 ?? null, cut: design.cut_area_m2 ?? null };
|
||||
};
|
||||
out.refresh = {
|
||||
noCopy: await run(undefined),
|
||||
copySame: await run(m.bermSpansFromStructures(job.structureCases[0])),
|
||||
copyEmpty: await run([]),
|
||||
unattached: await run(undefined),
|
||||
attachedSame: await run(job.storedBerm),
|
||||
attachedNone: await run(null),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* 467 시험 묶음 진입점 — 소단 원천 한 벌(시설 줄 사본 · 다시 셈 저장분 폴백 · 진짜 모듈 · vite ssr). */
|
||||
/* 467 · 577-2 시험 묶음 진입점 — 소단 원천 한 벌(측점 얹기 · 다시 셈 저장분 폴백 · 진짜 모듈 · vite ssr). */
|
||||
export { createFacilityLane } from "../../A00_Common/design/design_facility_lane";
|
||||
export { setPageRouteId } from "../../A00_Common/design/design_page_route";
|
||||
export { refreshCrossDesigns, storedBermSpec } from "../../B06_Section/B06_Section_Cross_Refresh";
|
||||
export { bermSpansFromStructures } from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
export { attachBermSpecs, bermSpansFromStructures } from "../../common_util/common_util_berm_spans";
|
||||
export { rememberRockBoundaryDefault } from "../../B06_Section/B06_Section_UI_Standard_Panel";
|
||||
export { readState, stateKey, writeState } from "../../A00_Common/b_page_state";
|
||||
|
||||
@@ -38,13 +38,6 @@ const R = 6;
|
||||
const AT = 40;
|
||||
m.writeState("std-cross", job.standard, P);
|
||||
m.rememberRockBoundaryDefault(P, job.rockDefault);
|
||||
// 소단 사본 = 측점을 덮는 구간(소단 깊이가 먹게)
|
||||
m.writeState(
|
||||
"berm",
|
||||
[{ start_m: 0, end_m: 100, width_m: 1, interval_m: 3, slope_deg: 0, depth_m: 1 }],
|
||||
P,
|
||||
R,
|
||||
);
|
||||
const canonDetail = () => ({
|
||||
longitudinal: { length_m: 100, stations: [], profile_alignment: { policy: {} } },
|
||||
cross_sections: [
|
||||
@@ -59,6 +52,8 @@ const canonDetail = () => ({
|
||||
center_y: 0,
|
||||
frame: { left_xy: [0, 1] },
|
||||
uphill_side: "left",
|
||||
// 측점에 얹힌 소단(577-2 · 소단 깊이가 먹게)
|
||||
berm: { width_m: 1, interval_m: 3, slope_deg: 0, depth_m: 1 },
|
||||
samples: job.ground,
|
||||
design: {
|
||||
design_elevation_m: 100,
|
||||
|
||||
@@ -80,8 +80,24 @@ STUBS["B05_Profile_Api_Replan"] = {
|
||||
station_snap: false,
|
||||
}),
|
||||
};
|
||||
// ⑪ 소단 프로젝트 = 저장 구조물에 소단 한 구간(577-2 · 측점에 얹힌 제원으로 셈)
|
||||
const BERM_SPAN = { from: 30, to: 70 };
|
||||
STUBS["B05_Profile_Api_Structures"] = {
|
||||
fetchStructures: async () => ({ structures: [] }),
|
||||
fetchStructures: async (projectId) => ({
|
||||
structures:
|
||||
projectId === "proj-365c"
|
||||
? [
|
||||
{
|
||||
structure_id: "berm-1",
|
||||
type_id: "berm",
|
||||
placement: "interval",
|
||||
start_m: BERM_SPAN.from,
|
||||
end_m: BERM_SPAN.to,
|
||||
options: { width_m: 1.5, interval_m: 5, slope_deg: 60, depth_m: 1 },
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}),
|
||||
fetchStructureTypes: async () => job.types,
|
||||
};
|
||||
STUBS["B05_Profile_Api_TerrainGrid"] = {
|
||||
@@ -421,17 +437,11 @@ async function run() {
|
||||
pageMod.disposePagePipeline(Q);
|
||||
page.clearDrafts(Q);
|
||||
|
||||
// ⑪ 초안 소단(365-5) — 소단 = 지금 구조물 구간(bermAt) · 쪽 깊이 = 세션 고침(측점 열쇠) → 묶음 cross
|
||||
// ⑪ 초안 소단(365-5) — 소단 = 지금 구조물 구간(측점에 얹힌 제원 · 577-2) · 쪽 깊이 = 세션 고침(측점 열쇠) → 묶음 cross
|
||||
const B = "proj-365c";
|
||||
window.localStorage.setItem("frd_current_project_id", B);
|
||||
const span = { from: 30, to: 70 };
|
||||
const bBridge = pageMod.routeDraftBridge(B, link(), {
|
||||
stationIntervalM: job.interval,
|
||||
bermAt: () => (c) =>
|
||||
c >= span.from && c <= span.to
|
||||
? { width_m: 1.5, interval_m: 5, slope_deg: 60, depth_m: 1 }
|
||||
: null,
|
||||
});
|
||||
const span = BERM_SPAN;
|
||||
const bBridge = pageMod.routeDraftBridge(B, link(), { stationIntervalM: job.interval });
|
||||
pageMod.pipelineEarthwork(B, { conversion: job.conversion });
|
||||
const bPipe = pageMod.pagePipeline(B);
|
||||
await settle(runs().length + 1);
|
||||
|
||||
@@ -69,10 +69,6 @@ const savedStations = m.buildStations(savedLine(), savedCurves(), pipes, structu
|
||||
});
|
||||
const crossSource = m.pipelineCrossSource(P, detail, savedStations, R);
|
||||
const edits = m.readAlignment(detail.longitudinal).edits;
|
||||
const bermAt = (list) => {
|
||||
const spans = m.bermSpansFromStructures(Array.isArray(list) ? list : []);
|
||||
return (chainageM) => m.bermSpecAt(spans, chainageM);
|
||||
};
|
||||
const crossStage = m.createCrossStage({ grid: () => ({ grid }) });
|
||||
const pipe = m.createDesignPipeline({
|
||||
stages: {
|
||||
@@ -119,7 +115,6 @@ const pipe = m.createDesignPipeline({
|
||||
ctx.inputs.cross,
|
||||
ctx.out.ground?.stations ?? [],
|
||||
m.shapeStatus(ctx.inputs.pipes, ctx.inputs.structures, types),
|
||||
bermAt(ctx.inputs.structures),
|
||||
),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -30,7 +30,6 @@ export {
|
||||
applyStructureAreaRows,
|
||||
structureAreaRows,
|
||||
} from "../../B06_Section/logic/B06_Section_Structure_Layouts";
|
||||
export { bermSpansFromStructures, bermSpecAt } from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
export { parseDtmGrid } from "../../common_util/common_util_surface_sampler";
|
||||
export { computeMassHaul } from "../../common_util/common_util_mass_haul";
|
||||
export { writeState, writeStateRaw } from "../../A00_Common/b_page_state";
|
||||
|
||||
@@ -49,7 +49,6 @@ refreshDesigns(
|
||||
rockOffsets: [],
|
||||
rockDefault: job.rockDefault,
|
||||
cutSlopes: [],
|
||||
bermSpans: null,
|
||||
bermDepths: [],
|
||||
choices: [],
|
||||
shoulderExtends: [],
|
||||
|
||||
@@ -183,14 +183,9 @@ await sync.handleDesignChange(20, { two_stage_slope: true }).catch(() => undefin
|
||||
out.choice = Object.fromEntries(m.crossDesignChoices("pD", 7));
|
||||
await sync.handleDesignChange(20, {}).catch(() => undefined);
|
||||
out.choiceAfterEmpty = Object.fromEntries(m.crossDesignChoices("pD", 7));
|
||||
// 소단 사본(시설 줄이 열 때 구조물에서 씀) — 떠남 확인에 안 잡힘 · 진짜 고침은 잡힘
|
||||
// 결과 통 칸(계산 묶음 결과) — 떠남 확인에 안 잡힘 · 진짜 고침은 잡힘(소단 사본 칸은 걷음 · 577-2)
|
||||
sessionStorage.clear();
|
||||
m.writeState(
|
||||
"berm",
|
||||
[{ start_m: 0, end_m: 40, width_m: 1, interval_m: 5, slope_deg: 45, depth_m: 1 }],
|
||||
"pB",
|
||||
7,
|
||||
);
|
||||
m.writeState("design-stations", [{ chainage_m: 0 }], "pB");
|
||||
out.dirtyBermOnly = m.designDirty("pB");
|
||||
m.writeCrossDesignChoice("pB", 7, 20, { ditch_choice: false });
|
||||
out.dirtyWithEdit = m.designDirty("pB");
|
||||
|
||||
@@ -35,8 +35,6 @@ const STUBS = {
|
||||
}),
|
||||
"ui_template_elements.ts": anyModule(),
|
||||
"B04_PreProcess_Api_Fetch.ts": anyModule(),
|
||||
// 소단 사본(467) — 끌어오는 줄이 import.meta 를 써 이 로더로 못 읽음 · 소단 몫은 test_272_berm_source(vite 묶음)
|
||||
"B06_Section_UI_Page_Persist.ts": anyModule({ bermSpansFromStructures: () => [] }),
|
||||
};
|
||||
|
||||
const resolve = (base) =>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""467 (요구 272 · 분석 4장 (B2)) — 소단 원천 한 벌.
|
||||
"""467 · 577-2 (요구 272 · 434 · 437) — 소단 원천 한 벌 = 측점에 얹은 제원(옹벽처럼).
|
||||
|
||||
못박는 것
|
||||
- 소단 사본(세션 `berm`) 쓰는 자리 = 페이지 시설 줄 하나 — 구조물 받기 전 안 씀 ·
|
||||
같으면 안 씀 · 바뀌면 한 번 · 지우면 빈 목록(B05 만 열어도 씀 · 횡단 보기는 안 씀).
|
||||
- 다시 셈 = 사본 없으면 저장분 `design.berm`(파이썬 `load_berm_spans` 규칙) —
|
||||
B05 만 연 값 = B06 연 뒤 값 = [저장] 서버 값(`enforce_design_lines`) · 빈 사본 = 소단 없음.
|
||||
- 측점 얹기 — 구조물 → 측점마다 `section.berm`(덮는 소단 없음 = null · 겹치면 먼저 놓은 것) ·
|
||||
TS `attachBermSpecs` ↔ 파이썬 `attach_berm_specs`.
|
||||
- 다시 셈 = 얹힌 제원 · 칸 없음(아직 안 얹음) = 저장분 `design.berm` · null = 소단 없음 —
|
||||
얹힌 값 = 저장분 값 = [저장] 서버 값(`enforce_design_lines`).
|
||||
- 거울 — TS `bermSpansFromStructures` ↔ 파이썬 `load_berm_spans` ·
|
||||
TS `storedBermSpec` ↔ 파이썬 `berm_from`.
|
||||
- 세션 사본(`berm` 칸 · 시설 줄 `syncBerm`) 없음.
|
||||
|
||||
Node 헬퍼 `helper_272_berm_source.mjs` 가 vite ssr 로 묶은 진짜 모듈을 부름.
|
||||
"""
|
||||
@@ -25,7 +26,10 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import load_berm_spans # noqa: E402
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import ( # noqa: E402
|
||||
attach_berm_specs,
|
||||
load_berm_spans,
|
||||
)
|
||||
from B06_Section.B06_Section_Router_Design import berm_from, enforce_design_lines # noqa: E402
|
||||
from config.config_system import ( # noqa: E402
|
||||
STANDARD_CROSS_SECTION,
|
||||
@@ -69,6 +73,7 @@ GROUND = [
|
||||
for o in range(-40, 41)
|
||||
]
|
||||
STORED_BERM = {"width_m": 1.0, "interval_m": 3.0, "slope_deg": 0.0, "depth_m": 1.0}
|
||||
CHAINAGES = [0.0, 10.0, 25.0, 30.0, 35.0, 55.0, 65.0, 100.0, 100.5, None]
|
||||
LONG = {
|
||||
"design_profiles": [
|
||||
{
|
||||
@@ -126,6 +131,7 @@ def out(tmp_path_factory):
|
||||
"storedCases": STORED_CASES,
|
||||
"ground": GROUND,
|
||||
"storedBerm": STORED_BERM,
|
||||
"chainages": CHAINAGES,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
@@ -163,25 +169,27 @@ def test_거울_저장분_소단_제원(out):
|
||||
assert ts == pytest.approx(want), spec
|
||||
|
||||
|
||||
def test_시설_줄이_사본을_씀(out):
|
||||
lane = out["lane"]
|
||||
assert lane["beforeKnown"] == {"copy": None, "writes": 0} # 구조물 받기 전 = 안 씀
|
||||
assert lane["loaded"]["writes"] == 1
|
||||
assert lane["loaded"]["copy"] == out["spans"][0] # 저장 구조물 소단 구간
|
||||
assert lane["same"]["writes"] == 1 # 같은 구간 · 관만 알림 = 안 씀
|
||||
assert lane["moved"]["writes"] == 2
|
||||
assert lane["moved"]["copy"][0]["start_m"] == 5.0 and lane["moved"]["copy"][0]["end_m"] == 105.0
|
||||
assert lane["removed"] == {
|
||||
"copy": [],
|
||||
"writes": 3,
|
||||
} # 소단 지움 = 빈 사본(저장분 소단으로 안 물러섬)
|
||||
def test_거울_측점_얹기(out, tmp_path):
|
||||
for index, (structures, ts) in enumerate(zip(STRUCTURE_CASES, out["attach"], strict=True)):
|
||||
spans = load_berm_spans(_project(tmp_path / f"a{index}", structures))
|
||||
py = [{"chainage_m": c} for c in CHAINAGES]
|
||||
attach_berm_specs(py, spans)
|
||||
assert [("berm" in r) for r in ts] == [("berm" in r) for r in py], index
|
||||
for got, exp in zip(ts, py, strict=True):
|
||||
if exp.get("berm") is None:
|
||||
assert got.get("berm") is None, (index, got, exp)
|
||||
else:
|
||||
assert got["berm"] == pytest.approx(exp["berm"]), (index, got, exp)
|
||||
first = out["attach"][0]
|
||||
assert first[1]["berm"] == pytest.approx(STORED_BERM) and first[8]["berm"] is None
|
||||
assert "berm" not in first[-1] # 누가거리 없음 = 칸 없음
|
||||
|
||||
|
||||
def test_다시_셈_B05만_B06_저장_같은_값(out, tmp_path):
|
||||
r = out["refresh"]
|
||||
no_copy, same, empty = r["noCopy"], r["copySame"], r["copyEmpty"]
|
||||
assert no_copy["fill"] == same["fill"] and no_copy["cut"] == same["cut"] # B05 만 = B06 연 뒤
|
||||
assert empty["fill"] != no_copy["fill"] # 빈 사본 = 소단 없음(저장분으로 안 물러섬)
|
||||
no_copy, same, empty = r["unattached"], r["attachedSame"], r["attachedNone"]
|
||||
assert no_copy["fill"] == same["fill"] and no_copy["cut"] == same["cut"] # 안 얹음 = 저장분
|
||||
assert empty["fill"] != no_copy["fill"] # 얹었는데 없음 = 소단 없음(저장분으로 안 물러섬)
|
||||
# [저장] = 서버가 구조물 정본으로 다시 셈
|
||||
root = _project(tmp_path / "save", SAVED)
|
||||
design = {"ground_type": "soil", "section_mode": "both_fill", "ditch_type": "standard"}
|
||||
@@ -193,11 +201,27 @@ def test_다시_셈_B05만_B06_저장_같은_값(out, tmp_path):
|
||||
assert abs(bare[0]["design"]["fill_area_m2"] - empty["fill"]) < 1e-3
|
||||
|
||||
|
||||
def test_쓰는_자리_하나():
|
||||
sync = (ROOT / "B06_Section" / "B06_Section_UI_Page_Design_Sync.ts").read_text(encoding="utf-8")
|
||||
assert "writeBermSpans" not in sync and "useStructureIds(projectId, structures)" in sync
|
||||
lane = (ROOT / "A00_Common" / "design" / "design_facility_lane.ts").read_text(encoding="utf-8")
|
||||
assert "writeBermSpans(projectId, routeId, next)" in lane
|
||||
def test_사본_없음():
|
||||
for rel in (
|
||||
"A00_Common/design/design_facility_lane.ts",
|
||||
"A00_Common/b_page_state.ts",
|
||||
"B06_Section/B06_Section_UI_Page_Persist.ts",
|
||||
"B06_Section/B06_Section_Cross_Refresh.ts",
|
||||
"A00_Common/design/design_shared.ts",
|
||||
):
|
||||
text = (ROOT / rel).read_text(encoding="utf-8")
|
||||
for word in (
|
||||
"writeBermSpans",
|
||||
"syncBerm",
|
||||
"putBerm",
|
||||
'readState<BermSpan[]>("berm"',
|
||||
"bermAt:",
|
||||
):
|
||||
assert word not in text, (rel, word)
|
||||
shape = (ROOT / "common_util" / "common_util_structure_shape_keys.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert '"berm"' in shape
|
||||
|
||||
|
||||
def test_시설_줄_종류_표_전_측점_안_칠함(out):
|
||||
|
||||
@@ -188,10 +188,14 @@ def test_every_shape_lands(ts: dict) -> None:
|
||||
"revetment",
|
||||
"pavement",
|
||||
"underdrain",
|
||||
"berm",
|
||||
} <= keys
|
||||
assert any(section.get("berm") for section in ts["cases"]["all"]) # 소단 = 측점에 얹음(577-2)
|
||||
old = ts["cases"]["all"][1] # 옛 물넘이 자리(5 m) — 걷힘
|
||||
assert "ford_pavement" not in old
|
||||
assert all(len(section) == 1 for section in ts["cases"]["empty"])
|
||||
# 구조물 없음 = 소단 「얹었는데 없음」(null)만
|
||||
assert all(set(section) == {"chainage_m", "berm"} for section in ts["cases"]["empty"])
|
||||
assert all(section["berm"] is None for section in ts["cases"]["empty"])
|
||||
|
||||
|
||||
def test_blank_chips(ts: dict) -> None:
|
||||
|
||||
@@ -104,6 +104,6 @@ def test_세션_고름은_바꾼_칸끼리_얹음(out):
|
||||
assert out["choiceAfterEmpty"] == out["choice"]
|
||||
|
||||
|
||||
def test_소단_사본은_떠남_확인에_안_잡힘(out):
|
||||
def test_결과_통은_떠남_확인에_안_잡힘(out):
|
||||
assert out["dirtyBermOnly"] is False
|
||||
assert out["dirtyWithEdit"] is True
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
못박는 것
|
||||
- 477-3 조작 놓은 직후(찍기 기다림 400 ms 안) ↶ 켜짐 · 단추 맞춤 알림 · 바뀐 것 없는 누름은 안 켜짐.
|
||||
- 기다림 중 ↶ = 먼저 찍고 되돌림(한 번에 조작 전) · ↷ = 다시 · 서버 다시 셈 뒤 쓰기 묶음은 그대로 한 걸음.
|
||||
- 477-4 `berm` = 결과 통(사진 · 떠남 물음 밖) · 떠남 제외 목록에 사본 줄 없음.
|
||||
- 477-4 결과 통 칸은 사진 밖 · 소단 사본 칸 `berm` 은 걷음(577-2 · 측점에 얹힌 제원) ·
|
||||
떠남 목록에 줄 없음.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_477_undo_now_berm.py -q
|
||||
"""
|
||||
@@ -41,7 +42,7 @@ const { createProfileHistory, onPageHistory } = loadTs(process.argv[4]);
|
||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const PIPES = "aislo:draft:pipes:p1";
|
||||
const CROSS = "aislo:draft:crossdesign:p1:7";
|
||||
const BERM = "aislo:result:berm:p1:7";
|
||||
const BERM = "aislo:result:design-stations:p1";
|
||||
const out = {};
|
||||
let changes = 0;
|
||||
onPageHistory({ change: () => (changes += 1) });
|
||||
@@ -81,7 +82,7 @@ sessionStorage.setItem(PIPES, "b");
|
||||
lift();
|
||||
await wait(200);
|
||||
sessionStorage.setItem(CROSS, "y");
|
||||
sessionStorage.setItem(BERM, '[{"s":1}]'); // 사본 = 결과 통 — 사진 밖
|
||||
sessionStorage.setItem(BERM, '[{"s":1}]'); // 결과 통 칸 — 사진 밖
|
||||
await wait(300);
|
||||
h2.undo();
|
||||
out.grouped = [sessionStorage.getItem(PIPES), sessionStorage.getItem(CROSS)];
|
||||
@@ -128,15 +129,15 @@ def test_기다림_중_되돌리기_먼저_찍고_한_걸음(result) -> None:
|
||||
|
||||
def test_서버_다시_셈_묶음_그대로(result) -> None:
|
||||
assert result["grouped"] == ["a", "x"] and result["groupedLeft"] is False
|
||||
assert result["bermKept"] == '[{"s":1}]' # 결과 통 사본은 되돌리기가 안 건드림
|
||||
assert result["bermKept"] == '[{"s":1}]' # 결과 통 칸은 되돌리기가 안 건드림
|
||||
|
||||
|
||||
def test_바뀐_것_없는_누름은_안_켜짐(result) -> None:
|
||||
assert result["idleCanUndo"] is False
|
||||
|
||||
|
||||
def test_소단_사본은_결과_통() -> None:
|
||||
def test_소단_사본_칸_없음() -> None:
|
||||
state = (ROOT / "A00_Common" / "b_page_state.ts").read_text(encoding="utf-8")
|
||||
assert re.search(r'^\s*berm: \{ bucket: "result", scope: "route" \}', state, re.M)
|
||||
assert not re.search(r"^\s*berm: \{", state, re.M) # 577-2 — 측점에 얹힌 제원(사본 걷음)
|
||||
leave = (ROOT / "A00_Common" / "design" / "design_leave.ts").read_text(encoding="utf-8")
|
||||
assert '"berm"' not in leave
|
||||
|
||||
@@ -57,9 +57,9 @@ def test_자리마다_판_열쇠_저장_길_안_부름():
|
||||
assert 'calcOnServerNow("quantity")' in refresh and '"F7.saved"' in refresh
|
||||
shared = _text("A00_Common/design/design_shared.ts")
|
||||
assert 'calcChecked("quantity", "structure.layouts"' in shared
|
||||
assert 'calcChecked("quantity", "cross.berm_spans"' in shared
|
||||
lane = _text("A00_Common/design/design_facility_lane.ts")
|
||||
assert '"C4.copy", bermRequest(structures)' in lane
|
||||
# 소단 = 측점에 얹힌 제원(577-2) — 사본 · 초안 구간 셈 부름 없음
|
||||
assert '"cross.berm_spans"' not in shared
|
||||
assert '"C4.copy"' not in _text("A00_Common/design/design_facility_lane.ts")
|
||||
mass = _text("B05_Profile/B05_Profile_UI_Profile_MassHaul.ts")
|
||||
assert 'calcFromServer<MassHaulSeries[]>("quantity", "masshaul.series"' in mass
|
||||
assert "openCalcSites(projectId)" in _text("A00_Common/design/design_page.ts")
|
||||
|
||||
Reference in New Issue
Block a user