diff --git a/A00_Common/design/logic/design_calc_registry.ts b/A00_Common/design/logic/design_calc_registry.ts index 5e7877080..be41eea05 100644 --- a/A00_Common/design/logic/design_calc_registry.ts +++ b/A00_Common/design/logic/design_calc_registry.ts @@ -18,7 +18,6 @@ * ts — TS 셈 자리 `<경로>#<함수>` · 없으면 null * runner — 실행기: `node:`(서버 Node 입구) · `py:<경로>#<함수>`(지금 파이썬) · * `browser:<경로>#<함수>`(browser_only 만) - * until — "472-4" = 파이썬 실행기는 472-4 짝 모으기 동안만(그 뒤 node:) * pair — 파이썬 짝 `<경로>#<함수>` · 짝 아니면 null * mirror — 거울 · 단위 시험(`resources/tester` 파일 이름 · .py 뺌) * @@ -41,7 +40,6 @@ export interface CalcEntry { stage: StageName | null; ts: string | null; runner: string; - until?: "472-4"; pair: string | null; mirror: string[]; note?: string; @@ -208,10 +206,9 @@ export const CALC_REGISTRY: readonly CalcEntry[] = [ stage: "ground", ts: `${U}common_util_surface_sampler.ts#heightAt`, runner: `py:${U}common_util_surface_sampler.py#build_surface_sampler`, - until: "472-4", pair: `${U}common_util_surface_sampler.py#build_surface_sampler`, - mirror: ["test_344_1_surface_sampler_mirror"], - note: "X2 — 브라우저 격자 .bin · 서버 npz(값 협의)", + mirror: ["test_344_1_surface_sampler_mirror", "test_511_b2_stations_node"], + note: "서버 측점 생성(DTM) = Node `stations.sections` 격자 표본 · 파이썬 표본기 남김 = DTM 아닌 지표면 · 조작 중 「서버」 표본 창구 · B04 · B07 · 추천 · X2 = 브라우저 격자 .bin · 서버 npz(값 협의)", }, { id: "ground.extend", @@ -453,9 +450,9 @@ export const CALC_REGISTRY: readonly CalcEntry[] = [ stage: "cross", ts: `${U}common_util_structure_underdrain.ts#attachUnderdrainSpecs`, runner: "node:facility.attach", - pair: `${B06}B06_Section_Engine_Tilt.py#attach_underdrains`, + pair: `${B06}B06_Section_Engine_Tilt.py#attach_underdrain_specs`, mirror: ["test_472_4_underdrain_mirror"], - note: "얹기 = GET 상세 Node facility.attach 안(511-B B-4) · 알려진 차 = 옛 underdrain 걷음 TS 만 · 기울임은 cross.design 안", + note: "얹기 = GET 상세 Node facility.attach 안(511-B B-4) · 파이썬 짝 = Node 못 부를 때(`_attach_python`) · 알려진 차 = 옛 underdrain 걷음 TS 만 · 기울임은 cross.design 안", }, { id: "cross.fit", diff --git a/A00_Common/design/logic/design_calc_registry_facility.ts b/A00_Common/design/logic/design_calc_registry_facility.ts index fc0d8bba5..7415c29cc 100644 --- a/A00_Common/design/logic/design_calc_registry_facility.ts +++ b/A00_Common/design/logic/design_calc_registry_facility.ts @@ -141,9 +141,9 @@ export const FACILITY_ROWS: readonly CalcEntry[] = [ stage: null, ts: `${U}common_util_excavation.ts#wallTrenchAreaM2`, runner: "node:excavation.trench", - pair: `${U}common_util_excavation.py#wall_trench_area_m2`, + pair: null, mirror: ["test_excavation_mirror"], - note: "파이썬 짝 운영 부름 0 — 걷음 후보(472-7)", + note: "서버 = Node 한 벌 · 파이썬 짝 운영 부름 0 → 걷음(511-B B-6)", }, { id: "facility.ford", diff --git a/B05_Profile/B05_Profile_Engine_Grade.py b/B05_Profile/B05_Profile_Engine_Grade.py index 706bc2635..21f4d1fda 100644 --- a/B05_Profile/B05_Profile_Engine_Grade.py +++ b/B05_Profile/B05_Profile_Engine_Grade.py @@ -9,6 +9,8 @@ 절·성토 균형은 계획고와 지반고 차이의 적분(A안)으로 판정한다. 횡단 설계가 나온 뒤 실제 단면적 기반 계획선을 추가할 수 있도록 결과는 배열의 한 원소로 반환한다. +남는 까닭(511-B B-6) — 기준표 · 설계 속도 = 서버 종단 입력(페이지 설정 · 재생성 옵션) · 추천 엔진 · +4순위 폴백(`design_grade_line`). """ import math diff --git a/B05_Profile/B05_Profile_Engine_Grade_Alignment.py b/B05_Profile/B05_Profile_Engine_Grade_Alignment.py index 3744807b0..3cb9cfa7f 100644 --- a/B05_Profile/B05_Profile_Engine_Grade_Alignment.py +++ b/B05_Profile/B05_Profile_Engine_Grade_Alignment.py @@ -18,6 +18,8 @@ scipy에 의존하지 않는 순수 기하 계산만 두어 프론트엔드 구 (거울 시험 `test_426_profile_straight.py`). 경고 글 짝: `build_alignment` warnings ↔ TS `buildAlignment` warnings — 같은 목록 (거울 시험 `test_472_save_warnings.py`). +남는 까닭(511-B B-6) — 계획선 짓기 정본 = Node `profile.alignment` · 이 짝 = 2~4 순위 폴백 · 추천 +엔진(`pass_through`). """ from dataclasses import dataclass @@ -187,11 +189,6 @@ class AlignmentPolicy: } -def tangent_elevation(pvi_s: np.ndarray, pvi_z: np.ndarray, targets: np.ndarray) -> np.ndarray: - """종단곡선을 빼고 변화점 직선만으로 본 표고(탄젠트 표고).""" - return np.interp(targets, pvi_s, pvi_z) - - #: 반올림만 다른 같은 측점(m) — 관 측점 0.001m(자동설계) ↔ 0.01m(화면 저장) 차이. #: 화면 `SAME_STATION_SNAP_M`(B05_Profile_UI_Profile_Alignment.ts) 와 짝. SAME_STATION_SNAP_M = 0.006 diff --git a/B05_Profile/B05_Profile_Engine_Grade_Carry.py b/B05_Profile/B05_Profile_Engine_Grade_Carry.py index 9d0690dd5..5eb8d852c 100644 --- a/B05_Profile/B05_Profile_Engine_Grade_Carry.py +++ b/B05_Profile/B05_Profile_Engine_Grade_Carry.py @@ -8,7 +8,7 @@ 「노선 그대로?」 · 범위. - 같은 자리(`line_matcher`) = 새 선 점을 옛 선에 투영 · 옆 `SAME_LINE_TOL_M` 안 · 방향 30° 안 (측점 스냅이 뒤 교점을 밀어도 이어받음). - - 자동 꼭짓점(`carry_base`) · 편집(`carry_edits`)은 **새 측점 위에** 다시 놓음 — 같은 자리면 + - 자동 꼭짓점(`carry_base`) · 편집(Node `carry_seat`)은 **새 측점 위에** 다시 놓음 — 같은 자리면 옛 선 그 자리 높이 · 아니면 새 지반 첫 계획선 · 편집은 옛 사용자 꼭짓점이 닿은 자리만 · R 은 가까운 측점. 편집 셈 = TS `carryEdits`(서버 Node · 511-B B-3) — 파이썬 짝 없음. @@ -142,27 +142,6 @@ def carry_base( return out -def carry_edits( - old_xy: list[tuple[float, float]], - new_xy: list[tuple[float, float]], - old_base: list[dict[str, float]], - edits: dict[str, Any] | None, - stations: list[float], - half_m: float, -) -> dict[str, dict[str, float]]: - """새 편집 — 새 측점마다 같은 자리 · 옛 사용자 꼭짓점이 닿은 자리면 옛 계획선 − 옛 자동선. - - 닿은 자리 = 꼭짓점 위(그 꼭짓점이 사용자) · 앞뒤 꼭짓점 하나가 사용자. 종단곡선 R = 옛 키에서 - `half_m` 안 가장 가까운 같은 자리 측점. 셈 = TS `carryEdits` 한 벌(서버 Node `profile.carry` · - 511-B B-3) — 노선 [저장]은 꼭짓점 세움과 한 부름(`carry_seat`). - """ - from B05_Profile.B05_Profile_Engine_Grade_Node import carry_seat - - if not old_base: - return {"station_offsets": {}, "curve_radii": {}} - return carry_seat(old_xy, new_xy, old_base, edits, stations, half_m)[0] - - def carry_policy( old_xy: list[tuple[float, float]], new_xy: list[tuple[float, float]], diff --git a/B05_Profile/B05_Profile_Engine_Grade_CurveLaw.py b/B05_Profile/B05_Profile_Engine_Grade_CurveLaw.py index 0090a970f..83dc7196f 100644 --- a/B05_Profile/B05_Profile_Engine_Grade_CurveLaw.py +++ b/B05_Profile/B05_Profile_Engine_Grade_CurveLaw.py @@ -8,6 +8,8 @@ 경고만 · 비포장 대수차 5 % 이하(생략 가능)는 뺌 · 45 % 규칙으로 줄어든 곡선도 같은 잣대. 짝: TS `logic/B05_Profile_Profile_CurveLaw.ts` — 거울 시험 `test_432_1_curve_law.py`. +남는 까닭(511-B B-6) — 종단곡선 법칙 정본 = Node `profile.alignment` 안 · 이 짝 = 추천 엔진 첫 +계획선 · 2~4 순위 폴백. """ from typing import Any diff --git a/B05_Profile/B05_Profile_Engine_Grade_Profile.py b/B05_Profile/B05_Profile_Engine_Grade_Profile.py index ff6b4f6a6..2d481033b 100644 --- a/B05_Profile/B05_Profile_Engine_Grade_Profile.py +++ b/B05_Profile/B05_Profile_Engine_Grade_Profile.py @@ -24,6 +24,8 @@ `B05_Profile/logic/B05_Profile_Grade_Ground.ts` `groundFollowingBasePvi` · 노선 바뀜 때 계획고 옮김은 `B05_Profile_Engine_Grade_Carry.py` ↔ `logic/B05_Profile_Grade_Carry.ts` · 거울 시험 = `resources/tester/test_344_2_route_pairs.py`. +남는 까닭(511-B B-6) — 1순위 첫 계획선 · 종단 다시 셈 = Node(`Engine_Grade_Node`) · 이 짝 = 2~4 순위 +폴백 · 추천 엔진 · 정본 꼴 입력. """ from collections import Counter diff --git a/B05_Profile/B05_Profile_Route_NodeEdits.py b/B05_Profile/B05_Profile_Route_NodeEdits.py index 56f658e2a..5664ef935 100644 --- a/B05_Profile/B05_Profile_Route_NodeEdits.py +++ b/B05_Profile/B05_Profile_Route_NodeEdits.py @@ -6,6 +6,8 @@ 곡선표 `curves` 는 **그린** R(접선 자리에 눌린 값)만 있어, 그것으로 다시 열면 눌린 R 이 넣은 값으로 굳고 R · 길이 고정이 [저장] 뒤 사라짐 — 넣은 값을 따로 둠. 자리 = 저장 노드 (앞 노드에서 0.5 m 안인 점은 `build_planned_polyline` 과 같이 거름). +남는 까닭(511-B B-6) — 노선 저장 편집표 = Node `route.polyline` · 이 짝 = 단순화 갈래 · Node 못 돌림 +때. """ from __future__ import annotations diff --git a/B05_Profile/B05_Profile_Route_Save_Parts.py b/B05_Profile/B05_Profile_Route_Save_Parts.py index 14ab36cc9..18736c8d5 100644 --- a/B05_Profile/B05_Profile_Route_Save_Parts.py +++ b/B05_Profile/B05_Profile_Route_Save_Parts.py @@ -267,23 +267,3 @@ def route_moved_stations( for station in stations if station.get("structure") and match(float(station["chainage_m"])) is None ) - - -def seat_route_moved( - alignment: dict[str, Any], born: Sequence[float], held: Sequence[float] = () -) -> list[dict[str, Any]]: - """옮긴 시설 측점(`born`)에 427 규칙으로 꼭짓점을 다시 세움(459 · 요구 270) — 그 자리 - 옛 꼭짓점(옮겨 붙은 지반 · 옛 높이)을 걷고 원지반 지남 · 이웃 1 mm 맞춤 · 균형 종단이면 - 옮기기 전 계획선 높이(`_seat_born_nodes`). `alignment` = 새 지반 표본 · 정책 · 옮겨 붙인 - `base_pvi` · `held` = 높이 고친 측점. TS 짝 = `seatRouteMoved`.""" - from B05_Profile.B05_Profile_Router_Confirm import _SAME_STATION_M, _seat_born_nodes - - base = alignment.get("base_pvi") or [] - if not born: - return base - kept = [ - node - for node in base - if all(abs(float(node["chainage_m"]) - value) >= _SAME_STATION_M for value in born) - ] - return _seat_born_nodes(alignment, kept, list(born), list(held)) diff --git a/B05_Profile/logic/B05_Profile_Grade_Carry.ts b/B05_Profile/logic/B05_Profile_Grade_Carry.ts index cb8f6a18b..2c0a7bc4b 100644 --- a/B05_Profile/logic/B05_Profile_Grade_Carry.ts +++ b/B05_Profile/logic/B05_Profile_Grade_Carry.ts @@ -182,8 +182,8 @@ export function carryBase( /** * 새 편집 — 새 측점마다 같은 자리이고 옛 사용자 꼭짓점이 닿은 자리(꼭짓점 위 · 앞뒤 꼭짓점 하나가 사용자)면 - * 옛 계획선 높이 − 옛 자동선 높이 · 종단곡선 R = 옛 키에서 `halfM` 안 가장 가까운 같은 자리 측점(파이썬 - * `carry_edits` 짝). 새 자동 꼭짓점이 같은 자리에서 옛 자동선 높이(`carryBase`)라 계획고가 그대로. + * 옛 계획선 높이 − 옛 자동선 높이 · 종단곡선 R = 옛 키에서 `halfM` 안 가장 가까운 같은 자리 측점(서버 = + * Node `profile.carry` · 파이썬 짝 없음). 새 자동 꼭짓점이 같은 자리에서 옛 자동선 높이(`carryBase`)라 계획고가 그대로. */ export function carryEdits( oldXy: ReadonlyArray, diff --git a/B05_Profile/logic/B05_Profile_Pipeline_Grade.ts b/B05_Profile/logic/B05_Profile_Pipeline_Grade.ts index 7d5c912f0..be68ffe06 100644 --- a/B05_Profile/logic/B05_Profile_Pipeline_Grade.ts +++ b/B05_Profile/logic/B05_Profile_Pipeline_Grade.ts @@ -9,7 +9,7 @@ * planline — 노선 초안이 없으면 저장 종단 기준 그대로 · 있으면 새 측점마다 같은 자리는 저장 꼭짓점 * 높이 · 바뀐 자리만 새 지반 첫 계획선(`Grade_Ground` · `carryBase`) · 옮긴 시설 측점(바뀐 자리 · * 사용자가 옮김)에 427 꼭짓점 다시 세움(459 · 짝 = 파이썬 `B05_Profile_Route_Save_Parts` - * `route_moved_stations` · `seat_route_moved` · 거울 시험 test_459_route_seat_mirror.py). + * `route_moved_stations` · 서버 세움 = Node `profile.carry` · 거울 시험 test_459_route_seat_mirror.py). * profile — `buildAlignment`(첫 계획선 + 계획고 편집) — 종단 보기가 쓰는 같은 함수. * * 화면 · fetch · 세션 없음(순수) — 저장 기준 · 저장 노선은 부르는 쪽이 넣음. @@ -288,7 +288,7 @@ export function routeMovedStations( } /** 옮긴 시설 측점(`born`)에 427 꼭짓점을 다시 세움 — 그 자리 꼭짓점을 걷고 `seatBornNodes`(원지반 지남 · - * 이웃 1 mm 맞춤 · 균형 종단이면 옮기기 전 계획선 높이). 파이썬 `seat_route_moved` 짝. */ + * 이웃 1 mm 맞춤 · 균형 종단이면 옮기기 전 계획선 높이). 서버 = Node `profile.carry` 의 seat(거울 기준 = 시험 헬퍼 `seat_route_moved`). */ export function seatRouteMoved( base: Pick, born: ReadonlyArray, diff --git a/B06_Section/B06_Section_Engine_Areas.py b/B06_Section/B06_Section_Engine_Areas.py index 62220348c..1ac554e12 100644 --- a/B06_Section/B06_Section_Engine_Areas.py +++ b/B06_Section/B06_Section_Engine_Areas.py @@ -8,6 +8,8 @@ `_Engine_Design.py`가 700줄을 넘겨, 「설계선을 어떻게 세우나」(그쪽)와 「그 선과 지반 사이 넓이를 어떻게 재나」(여기)로 갈랐다. 두 함수 모두 지반선과 설계선의 **차이 배열**만 받으므로 설계 로직을 전혀 모른다 — 그래서 따로 떼어 검산하기도 쉽다. +남는 까닭(511-B B-6) — `compute_cross_design` 안 몫 — 그 짝과 같은 까닭(B07 · 추천 · DTM 아닌 지표면 +· Node 못 부를 때). """ diff --git a/B06_Section/B06_Section_Engine_Culvert.py b/B06_Section/B06_Section_Engine_Culvert.py index 47b03b31a..bd0611827 100644 --- a/B06_Section/B06_Section_Engine_Culvert.py +++ b/B06_Section/B06_Section_Engine_Culvert.py @@ -18,6 +18,7 @@ B05 배수유역도가 찍은 관 지점(`pipe_points.json` = 배관 정본)을 ⚠ 아래 세 상수는 **임도 확정값이 아니다**. 지식DB에 임도 기준이 없어 교차 참조로 채택했고(2026-08-19 사용자 확정), 임도 기준이 확보되면 그 값으로 교체한다. +남는 까닭(511-B B-6) — B04 · B07 · B08 파이썬 소비자(관 벽 높이 · 세트). """ from __future__ import annotations diff --git a/B06_Section/B06_Section_Engine_Design.py b/B06_Section/B06_Section_Engine_Design.py index 9a25e6a31..e4d89bc54 100644 --- a/B06_Section/B06_Section_Engine_Design.py +++ b/B06_Section/B06_Section_Engine_Design.py @@ -25,6 +25,8 @@ 좌표 규약(generate_sections cad_exchange 준수): offset_m 양수=좌, 음수=우. 경사비는 수평:수직 = ratio:1 (예: 1:1.2 → ratio=1.2). +남는 까닭(511-B B-6) — [저장] · [확정] · 기본 설계 = Node `cross.design` · 이 짝 = B07 · 추천 엔진 +되풀이 · DTM 아닌 지표면 · Node 못 부를 때. """ from typing import Any diff --git a/B06_Section/B06_Section_Engine_SampleExtend.py b/B06_Section/B06_Section_Engine_SampleExtend.py index 4b69f70ce..66a02fe02 100644 --- a/B06_Section/B06_Section_Engine_SampleExtend.py +++ b/B06_Section/B06_Section_Engine_SampleExtend.py @@ -9,6 +9,8 @@ ⚠ 계산식은 안 바뀐다 — 샘플(입력)만 넓어진다. 넓힌 샘플은 횡단 파일에 남겨 화면(TS)·Node·B08 이 같은 지반을 본다(한 벌). ⚠ 파이썬·TS 짝 — `extend_unclosed` ↔ `B05_Profile_Sections_Ground.ts`(B05 logic) `extendUnclosed`. +남는 까닭(511-B B-6) — [저장] 넓힘 = DTM 이면 Node `cross.enforce` 안 · 이 짝 = DTM 아닌 지표면 · +Node 못 부를 때. """ from __future__ import annotations diff --git a/B06_Section/B06_Section_Engine_Structures_Wall.py b/B06_Section/B06_Section_Engine_Structures_Wall.py index 88607f211..a427db9dd 100644 --- a/B06_Section/B06_Section_Engine_Structures_Wall.py +++ b/B06_Section/B06_Section_Engine_Structures_Wall.py @@ -14,6 +14,7 @@ **짝**: `common_util/common_util_structure_walls.ts` — 브라우저는 아직 저장하지 않은 목록으로 같은 제원을 만들어야 해서 한 벌을 더 둔다. 거울 테스트 `tmp/tests/test_b06_structure_walls_mirror.py` 가 같은 값이 나오는지 대조한다. +남는 까닭(511-B B-6) — B07 단 추가(`tier_extras`) · 형상 얹기 · 소단 구간 Node 못 부를 때. """ import logging @@ -67,18 +68,6 @@ def tier_extras(type_id: str, options: dict[str, Any]) -> list[dict[str, Any]]: ] -def load_wall_structures(project_root: Path) -> list[dict[str, Any]]: - """구조물 정본에서 벽(표 형상 wall · 322-1) 목록을 읽는다. 실패하면 빈 목록. - - 소단은 형상 berm — 사면을 계단으로 끊는 시설이라 벽 제원 자리에 안 얹음(계획서 3-9). - """ - try: - return wall_specs_from(load_structures(str(project_root))[1]) - except Exception: # noqa: BLE001 — 정본을 못 읽어도 횡단 조회는 이어 간다 - logger.exception("B06 구조물 벽 정본을 읽지 못했습니다 (없는 것으로 본다)") - return [] - - def wall_specs_from(structures: list[Any]) -> list[dict[str, Any]]: """구조물 목록 → 벽 제원(TS `wallSpecsFrom` 짝 · 형상 얹기 한 벌 323-4).""" types = structure_type_map() @@ -185,19 +174,6 @@ def berm_span_at( return None -def attach_wall_structures(project_root: Path, cross_sections: list[dict[str, Any]]) -> int: - """구간 안 측점의 횡단 dict에 `revetment` 키를 얹는다. 얹은 개수를 돌려준다. - - 이미 관 정본이 얹은 세트(`culvert`·`revetment`)가 있는 측점은 **건드리지 않는다** — - 관 유입·유출 벽과 구조물 벽이 한 자리에 겹치면 어느 쪽 그림인지 읽히지 않는다. - 한 측점에 여러 개가 겹치면 먼저 시작한 것을 쓴다(겹침 정리는 사용자 몫). - """ - attached = attach_wall_specs(cross_sections, load_wall_structures(project_root)) - if attached: - logger.info("B06 구조물 벽 %d개 측점에 얹음", attached) - return attached - - def attach_wall_specs(cross_sections: list[dict[str, Any]], walls: list[dict[str, Any]]) -> int: """구간 안 측점에 벽 제원을 얹는다(TS `attachWallSpecs` 짝). 얹은 개수를 돌려준다.""" if not walls: diff --git a/B06_Section/B06_Section_Engine_Tilt.py b/B06_Section/B06_Section_Engine_Tilt.py index 9e78a1546..da897bb6e 100644 --- a/B06_Section/B06_Section_Engine_Tilt.py +++ b/B06_Section/B06_Section_Engine_Tilt.py @@ -9,6 +9,8 @@ tilt_pct=…)`). 경사 = 물넘이 경사각도(%) · 맹암거 = 표준 횡단 판정 폭 = 포장 범위와 같은 0.02 m(`paved_at`). 짝(349-0n): `tilt_ranges` · `tilt_args` ↔ `B06_Section/logic/B06_Section_Cross_Enforce.ts` (거울 시험 `test_349_0n_cross_enforce_mirror.py`). +남는 까닭(511-B B-6) — 얹기 = GET 형상 Node `facility.attach` · 이 짝 = Node 못 부를 +때(`_attach_python`) · 파이썬 횡단 설계 폴백 기울기. """ from __future__ import annotations @@ -92,11 +94,6 @@ def tilt_args(chainage_m: float, ranges: list[TiltRange]) -> dict[str, Any]: return {"tilted": hit is not None, "tilt_pct": float(pct) if pct is not None else None} -def attach_underdrains(project_root: Path, cross_sections: list[dict[str, Any]]) -> int: - """맹암거 기준 측점의 횡단 dict 에 `underdrain` 을 얹는다. 얹은 개수를 돌려준다.""" - return attach_underdrain_specs(cross_sections, load_underdrains(project_root)) - - def attach_underdrain_specs( cross_sections: list[dict[str, Any]], drains: list[dict[str, Any]] ) -> int: diff --git a/B06_Section/B06_Section_Router_Design.py b/B06_Section/B06_Section_Router_Design.py index 43f34f940..19096659e 100644 --- a/B06_Section/B06_Section_Router_Design.py +++ b/B06_Section/B06_Section_Router_Design.py @@ -1,6 +1,8 @@ """B06 라우터의 횡단 설계 파일 읽기와 프리뷰 계산. 포장 · 세월교 구간 읽기 · 판정(짝 349-0n) = `B06_Section_Router_Design_Ranges.py` · 다시 내보냄. +남는 까닭(511-B B-6) — [저장] 횡단 보정 = DTM 이면 Node `cross.enforce` · 이 파이썬 길(`enforce_*`) += DTM 아닌 지표면 · Node 못 부를 때 · 확정 승계 칸 읽기는 본 길. """ import json diff --git a/B06_Section/B06_Section_Router_Design_Ranges.py b/B06_Section/B06_Section_Router_Design_Ranges.py index 9f2d7f426..d8b590504 100644 --- a/B06_Section/B06_Section_Router_Design_Ranges.py +++ b/B06_Section/B06_Section_Router_Design_Ranges.py @@ -3,6 +3,8 @@ 짝(349-0n): `pavement_ranges` · `ford_surface_drops` · `paved_at` · `ford_drop_at` ↔ `B06_Section/logic/B06_Section_Cross_Enforce.ts` · 거울 시험 `test_349_0n_cross_enforce_mirror.py`. 부르는 쪽은 `B06_Section_Router_Design` 이름으로 씀(다시 내보냄 · 시험이 그 이름을 갈아 끼움). +남는 까닭(511-B B-6) — 포장 제안 · 세월교 하강 = B05 확정 본 길 · 구간 = 기본 설계 Node 못 부를 +때(`_python_facility`). """ import logging diff --git a/common_util/common_util_cross_berm.py b/common_util/common_util_cross_berm.py index d69e9ad4d..47dbabc59 100644 --- a/common_util/common_util_cross_berm.py +++ b/common_util/common_util_cross_berm.py @@ -20,6 +20,8 @@ 그래서 지식DB 에도 적지 않는다(사용자 지시). · 성토 소단(178-2) = 노면 끝 아래 깊이 `depth_m`(기본 1.0 · 사용자 값)에 **한 단만** · 0 미만은 0(도로보다 높을 수 없음). 간격은 성토에 안 씀 · 절토는 소단을 안 넣음. +남는 까닭(511-B B-6) — `compute_cross_design` 안 몫 — 그 짝과 같은 까닭(B07 · 추천 · DTM 아닌 지표면 +· Node 못 부를 때). """ import logging diff --git a/common_util/common_util_excavation.py b/common_util/common_util_excavation.py deleted file mode 100644 index 61cfffe2c..000000000 --- a/common_util/common_util_excavation.py +++ /dev/null @@ -1,126 +0,0 @@ -"""터파기 단면 — 관·벽 두 갈래. **근거의 급이 다르므로 갈라 둔다.** - -⚠⚠ TS 짝 파일과 **한 벌**이다 — `common_util/common_util_excavation.ts`. - 거울 시험: `resources/tester/test_excavation_mirror.py` (고칠 때 같이 돌릴 것). - 화면(B06 횡단도)이 그리고 서버(B08 수량)가 세는 값이라 두 쪽에 같은 수가 있어야 한다. - -① 관 — **법정 시방서에 표가 있다** - `KCS 44 40 10 배수관(23.01) §3.2(4) 그림 3.2-1`. - 「설계도서에 따로 표기되지 않았을 때에는 그림 3.2-1 터파기 기준 폭에 따라야 한다」 - ⇒ 기본값으로 쓰고 사용자가 덮어쓰는 구조가 원문과 맞다. - ⚠ **관경 + 2b 로 계산하지 않는다** — 표가 관 두께·작업여유를 이미 담고 있다 - (Ø800 은 800+2×300=1400 이 아니라 **1600**). - 여유폭 b 는 같은 그림의 값: 보통지반 0.30 m · 연약지반 0.50 m(한쪽). - -② 벽(돌쌓기·기슭막이) — **법정 근거 없음. 실무 정본 식이 있다** - 법령·교본은 「설계도서 지시 폭·기울기·깊이」까지만 정한다 - (지식DB `01_임도/02_상세설계/구조물/구조물_기초_토공.md` §1 — 전수 확인). - 실무 정본 `original/구조물도/기슭막이/04.구조도(기슭막이).xls` 7탭: - - 터파기 = 기초 0.5 × (0.7 + 0.2) × 1.0 = 0.45 (버림만 있는 탭은 0.1 × 0.7 = 0.07) - + 비탈 H × (평균두께 + 0.2) × 1.0 - - ⇒ **여유폭은 「+0.2」**(평균두께에 더함), **기초는 깊이 0.5 · 폭 0.9**. - ⇒ 정본 탭 제목이 **「기초유 / 기초버림」**이다 — 기초를 안 두는 것이 아니라 - **버림으로 얕게 두는 것**이라 뜻이 다르다. 그 축이 곧 **0.45 냐 0.07 이냐**를 가른다. - ⚠ **비탈을 주지 않는다 — 정본이 수직으로 센다.** 기울기 항이 식에 아예 없다. - 임의로 기울기를 주면 그림과 물량이 어긋난다. - -⚠ 값을 지어내지 않는다 — 여기 있는 수는 전부 위 두 출처의 것이고, 사용자가 덮어쓰면 - 그 값이 이긴다. 화면에는 **근거의 급**(법정 표 / 실무 식)을 노티스로 드러낸다. -""" - -from __future__ import annotations - -#: 관경(㎜) → 터파기 폭(㎜). `KCS 44 40 10` 그림 3.2-1 표 그대로. -PIPE_TRENCH_WIDTH_MM: dict[int, int] = { - 300: 700, - 350: 800, - 400: 900, - 450: 1000, - 500: 1100, - 600: 1200, - 700: 1400, - 800: 1600, - 900: 1700, - 1000: 1800, - 1100: 2000, - 1200: 2100, - 1300: 2200, - 1400: 2400, - 1500: 2500, -} - -#: 한쪽 여유폭(m) — 같은 그림. 표에 없는 관경을 사용자가 넣었을 때의 안내용이다. -PIPE_TRENCH_CLEARANCE_NORMAL_M = 0.30 -PIPE_TRENCH_CLEARANCE_SOFT_M = 0.50 - -#: 벽 터파기 — 실무 정본 식의 수(위 문서 참조). -WALL_TRENCH_CLEARANCE_M = 0.2 # 평균두께에 더하는 여유 -WALL_FOUNDATION_DEPTH_M = 0.5 # 기초 깊이 -WALL_FOUNDATION_WIDTH_M = 0.9 # 기초 폭 (0.7 + 0.2) -WALL_BLINDING_DEPTH_M = 0.1 # 기초 없이 버림만 있을 때의 깊이 -WALL_BLINDING_WIDTH_M = 0.7 # 그때의 폭 (여유폭 0.0 — 버림 탭에는 여유가 없다) - -#: 근거의 급 — 화면 노티스가 이 값을 그대로 쓴다. -BASIS_PIPE = "법정 표(KCS 44 40 10 그림 3.2-1) — 설계도서에 따로 없으면 이 값" -#: ⭐ 2026-09-09 **사용자 확정 5차 1번** — 「비탈 터파기를 셀 것인가」에 **「지금 이대로」**로 답이 -#: 왔다(기초 + 비탈 두 줄). 그래서 문구에서 **「확인 후 쓸 것」을 걷는다** — 확인이 끝났다. -#: ⚠ 「법정 근거 없음」은 그대로 둔다(사실이 안 바뀜) — 근거의 급은 여전히 실무 정본이다. -#: ⚠ 소광리 07-구조도는 **비탈 몫을 안 적는다**(설계사무소 간 차이) — 그래도 우리는 정본 -#: 04.구조도(기슭막이)의 두 줄을 따른다는 것이 이 확정의 뜻이다. -BASIS_WALL = ( - "법정 근거 없음 · 실무 정본 식(구조도 기슭막이 xls) — 기초 + 비탈 두 줄." - " **2026-09-09 사용자 확정 5차 1번으로 「지금 이대로」 확정**" -) - - -def pipe_trench_width_m(diameter_mm: float | int | None) -> float | None: - """관경(㎜)에 대응하는 터파기 폭(m). 표에 없으면 `None`(지어내지 않는다).""" - if diameter_mm is None: - return None - try: - key = int(round(float(diameter_mm))) - except (TypeError, ValueError): - return None - width = PIPE_TRENCH_WIDTH_MM.get(key) - return None if width is None else width / 1000.0 - - -def wall_trench_area_m2( - height_m: float | None, - average_thickness_m: float | None, - *, - has_foundation: bool, -) -> float | None: - """벽 터파기 단면적(㎡/m). 높이나 평균두께가 없으면 `None`. - - 실무 정본 식 그대로: `기초분 + H × (평균두께 + 0.2)`. - `has_foundation=False` 면 기초분이 **버림 몫(0.1 × 0.7)**으로 줄어든다. - """ - if height_m is None or average_thickness_m is None: - return None - try: - height = float(height_m) - thickness = float(average_thickness_m) - except (TypeError, ValueError): - return None - if height <= 0 or thickness <= 0: - return None - base = ( - WALL_FOUNDATION_DEPTH_M * WALL_FOUNDATION_WIDTH_M - if has_foundation - else WALL_BLINDING_DEPTH_M * WALL_BLINDING_WIDTH_M - ) - return base + height * (thickness + WALL_TRENCH_CLEARANCE_M) - - -def wall_trench_width_m(average_thickness_m: float | None) -> float | None: - """벽 터파기 폭(m) — 평균두께 + 여유 0.2. 값이 없으면 `None`.""" - if average_thickness_m is None: - return None - try: - thickness = float(average_thickness_m) - except (TypeError, ValueError): - return None - return None if thickness <= 0 else thickness + WALL_TRENCH_CLEARANCE_M diff --git a/common_util/common_util_excavation.ts b/common_util/common_util_excavation.ts index bdd181175..350be22d4 100644 --- a/common_util/common_util_excavation.ts +++ b/common_util/common_util_excavation.ts @@ -2,9 +2,8 @@ * common_util_excavation.ts * 터파기 단면 — 관·벽 두 갈래. **근거의 급이 다르므로 갈라 둔다.** * - * ⚠⚠ 파이썬 짝과 **한 벌**이다 — `common_util/common_util_excavation.py`. - * 거울 시험: `resources/tester/test_excavation_mirror.py`. 어느 쪽을 고치든 같이 돌릴 것. - * 화면(B06 횡단도)이 그리고 서버(B08 수량)가 세는 값이라 두 쪽 수가 같아야 한다. + * 한 벌 — 화면(B06 횡단도)이 그리고 서버(B08 수량 · Node `excavation.trench`)가 셈. + * 파이썬 짝 = 운영 부름 0 이라 걷음(511-B B-6) · 시험 `resources/tester/test_excavation_mirror.py`. * * ① 관 — 법정 시방서에 표가 있다: `KCS 44 40 10 배수관(23.01) §3.2(4) 그림 3.2-1`. * 「설계도서에 따로 표기되지 않았을 때에는 … 기준 폭에 따라야 한다」 diff --git a/common_util/common_util_route_follow.py b/common_util/common_util_route_follow.py index 0056e1a98..38aebbefd 100644 --- a/common_util/common_util_route_follow.py +++ b/common_util/common_util_route_follow.py @@ -15,6 +15,8 @@ (관 — 파이썬 본체 = `common_util_drainage_pipes.project_pipe_points`) · `placeOnLine`(좌표 낡음 판정) · 거울 시험 = `resources/tester/test_344_2_route_pairs.py` · `test_410_follow_xy.py` · 끌기 여러 걸음 화면 자리 = [저장] 한 번 자리(관 · 구조물 · 추가 측점) = `test_493_3_follow_screen_save.py`. +남는 까닭(511-B B-6) — 따라감 = Node `facility.follow` · 좌표 맞춤 파이썬 짝 = Node 못 돌림 때 · +`route_outside` 는 본 길. """ from __future__ import annotations diff --git a/common_util/common_util_station_key.py b/common_util/common_util_station_key.py index 36427cf00..0806853f7 100644 --- a/common_util/common_util_station_key.py +++ b/common_util/common_util_station_key.py @@ -4,19 +4,17 @@ 시종점 = ``end:BP`` · ``end:EP`` · id 를 모르는 구조물 측점 = ``at:<누가거리 0.01 m>`` · 곡선 측점(MC · 252-2) = ``curve:<누가거리 0.01 m>``. 구간형 구조물 시작 · 끝 측점 = ``structure::start`` · ``:end``(기준 측점은 ``structure:``). -한 목록에 같은 열쇠가 둘이면 둘째부터 ``at:<누가거리>``(``dedupe_station_keys``). +한 목록에 같은 열쇠가 둘이면 둘째부터 ``at:<누가거리>``(TS ``dedupeStationKeys``). MC 가 움직여 곡선 열쇠가 바뀌면 넘길 때 가까운 새 곡선 측점으로 이음(``curve_moves`` · 432-3). ⚠ 파이썬·TS 짝 — ``common_util_station_key.ts`` ``stationKey`` ↔ ``station_key``. +남는 까닭 = DTM 아닌 지표면 · Node 못 돌림 때 노선 저장 이월 열쇠(``station_keys``). 한쪽을 고치면 다른 쪽도 고치고 거울 시험 ``resources/tester/test_344_3_station_key_mirror.py``. """ from __future__ import annotations import math -import re - -_LOOSE = re.compile(r"^-?\d+(\.\d+)?$") def _half_up(value: float) -> int: @@ -65,23 +63,6 @@ def station_key( return loose -def dedupe_station_keys(keys: list[str], chainages: list[float]) -> list[str]: - """한 목록에 같은 열쇠가 둘이면 둘째부터 ``at:<누가거리>`` — 열쇠마다 측점 하나.""" - seen: set[str] = set() - out: list[str] = [] - for key, chainage in zip(keys, chainages, strict=True): - unique = f"at:{chainage_key(chainage)}" if key in seen else key - seen.add(unique) - out.append(unique) - return out - - -def loose_chainage(key: str) -> float | None: - """옛 초안 열쇠(누가거리 글) · ``at:`` 열쇠의 누가거리 — 측점 열쇠면 None.""" - text = key[3:] if key.startswith("at:") else key - return float(text) if _LOOSE.match(text) else None - - #: 옮긴 곡선 측점으로 보는 거리(m) — 옛 MC 자리에서 이 안의 새 MC. CURVE_NEAR_M = 10.0 diff --git a/common_util/common_util_structure_station.py b/common_util/common_util_structure_station.py index 9382b262c..f5bc4944e 100644 --- a/common_util/common_util_structure_station.py +++ b/common_util/common_util_structure_station.py @@ -8,6 +8,7 @@ TS 짝 = `common_util/common_util_structure_station.ts` 측점 계획 `plan_stations` ↔ `planStations` · 322-3 · 추가 측점 `plus` 368-2). 거울 시험 = `resources/tester/test_234_1_structure_station.py`. 일반 측점 = 격자(간격) · 시종점 — 끝(노선 길이)을 넘지 않게 자르는 데만 씀. +남는 까닭(511-B B-6) — B07 · 배수(snap · 일반 측점) 파이썬 소비자 · 측점 생성 Node 못 돌림 때. """ from __future__ import annotations diff --git a/resources/tester/helper_322_3_flow.cjs b/resources/tester/helper_322_3_flow.cjs index 1c4604c72..6124da24d 100644 --- a/resources/tester/helper_322_3_flow.cjs +++ b/resources/tester/helper_322_3_flow.cjs @@ -69,6 +69,7 @@ function load(target) { cache.set(full, box.exports); const need = (spec) => { if (spec.startsWith("@ui/")) return { showToast: () => {} }; + if (spec === "@config/config_frontend") return { API_BASE_URL: "/api" }; const alias = spec.startsWith("@util/") ? path.join(ROOT, "common_util", spec.slice(6)) : null; const base = alias ?? path.join(path.dirname(full), spec); const hit = [`${base}.ts`, base.replace(/\.js$/, ".ts"), base].find( diff --git a/resources/tester/helper_475_1_moved.py b/resources/tester/helper_475_1_moved.py index c48b63d44..ca7d883fd 100644 --- a/resources/tester/helper_475_1_moved.py +++ b/resources/tester/helper_475_1_moved.py @@ -10,22 +10,43 @@ from __future__ import annotations import json +from collections.abc import Sequence from pathlib import Path from typing import Any import helper_472_1_paths as P +def seat_route_moved( + alignment: dict[str, Any], born: Sequence[float], held: Sequence[float] = () +) -> list[dict[str, Any]]: + """옮긴 시설 측점(`born`)에 427 규칙으로 꼭짓점을 다시 세움(459) — 서버 [저장] 길(Node + `carry_seat` 의 seat) 앞뒤 그대로: 그 자리 옛 꼭짓점을 걷고 `_seat_born_nodes`. + 파이썬 짝(`Route_Save_Parts.seat_route_moved`) = 운영 부름 0 이라 걷음(511-B B-6) — 거울 기준만 + 여기(`test_459_route_seat_mirror` · `test_475_1_route_digits`).""" + from B05_Profile.B05_Profile_Router_Confirm import _SAME_STATION_M, _seat_born_nodes + + base = alignment.get("base_pvi") or [] + if not born: + return base + kept = [ + node + for node in base + if all(abs(float(node["chainage_m"]) - value) >= _SAME_STATION_M for value in born) + ] + return _seat_born_nodes(alignment, kept, list(born), list(held)) + + def server_moved(root: Path, move: dict[str, Any]) -> dict[str, Any]: from B05_Profile.B05_Profile_Engine import _route_geojson from B05_Profile.B05_Profile_Engine_AsPlanned import _vertex_z from B05_Profile.B05_Profile_Engine_Grade import GradeDesignOptions from B05_Profile.B05_Profile_Engine_Grade_Carry import ( carry_base, - carry_edits, carry_policy, changed_span, ) + from B05_Profile.B05_Profile_Engine_Grade_Node import carry_seat from B05_Profile.B05_Profile_Engine_Sections import run_section_generation from B05_Profile.B05_Profile_Engine_Sections_Core import SectionGenerationOptions from B05_Profile.B05_Profile_Plus_Stations import save_plus_stations @@ -33,7 +54,6 @@ def server_moved(root: Path, move: dict[str, Any]) -> dict[str, Any]: carry_cross_inputs, follow_pipes, route_moved_stations, - seat_route_moved, station_keys, vertices_of, ) @@ -125,9 +145,9 @@ def server_moved(root: Path, move: dict[str, Any]) -> dict[str, Any]: stations = [float(r["chainage_m"]) for r in longitudinal.get("stations") or []] half = float((alignment.get("policy") or {}).get("station_interval_m") or 20.0) / 2 base = carry_base(old_line, points, old_alignment["base_pvi"], alignment["base_pvi"]) - edits = carry_edits( + edits = carry_seat( old_line, points, old_alignment["base_pvi"], old_alignment.get("edits"), stations, half - ) + )[0] alignment["policy"] = carry_policy(old_line, points, old_alignment, alignment) alignment["base_pvi"] = base if span is not None: diff --git a/resources/tester/test_178_3_slope_tiers.py b/resources/tester/test_178_3_slope_tiers.py index 4e3831625..837f10397 100644 --- a/resources/tester/test_178_3_slope_tiers.py +++ b/resources/tester/test_178_3_slope_tiers.py @@ -77,7 +77,7 @@ def test_명세_저장_왕복(out): def test_서버_저장_뒤_벽_제원(tmp_path): - from B05_Profile.B05_Profile_Structures_Repository import save_structures + from B05_Profile.B05_Profile_Structures_Repository import load_structures, save_structures from B06_Section import B06_Section_Engine_Structures_Wall as wall items = [ @@ -99,7 +99,8 @@ def test_서버_저장_뒤_벽_제원(tmp_path): ), ] save_structures(str(tmp_path), items, base_revision=0) - got = {spec["type_id"]: spec for spec in wall.load_wall_structures(tmp_path)} + stored = load_structures(str(tmp_path))[1] + got = {spec["type_id"]: spec for spec in wall.wall_specs_from(stored)} assert got["soil_guard"]["tiers"] == 3 assert got["soil_guard"]["extras"] == [ {"form": "콘크리트", "height_m": 2}, diff --git a/resources/tester/test_288_underdrain_cross.py b/resources/tester/test_288_underdrain_cross.py index 0abf3f19f..73ea796cd 100644 --- a/resources/tester/test_288_underdrain_cross.py +++ b/resources/tester/test_288_underdrain_cross.py @@ -3,7 +3,7 @@ 못박는 것 - 288-1 맹암거 기준 측점 = 측구 없음 · 노면 = 표준 경사로 지대 낮은 쪽 한 경사 (물넘이 286 · 287 길 — 서버 `tilt_ranges` · `tilt_args` · TS 짝 `tilted` · 암거 경사는 - 노면에 안 감 371-3) · 제원 얹기 서버 `attach_underdrains` ↔ TS `attachUnderdrainSpecs` + 노면에 안 감 371-3) · 제원 얹기 서버 `attach_underdrain_specs` ↔ TS `attachUnderdrainSpecs` 같은 값 · 형상 = 노면을 암거 높이만큼 아래로 옵셋 → 성토 쪽은 성토 사면과 만남 · 절토 쪽은 절토 사면을 늘여 만남 · 그 안 돌 무늬 · 높이 비면 안 그림. - 371-3 암거 경사 = 형상 하단 경계 — 높은 쪽 노면 끝 − 높이에서 암거 경사로 낮은 쪽으로 내림. @@ -97,7 +97,8 @@ def test_맹암거_자리_기울기_서버(project): def test_제원_얹기_서버_TS_같은_값(project, ts): sections = [dict(s) for s in SECTIONS] - assert tilt.attach_underdrains(project, sections) == 2 == ts["attached"] + attached = tilt.attach_underdrain_specs(sections, tilt.load_underdrains(project)) + assert attached == 2 == ts["attached"] assert [s.get("underdrain") for s in sections] == ts["sections"] assert ts["sections"][1] == { "structure_id": "ud-1", diff --git a/resources/tester/test_322_0_golden.py b/resources/tester/test_322_0_golden.py index de282db89..425dd7f99 100644 --- a/resources/tester/test_322_0_golden.py +++ b/resources/tester/test_322_0_golden.py @@ -22,12 +22,12 @@ from pathlib import Path import pytest from B05_Profile.B05_Profile_Engine_Sections import relabel_pipe_stations, resolve_extra_stations -from B05_Profile.B05_Profile_Structures_Repository import structures_file_path +from B05_Profile.B05_Profile_Structures_Repository import load_structures, structures_file_path from B05_Profile.B05_Profile_Structures_Schema import load_definition from B06_Section.B06_Section_Engine_Culvert import pipe_points_file from B06_Section.B06_Section_Engine_Culvert_Sets import attach_culvert_sets -from B06_Section.B06_Section_Engine_Structures_Wall import attach_wall_structures -from B06_Section.B06_Section_Engine_Tilt import attach_underdrains +from B06_Section.B06_Section_Engine_Structures_Wall import attach_wall_specs, wall_specs_from +from B06_Section.B06_Section_Engine_Tilt import attach_underdrain_specs, load_underdrains from common_util.common_util_drainage_pipes import parse_pipe_points from common_util.common_util_structure_pavement import attach_pavement_structures from M02_MasterTemplete.StructOptions.M02_SO_Options import with_visible @@ -139,9 +139,9 @@ def _python_row(sample: dict, root: Path, plain: dict) -> dict: sections.sort(key=lambda s: s["chainage_m"]) attach_culvert_sets(root, sections) relabel_pipe_stations(root, sections) - attach_wall_structures(root, sections) + attach_wall_specs(sections, wall_specs_from(load_structures(str(root))[1])) attach_pavement_structures(root, sections) - attach_underdrains(root, sections) + attach_underdrain_specs(sections, load_underdrains(root)) return { "station_py": [f"{label}@{at:.2f}" for at, label in extras], "cards_py": { diff --git a/resources/tester/test_344_2_route_pairs.py b/resources/tester/test_344_2_route_pairs.py index 3c140cfaf..0868d368f 100644 --- a/resources/tester/test_344_2_route_pairs.py +++ b/resources/tester/test_344_2_route_pairs.py @@ -34,10 +34,10 @@ from B05_Profile.B05_Profile_Engine_Grade import resolve_grade_options # noqa: from B05_Profile.B05_Profile_Engine_Grade_Alignment import resolve_pvi # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Carry import ( # noqa: E402 carry_base, - carry_edits, changed_span, line_matcher, ) +from B05_Profile.B05_Profile_Engine_Grade_Node import carry_seat # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402 design_ground_following_profile, ) @@ -58,6 +58,11 @@ pytestmark = pytest.mark.skipif( # ── 입력 만들기 ─────────────────────────────────────────────────────────────── +def carry_edits(*args): + """파이썬 짝 걷음(511-B B-6) — 서버 = Node `carry_seat` 의 편집 몫.""" + return carry_seat(*args)[0] + + def _longitudinal(total: float, *, wave: float, holes: tuple[int, ...] = ()) -> dict: samples = [] for index in range(int(total) + 1): diff --git a/resources/tester/test_344_3_station_key_mirror.py b/resources/tester/test_344_3_station_key_mirror.py index 4f2316b76..4c01f5aa5 100644 --- a/resources/tester/test_344_3_station_key_mirror.py +++ b/resources/tester/test_344_3_station_key_mirror.py @@ -14,12 +14,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[2] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) -from common_util.common_util_station_key import ( # noqa: E402 - chainage_key, - dedupe_station_keys, - loose_chainage, - station_key, -) +from common_util.common_util_station_key import chainage_key, station_key # noqa: E402 TS = PROJECT_ROOT / "common_util" / "common_util_station_key.ts" TYPESCRIPT = PROJECT_ROOT / "config" / "node_modules" / "typescript" @@ -53,6 +48,9 @@ CASES = [ ] DEDUPE = (["grid:1", "structure:st-1", "grid:1", "structure:st-1"], [20.0, 25.0, 20.004, 25.006]) LOOSE = ["85.05", "at:85.05", "grid:4", "pipe:pipe-s3", "-1.5", "end:BP"] +# 파이썬 `loose_chainage` · `dedupe_station_keys` = 운영 부름 0 → 걷음(511-B B-6) · 걷기 전 값 +LOOSE_WANT = [85.05, 85.05, None, None, -1.5, None] +DEDUPE_WANT = ["grid:1", "structure:st-1", "at:20.00", "at:25.01"] SCRIPT = r""" const ts = require(process.argv[1]); @@ -92,8 +90,8 @@ def test_파이썬과_TS_열쇠_같음(): out = json.loads(proc.stdout) assert out["keys"] == [station_key(*case) for case in CASES] assert out["chainage"] == [chainage_key(case[1]) for case in CASES] - assert out["loose"] == [loose_chainage(key) for key in LOOSE] - assert out["dedupe"] == dedupe_station_keys(*DEDUPE) + assert out["loose"] == LOOSE_WANT + assert out["dedupe"] == DEDUPE_WANT def test_열쇠_규칙(): @@ -102,10 +100,8 @@ def test_열쇠_규칙(): assert station_key("regular", 50.0, 20.0) == "grid:3" assert station_key("irregular", 85.054, 20.0, "pipe-s3", "pipe") == "pipe:pipe-s3" assert station_key("irregular", 85.054, 20.0) == "at:85.05" - assert loose_chainage("85.05") == 85.05 and loose_chainage("grid:4") is None # 5파 — 구간형 시작 · 끝 · 기준은 그대로 · 원래 격자 번호 · 같은 열쇠 둘째는 at: assert station_key("structure", 30.0, 20.0, "st-9", role="start") == "structure:st-9:start" assert station_key("structure", 33.0, 20.0, "st-9") == "structure:st-9" assert station_key("grid", 41.3, 20.0, "2") == "grid:2" assert station_key("end", 99.0, 20.0) == "end:EP" - assert dedupe_station_keys(*DEDUPE) == ["grid:1", "structure:st-1", "at:20.00", "at:25.01"] diff --git a/resources/tester/test_349_0v_route_save_span.py b/resources/tester/test_349_0v_route_save_span.py index 3de52f75d..b683b8ab0 100644 --- a/resources/tester/test_349_0v_route_save_span.py +++ b/resources/tester/test_349_0v_route_save_span.py @@ -3,7 +3,7 @@ 화면 planline 단계: 옛 = 저장 계획 폴리라인(`read_route_plan` planned) · 새 = `buildEditedPolyline`(노드 초안) → `changedSpan` → `carryBase` · `carryEdits`. 서버 노선 저장: 옛 = 같은 계획 폴리라인 -CSV · 새 = `_write_planned_polyline` 이 쓴 CSV → `changed_span` → `carry_base` · `carry_edits`(400). +CSV · 새 = `_write_planned_polyline` 이 쓴 CSV → `changed_span` → `carry_base` · Node `carry_seat`(400). 두 길이 같은 구간 · 같은 계획고를 내야 함(풀린 노선 geojson 으로 가르면 점 간격이 달라 앞머리가 일찍 갈렸음). """ @@ -26,9 +26,9 @@ if str(ROOT) not in sys.path: from B05_Profile import B05_Profile_Router_Replan as replan # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Carry import ( # noqa: E402 carry_base, - carry_edits, changed_span, ) +from B05_Profile.B05_Profile_Engine_Grade_Node import carry_seat # noqa: E402 pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "typescript").is_dir(), @@ -44,6 +44,11 @@ CURVE_ON = [True] * len(OLD_NODES) RADII: list[float | None] = [None] * len(OLD_NODES) +def carry_edits(*args): + """파이썬 짝 걷음(511-B B-6) — 서버 = Node `carry_seat` 의 편집 몫.""" + return carry_seat(*args)[0] + + def _csv_line(path: Path, nodes) -> list[list[float]]: replan._write_planned_polyline( path, list(nodes), RADIUS_M, simplify=False, curve_flags=CURVE_ON, radii=RADII diff --git a/resources/tester/test_400_profile_carry.py b/resources/tester/test_400_profile_carry.py index b0f9021dd..615313b38 100644 --- a/resources/tester/test_400_profile_carry.py +++ b/resources/tester/test_400_profile_carry.py @@ -2,7 +2,7 @@ """400 평면 고친 뒤 종단 편집 이어받기 — 같은 자리(옆 1 m · 방향 30° 안)는 옛 계획고를 새 측점 위에. 거울(파이썬 = 서버 [저장] 정본 · TS = 브라우저 즉시 · 같은 값): - `B05_Profile_Engine_Grade_Carry` line_matcher · carry_base · carry_edits ↔ + `B05_Profile_Engine_Grade_Carry` line_matcher · carry_base · Node carry_seat(편집 · 파이썬 carry_edits 걷음) ↔ `logic/B05_Profile_Grade_Carry.ts` lineMatcher · carryBase · carryEdits. 묶음 follow(`logic/B05_Profile_Pipeline_Grade.ts`) — 걸음 수와 무관(기준 한 벌에서 한 번) · 되돌린 노선 = 옛 편집. @@ -28,9 +28,9 @@ from B05_Profile.B05_Profile_Engine_Grade_Alignment import resolve_pvi # noqa: from B05_Profile.B05_Profile_Engine_Grade_Carry import ( # noqa: E402 SAME_LINE_TOL_M, carry_base, - carry_edits, line_matcher, ) +from B05_Profile.B05_Profile_Engine_Grade_Node import carry_seat # noqa: E402 pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "typescript").is_dir(), @@ -44,6 +44,11 @@ HALF = INTERVAL / 2 # ── 입력 만들기 ─────────────────────────────────────────────────────────────── +def carry_edits(*args): + """파이썬 짝 걷음(511-B B-6) — 서버 = Node `carry_seat` 의 편집 몫.""" + return carry_seat(*args)[0] + + def _dense(nodes: list[tuple[float, float]], step: float = 2.0) -> list[list[float]]: """꺾은선을 `step` m 안팎 점으로(그린 노선처럼 정점이 많게).""" out = [list(nodes[0])] diff --git a/resources/tester/test_459_route_seat_mirror.py b/resources/tester/test_459_route_seat_mirror.py index 2127afb86..e35076c18 100644 --- a/resources/tester/test_459_route_seat_mirror.py +++ b/resources/tester/test_459_route_seat_mirror.py @@ -27,6 +27,8 @@ ROOT = HERE.parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) +from helper_475_1_moved import seat_route_moved # noqa: E402 + from B05_Profile.B05_Profile_Engine_Grade import GradeDesignOptions # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Alignment import ( # noqa: E402 AlignmentPolicy, @@ -42,10 +44,7 @@ from B05_Profile.B05_Profile_Plus_Stations import ( # noqa: E402 plus_chainages, save_plus_stations, ) -from B05_Profile.B05_Profile_Route_Save_Parts import ( # noqa: E402 - route_moved_stations, - seat_route_moved, -) +from B05_Profile.B05_Profile_Route_Save_Parts import route_moved_stations # noqa: E402 from B05_Profile.B05_Profile_Router_Confirm import _seat_born_nodes # noqa: E402 from common_util.common_util_drainage_pipes import PipePoint # noqa: E402 from common_util.common_util_route_follow import route_outside # noqa: E402 diff --git a/resources/tester/test_472_1_golden.py b/resources/tester/test_472_1_golden.py index 79ae0e1b1..347099ec7 100644 --- a/resources/tester/test_472_1_golden.py +++ b/resources/tester/test_472_1_golden.py @@ -8,7 +8,8 @@ 면적(절 · 성 · 토사 · 암 · 측구) · 층따기 밑수 · 단면 모양 · 누가토량(측점별 · 끝값) · 관 길이 · 종단 경고 · 기울기 위반 수. - 두 길 사이 = 「알려진 차」(`KNOWN`)만 · 그 밖 차는 실패 · 안 나타난 알려진 차도 실패 - (목록에서 지움 — 472-4 가 짝을 하나 옮길 때마다 한 줄씩 지워 마지막에 빈 목록). + (목록에서 지움). 짝 모으기(511-B) 끝 — 남은 줄은 계산 짝이 아닌 까닭(표본 출처 · 입력 선 + 자릿수 · 저장 때 코드) · 줄마다 `why`. - 화면 참값 — 정본 누가토량 −17,570.70 ㎥ · 측점 57(465 기준값). 이 고정 자료가 안 거치는 짝인데 값이 갈리는 자리 — X1 단면 모양 기본값(설계 없는 새 측점 없음) · X3 면적 두 갈래(구조물 = 관 351 하나 · 두 길 다 Node TS 로 셈) · X4 · X6(노선 안 바뀜). @@ -58,7 +59,8 @@ KNOWN: dict[tuple[str, str], list[Known]] = { Known( "R7", {"fill_m2": 0.00011, "cumulative_m3": 0.011}, - "다시 뜬 표본 마지막 자리 — grid:37 성토 1e-4 ㎡ · 누가토량 0.01 ㎥ 넷(끝값 같음)", + "지반 표본 출처(G1) — 정본 = 10-17 파이썬 표본 · 노선 저장 = Node 격자 표본 · " + "grid:37 성토 1e-4 ㎡ · 누가토량 0.01 ㎥ 넷(끝값 같음 · 기준값 다시 뜸 안 함 B-2)", ), Known( "W0", diff --git a/resources/tester/test_472_3_calc_registry.py b/resources/tester/test_472_3_calc_registry.py index 442a72043..c7addd471 100644 --- a/resources/tester/test_472_3_calc_registry.py +++ b/resources/tester/test_472_3_calc_registry.py @@ -12,7 +12,7 @@ Node 입구 = `A00_Common/design/logic/design_calc_node.ts`(`build:design-calc` - Node 입구 도달 집합 = logic · common_util · 자료만 + 「알려진 끌림」(`KNOWN_PULLS`) — 그 밖은 실패 · 안 나타난 알려진 끌림도 실패(목록에서 지움 — 472-2 2-B · 472-4 가 하나씩 걷어 빈 목록). - Node 입구 값(고정 자료 `fixtures/golden_472` · 면적 · 다단 벽 · 누가토량 끝값 · 배분 두 벌 · - corridor 3D = corridor 묶음) · 터파기 = 파이썬 짝. + corridor 3D = corridor 묶음) · 터파기 = 걷기 전 파이썬 값. ./venv/Scripts/python.exe -m pytest resources/tester/test_472_3_calc_registry.py -q """ @@ -328,9 +328,8 @@ def test_corridor_묶음과_같음() -> None: assert co and out["results"]["corridor.mesh"] == co -def test_터파기_파이썬_짝과_같음() -> None: - from common_util import common_util_excavation as py - +def test_터파기_값() -> None: + # 파이썬 짝 걷음(511-B B-6 · 운영 부름 0) — 기대값 = 걷기 전 파이썬 값 pipes = [{"diameter_mm": d} for d in (300, 800, 1500, 250, None)] walls = [ {"height_m": 2.0, "average_thickness_m": 0.5, "has_foundation": True}, @@ -341,15 +340,11 @@ def test_터파기_파이썬_짝과_같음() -> None: {"run": ["excavation.trench"], "inputs": {"excavation": {"pipes": pipes, "walls": walls}}} ) got = out["results"]["excavation.trench"] - assert [p["width_m"] for p in got["pipes"]] == [ - py.pipe_trench_width_m(p["diameter_mm"]) for p in pipes - ] - for row, wall in zip(got["walls"], walls, strict=True): - assert row["width_m"] == py.wall_trench_width_m(wall["average_thickness_m"]) - want = py.wall_trench_area_m2( - wall["height_m"], wall["average_thickness_m"], has_foundation=wall["has_foundation"] - ) - assert row["area_m2"] == pytest.approx(want) if want is not None else row["area_m2"] is None + assert [p["width_m"] for p in got["pipes"]] == [0.7, 1.6, 2.5, None, None] + assert [w["width_m"] for w in got["walls"]] == pytest.approx([0.7, 0.55, 0.6], abs=1e-12) + assert got["walls"][0]["area_m2"] == pytest.approx(1.85) + assert got["walls"][1]["area_m2"] == pytest.approx(0.73) + assert got["walls"][2]["area_m2"] is None def test_모르는_id_는_실패() -> None: diff --git a/resources/tester/test_472_4_underdrain_mirror.py b/resources/tester/test_472_4_underdrain_mirror.py index 947c16d13..15a905487 100644 --- a/resources/tester/test_472_4_underdrain_mirror.py +++ b/resources/tester/test_472_4_underdrain_mirror.py @@ -5,7 +5,8 @@ - 구조물 목록 → 제원(높이 · 경사 · 빈 값 · 0 · 음수 · 글 · 맹암거 아닌 종류) 두 쪽 같음. - 기준 측점(±0.02 m) 얹기 · 얹은 수 같음. - 알려진 차 하나 — 옛 `underdrain` 이 남은 측점: TS 는 걷음 · 파이썬은 둠 - (서버는 파일에서 새로 읽어 옛 값 없음 · Node 로 옮길 때 한 벌). + (서버 GET = Node `facility.attach` = TS · 파이썬 짝은 Node 못 부를 때만 · 그 길은 정본 파일을 + 새로 읽어 옛 값 없음). ./venv/Scripts/python.exe -m pytest resources/tester/test_472_4_underdrain_mirror.py -q """ diff --git a/resources/tester/test_511_b1_calc_node_stages.py b/resources/tester/test_511_b1_calc_node_stages.py index 39d9e8124..ac3af1462 100644 --- a/resources/tester/test_511_b1_calc_node_stages.py +++ b/resources/tester/test_511_b1_calc_node_stages.py @@ -3,8 +3,8 @@ 못박는 것 - 단계 id 다섯(`stage.route` · `follow` · `stations` · `profile` · `cross`) = TS 표 = 파이썬 `STAGE_IDS` · `available`(등록부 `node:`) 밖 · 창구(`run_calc_served`)가 받음. - - 판 = 단계 줄(잠금 · 기본 both · 까닭 「짝 모으는 중 · B-n」 · stations = B-2 · route = B-5 가 - 엶) · F9 = 폼 기본값 · + - 판 = 단계 줄(B-2 ~ B-5 가 엶 · 기본 both · 옛 까닭 「짝 모으는 중 · B-n」 = B-6 걷음) · + F9 = 폼 기본값 · 커밋한 판 그대로. - 고정 자료(`fixtures/golden_472`) — Node 단계가 저장 노선 한 바퀴를 돎 (측점 = 저장 상세 측점 0.01 m). @@ -108,8 +108,7 @@ def test_판_다섯_줄_잠금_기본값_그대로(): ).stdout assert calc_sites.PATH.read_bytes().replace(b"\r\n", b"\n") == committed.replace(b"\r\n", b"\n") locale = (ROOT / "ui_template" / "ui_template_locale_m1.ts").read_text(encoding="utf-8") - for n in (4,): - assert f'B01_CalcSites_Why_pair_B{n}: ["짝 모으는 중 · B-{n}"' in locale + assert "짝 모으는 중" not in locale # B-6 — 단계 줄 모두 엶 · 옛 까닭 글 걷음 def test_페이지_빼기만_같은_맞춤_함수(): diff --git a/resources/tester/test_511_b3_profile_node.py b/resources/tester/test_511_b3_profile_node.py index 630b272f4..c80f2720d 100644 --- a/resources/tester/test_511_b3_profile_node.py +++ b/resources/tester/test_511_b3_profile_node.py @@ -34,7 +34,6 @@ from B05_Profile.B05_Profile_Engine_Grade_Alignment import ( # noqa: E402 AlignmentPolicy, build_alignment, ) -from B05_Profile.B05_Profile_Engine_Grade_Carry import carry_edits # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Node import carry_seat # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402 design_ground_following_profile, @@ -180,7 +179,7 @@ def test_저장_선형_없으면_ValueError() -> None: def test_계획고_옮김_꼭짓점_세움_한_부름() -> None: """노선 [저장] 몫 — 편집 옮김(`carryEdits`)과 꼭짓점 세움(`seatRouteMoved`)이 Node 한 부름 · - 편집만 부르는 겉(`carry_edits`)과 같은 편집.""" + 편집만 부른 값(seat 없음)과 같은 편집.""" old_xy = [[0.0, 0.0], [200.0, 0.0], [400.0, 0.0]] new_xy = [[0.0, 0.0], [200.0, 0.0], [300.0, 60.0], [420.0, 60.0]] longitudinal = _longitudinal(0.4) @@ -196,12 +195,8 @@ def test_계획고_옮김_꼭짓점_세움_한_부름() -> None: "born": [251.2], } moved, seated = carry_seat(old_xy, new_xy, base, edits, stations, 10.0, seat) - assert moved == carry_edits(old_xy, new_xy, base, edits, stations, 10.0) + assert moved == carry_seat(old_xy, new_xy, base, edits, stations, 10.0)[0] assert set(moved["station_offsets"]) == {"60.000", "120.000"} assert moved["curve_radii"] == {"120.000": 400.0} assert all(isinstance(v, float) for v in moved["station_offsets"].values()) assert any(abs(node["chainage_m"] - 251.2) < 1e-6 for node in seated) - assert carry_edits(old_xy, new_xy, [], edits, stations, 10.0) == { - "station_offsets": {}, - "curve_radii": {}, - } diff --git a/resources/tester/test_b06_structure_walls_mirror.py b/resources/tester/test_b06_structure_walls_mirror.py index fd758950e..19957114d 100644 --- a/resources/tester/test_b06_structure_walls_mirror.py +++ b/resources/tester/test_b06_structure_walls_mirror.py @@ -82,7 +82,6 @@ writeFileSync(process.argv[3], JSON.stringify({ specs, sections })); def _python_result() -> dict: from B06_Section.B06_Section_Engine_Structures_Wall import ( _wall_form, - attach_wall_structures, tier_extras, ) @@ -126,7 +125,6 @@ def _python_result() -> dict: if spec["start_m"] - 0.02 <= section["chainage_m"] <= spec["end_m"] + 0.02: section["revetment"] = spec break - assert callable(attach_wall_structures) return {"specs": specs, "sections": sections} diff --git a/resources/tester/test_excavation_mirror.py b/resources/tester/test_excavation_mirror.py index 415cbdf66..d2ca6a07b 100644 --- a/resources/tester/test_excavation_mirror.py +++ b/resources/tester/test_excavation_mirror.py @@ -1,7 +1,8 @@ -"""터파기 단면 — 파이썬·TS 짝이 같은 값을 내는지 (2026-09-09). +"""터파기 단면 — TS 한 벌(`common_util_excavation.ts`)이 법정 표 · 실무 정본 값을 냄 (2026-09-09). -화면(B06 횡단도)이 그리고 서버(B08 수량)가 세는 값이라 두 쪽이 갈리면 -「그림은 이런데 수량은 저렇다」가 된다. +화면(B06 횡단도)이 그리고 서버(B08 수량 · Node `excavation.trench`)가 세는 값. +파이썬 짝(`common_util_excavation.py`)은 운영 부름 0 이라 걷음(511-B B-6) — 기대값 = 걷기 전 +파이썬 값 그대로. ⚠ 근거의 급이 다른 두 갈래를 함께 잠근다 — 관은 **법정 표**(KCS 44 40 10 그림 3.2-1), 벽은 **실무 정본 식**(구조도 기슭막이 xls). @@ -12,24 +13,19 @@ from __future__ import annotations import json import re import subprocess -import sys from pathlib import Path import pytest PROJECT_ROOT = Path(__file__).resolve().parents[2] -if str(PROJECT_ROOT) not in sys.path: - sys.path.insert(0, str(PROJECT_ROOT)) - -from common_util.common_util_excavation import ( # noqa: E402 - PIPE_TRENCH_WIDTH_MM, - WALL_FOUNDATION_DEPTH_M, - WALL_FOUNDATION_WIDTH_M, - WALL_TRENCH_CLEARANCE_M, - pipe_trench_width_m, - wall_trench_area_m2, - wall_trench_width_m, -) +# 걷기 전 파이썬 짝 값(511-B B-6) +PIPE_TABLE = { + 300: 700, 350: 800, 400: 900, 450: 1000, 500: 1100, 600: 1200, 700: 1400, 800: 1600, + 900: 1700, 1000: 1800, 1100: 2000, 1200: 2100, 1300: 2200, 1400: 2400, 1500: 2500, +} # fmt: skip +WALL_FOUNDATION_DEPTH_M = 0.5 +WALL_FOUNDATION_WIDTH_M = 0.9 +WALL_TRENCH_CLEARANCE_M = 0.2 TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" @@ -37,6 +33,9 @@ _RUNNER = """ import { writeFileSync } from "node:fs"; import { PIPE_TRENCH_WIDTH_MM, + WALL_FOUNDATION_DEPTH_M, + WALL_FOUNDATION_WIDTH_M, + WALL_TRENCH_CLEARANCE_M, pipeTrenchWidthM, wallTrenchAreaM2, wallTrenchWidthM, @@ -49,6 +48,8 @@ const out = { wallBlindingOnly: [1.0, 2.0, 3.0].map((h) => wallTrenchAreaM2(h, 0.7, false)), wallWidth: [0.7, 1.05, 0].map((t) => wallTrenchWidthM(t)), nulls: [pipeTrenchWidthM(null), wallTrenchAreaM2(null, 0.7, true), wallTrenchAreaM2(1, null, true)], + practice: [1.0, 2.0, 3.0].map((h) => wallTrenchAreaM2(h, 0.83, true)), + consts: [WALL_FOUNDATION_DEPTH_M, WALL_FOUNDATION_WIDTH_M, WALL_TRENCH_CLEARANCE_M], }; writeFileSync(process.argv[2], JSON.stringify(out)); """ @@ -92,50 +93,51 @@ def _ts_values(tmp_path: Path) -> dict: return json.loads(result.read_text(encoding="utf-8")) -@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") -def test_두_쪽이_같은_값을_낸다(tmp_path: Path) -> None: - ts = _ts_values(tmp_path) +@pytest.fixture(scope="module") +def ts(tmp_path_factory) -> dict: + if not TSC.is_file(): + pytest.skip("프론트엔드 의존성 미설치") + return _ts_values(tmp_path_factory.mktemp("excavation")) - assert {int(k): v for k, v in ts["table"].items()} == PIPE_TRENCH_WIDTH_MM - assert ts["pipe"] == [pipe_trench_width_m(d) for d in (300, 800, 1000, 1200, 1500, 250, 1600)] - assert ts["wallWithFoundation"] == [ - wall_trench_area_m2(h, 0.7, has_foundation=True) for h in (1.0, 2.0, 3.0) - ] - assert ts["wallBlindingOnly"] == [ - wall_trench_area_m2(h, 0.7, has_foundation=False) for h in (1.0, 2.0, 3.0) - ] - assert ts["wallWidth"] == [wall_trench_width_m(t) for t in (0.7, 1.05, 0)] + +def test_걷기_전_파이썬_값과_같다(ts: dict) -> None: + assert {int(k): v for k, v in ts["table"].items()} == PIPE_TABLE + assert ts["pipe"] == [0.7, 1.6, 1.8, 2.1, 2.5, None, None] + assert ts["wallWithFoundation"] == pytest.approx([1.35, 2.25, 3.15], abs=1e-12) + assert ts["wallBlindingOnly"] == pytest.approx([0.97, 1.87, 2.77], abs=1e-12) + assert ts["wallWidth"][:2] == pytest.approx([0.9, 1.25], abs=1e-12) + assert ts["wallWidth"][2] is None assert ts["nulls"] == [None, None, None] + assert ts["consts"] == [ + WALL_FOUNDATION_DEPTH_M, + WALL_FOUNDATION_WIDTH_M, + WALL_TRENCH_CLEARANCE_M, + ] -def test_관은_표를_그대로_쓴다() -> None: - """⚠ 관경 + 2b 로 계산하지 않는다 — 표가 관 두께·작업여유를 담고 있다.""" - assert pipe_trench_width_m(800) == 1.6 - assert pipe_trench_width_m(800) != 0.8 + 2 * 0.3 - for diameter in (800, 1000, 1200, 1500): - assert pipe_trench_width_m(diameter) is not None, diameter +def test_관은_표를_그대로_쓴다(ts: dict) -> None: + """⚠ 관경 + 2b 로 계산하지 않는다 — 표가 관 두께·작업여유를 담고 있다(800 → 1.6).""" + width_800 = ts["pipe"][1] + assert width_800 == 1.6 and width_800 != 0.8 + 2 * 0.3 + assert all(width is not None for width in ts["pipe"][1:5]) # 800 · 1000 · 1200 · 1500 -def test_표에_없는_관경은_지어내지_않는다() -> None: - assert pipe_trench_width_m(250) is None - assert pipe_trench_width_m(1600) is None +def test_표에_없는_관경은_지어내지_않는다(ts: dict) -> None: + assert ts["pipe"][5:] == [None, None] # 250 · 1600 -def test_실무_정본_수치와_맞는다() -> None: +def test_실무_정본_수치와_맞는다(ts: dict) -> None: """xls 7탭: H=1.0 → 1.03+0.45 = 1.48 · H=2.0 → 2.51 · H=3.0 → 3.54 (평균두께 0.83).""" - thickness = 0.83 - for height, expected in ((1.0, 1.48), (2.0, 2.51), (3.0, 3.54)): - area = wall_trench_area_m2(height, thickness, has_foundation=True) - assert area == pytest.approx(expected, abs=0.01), height + for area, expected in zip(ts["practice"], (1.48, 2.51, 3.54), strict=True): + assert area == pytest.approx(expected, abs=0.01) -def test_기초_유무가_기초분을_가른다() -> None: - with_base = wall_trench_area_m2(2.0, 0.7, has_foundation=True) - without = wall_trench_area_m2(2.0, 0.7, has_foundation=False) +def test_기초_유무가_기초분을_가른다(ts: dict) -> None: + with_base, without = ts["wallWithFoundation"][1], ts["wallBlindingOnly"][1] # H 2.0 · 0.7 assert with_base - without == pytest.approx( WALL_FOUNDATION_DEPTH_M * WALL_FOUNDATION_WIDTH_M - 0.1 * 0.7 ) -def test_여유폭은_평균두께에_더한다() -> None: - assert wall_trench_width_m(0.7) == pytest.approx(0.7 + WALL_TRENCH_CLEARANCE_M) +def test_여유폭은_평균두께에_더한다(ts: dict) -> None: + assert ts["wallWidth"][0] == pytest.approx(0.7 + WALL_TRENCH_CLEARANCE_M) diff --git a/ui_template/ui_template_locale_m1.ts b/ui_template/ui_template_locale_m1.ts index 76e3887dd..04c28d9a3 100644 --- a/ui_template/ui_template_locale_m1.ts +++ b/ui_template/ui_template_locale_m1.ts @@ -132,7 +132,6 @@ export const ui_locales_m1 = { "끄면 첫 계획선 · 계획선 짓기를 서버 Node 가 셈(조작 중 왕복)", "Off = server Node builds the first grade line · grade line (round trip while editing)", ], - B01_CalcSites_Why_pair_B4: ["짝 모으는 중 · B-4", "Merging pairs · B-4"], B01_CalcSites_Why_cross: [ "끄면 횡단을 서버 Node 가 셈(잠잠 뒤 · 왕복) · DTM 아닌 지표면 = 브라우저", "Off = server Node calcs cross sections (after a pause · round trip) · non-DTM surface = browser",