Files
Aislo/common_util/common_util_spreadsheet.py
T
eomsangdonandClaude Opus 5.5 3ded39875f feat(M02): 면적산출 범위 → 도면 표 서버 — Node 번들 변환 · 표 길 · 면적 저장 때 도면 표 칸 글 맞춤 (41-1)
- common_util_spreadsheet_node.ts areaTable: 시트 · 범위 → CAD 표 객체(표시 글 = formatValue · cellStyleAt · 병합 → colSpan/rowSpan · 범위 밖 병합 잘라냄 · 숨긴 행열 뺌 · 가로 정렬 · 굵게 · 열 너비 · 행 높이 비율 · 칸 수 한도 · 칸 key 「시트id!A1」 · style.연결 빈 글)
- 파이썬은 부르기만(common_util_spreadsheet.area_tables · 새 M02_Area_Table.py)
- POST /api/m02/layers/{층}/area/{열}/table {시트, 범위, 글자크기} → {표, 시트, 범위, 값} · 저장 안 함 · 층 읽기 권한
- 면적 저장(Store.write · 비우기 · 복사 · 프로젝트 층 길) 뒤 그 열 도면 연결표마다 표 칸 글 다시 · 마지막 값 갱신 · 표 없으면 기록 둠

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
2026-10-04 22:45:09 +09:00

68 lines
3.0 KiB
Python

"""산출근거 스프레드시트(통합문서) 서버 재계산 — 화면과 같은 TS 엔진을 Node 로 돌리는 껍데기.
CLAUDE.md 5장 ② — 계산은 `A00_Common/spreadsheet/` 한 벌. 여기는 번들을 부르기만 함
(`npm run build:spreadsheet` → `config/spreadsheet_node/`). [저장] 때 브라우저가 보낸 `계산값` 은
버리고 이 결과를 정본으로 적음.
결과 한 권 = `{시트 id: {A1: {수|글|참|오류}}}`(식 칸만) · 수는 십진 글.
"""
from __future__ import annotations
import copy
from typing import Any
from common_util.common_util_node_bundle import ROOT, build_bundle, run_bundle_json
BUNDLE = ROOT / "config" / "spreadsheet_node" / "common_util_spreadsheet_node.js"
NPM_SCRIPT = "build:spreadsheet"
ENTRY = ROOT / "common_util" / "common_util_spreadsheet_node.ts"
SOURCES = (ROOT / "A00_Common" / "spreadsheet", ROOT / "ui_template" / "sheet")
class RecalcError(Exception):
"""번들 빌드 · 실행 실패 또는 엔진이 그 통합문서를 못 풂 — 저장을 막음(검산 없이 적지 않음)."""
def _stale() -> bool:
"""번들이 없거나 엔진 TS 원본보다 오래됐으면 참 — 낡으면 화면과 서버가 다른 값을 냄."""
if not BUNDLE.is_file():
return True
built_at = BUNDLE.stat().st_mtime
paths = [ENTRY, *(p for folder in SOURCES for p in folder.glob("*.ts"))]
return any(p.stat().st_mtime > built_at for p in paths)
def recalc_workbooks(books: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""통합문서 여럿 → 입력 차례 그대로 `계산값`. 하나라도 못 풀면 `RecalcError`."""
if _stale() and not build_bundle(NPM_SCRIPT):
raise RecalcError("스프레드시트 계산 번들을 못 만듦")
out = run_bundle_json(BUNDLE, NPM_SCRIPT, {"books": books})
if out is None:
raise RecalcError("스프레드시트 계산 번들 실행 실패")
values: list[dict[str, Any]] = []
for result in out["results"]:
if not result.get("ok"):
raise RecalcError(f"스프레드시트 계산 실패 — {result.get('까닭')}")
values.append(result["계산값"])
return values
def with_server_values(book: dict[str, Any]) -> dict[str, Any]:
"""[저장] 직전 — 브라우저 `계산값` 은 버리고 서버 풀이로 새로 적은 사본."""
clean = copy.deepcopy(book)
clean.pop("계산값", None)
clean["계산값"] = recalc_workbooks([clean])[0]
return clean
def area_tables(asks: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""면적산출 시트 · 범위 → CAD 표 객체(41-1 · 변환은 번들 `areaTable` 한 벌) — 물음 차례 그대로
`{ok, 표, 시트, 범위, 값}` 또는 `{ok: False, 까닭}`. 번들을 못 돌리면 `RecalcError`."""
if _stale() and not build_bundle(NPM_SCRIPT):
raise RecalcError("스프레드시트 계산 번들을 못 만듦")
out = run_bundle_json(BUNDLE, NPM_SCRIPT, {"tables": asks})
if out is None:
raise RecalcError("스프레드시트 계산 번들 실행 실패")
return out["tables"]