Files
Aislo/config/config_frontend.ts
T
eomsangdonandClaude Opus 5 a9c46b9711 feat(B05): 배수 추천 구조물·관경 자동 설정 + 물넘이·세월교 개략 단면
지시 2 — 배수유역도가 유효직경 다음에 추천을 보여 주고, 초기 전처리 계산이
그 값으로 관 지점을 설정한다:
- recommend_structure(): 유효직경 임계로 구조물·관경을 고른다. D≤1500 배관(규격
  스냅 800·1000·1200·1500), 1500<D≤2000 BOX암거 후보, D>2000 세월교 검토.
  판정 근거는 유량뿐 — 계곡 횡단경사·하천 차수는 지형 계산이 필요해 뺐고 화면이
  "현장 확인"으로 안내한다(2026-08-17 사용자 확정)
- WatershedBasin에 recommended_facility·recommended_diameter_mm·required_area_m2
  추가, 응답 payload와 DetailBasin 타입까지 배선
- _apply_recommendations(): 자동 배치 관의 **빈칸에만** 추천을 채운다. 사용자가
  놓은 관(source=user)·이미 바꾼 시설·이미 있는 옵션 키는 건드리지 않는다.
  carry_facility_attributes 뒤에 얹어 승계값이 덮이지 않게 했다
- 유역 목록 표기: `Ø1234mm → 추천 Ø1500 배관` / `→ BOX암거 후보`
- 추천 관경 목록은 레지스트리 pipe_diameter_mm 선택지와 같아야 한다 — 폼에서 못
  고르는 값을 추천하지 않도록 테스트로 묶었다

지시 1 — 물넘이포장·세월교는 지정할 옵션이 거의 없으니 계산값이라도 보여 준다:
- 레지스트리에 ford_width_m(월류 폭) 신설. 기본값 없음 — 지식DB에 폭 근거가 없어
  프로그램이 지어내지 않는다
- fordSection(): 설계유량과 폭으로 필요 수심을 되짚는다. 광폭 근사로 출발해 실제
  동수반경(R=A/P)으로 수렴시킨다 — 근사값을 그대로 쓰면 통수능이 늘 설계유량에
  못 미쳐 판정이 무의미해진다. n=0.017·S=10%는 실무 수리계산서 역산값
  (울진1 물넘이·관 시트가 둘 다 S=0.10에서 검산 일치)
- 폼에 월류 폭 한 칸 + 읽기전용 요약(설계유량·필요 수심·필요 단면·유속). 설계유량은
  유역 payload에서 폼까지 배선(onPipesChanged → Page → setFacility)

⚠ 실무 시트의 수심 0.27m는 Qd 역산값이 아니라 가정 수심이다(B=20·h=0.27의 통수능
41.2㎥/s ≫ 그 시트 Qd 2.54㎥/s). 실무는 단면을 가정해 통수능을 검증하고, 프로그램은
필요 최소 수심을 낸다 — 화면 문구도 "필요 수심"으로 적었다.

검증: pytest tmp/tests 124 passed·7 skipped(신규 17건), npm run typecheck 무오류,
npm run build 성공. 물넘이 산식은 프론트 전용이라 수치 손검증으로 대신했다 —
Qd=2.54·B=20에서 필요 수심 0.0503m·유속 2.526m/s, 통수능이 2.5400㎥/s로 Qd와 일치.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 19:18:28 +09:00

167 lines
7.1 KiB
TypeScript

