Files
Aislo/B06_Section/logic/B06_Section_UI_Cross_FillSlope_Warn.ts
T
eomsangdonandClaude Opus 5.5 281c71b55b refactor(logic): 348-1 순수 계산 TS 49 파일 <페이지>/logic/ 로 옮김 — 이름 앞머리 그대로 · 동작 고침 없음
- 옮김: B04 2 · B05 23 · B06 11 · A00_Common/design 13 (9,299 줄) · 이미 logic/ 에 있던 16 파일 그대로
- 안 옮김: 묶음 입구(package.json build:*) · 파이썬 · 순수지만 계산 아닌 8 (보기 결선 · 콜백 · API 타입 · 갈아끼움 알림판)
- import 266 줄(142 파일) · 시험 · 주석 · 짝 표시 문자열 경로 고침 · B05 · B06 CLAUDE.md 짝 줄 logic/
- 시험 도우미 새 경로: 가짜 require 열쇠 · B05()/B06() 부름 · 형제 import 자리 · tsc 묶어 돌리는 시험 · 정규식 · rglob
- design_view_cross 줄 한도 시험 685 → 688 (긴 import 경로 줄바꿈 3줄)
- 확인: typecheck 0 · 서버 묶음 넷(corridor · server-calc · b07-extent · b07-note) 다시 지어 코드 같음(//#region 경로 주석만 다름) · 관련 시험 270 파일 — 남은 실패 19 는 옮기기 전과 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
2026-10-16 13:44:50 +09:00

89 lines
4.1 KiB
TypeScript

/* =============================================================================
* 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<FillSlopeSideName> {
const sides = new Set<FillSlopeSideName>();
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<CrossSection>,
lengthsOf: (section: CrossSection) => Record<FillSlopeSideName, FillSlopeSideLength | null>,
): 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;
}