- E03_StabSection_WallCheck: Coulomb · Rankine · M-O 토압 · 상재하중 · 수압 · 양압력 · Meyerhof 지지력 · 평상시 / 지진 시 판정 - E03_StabSection_WhatIf: 한 쪽 비탈 1:n(+ 소단) 다시 그림 · 원지반 만나는 가장 완만한 n · 성긴 걸음 + 이분 최소 완화(완만할수록 늘 안전하지 않음) - E03_StabSection_Router_WhatIf: POST whatif · wallcheck · GET whatif · 저장본 _derived/stab2d_whatif(+ _view) · 원본 측점 · 요약 그대로 - E05: s8 보강검토(양식 · 자리표) · 저장본 없으면 시트 뺌 · 갱신 때 사라진 프로그램 몫 뺌 - 시험 test_536_3 12 · 534-3 양식 시험 시트 8(보강검토 빈 판 1쪽) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
"""E05 보고서 storage — 작업본 · 초기값 · 그림 · 옛 판(요구 347-5 · PLAN 534-3).
|
|
|
|
storage(E 프로젝트 루트):
|
|
E05_StabReport/report.json 작업본(Workbook · 서버 재계산 `계산값` 포함)
|
|
E05_StabReport/report_meta.json 해석 지문 · 만든 때 · 프로그램 몫 시트 서명(덮임 알림용)
|
|
E05_StabReport/fig/<열쇠>.png 그림(열쇠 = `fig/<측점 키>.png` · `fig/3d_band.png`)
|
|
E05_StabReport/_지난/<때>.json 갱신이 덮은 옛 판(덮인 시트에 사용자 고침이 있었을 때)
|
|
initial_snapshot/E05_StabReport/ 초기값 = 처음 만든 직후 한 벌(report · meta · fig)
|
|
|
|
시트 채우기 · 그림 SVG = `E05_StabReport_Build.py` · 데이터 3층 = CLAUDE.md 5장.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import shutil
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from common_util.common_util_json import atomic_write_json
|
|
from common_util.common_util_spreadsheet import with_server_values
|
|
from common_util.common_util_svg_png import svgs_to_pngs
|
|
from E05_StabReport.E05_StabReport_Build import (
|
|
META,
|
|
PROGRAM_SHEETS,
|
|
REPORT,
|
|
_read,
|
|
build_program,
|
|
fig_path,
|
|
fill_cover,
|
|
form,
|
|
gather,
|
|
initial_dir,
|
|
sheet_sign,
|
|
stage_dir,
|
|
)
|
|
|
|
|
|
def revision(project_root: Path) -> str | None:
|
|
path = stage_dir(project_root) / REPORT
|
|
return hashlib.sha1(path.read_bytes()).hexdigest()[:16] if path.is_file() else None
|
|
|
|
|
|
def load(project_root: Path) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
|
|
folder = stage_dir(project_root)
|
|
return _read(folder / REPORT), _read(folder / META)
|
|
|
|
|
|
def _write_figures(folder: Path, jobs: list[tuple[str, str]]) -> list[str]:
|
|
pngs = svgs_to_pngs([svg for _, svg in jobs])
|
|
target = Path(folder) / "fig"
|
|
if target.is_dir():
|
|
shutil.rmtree(target)
|
|
target.mkdir(parents=True)
|
|
for (key, _), png in zip(jobs, pngs):
|
|
fig_path(folder, key).write_bytes(png)
|
|
return [key for key, _ in jobs]
|
|
|
|
|
|
def _write(project_root: Path, book: dict[str, Any], meta: dict[str, Any]) -> None:
|
|
folder = stage_dir(project_root)
|
|
atomic_write_json(folder / REPORT, book)
|
|
atomic_write_json(folder / META, meta)
|
|
|
|
|
|
def _meta(book: dict[str, Any], inputs: dict[str, Any], figures: list[str]) -> dict[str, Any]:
|
|
return {
|
|
"fingerprint": inputs["fingerprint"],
|
|
"built_at": datetime.now().isoformat(timespec="seconds"),
|
|
"signs": {s["id"]: sheet_sign(s) for s in book["시트"] if s["id"] in PROGRAM_SHEETS},
|
|
"figures": figures,
|
|
}
|
|
|
|
|
|
def create(project_root: Path) -> dict[str, Any]:
|
|
"""처음 만듦 — 양식 전부 · 사용자 몫 처음 채움 · 초기값 한 벌. 돌려줌 = meta."""
|
|
inputs = gather(project_root)
|
|
book = form()
|
|
fill_cover(next(s for s in book["시트"] if s["id"] == "s1"), inputs)
|
|
jobs = build_program(book, inputs)
|
|
book = with_server_values(book)
|
|
folder = stage_dir(project_root)
|
|
figures = _write_figures(folder, jobs)
|
|
meta = _meta(book, inputs, figures)
|
|
_write(project_root, book, meta)
|
|
init = initial_dir(project_root)
|
|
if init.is_dir():
|
|
shutil.rmtree(init)
|
|
init.mkdir(parents=True)
|
|
for name in (REPORT, META):
|
|
shutil.copy2(folder / name, init / name)
|
|
shutil.copytree(folder / "fig", init / "fig")
|
|
return meta
|
|
|
|
|
|
def refresh(project_root: Path) -> dict[str, Any]:
|
|
"""[보고서 갱신] — 프로그램 몫 시트 · 그림만 다시. 사용자 몫 · 사용자가 더한 시트는 그대로.
|
|
덮인 시트에 사용자 고침이 있었으면 `overwritten`(시트 이름) · 옛 판 `_지난/`."""
|
|
book, meta = load(project_root)
|
|
if book is None:
|
|
return {"created": True, "overwritten": [], **create(project_root)}
|
|
inputs = gather(project_root)
|
|
fresh = form()
|
|
jobs = build_program(fresh, inputs)
|
|
built = {s["id"]: s for s in fresh["시트"] if s["id"] in PROGRAM_SHEETS}
|
|
signs = (meta or {}).get("signs") or {}
|
|
overwritten = [
|
|
s["이름"]
|
|
for s in book["시트"]
|
|
if s["id"] in built and signs.get(s["id"]) and sheet_sign(s) != signs[s["id"]]
|
|
]
|
|
folder = stage_dir(project_root)
|
|
if overwritten:
|
|
old = folder / "_지난"
|
|
old.mkdir(parents=True, exist_ok=True)
|
|
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
shutil.copy2(folder / REPORT, old / f"{stamp}.json")
|
|
have = {s["id"] for s in book["시트"]}
|
|
# 새 판에 없는 프로그램 몫(보강검토 — 저장본 없음)은 뺌
|
|
sheets = [
|
|
built.get(s["id"], s)
|
|
for s in book["시트"]
|
|
if s["id"] in built or s["id"] not in PROGRAM_SHEETS
|
|
]
|
|
sheets += [
|
|
built[sid] for sid in PROGRAM_SHEETS if sid in built and sid not in have
|
|
] # 지운 시트는 끝에 다시
|
|
book = with_server_values({**book, "시트": sheets})
|
|
figures = _write_figures(folder, jobs)
|
|
new_meta = _meta(book, inputs, figures)
|
|
_write(project_root, book, new_meta)
|
|
return {"created": False, "overwritten": overwritten, **new_meta}
|
|
|
|
|
|
def reset(project_root: Path) -> bool:
|
|
"""[초기화] — 초기값을 작업본에 덮어씀(재계산 아님) · 초기값 없으면 False."""
|
|
init = initial_dir(project_root)
|
|
if not (init / REPORT).is_file():
|
|
return False
|
|
folder = stage_dir(project_root)
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
for name in (REPORT, META):
|
|
shutil.copy2(init / name, folder / name)
|
|
if (folder / "fig").is_dir():
|
|
shutil.rmtree(folder / "fig")
|
|
shutil.copytree(init / "fig", folder / "fig")
|
|
return True
|
|
|
|
|
|
def save(project_root: Path, book: dict[str, Any]) -> None:
|
|
"""[저장] — 브라우저 `계산값` 은 버리고 서버 풀이로 · meta(지문 · 서명)는 그대로."""
|
|
clean = with_server_values(book)
|
|
atomic_write_json(stage_dir(project_root) / REPORT, clean)
|
|
|
|
|
|
def figures_of(project_root: Path, book: dict[str, Any]) -> dict[str, bytes]:
|
|
"""책이 가리키는 그림 열쇠 → PNG(있는 것만) — xlsx · PDF 내보내기용."""
|
|
folder = stage_dir(project_root)
|
|
out: dict[str, bytes] = {}
|
|
for sheet in book.get("시트") or []:
|
|
for fig in sheet.get("그림") or []:
|
|
path = fig_path(folder, fig["열쇠"])
|
|
if path.is_file():
|
|
out[fig["열쇠"]] = path.read_bytes()
|
|
return out
|