"""절토 경사를 **사용자가 직접 넣는** 길이 끊기지 않는지 (2026-09-07 사용자 지시). 사용자 원문 — 「대신 경고부분은 삭제해주고 대신 각도를 사용자가 넣을수 있게 반영. 전체 공통으로 변경하는 경우에는 기본값 지정으로 하면 되지만 횡단도 하나만 변경하는 폼은 가져야함」 및 「개별 횡단도에는 암 절토 각도의 개별 수정 가능해야함 / 전체 변경을 위해서는 좌측 패널의 표준 횡단면 설정을 이용. (표준 횡단면 설정으로 변경시 사용자가 기본값을 사용하지 않는 값들은 변경되면 안됨.)」 ⚠ **경사는 「나르는 값」이 아니라 「기하 입력」이다.** 계산이 끝난 결과에 키만 베껴 붙이면 설계선은 옛 경사로 그려지고 숫자만 새것이 되어 어긋난다 — 소단에서 겪은 자리와 같다. 그래서 여기서는 ① 값이 실제로 **계산에 들어가** 절토 면적이 달라지는지 ② 부르는 자리마다 계산 **전에** 넘어가는지를 함께 지킨다. """ import re import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402 from B06_Section.B06_Section_Router_Design import ( # noqa: E402 USER_TOUCHED_KEYS, stored_cut_slope, ) from config.config_system import STANDARD_CROSS_SECTION # noqa: E402 _B06 = PROJECT_ROOT / "B06_Section" _REFRESH = _B06 / "B06_Section_Cross_Refresh.ts" _CARD = _B06 / "B06_Section_UI_Cross_CutSlope.ts" _CHROME = _B06 / "B06_Section_UI_Cross_Card_Chrome.ts" _ROUTER_DESIGN = _B06 / "B06_Section_Router_Design.py" def _ground(slope: float) -> list[dict]: """가로 -12~+12m 를 0.5m 간격으로 훑은 비탈 지반선.""" samples = [] offset = -12.0 while offset <= 12.0001: samples.append( { "offset_m": round(offset, 3), "elevation_m": round(100.0 + slope * offset, 4), "valid": True, } ) offset += 0.5 return samples def _design(**options): return compute_cross_design( _ground(-0.5), 99.8, ground_type="ripping_rock", section_mode="left_cut", ditch_side="left", standard=STANDARD_CROSS_SECTION, rock_boundary_offset_m=-1.0, **options, ) def test_넣은_경사가_실제로_그려진다(): """표준 1:0.4 → 사용자 1:1.0. 경사비도 절토 면적도 함께 달라져야 한다.""" base = _design() changed = _design(cut_slope_ratio=1.0) assert base["cut_slope_ratio"] == 0.4, "표준 절토 경사가 바뀐 듯 — 전제 확인 필요" assert changed["cut_slope_ratio"] == 1.0, "넣은 경사가 설계에 안 들어감" # 사면이 눕는 만큼 더 파므로 절토 면적이 커진다 — 숫자가 그대로면 그림만 바뀐 것이다. assert changed["cut_area_m2"] > base["cut_area_m2"], "경사를 눕혔는데 절토량이 그대로임" def test_무릎_위_토사_경사는_그대로다(): """사용자가 넣는 것은 **암 절토각**이다 — 무릎 위(토사)는 표준값 그대로 간다.""" changed = _design(cut_slope_ratio=1.0, two_stage_slope=True) assert changed["soil_cut_slope_ratio"] == STANDARD_CROSS_SECTION["soil"]["cut_slope_ratio"] def test_0_은_표준값으로_되돌림이다(): """되돌리기(↺)는 세션에서 지우는 게 아니라 **0 을 남긴다** — 지우면 정본의 옛 값이 되살아나 표준으로 못 돌아간다.""" assert stored_cut_slope({"cut_slope_ratio_user": 0}) is None assert stored_cut_slope({"cut_slope_ratio_user": 0.8}) == 0.8 assert stored_cut_slope({}) is None def test_다시_계산해도_사용자_값이_남는다(): """표준 횡단면 설정을 바꿔도 개별로 고친 측점은 그대로 (사용자 원문 끝줄).""" assert "cut_slope_ratio_user" in USER_TOUCHED_KEYS assert '"cut_slope_ratio_user"' in _REFRESH.read_text(encoding="utf-8") def test_계산_전에_넘어간다(): """⚠ 이 시험이 이 건의 요지다 — 값을 계산 **인자**로 넘기는지. 재계산 경로(포장 강제·세월교 하강·선형 재계산)가 모두 인자로 받아야 한다. 한 곳이라도 빠지면 그 경로에서만 옛 경사로 그려지고 값만 새것이 된다. """ router = _ROUTER_DESIGN.read_text(encoding="utf-8") assert router.count("cut_slope_ratio=") >= 3, "재계산 경로 중 값을 안 넘기는 곳이 있음" refresh = _REFRESH.read_text(encoding="utf-8") assert "cutSlopeRatio: cutSlopeAt(" in refresh, "브라우저 재계산이 값을 안 넘김" # 계산이 끝난 뒤 베껴 붙이는 꼴이면 안 된다. assert not re.search(r"design\.cut_slope_ratio\s*=", refresh), "계산 뒤에 값만 덮어쓰고 있음" def test_각도와_경사비가_서로_바뀐다(): """화면은 각도(°), 속은 경사비(1:n). 1:0.4 = 68.2° · 1:1.0 = 45°.""" source = _CARD.read_text(encoding="utf-8") assert "ratioToDegrees" in source and "degreesToRatio" in source # 45°는 1:1 — 두 함수가 서로의 역이어야 한다(코드에 그 식이 있는지). assert "Math.atan(1 / Math.max(ratio, 1e-6))" in source assert "1 / Math.tan((clamped * Math.PI) / 180)" in source def test_칸은_암_측점에만_선다(): """토사 측점은 암반이 없어 암 절토각이 쓰이지 않는다(2026-09-07 사용자 확정).""" chrome = _CHROME.read_text(encoding="utf-8") assert 'cutSlope && section.design?.geometry_preset === "rock"' in chrome # ── 지반유형 버튼 정리 (2026-09-07 사용자 확정) ──────────────────────────────── # 「리핑암과 발파암 버튼을 삭제하면서 구분의 의미가 없어졌어. … 토사버튼만 존재하고 이값은 # 활성화/비활성화로 반영(기본값은 비활성화)」 — 켜면 토사, 끄면 암이다. _DESIGN_UI = _B06 / "B06_Section_UI_Cross_Design.ts" def test_지반유형은_토사_토글_하나다(): source = _DESIGN_UI.read_text(encoding="utf-8") assert "GROUND_OPTIONS" not in source, "지반유형 버튼 셋이 남아 있음" assert "groundToggle" in source, "토사 토글이 없음" # 저장값은 종전 그대로 — 끔 = ripping_rock, 켬 = soil. assert 'GROUND_ROCK_DEFAULT: GroundType = "ripping_rock"' in source def test_기본은_암이다(): """사용자 원문 「기본값은 비활성화」 — 설계가 없는 측점도 암으로 선다.""" source = _DESIGN_UI.read_text(encoding="utf-8") assert "design?.ground_type ?? GROUND_ROCK_DEFAULT" in source def test_옛_발파암_자료도_암으로_읽힌다(): """버튼은 없앴지만 저장분에 남은 `blasting_rock` 은 그대로 암 기하로 계산된다.""" from config.config_system_design import SECTION_GROUND_TYPE_PRESET assert SECTION_GROUND_TYPE_PRESET["blasting_rock"] == "rock" assert SECTION_GROUND_TYPE_PRESET["ripping_rock"] == "rock"