From c39212d7b5a2943cb5a3ffb93cd339dea2a97e47 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 19:23:36 +0900 Subject: [PATCH] =?UTF-8?q?feat(B07):=20=ED=9A=A1=EB=8B=A8=EB=8F=84=20?= =?UTF-8?q?=EC=88=98=EB=9F=89=ED=91=9C=204=EC=B9=B8=20=E2=86=92=2014?= =?UTF-8?q?=EC=B9=B8=20=E2=80=94=20=EC=A0=80=EC=9E=A5=EB=90=9C=20=EC=84=A4?= =?UTF-8?q?=EA=B3=84=EC=97=90=EC=84=9C=20=EC=B1=84=EC=9A=B0=EB=8A=94=20?= =?UTF-8?q?=ED=86=B5=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 별표2 Ⅰ.1.나.(5) 가 요구하는 여덟(지반고·계획고·절토고·성토고·단면적·지장목 제거· 측구터파기 단면적·사면보호공) 중 뒤 넷이 빈칸으로 나가던 자리. 빈칸의 까닭은 「값이 없어서」가 아니라 **통로가 없어서**였음 — 표가 `source["quantities"]` 를 보는데 그 키가 원본 파일에 아예 없음(실측: 횡단 원본 15키에 `samples`·`center_z`· `chainage_m` 은 있고 `quantities` 는 없음). 반면 저장된 설계에는 단면적이 그대로 있고 사면길이도 설계선에서 유도됨. ⚠ 계산을 새로 짜지 않음 — 단면적은 B06 저장값을 그대로 읽고, 사면 계열은 B08 이 쓰는 `station_slope` 를 그대로 부름. 계열 이름·어느 면을 쓰나도 `SlopeArea` 정의를 빌림 (거기서 밑수가 바뀌면 이 표도 같이 움직여야 함). 채운 열 칸 단면적 3 깍기 토사 · 깍기 암석 · 쌓기 사면 7 층따기 · 면고르기(성·절) · 지장목제거(성·절) · 성토파종 · 절토살포 ⚠ 사면 칸의 단위 — B08 은 측점 사이를 적분해 ㎡ 를 내나 그것은 두 측점이 있어야 나옴. 한 장짜리 횡단도에 들어가는 것은 그 측점의 **사면길이(m)** 이고 1m 폭 조각의 ㎡/m 와 수치가 같음. 표기를 m 로 볼지 ㎡/m 로 볼지는 표기 문제이고 값은 하나임. 안 채운 칸 — 근거가 없어 임의로 안 넣음 측구 토사/암석(저장값이 `ditch_area_m2` 한 값뿐) · 표토제거(두께 칸 없음) · 편책(별도 일위대가 제작 뒤 연결로 확정) · 제근(입목 본수 안 듦) · 노면다짐 자체검증 — 새 시험 8건(사면길이는 손으로 잰 √5 · √(2.4²+2²) 와 대조, `is not None` 만 보면 0 이어도 통과하므로 실수치로 맞춤), 회귀 570 통과 · 0 실패. 실프로젝트 936be972 세 측점에서 **21칸 중 14칸**에 값이 찍힘(앞서 4칸). Co-Authored-By: Claude Opus 5 (1M context) --- .../B07_DesignDetail_Engine_Cross_Quantity.py | 91 +++++++++++++++++++ .../B07_DesignDetail_Router_Support.py | 8 ++ 2 files changed, 99 insertions(+) create mode 100644 B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py new file mode 100644 index 00000000..5306f1ee --- /dev/null +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cross_Quantity.py @@ -0,0 +1,91 @@ +"""횡단도 아래 수량 산출표 — **저장된 횡단 설계에서 칸을 채운다**. + +왜 필요한가 (법정 요구) + 별표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 diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support.py b/B07_DesignDetail/B07_DesignDetail_Router_Support.py index ef7bfcc3..b2b9488c 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support.py @@ -54,6 +54,7 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Standard import ( from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Table import ( QUANTITY_VALUE_KEYS, ) +from B07_DesignDetail.B07_DesignDetail_Engine_Cross_Quantity import derived_cells from B07_DesignDetail.B07_DesignDetail_Engine_Template import add_title_fields # 유역도 배경·파일 입출력 조각은 700줄 제한으로 떼어냈다(2026-09-04). @@ -270,6 +271,11 @@ def _quantity_table( **계획고는 횡단 설계(design)에도 있다** — 장 배치 입력의 원본에는 그 값이 없어 계획고·절토고·성토고 세 칸이 통째로 비어 나갔다(2026-09-03 실측: 장 확정 시 21개 항목 중 지반고 하나만 채워짐). 원본에 없으면 설계에서 읽는다. + + ⚠ **본문 칸도 설계에서 온다**(2026-09-09) — `source["quantities"]` 키는 원본 파일에 + 아예 없어 열일곱 칸이 통째로 비어 나갔다. 채울 수 있는 것은 `_Engine_Cross_Quantity` + 가 낸다(단면적은 저장값, 사면 계열은 B08 이 쓰는 함수 그대로). 별표2 법정 요구 + 여덟 중 뒤 넷이 비던 자리다. """ def num(value: Any) -> float | None: @@ -289,6 +295,8 @@ def _quantity_table( "cut": cut, "fill": fill, } + chainage = num(source.get("chainage_m")) or 0.0 + table.update(derived_cells(chainage, design)) for key in QUANTITY_VALUE_KEYS: table.setdefault(key, num(quantities.get(key))) return table