Files
Aislo/resources/tester/probe_unclosed_width.py
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

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

100 lines
4.2 KiB
Python

"""미교차 측점이 **얼마나 더 넓히면 닫히는지** — 저장분만으로 잰다(읽기 전용).
재는 법 — 저장된 `design_line` 의 바깥 끝(사면이 잘린 자리)에서 시작해, 지반이 **바깥 5m 평균
기울기**로 이어진다고 보고 설계 사면선과 만나는 거리를 푼다. 지형이 그대로 이어진다는 가정이라
**하한 추정**이며, 지형이 설계 사면보다 가파르면 「아무리 넓혀도 안 닫힘」으로 잡힌다.
"""
import asyncio
import json
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))
import config.config_db as db # noqa: E402
from config.config_system import STORAGE_BASE_DIR # noqa: E402
ROUTE_ID = 169
PROJECT = "5601e828-feea-487a-9b25-415e5199f2f5"
SAMPLES = Path(STORAGE_BASE_DIR) / "1" / "3" / PROJECT / "B06_Section" / "cross_sections"
def ground_of(chainage: float):
path = SAMPLES / f"cross_{int(round(chainage)):05d}m.json"
if not path.exists():
return None
doc = json.loads(path.read_text(encoding="utf-8"))
pts = [
(float(s["offset_m"]), float(s["elevation_m"]))
for s in doc.get("samples") or []
if s.get("valid") is not False and s.get("elevation_m") is not None
]
return sorted(pts) or None
def need_width(design_line, ground, ratio_fill, ratio_cut):
"""양 끝에서 필요한 추가 폭(m). 못 닫히면 None."""
out = {}
for side, pick in (("left", min), ("right", max)):
edge = pick(design_line, key=lambda p: p["offset_m"])
tip_x, tip_z = float(edge["offset_m"]), float(edge["elevation_m"])
near = [p for p in ground if abs(p[0] - tip_x) <= 5.0]
if len(near) < 2:
continue
run = near[-1][0] - near[0][0]
if abs(run) < 1e-6:
continue
terrain = (near[-1][1] - near[0][1]) / run # 바깥으로 갈수록 (+)면 오름
gap = tip_z - (near[-1][1] if side == "right" else near[0][1])
# 절토(사면이 올라감)면 1/cut, 성토(내려감)면 -1/fill. 바깥 방향 부호를 맞춘다.
rising = gap < 0 # 설계선이 지반보다 낮다 = 절토측
design = (1.0 / ratio_cut) if rising else (-1.0 / ratio_fill)
if side == "left":
design, terrain = -design, -terrain
closing = design - terrain
if abs(closing) < 1e-9 or (gap < 0) != (closing > 0):
out[side] = None # 벌어지기만 함 — 아무리 넓혀도 안 닫힘
continue
out[side] = abs(gap / closing)
return out
async def main() -> None:
await db.init_db_pool()
pool = db.get_db_pool()
rows = []
async with pool.acquire() as conn, conn.cursor() as cur:
await cur.execute("SELECT chainage_m, data FROM cross_sections WHERE route_id=%s", (ROUTE_ID,))
for chainage, data in await cur.fetchall():
doc = (json.loads(data) if isinstance(data, str) else data) or {}
design = doc.get("design") or {}
if not design.get("slope_unclosed"):
continue
ground = ground_of(float(chainage))
line = design.get("design_line") or []
if not ground or len(line) < 2:
continue
need = need_width(
line, ground, float(design.get("fill_slope_ratio") or 1.2),
float(design.get("cut_slope_ratio") or 1.0),
)
rows.append((float(chainage), need))
await db.close_db_pool()
never = [(c, s) for c, n in rows for s, v in n.items() if v is None]
finite = [(c, s, v) for c, n in rows for s, v in n.items() if v is not None]
print(f"미교차 측점 {len(rows)}곳")
print(f"· 아무리 넓혀도 안 닫히는 측·조합: {len(never)}건")
if finite:
finite.sort(key=lambda r: -r[2])
print(f"· 넓히면 닫히는 조합: {len(finite)}건 — 필요한 추가 폭 최대 {finite[0][2]:.1f}m, "
f"중앙값 {sorted(v for _, _, v in finite)[len(finite)//2]:.1f}m")
for row in finite[:6]:
print(f" {row[0]:8.1f}m {row[1]:5s} +{row[2]:.1f}m")
asyncio.run(main())