Files
Aislo/resources/tester/helper_b06_fill_slope_length.py
eomsangdonandClaude Opus 5 265ba8352e fix(tester): 옮긴 시험이 옛 경로(tmp/tests)를 부르던 것 바로잡음
시험을 `resources/tester` 로 옮긴 뒤 **한 건이 실패**하고 있었음 —
`test_preview_cost` 가 짝 파일을 `tmp.tests.…` 로 불러 못 찾았음(옮긴 폴더에 없음).

- `from resources.tester.test_preview_assets import …` 로 고침
- 실행 안내 문구 셋도 새 경로로(`diag_structures` · `helper_b06_fill_slope_length` ·
  `test_common_util_crs`)
- 정책 시험 설명은 **사실이 바뀌었으므로** 고쳐 적음 — 「git 밖이라」가 아니라
  「옮겨서 함께 건너가되, 까닭은 여전히 값 옆(정본)에 두는 것이 옳다」

`resources/tester/` 1183 통과 / 22 건너뜀 / **실패 0**.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 18:23:46 +09:00

115 lines
4.5 KiB
Python

"""B06 성토사면 경사길이 교차검증 헬퍼 (2026-09-03).
화면(`B06_Section_UI_Cross_Fit.fillSlopeLengths`)이 낸 값을 **API 원본으로 다시 계산해**
대조한다. 프론트 구현과 독립적으로 설계선·지반선만 보고 재는 것이 목적이라 pytest 가
아니라 실행 스크립트다.
1) 공용 브라우저에서 `/api/projects/{pid}/sections/{route}/detail` 응답을 파일로 저장
2) ./venv/Scripts/python.exe resources/tester/helper_b06_fill_slope_length.py <그 파일> [측점...]
「≥」는 사면이 설계선 끝(계산 반폭)까지 원지반을 못 만난 측점 — 거기까지의 하한값이다.
허용 오차 — 화면은 교차 판정에 2cm 허용오차(`MEET_TOLERANCE_M`)를 쓰고 여기서는 부호
전환만 보므로, 사면이 지반과 나란히 붙는 자리에서 최대 0.15m 차이가 난다(2026-09-03 실측).
"""
import json
import math
import pathlib
import sys
def interp(points):
"""(offset, elevation) 목록의 선형보간 함수와 정의역 양 끝을 준다."""
pts = sorted(points, key=lambda t: t[0])
def at(x):
if x <= pts[0][0]:
return pts[0][1]
if x >= pts[-1][0]:
return pts[-1][1]
lo, hi = 0, len(pts) - 1
while hi - lo > 1:
mid = (lo + hi) // 2
if pts[mid][0] <= x:
lo = mid
else:
hi = mid
(x0, z0), (x1, z1) = pts[lo], pts[hi]
return z0 if x1 == x0 else z0 + (z1 - z0) * (x - x0) / (x1 - x0)
return at, pts[0][0], pts[-1][0]
def slope_starts(design):
"""좌·우 사면 시작 오프셋 — 노견 끝(그 측에 측구가 있으면 측구 바깥)."""
edges = design["road_edges"]
left = max(edges["left"]["offset_m"], edges["right"]["offset_m"])
right = min(edges["left"]["offset_m"], edges["right"]["offset_m"])
spec = design.get("ditch") or {"type": "none"}
width = 0.0
if design.get("ditch_enabled") is not False and spec.get("type") != "none":
width = spec["top_width_m"] if spec["type"] == "standard" else spec["width_m"]
return {
"left": left + (width if design.get("ditch_side") == "left" else 0.0),
"right": right - (width if design.get("ditch_side") == "right" else 0.0),
}
def fill_slope_lengths(section, step=0.005):
"""성토측 사면 경사길이 — {side: (길이 m, 미교차 여부)}."""
design = section["design"]
ground_at, _, _ = interp(
[
(p["offset_m"], p["elevation_m"])
for p in section["samples"]
if p.get("valid") is not False and p.get("elevation_m") is not None
]
)
design_at, line_min, line_max = interp(
[(p["offset_m"], p["elevation_m"]) for p in design["design_line"]]
)
starts = slope_starts(design)
slant = math.hypot(1, 1 / design["fill_slope_ratio"])
result = {}
for side in ("left", "right"):
if design["section_mode"] in ("both_cut", f"{side}_cut"):
continue
outward = 1 if side == "left" else -1
start = starts[side]
limit = line_max if side == "left" else line_min
previous = design_at(start) - ground_at(start)
offset, meet = start, None
while (limit - offset) * outward > 0:
offset = start + outward * min(abs(offset - start) + step, abs(limit - start))
diff = design_at(offset) - ground_at(offset)
if previous != 0 and (diff > 0) != (previous > 0):
# 부호가 뒤집힌 두 걸음 사이를 선형보간한 자리가 사면 끝이다.
meet = offset - outward * step + outward * step * (previous / (previous - diff))
break
previous = diff
end = limit if meet is None else meet
result[side] = (abs(end - start) * slant, meet is None)
return result
def main(argv):
detail = json.loads(pathlib.Path(argv[1]).read_text(encoding="utf-8"))
want = {round(float(value), 1) for value in argv[2:]}
for section in detail["cross_sections"]:
if not section.get("design"):
continue
chainage = round(section["chainage_m"], 1)
if want and chainage not in want:
continue
lengths = fill_slope_lengths(section)
if not lengths:
continue
text = " ".join(
f"{side}={'≥' if open_ else ''}{length:.2f}m" for side, (length, open_) in lengths.items()
)
print(f"ch={chainage:8.1f} {section['design']['section_mode']:10s} {text}")
if __name__ == "__main__":
main(sys.argv)