/* =============================================================================
* config_frontend.ts
* 프론트엔드 제어 및 웹 환경 설정 (API 주소, 업로드 제한, 라우트 등)
*
* 규칙:
* - 프론트엔드의 모든 제어 상수는 여기서 관리. 컴포넌트 내 하드코딩 금지.
* - API 주소, 파일 제한, WebCAD 렌더 옵션 등을 중앙 집중.
* ========================================================================== */
/* -----------------------------------------------------------------------------
* 1. API 통신
* -------------------------------------------------------------------------- */
/** 백엔드 API 기본 경로. vite proxy(/api → :8000)를 통해 FastAPI로 전달. */
export const API_BASE_URL = "/api";
/** API 요청 타임아웃 (ms) */
export const API_TIMEOUT_MS = 30_000;
/** 격자 해석처럼 수십 초가 걸리는 분석 요청용 타임아웃 (ms).
* 일반 요청에 이 값을 쓰면 장애 시 화면이 오래 멈추므로 분석 엔드포인트에만 쓴다. */
export const API_ANALYSIS_TIMEOUT_MS = 60_000;
/** B03~B09 워크플로우에서 사용할 현재 프로젝트 UUID 저장 키 */
export const CURRENT_PROJECT_ID_KEY = "frd_current_project_id";
/* -----------------------------------------------------------------------------
* 2. 파일 업로드 제한 (B03_FileInput)
* -------------------------------------------------------------------------- */
/** 단일 파일 최대 크기 (MB) */
export const UPLOAD_MAX_MB = 30 * 1024;
/** 한 요청에서 선택 가능한 최대 파일 수 */
export const UPLOAD_MAX_FILES = 5;
/** 청크 업로드 단위 (MB) */
export const UPLOAD_CHUNK_SIZE_MB = 1024;
/** 업로드 진행 UI 갱신 주기 (ms) */
export const PROGRESS_UPDATE_INTERVAL_MS = 10_000;
/** B03 업로드 Service Worker 번들 경로 */
export const SERVICE_WORKER_PATH = "/assets/B03_FileInput_ServiceWorker.js";
/** 허용 확장자 (계획노선/지형/포인트클라우드/도면) */
export const UPLOAD_ALLOWED_EXT = [".csv", ".las", ".laz", ".tif", ".tfw", ".prj", ".dxf"] as const;
/* -----------------------------------------------------------------------------
* 3. WebCAD / 3D 렌더링 옵션
* -------------------------------------------------------------------------- */
export const RENDER_OPTIONS = {
/** Three.js 배경색 (theme.css 변수와 별개로 캔버스 clear color) */
canvasClearColorLight: 0xf6f7fa,
canvasClearColorDark: 0x16121f,
pointColor: 0x3e0079,
gridCenterColor: 0x9491a1,
gridLineColor: 0xe6e2e3,
/** 카메라 기본 시야각 */
cameraFov: 60,
/** 카메라 near/far */
cameraNear: 0.1,
cameraFar: 100_000,
/** 안티앨리어싱 사용 여부 */
antialias: true,
/** 픽셀 비율 상한 (고DPI 성능 보호) */
maxPixelRatio: 2,
} as const;
/* -----------------------------------------------------------------------------
* 4. 라우트 상수 (해시 기반 SPA 라우팅)
* 각 페이지 폴더명과 1:1 매핑.
* -------------------------------------------------------------------------- */
export const ROUTES = {
// 로그인 전
A01_HOME: "a01-home",
A02_PROG_DETAIL: "a02-prog-detail",
A03_COMP_DETAIL: "a03-comp-detail",
A04_NEWS_HISTORY: "a04-news-history",
A05_EDU_DETAIL: "a05-edu-detail",
A06_LOGIN: "a06-login",
A07_REGISTER: "a07-register",
A08_SUPPORT: "a08-support",
A09_SECURITY: "a09-security",
// 로그인 후
B01_ACCOUNT: "b01-account",
B02_PROJ_REGISTER: "b02-proj-register",
B03_FILE_INPUT: "b03-file-input",
B04_PREPROCESS: "b04-preprocess",
B05_PROFILE: "b05-profile",
B06_SECTION: "b06-section",
B07_QUANTITY: "b07-quantity",
B08_DESIGN_DETAIL: "b08-design-detail",
B09_ESTIMATION: "b09-estimation",
B10_PAYMENT: "b10-payment",
B11_STATUS: "b11-status",
// 대시보드에서 B그룹으로 처음 들어갈 때 3D·등고선을 미리 받아 두는 준비 화면.
B11_LOADING: "b11-loading",
} as const;
export type RouteKey = keyof typeof ROUTES;
export type RoutePath = (typeof ROUTES)[RouteKey];
/** 기본 진입 라우트 */
export const DEFAULT_ROUTE: RoutePath = ROUTES.A01_HOME;
/** 로그인이 필요한 라우트 (인증 가드용) */
export const PROTECTED_ROUTES: readonly RoutePath[] = [
ROUTES.B01_ACCOUNT,
ROUTES.B02_PROJ_REGISTER,
ROUTES.B03_FILE_INPUT,
ROUTES.B04_PREPROCESS,
ROUTES.B05_PROFILE,
ROUTES.B06_SECTION,
ROUTES.B07_QUANTITY,
ROUTES.B08_DESIGN_DETAIL,
ROUTES.B09_ESTIMATION,
ROUTES.B10_PAYMENT,
ROUTES.B11_STATUS,
ROUTES.B11_LOADING,
];
/* -----------------------------------------------------------------------------
* 5. 테마 / 언어 저장 키
* -------------------------------------------------------------------------- */
export const THEME_STORAGE_KEY = "frd_theme";
export const LANG_STORAGE_KEY = "frd_lang";
/* -----------------------------------------------------------------------------
* 6. B05 구조물 옵션 (드롭다운 목록·기본값)
* 정본은 config/config_system.py의 STRUCTURE_* / PIPE_* 상수 — 값을 바꿀 때는
* 양쪽을 함께 고친다(2026-08-05 사용자 확정. 계산은 백엔드, 표시는 프론트).
* -------------------------------------------------------------------------- */
export const STRUCTURE_TYPES = ["배관", "기성막이", "대피로", "기타"] as const;
export type StructureType = (typeof STRUCTURE_TYPES)[number];
export const STRUCTURE_DEFAULT_TYPE: StructureType = "배관";
/** 관종별 직경(mm) 목록. */
export const PIPE_DIAMETERS_MM: Record<string, readonly number[]> = {
이중벽관: [150, 200, 250, 300, 400, 450, 500, 600, 700, 800, 900, 1000, 1200, 1500],
삼중벽관: [200, 250, 300, 400, 450, 500, 600, 700, 800, 900, 1000, 1200, 1500],
파형강관: [
150, 200, 250, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 1200, 1350, 1500, 1650, 1800,
2000,
],
} as const;
export const PIPE_TYPES = Object.keys(PIPE_DIAMETERS_MM);
export const PIPE_DEFAULT_TYPE = "파형강관";
/** 자동 지정 기본 관경 — 별표2 (나) 예외 하한 800mm. 유효직경이 더 크면 바로 위 규격. */
export const PIPE_DEFAULT_DIAMETER_MM = 800;
/** 물넘이 개수로 조도계수 — KDS 표 2.7-1 콘크리트 수로 "보통"값이자 실무 물넘이 관측치.
* 백엔드 `config_system.FORD_MANNING_N`과 같은 값이어야 한다. */
export const FORD_MANNING_N = 0.017;
/** 물넘이 수리계산 경사 — 실무 수리계산서 역산값 10%(2026-08-17 확인).
* 백엔드 `config_system.FORD_SLOPE`와 같은 값이어야 한다. */
export const FORD_SLOPE = 0.1;
export const ESCAPE_ROUTE_WIDTHS_M = [1.5, 2.0, 2.5, 3.0] as const;
export const ESCAPE_ROUTE_DEFAULT_WIDTH_M = 2.0;
export const STRUCTURE_ETC_DEFAULT_NAME = "기타 구조물";
/** 유효직경(mm) 이상인 가장 작은 규격을 고른다. 목록을 넘으면 최대 규격. */
export function pickPipeDiameter(pipeType: string, effectiveDiameterMm: number | null): number {
const sizes = PIPE_DIAMETERS_MM[pipeType] ?? PIPE_DIAMETERS_MM[PIPE_DEFAULT_TYPE];
const need = Math.max(PIPE_DEFAULT_DIAMETER_MM, effectiveDiameterMm ?? 0);
return sizes.find((size) => size >= need) ?? sizes[sizes.length - 1];
}