Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_1', 'origin/sub_laptop_2', 'origin/sub_laptop_3', 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into sub_laptop_8
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile/logic/B05_Profile_Drainage_Live.ts
|
||||
* 배수유역 자동/수동 다시 셈(347-2) — 순수 규칙만. 화면 · fetch 는 `B05_Profile_UI_Drainage_Live.ts`.
|
||||
*
|
||||
* - 대상 = 평면(노선 초안) · 관 자리만 — 종단 · 횡단 조작은 배수유역 결과를 안 바꿈(묶음 deps 와 같음).
|
||||
* - 노선 초안이 없으면(저장 노선 그대로) 낡음 아님 — 관 조작은 세부 유역 나눔(0.2 s)이 바로 맞춤.
|
||||
* - 자동 = 조용한 뒤 `QUIET_MS` · 수동 = 흐림 + [업데이트] · 기본 수동.
|
||||
* - 답 채택 = 보낸 차례가 지금 차례와 같고 서버가 그만두지 않았을 때만(늦은 답 버림).
|
||||
* ========================================================================== */
|
||||
|
||||
export type DrainageMode = "manual" | "auto";
|
||||
|
||||
/** 자동 모드 — 마지막 조작 뒤 이만큼 조용하면 셈(ms · 341 결정 거리 6 · 3 초). */
|
||||
export const QUIET_MS = 3000;
|
||||
|
||||
/** 계정 화면 설정 글자 → 모드(없거나 모르는 값 = 수동). */
|
||||
export const parseDrainageMode = (raw: string | null | undefined): DrainageMode =>
|
||||
raw === "auto" ? "auto" : "manual";
|
||||
|
||||
/** 묶음 drainage 단계 입력 → 낡았는가 — 노선 초안이 있을 때만. */
|
||||
export const draftMakesStale = (route: unknown): boolean => route !== null && route !== undefined;
|
||||
|
||||
/** 「지금 노선으로 셈」 몸통 — 서버 `post_drainage_live`. */
|
||||
export function liveRequestBody<P>(
|
||||
generation: number,
|
||||
route: unknown,
|
||||
points: P[],
|
||||
): { generation: number; route: unknown; points: P[] } {
|
||||
return { generation, route: route ?? null, points };
|
||||
}
|
||||
|
||||
/** 답을 화면에 올리는가 — 서버가 그만둔 답 · 옛 차례 답은 버림. */
|
||||
export function acceptLive(
|
||||
response: { status?: string; generation?: number },
|
||||
sentGeneration: number,
|
||||
currentGeneration: number,
|
||||
): boolean {
|
||||
return (
|
||||
response.status !== "superseded" &&
|
||||
sentGeneration === currentGeneration &&
|
||||
(response.generation === undefined || response.generation === sentGeneration)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile/logic/B05_Profile_Drainage_Suggest.ts
|
||||
* 배수유역 제안(347-3) — 관경 · 시설 종류 · 관 자리 후보를 모으고 [적용] 때 관 초안에 한 번에 얹음.
|
||||
* 순수 함수만(화면 = `B05_Profile_UI_Drainage_Suggest.ts`).
|
||||
*
|
||||
* - 관경 = 유역 추천이 배관이고 추천 관경이 지금 관경과 다를 때(「Ø→Ø 배관 제안」).
|
||||
* - 시설 = 유역 추천이 BOX암거 · 세월교이고 지금 관이 그 종류가 아닐 때.
|
||||
* - 관 자리 = 서버 `suggest_pipe_positions`(간격 보충 규칙 · 기존 관에서 먼 곳).
|
||||
* - 자동 바꿈 없음 — 모으기만 하고 바꾸는 것은 `applySuggestions`([적용])뿐.
|
||||
* ========================================================================== */
|
||||
|
||||
/** 관 자리가 같은 유역으로 볼 거리(m) — 화면 `PIPE_MATCH_M` 과 같은 값. */
|
||||
const SAME_PIPE_M = 0.5;
|
||||
|
||||
interface PipeLike {
|
||||
chainage_m: number;
|
||||
source?: string;
|
||||
facility?: string;
|
||||
options?: Record<string, string | number>;
|
||||
}
|
||||
|
||||
interface BasinLike {
|
||||
chainage_m: number;
|
||||
recommended_facility?: string;
|
||||
recommended_diameter_mm?: number | null;
|
||||
}
|
||||
|
||||
export type Suggestion =
|
||||
| { kind: "diameter"; chainageM: number; from: number | null; to: number }
|
||||
| { kind: "facility"; chainageM: number; from: string; to: string }
|
||||
| { kind: "position"; chainageM: number };
|
||||
|
||||
const near = (a: number, b: number): boolean => Math.abs(a - b) < SAME_PIPE_M;
|
||||
|
||||
/** 응답(유역 · 관 자리 후보) + 지금 관 목록 → 제안 목록(관 자리 차례). */
|
||||
export function collectSuggestions(
|
||||
pipes: ReadonlyArray<PipeLike>,
|
||||
basins: ReadonlyArray<BasinLike>,
|
||||
candidates: ReadonlyArray<{ chainage_m: number }>,
|
||||
): Suggestion[] {
|
||||
const found: Suggestion[] = [];
|
||||
basins.forEach((basin) => {
|
||||
const pipe = pipes.find((item) => near(item.chainage_m, basin.chainage_m));
|
||||
if (!pipe) return;
|
||||
const facility = pipe.facility ?? "pipe";
|
||||
const want = basin.recommended_facility ?? "pipe";
|
||||
if (want !== "pipe" && facility !== want) {
|
||||
found.push({ kind: "facility", chainageM: pipe.chainage_m, from: facility, to: want });
|
||||
return;
|
||||
}
|
||||
const to = basin.recommended_diameter_mm;
|
||||
const raw = pipe.options?.pipe_diameter_mm;
|
||||
const from = raw === undefined || raw === "" ? null : Number(raw);
|
||||
if (want === "pipe" && facility === "pipe" && to != null && from !== to)
|
||||
found.push({ kind: "diameter", chainageM: pipe.chainage_m, from, to });
|
||||
});
|
||||
candidates
|
||||
.filter((item) => !pipes.some((pipe) => near(pipe.chainage_m, item.chainage_m)))
|
||||
.forEach((item) => found.push({ kind: "position", chainageM: item.chainage_m }));
|
||||
return found.sort((a, b) => a.chainageM - b.chainageM);
|
||||
}
|
||||
|
||||
/** [적용] — 제안을 관 초안에 한 번에(관 더함 · 관경 · 시설 바꿈). 입력 목록은 안 고침. */
|
||||
export function applySuggestions<P extends PipeLike>(
|
||||
pipes: ReadonlyArray<P>,
|
||||
suggestions: ReadonlyArray<Suggestion>,
|
||||
): P[] {
|
||||
const next = pipes.map(
|
||||
(pipe) => ({ ...pipe, options: pipe.options && { ...pipe.options } }) as P,
|
||||
);
|
||||
suggestions.forEach((item) => {
|
||||
if (item.kind === "position") {
|
||||
if (!next.some((pipe) => near(pipe.chainage_m, item.chainageM)))
|
||||
next.push({ chainage_m: item.chainageM, source: "spacing" } as P);
|
||||
return;
|
||||
}
|
||||
const pipe = next.find((candidate) => near(candidate.chainage_m, item.chainageM));
|
||||
if (!pipe) return;
|
||||
if (item.kind === "facility") pipe.facility = item.to;
|
||||
else pipe.options = { ...(pipe.options ?? {}), pipe_diameter_mm: item.to };
|
||||
});
|
||||
return next.sort((a, b) => a.chainage_m - b.chainage_m);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* =============================================================================
|
||||
* 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/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 };
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user