fix(534-3): E05 길 저장 · 갱신 · 초기화가 옛 모듈을 부름 · 시트 서명 수 꼴 맞춤 · 화면 높이

- 길: asyncio.to_thread(build.save · refresh · reset) → store(나눈 storage 모듈) — PUT 500 고침
- 시트 서명: 수를 float 로 맞춤 — 화면(JS)을 거친 10.0 → 10 을 사용자 고침으로 잘못 알리던 덮임 알림 고침
- 화면: 본문 칸이 min-height 뿐이라 내장 엑셀 격자 높이 0 — 본문 높이 calc(100dvh - 머리)
- 그림: 우기 · 지진 임계면 점선(건기와 겹쳐도 보임) · 해석 방법 글 짧게(Bishop)
- 시험: 길(TestClient — 판 409 · 저장 · 갱신 · 그림 · xlsx · 초기화) · 서명 화면 왕복 같음 — 7 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 20:58:02 +09:00
co-authored by Claude Opus 5.5
parent e7afaa6664
commit 340e90d6cc
5 changed files with 74 additions and 11 deletions
+13 -2
View File
@@ -48,7 +48,7 @@ LOWEST_N = 5 # representative.json 이 없을 때 — 최소 안전율 하위 N
CASES = ("dry", "wet", "seismic")
SIDE_NAMES = {"cut": "깎기", "fill": "쌓기"}
METHOD_NAMES = {
"bishop": "Bishop 간편법",
"bishop": "Bishop",
"janbu": "Janbu 보정",
"janbu_corrected": "Janbu 보정",
"ordinary": "Fellenius",
@@ -589,5 +589,16 @@ def sheet_sign(sheet: dict[str, Any]) -> str:
"행": {k: v for k, v in (sheet.get("행") or {}).items() if v},
"열": {k: v for k, v in (sheet.get("열") or {}).items() if v},
}
text = json.dumps(body, ensure_ascii=False, sort_keys=True)
text = json.dumps(_numbers(body), ensure_ascii=False, sort_keys=True)
return hashlib.sha1(text.encode("utf-8")).hexdigest()
def _numbers(value: Any) -> Any:
"""수는 float 로 — 화면(JS)을 거치면 10.0 이 10 으로 옴(같은 값을 다르다고 보지 않게)."""
if isinstance(value, dict):
return {k: _numbers(v) for k, v in value.items()}
if isinstance(value, list):
return [_numbers(v) for v in value]
if isinstance(value, int | float) and not isinstance(value, bool):
return round(float(value), 9)
return value
+5 -2
View File
@@ -21,6 +21,7 @@ BAND_W, BAND_H = 684, 320
OK, NG, MUTED, INK = "#2E7D32", "#C00000", "#9E9E9E", "#222222"
CONDITION_COLORS = {"dry": "#D32F2F", "wet": "#1565C0", "seismic": "#7B1FA2"}
CONDITION_NAMES = {"dry": "건기", "wet": "우기", "seismic": "지진"}
SLIP_DASH = {"wet": "7 4", "seismic": "2 3"} # 건기 · 우기 임계면이 겹쳐도 둘 다 보이게
FONT = "font-family='Malgun Gothic, sans-serif'"
@@ -155,7 +156,9 @@ def section_svg(
# 임계 활동면
for slip in slips:
color = CONDITION_COLORS.get(slip["condition"], NG)
out.append(_poly(screen(slip["points"]), stroke=color, stroke_width="2.2"))
dash = SLIP_DASH.get(slip["condition"])
extra = {"stroke_dasharray": dash} if dash else {}
out.append(_poly(screen(slip["points"]), stroke=color, stroke_width="2.2", **extra))
# 치수 — 노폭 · 비탈 높이
if road:
r_off, l_off = road
@@ -206,7 +209,7 @@ def section_svg(
(
CONDITION_COLORS.get(slip["condition"], NG),
f"{name} 임계면 Fs {f:.2f}" if f else name,
"",
SLIP_DASH.get(slip["condition"], ""),
)
)
if wet:
+3 -3
View File
@@ -137,7 +137,7 @@ async def put_report(project_id: UUID, payload: SaveRequest) -> dict[str, Any]:
status_code=409, detail="다른 곳에서 보고서가 바뀌었습니다 — 다시 여세요."
)
try:
await asyncio.to_thread(build.save, root, payload.book)
await asyncio.to_thread(store.save, root, payload.book)
except RecalcError as error:
raise _recalc_errors(error) from error
return {"status": "success", **await asyncio.to_thread(_state, root)}
@@ -153,7 +153,7 @@ async def refresh_report(project_id: UUID, payload: RevRequest) -> dict[str, Any
status_code=409, detail="다른 곳에서 보고서가 바뀌었습니다 — 다시 여세요."
)
try:
result = await asyncio.to_thread(build.refresh, root)
result = await asyncio.to_thread(store.refresh, root)
except build.MissingInput as error:
raise HTTPException(status_code=409, detail=str(error)) from error
except (RecalcError, PrintError) as error:
@@ -169,7 +169,7 @@ async def refresh_report(project_id: UUID, payload: RevRequest) -> dict[str, Any
async def reset_report(project_id: UUID) -> dict[str, Any]:
root, _ = await _project(project_id)
async with _lock(project_id):
if not await asyncio.to_thread(build.reset, root):
if not await asyncio.to_thread(store.reset, root):
raise HTTPException(status_code=404, detail="초기값이 없습니다.")
return {"status": "success", **await asyncio.to_thread(_state, root)}
+1 -1
View File
@@ -6,7 +6,7 @@
.e05-page {
display: flex;
flex-direction: column;
height: 100%;
height: calc(100dvh - var(--app-header-h)); /* 본문 칸은 min-height 뿐 — 100% 가 안 풀림 */
min-height: 0;
box-sizing: border-box;
}