feat(spreadsheet): F 서버 재계산 · 산출근거 따로 저장 · M02 끼우기 · 금빛 시험
- common_util_spreadsheet_node.ts · common_util_spreadsheet.py · build:spreadsheet — [저장] 때 화면과 같은 TS 엔진을 Node 로 돌려 계산값 정본(브라우저 값 버림 · 못 풀면 503) - M02 새 종류 basis — resources/master_template/basis/<열 id>.json(구조물집계표 열마다) · 층 복사 · 뼈대 검사 · 옛 구조물 문서 산출근거 49 벌 옮김(migrate_basis) · 새 열도 같이 만듦 - 상세 산출근거 카드 = 새 스프레드시트(import() 로 늦게 받음) · basis 읽기 · 저장 - ⚠ 임시 [스프레드시트 시험] 단추 — 빈 통합문서(시트 셋) · 저장 자리 tmp/spreadsheet_trial.json · 1단계 검증 뒤 지움 - 시험 resources/tester/spreadsheet/ — 저장 · 서버 다리 · 크기 예산 · M02 만 늦게 불러옴 · 실무 구조도 xlsx 세 벌 수식 2,699 칸 엑셀 캐시값 대조(엔진 빈 몸이면 건너뜀) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168FQuoV7vDh5nhnSowW5cp
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""산출근거 스프레드시트(통합문서) 서버 재계산 — 화면과 같은 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
|
||||
Reference in New Issue
Block a user