/* ============================================================================= * B06_Section_UI_Cross_FillSlope_Warn.ts * 성토사면 길이 5m 초과 **경고** 판정(2026-09-14 브레인 승인 (나)) — 값만 가리고 그리지 않는다. * * 근거 — 산림자원법 시행규칙 별표2 Ⅰ.2.차.(3).(나) · 임도설치 규정 별표7 2.차.(3).(나) * 「성토사면 길이 5m 초과 시 옹벽·석축」. 실무 표본(오솔길 W열 성토면 거리)에서도 흔해 * (영월 63% · 봉화 49%) **경고까지만** — 구조물을 자동으로 세우지 않는다(설계자 판단). * * 벽이 선 쪽은 뺀다 — 기슭막이·옹벽이 사면을 끊은 쪽은 이미 조치된 자리다. **좌·우를 갈라** * 한쪽에만 벽이 서면 반대쪽은 그대로 경고한다. * 길이는 `fillSlopeLengths`(카드 머리 「성토사면」 칸과 같은 값)를 받아 쓴다 — 여기서 다시 재지 않는다. * ========================================================================== */ import type { CrossSection } from "./B06_Section_Api_Fetch"; import { FILL_SLOPE_MAX_LENGTH_M } from "./B06_Section_UI_Cross_Culvert_Const"; /** 브레인 승인 문구 그대로(2026-09-14) — 고치면 승인을 다시 받을 것. */ export const FILL_SLOPE_WARN_TEXT = "성토사면 길이 5m 초과 — 법령상 옹벽·석축 설치 대상 (산림자원법 시행규칙 별표2 Ⅰ.2.차.(3).(나) · 임도설치 규정 별표7 2.차.(3).(나)) ※ 실무 표본에서도 흔함(영월 63% · 봉화 49%) — 설치 여부는 설계자 판단"; export type FillSlopeSideName = "left" | "right"; export interface FillSlopeSideLength { lengthM: number; /** 원지반을 못 만나 거기까지만 잰 하한값. */ open: boolean; } export interface FillSlopeWarning { section: CrossSection; sides: Array<{ side: FillSlopeSideName } & FillSlopeSideLength>; } /** 벽이 서서 성토사면을 끊는 쪽 — 배관 기슭막이 · 독립 기슭막이 · 세월교·BOX암거 측벽. */ export function wallSides(section: CrossSection): Set { const sides = new Set(); if (section.ford || section.box) return new Set(["left", "right"]); const culvert = section.culvert; if (culvert?.hidden_pipe) { // 독립 기슭막이 설치 측 — 좌 = +offset(`restrictToSide`) · 양쪽·미지정은 둘 다. if (culvert.side !== "우") sides.add("left"); if (culvert.side !== "좌") sides.add("right"); } else if (culvert) { // 유입 = 상단측(미상이면 좌) · 집수정은 벽이 아니다. const inlet: FillSlopeSideName = (section.uphill_side ?? "left") === "left" ? "left" : "right"; if (culvert.inlet.structure !== "집수정") sides.add(inlet); if (culvert.outlet.structure !== "집수정") sides.add(inlet === "left" ? "right" : "left"); } const revetment = section.revetment; if (revetment) { // 설치 측이 비면 성토가 나는 쪽(`computeRevetmentLayout` 과 같은 규칙). const mode = section.design?.section_mode; const side = revetment.side === "우" ? "right" : revetment.side === "좌" ? "left" : mode === "left_cut" ? "right" : mode === "right_cut" || mode === "both_fill" ? "left" : null; if (side) sides.add(side); } return sides; } /** 5m 를 **넘는** 성토사면(벽 선 쪽 뺌)이 있는 측점만 — 측점 순서 그대로. */ export function fillSlopeWarnings( sections: ReadonlyArray, lengthsOf: (section: CrossSection) => Record, ): FillSlopeWarning[] { const warnings: FillSlopeWarning[] = []; for (const section of sections) { const lengths = lengthsOf(section); const walls = wallSides(section); const sides = (["left", "right"] as const) .filter((side) => !walls.has(side)) .flatMap((side) => { const length = lengths[side]; return length && length.lengthM > FILL_SLOPE_MAX_LENGTH_M + 1e-6 ? [{ side, ...length }] : []; }); if (sides.length) warnings.push({ section, sides }); } return warnings; }