Files
Aislo/B05_Profile/logic/B05_Profile_Drainage_Suggest.ts
T
eomsangdonandClaude Opus 5.5 a5c36a4a29 feat(B05 배수유역): 347-2 자동/수동 다시 셈 · ⚙ 설정 창 · 347-3 제안 목록 · [적용]
- 계산 묶음 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
2026-10-16 11:47:55 +09:00

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);
}