feat(drainage): 확률강우량 본토 오류 개선 — WAMIS 관측소 방식 도입, 제주는 등우선 유지

- common_util_wamis_station.py 신규: 관측소 목록/확률강우량 xlsx 수급(프로젝트별 영구저장),
  최근접+반경 10km 후보 중 100년/24hr 최대 지점 선정(Aislo 자체 로직), 후보표 기록(성과품용)
- 강우강도 이원화: Mononobe형(기본, 실무 방식 rt=(R24/24)(24/T)^0.557) + General형 적합 옵션
  — Mononobe는 b=0 General형과 동일해 하류 idf_intensity/size_pipe 무수정
- _build_rainfall_sync: 계획노선 좌표로 제주/본토 분기, 본토 실패 시 예외 전파(제주 폴백 금지)
- value_at: 등우선 커버리지(제주+30km) 밖 좌표 차단 — 조용히 틀린 값 재발 방지
- config: WAMIS_STATION_RADIUS_M/DRAINAGE_RAINFALL_STATION_DIRNAME/DRAINAGE_RAINFALL_IDF_METHOD
- 검증: 제주 등우선 96값 정상, 본토 차단, 울진 관측소 R24=305.83, Mononobe 검산 실무 일치

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 18:51:25 +09:00
co-authored by Claude Fable 5
parent e6a010314c
commit 69df533350
4 changed files with 349 additions and 15 deletions
@@ -46,13 +46,20 @@ from common_util.common_util_wamis_rainfall import (
build_rainfall_table,
ensure_contour_cache,
)
from common_util.common_util_wamis_station import (
build_station_rainfall_table,
is_jeju,
)
from config.config_db import get_db_pool
from config.config_system import (
DRAINAGE_ARROW_SPACING_M,
DRAINAGE_DESIGN_RETURN_PERIOD_YR,
DRAINAGE_RAINFALL_FILENAME,
DRAINAGE_RAINFALL_IDF_METHOD,
DRAINAGE_RAINFALL_STATION_DIRNAME,
DRAINAGE_RESPONSE_FILENAME,
WAMIS_CONTOUR_CACHE_DIR,
WAMIS_STATION_RADIUS_M,
)
logger = logging.getLogger(__name__)
@@ -249,18 +256,35 @@ def _route_center_lonlat(stored_path: str, fallback_epsg: int | None) -> tuple[f
return lat, lon
def _build_rainfall_sync(lat: float, lon: float) -> dict[str, Any]:
"""등우선 전역 캐시 확보(없는 파일만 다운로드) 후 지점 강우량표를 만든다."""
cached, failures = ensure_contour_cache(WAMIS_CONTOUR_CACHE_DIR)
table = build_rainfall_table(
def _build_rainfall_sync(lat: float, lon: float, stored_path: str) -> dict[str, Any]:
"""계획노선 좌표로 지역을 분별해 강우량표를 만든다 (PLAN.md A, 2026-08-13).
제주: 등우선 내삽(서버 원본이 제주만 커버) / 본토: 관측소 방식(최근접+10km
최대값). 본토에서 관측소 수급이 실패하면 그대로 예외를 올린다 — 제주 등우선으로
폴백하면 "조용히 틀린 값"이 재발하므로 금지.
"""
if is_jeju(lat, lon):
cached, failures = ensure_contour_cache(WAMIS_CONTOUR_CACHE_DIR)
table = build_rainfall_table(
lat,
lon,
WAMIS_CONTOUR_CACHE_DIR,
design_return_period=DRAINAGE_DESIGN_RETURN_PERIOD_YR,
)
table["contour_cache_files"] = cached
if failures:
table["failures"] = (table.get("failures") or []) + failures
table["region_mode"] = "jeju_contour"
return table
table = build_station_rainfall_table(
lat,
lon,
WAMIS_CONTOUR_CACHE_DIR,
drainage_dir(stored_path) / DRAINAGE_RAINFALL_STATION_DIRNAME,
design_return_period=DRAINAGE_DESIGN_RETURN_PERIOD_YR,
radius_m=WAMIS_STATION_RADIUS_M,
idf_method=DRAINAGE_RAINFALL_IDF_METHOD,
)
table["contour_cache_files"] = cached
if failures:
table["failures"] = (table.get("failures") or []) + failures
table["region_mode"] = "mainland_station"
return table
@@ -273,7 +297,7 @@ async def _ensure_rainfall_table(stored_path: str, fallback_epsg: int | None) ->
if center is None:
return
try:
table = await asyncio.to_thread(_build_rainfall_sync, *center)
table = await asyncio.to_thread(_build_rainfall_sync, *center, stored_path)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(table, ensure_ascii=False, indent=1), encoding="utf-8")
logger.info("확률강우량표 저장: %s (값 %d개)", path, len(table.get("values") or []))
@@ -307,7 +331,7 @@ async def get_drainage_rainfall(
content={"status": "error", "message": "계획 노선 파일이 업로드되지 않았습니다."},
)
try:
table = await asyncio.to_thread(_build_rainfall_sync, *center)
table = await asyncio.to_thread(_build_rainfall_sync, *center, stored_path)
except Exception as exc: # noqa: BLE001
return JSONResponse(
status_code=502,