"""B08 4탭 홍수량 계산서 — 기본값 · 선택지 · B05 연결값 한 곳 (PLAN 62-3 · 73-11 · 73-12 · 96). 표준값(유출계수 · 할증 · 재현기간 · 최소 도달시간)은 B05 배수 설정(config DRAINAGE_*)과 같은 값. 측점 고유 값은 B05 배수유역 저장본의 연결값(linked · 96-1) · 하상경사 · 관 종류 · n 은 안 이음. 근거 후보: docs/raw/조사/2026-10-06_62-1_홍수량_방법.md (별표2 2.0배 · KDS 표 2.7-1 · 실무 견본). """ from __future__ import annotations from typing import Any from common_util.common_util_drainage_pipes import carry_indices from config.config_system import FORD_MANNING_N, FORD_SLOPE from config.config_system_design import ( DRAINAGE_DESIGN_FLOW_FACTOR, DRAINAGE_DESIGN_RETURN_PERIOD_YR, DRAINAGE_RUNOFF_COEFFICIENT, DRAINAGE_TC_MIN_MINUTES, ) #: 할증(Qd = Q × 할증) — 2.0 별표2 법정 · 1.5 견본(울진 2공구) · 1.2 구기준 FACTORS = (2.0, 1.5, 1.2) #: 단면 가정 — 만관 · 240° 유효(수심 0.75D) · 유효 70 %(배관 제안 방식) SECTIONS = {"full": "만관", "arc240": "240° 유효단면", "eff70": "유효 70 %"} #: 관 종류별 조도계수 n — 파형강관은 KDS 표에 없음(지금 배관 제안 0.024) PIPE_KINDS = {"파형강관": 0.024, "흄관": 0.013, "콘크리트관": 0.015} #: 처음 값 = 표준 값만(69-6) · 측점 고유 값(유역면적 · L · H · D · S)은 None(빈칸) #: R24 · 지역은 공사지 자료(B08_Quantity_Hydro_Site) — 자료가 없을 때만 화면 입력 DEFAULTS: dict[str, Any] = { "factor": DRAINAGE_DESIGN_FLOW_FACTOR, "runoff": DRAINAGE_RUNOFF_COEFFICIENT, # 유출계수 f — KDS 가파른 산지 "area_km2": None, "length_m": None, # 산정에서 하천시점까지 거리 "height_m": None, # 산정에서 하천시점까지 표고차 "tc_min_minutes": DRAINAGE_TC_MIN_MINUTES, # 최소 도달시간(KDS 2.4) "r24_mm": None, "region": "", "return_period": DRAINAGE_DESIGN_RETURN_PERIOD_YR, "pipe_kind": "파형강관", "n": None, # None = 관 종류 값 "diameter_mm": None, "slope_denominator": None, # 하상경사 S = 1 / 이 값 "section": "full", } #: 시설 → 계산서 장(73-11 · 73-12) — 박스 = 사각 단면 · 세월교 · 물넘이 = 월류 높이 · 그 밖 = 관 SHEETS = { "box_culvert": ("box", "박스암거"), "ford_bridge": ("ford", "세월교"), "ford_pavement": ("ford", "물넘이포장"), } #: 장별 처음 값 — 박스 수심비 0.8H · n 0.013(울진 산출서 · 조사 69-7) · 내공 · 경사는 빈칸 #: 세월교 · 물넘이 월류 폭 10 m(사용자 10-7) · n · 경사 = B05 세월교 높이 계산과 같은 값 SHEET_DEFAULTS: dict[str, dict[str, Any]] = { "box": {"n": 0.013, "depth_ratio": 0.8, "box_width_m": None, "box_height_m": None}, "ford": { "n": FORD_MANNING_N, "overflow_width_m": 10.0, "slope_denominator": round(1 / FORD_SLOPE, 6), }, } def _positive(value: Any) -> float | None: """양수만 — 글 '1000' 도 숫자로 · 0 · 빈 값 · 글자는 None(빈칸).""" try: number = float(value) except (TypeError, ValueError): return None return number if number > 0 else None def _linked(sheet: str, options: dict[str, Any], basin: dict[str, Any] | None) -> dict[str, Any]: """B05 값 → 계산서 칸(96-1) — 값이 있는 칸만 · A · L · H 는 유역 · 치수는 시설 옵션.""" values: dict[str, Any] = {} if basin is not None: area_m2 = _positive(basin.get("area_m2")) values["area_km2"] = area_m2 / 1e6 if area_m2 else None values["length_m"] = _positive(basin.get("flow_length_m")) values["height_m"] = _positive(basin.get("relief_m")) if sheet == "pipe": values["diameter_mm"] = _positive(options.get("pipe_diameter_mm")) elif sheet == "box": values["box_width_m"] = _positive(options.get("body_width_m")) values["box_height_m"] = _positive(options.get("body_height_m")) else: values["overflow_width_m"] = _positive(options.get("ford_width_m")) return {k: v for k, v in values.items() if v is not None} def station_rows( points: list[Any], basins: list[dict[str, Any]] | None = None ) -> list[dict[str, Any]]: """관 지점 → 보조 탭 줄(측점 차례) · 장 종류(pipe · box · ford). basins(B05 세부유역 속성) 를 주면 측점마다 연결값 `linked` · 비교용 `b05`(Qd · T) — 측점 짝은 `carry_indices`(±0.05 m) · 안 주면 둘 다 None. """ points = sorted(points, key=lambda p: p.chainage_m) picks = ( carry_indices([p.chainage_m for p in points], [float(b["chainage_m"]) for b in basins]) if basins is not None else [None] * len(points) ) rows = [] for p, pick in zip(points, picks): sheet, kind = SHEETS.get(p.facility, ("pipe", "")) row = {"chainage_m": p.chainage_m, "facility": p.facility, "kind": kind, "sheet": sheet} row["linked"] = row["b05"] = None if basins is not None: basin = basins[pick] if pick is not None else None row["linked"] = _linked(sheet, getattr(p, "options", None) or {}, basin) if basin is not None: row["b05"] = { "design_flow_m3s": basin.get("design_flow_m3s"), "tc_minutes": basin.get("tc_minutes"), } rows.append(row) return rows def options() -> dict[str, Any]: """화면 선택지 — 기본값 · 할증 · 단면 가정 · 관 종류 n.""" return { "defaults": DEFAULTS, "factors": list(FACTORS), "sections": SECTIONS, "pipe_kinds": PIPE_KINDS, "sheet_defaults": SHEET_DEFAULTS, }