- 계산 묶음 drainage 단계(pipelineStage · 붙이기 = 묶음 쪽) — 노선 초안 · 관이 바뀌면 낡음 · 자동 = 3 s 조용한 뒤 「지금 노선으로 셈」 · 수동 = 칸만 흐림 + 가운데 [업데이트] · 구석 「계산 중」 · 다시 조작하면 요청 끊고 처음으로 · 덮개 없음 - ⚙ 설정 창(배수유역 칸 머리 · openModal) 수동(기본) / 자동 · 계정 화면 설정 drainage-mode(b_page_state 등록표 한 줄 · build:corridor 다시 지음) - 늦은 답 버림 = 차례 번호 · 서버 superseded · 세부유역 요청 감싸기와 같은 차례(B05_Profile_UI_Drainage_Run 으로 나눔) - 제안 = 관경(Ø→Ø) · 시설(BOX암거 · 세월교) · 관 자리 후보(지도 점선 원) · [적용] 한 번에 관 초안 · 자동 바꿈 없음 - [확정] savePipes(confirm) = PUT ?recompute=true · [저장] = 낡음 달고 저장 · 패널 691줄 - 순수 규칙 logic/B05_Profile_Drainage_Live · _Suggest · 시험 test_347_2_drainage_live_ui · typecheck 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
/* =============================================================================
|
|
* 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);
|
|
}
|