"""횡단도 아래 수량 산출표 — **저장된 횡단 설계에서 칸을 채운다**. 왜 필요한가 (법정 요구) 별표2 Ⅰ.1.나.(5) 가 횡단면도에 「지반고·계획고·절토고·성토고·**단면적·지장목 제거· 측구터파기 단면적·사면보호공**」 여덟을 요구한다. 지금 나가는 것은 **앞 넷뿐**이고 뒤 넷이 빈칸으로 나갔다. 그 구멍을 메우는 자리다. ⚠ 빈칸의 까닭은 「값이 없어서」가 아니었다 표가 `source["quantities"]` 를 보는데 **그 키가 원본 파일에 아예 없다**(실측: `cross_00960m.json` 에 `samples`·`center_z`·`frame` 뿐). 반면 **저장된 횡단 설계에는 단면적이 그대로 있고**(`cut_soil_area_m2` 등), **사면길이도 그 설계선에서 유도된다** (`B08_Quantity_Engine_SlopeLength.station_slope`). 즉 **통로만 없었다.** ⚠ 계산을 새로 짜지 않는다 (CLAUDE.md 5장) 단면적은 B06 이 낸 저장값을 **그대로 읽고**, 사면 계열은 **B08 이 쓰는 그 함수**를 부른다. 계열 이름과 「어느 면을 쓰나」도 `B08_Quantity_Engine_SlopeArea` 의 정의를 빌려 쓴다 — 거기서 밑수가 바뀌면 이 표도 같이 움직여야 하기 때문이다. ⚠ 사면 계열 칸의 **단위** B08 은 측점 사이를 평균단면적법으로 적분해 **면적(㎡)** 을 내지만, 그것은 두 측점이 있어야 나오는 값이라 **한 장짜리 횡단도에는 못 쓴다.** 횡단도 칸에 들어가는 것은 그 측점의 **사면길이(m)** 이고, 이는 **1m 폭 조각의 면적(㎡/m)과 수치가 같다.** ⇒ 표에 「m 로 볼 것인가 ㎡/m 로 볼 것인가」는 표기 문제이고 **값은 하나다.** 아직 안 채우는 칸 — 근거가 없다(임의로 넣지 않는다) · 측구 토사/암석 — 저장값이 `ditch_area_m2` **한 값뿐**이라 토사·암석으로 못 가른다 · 표토제거 성토/절토 — 법은 「전량 제거」인데 **두께 칸이 없어** 물량이 안 선다 · 편책 — 별도 일위대가를 만들어 잇기로 확정(2026-09-09 ⑧-4). 밑수는 그때 붙는다 · 제근 — 입목 본수를 안 든다 · 노면다짐 — 밑수(노면 폭)는 있으나 이 표의 다른 칸과 축이 달라 뒤로 미룸 """ from __future__ import annotations from typing import Any from B08_Quantity.B08_Quantity_Engine_SlopeArea import PROTECTION_SOURCE, _key, _length_of from B08_Quantity.B08_Quantity_Engine_SlopeLength import station_slope #: 저장된 설계 단면적을 그대로 옮기는 칸 — `표 키 → 설계 키`. AREA_KEYS: tuple[tuple[str, str], ...] = ( ("cut_soil", "cut_soil_area_m2"), ("cut_rock", "cut_rock_area_m2"), ("embankment", "fill_area_m2"), ) #: 사면 계열에서 오는 칸 — `표 키 → (B08 계열, 면)`. #: 성토파종·절토살포는 **법면보호공**이고, 그것은 B08 에서 면고르기를 참조한다 #: (`PROTECTION_SOURCE`). 참조를 끊으면 그쪽 한 곳만 고치면 이 표도 따라온다. SLOPE_KEYS: tuple[tuple[str, str, str], ...] = ( ("benching", "bench_cut", "fill"), ("grading_fill", "face_dressing", "fill"), ("grading_cut", "face_dressing", "cut"), ("tree_removal_fill", "tree_removal", "fill"), ("tree_removal_cut", "tree_removal", "cut"), ("fill_seeding", PROTECTION_SOURCE, "fill"), ("cut_spraying", PROTECTION_SOURCE, "cut"), ) def _num(value: Any) -> float | None: return float(value) if isinstance(value, (int, float)) else None def derived_cells(chainage_m: float, design: dict[str, Any] | None) -> dict[str, float | None]: """저장된 횡단 설계 하나에서 **채울 수 있는 칸**만 낸다. 설계가 없거나 설계선이 없으면 **빈 dict** 를 낸다 — 0 으로 때우지 않는다. 도면에 0 이 찍히면 「없다」와 「안 쟀다」를 구별할 수 없다. """ if not isinstance(design, dict) or not design: return {} cells: dict[str, float | None] = {} for table_key, design_key in AREA_KEYS: value = _num(design.get(design_key)) if value is not None: cells[table_key] = value # 사면길이는 설계선에서 유도한다 — 설계선이 없으면 유도할 것이 없다. if not design.get("design_line"): return cells slope = station_slope(float(chainage_m), design) lengths = { _key(series, face): _length_of(slope, series, face) for _table_key, series, face in SLOPE_KEYS } for table_key, series, face in SLOPE_KEYS: cells[table_key] = lengths[_key(series, face)] return cells