feat(M02): 산출근거 표번 — 도번과 같은 규칙 자동 채번 · 좌측 「산출근거」 목록

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168FQuoV7vDh5nhnSowW5cp
This commit is contained in:
2026-09-28 00:30:32 +09:00
co-authored by Claude Sonnet 5
parent 7a8d37d544
commit e31e1b0016
56 changed files with 220 additions and 61 deletions
+34 -2
View File
@@ -31,6 +31,7 @@ _BASIS_HEAD_STYLE = {
"테두리": {side: {"선": "thin"} for side in ("위", "아래", "왼", "오른")},
}
_NUMBER = re.compile(r"^구-(\d+)$")
_TABLE_NUMBER = re.compile(r"^표-(\d+)$")
_BAD_NAME = re.compile(r'[\\/:*?"<>|\x00-\x1f]|\.\.')
# 도각 없는 빈 CAD 문서의 기본 도면층 — 없으면 B07 CAD 가 못 엶("Cannot read properties
# of undefined (reading 'length')") · CAD 쪽도 없으면 새로 채우지만(2026-09-27 sub1 고침)
@@ -221,16 +222,47 @@ def basis_doc(column: str, old: Any = None) -> dict[str, Any]:
}
def _basis_docs() -> list[tuple[str, Any]]:
folder = FOLDER / "basis"
return [(p.stem, json.loads(p.read_bytes())) for p in sorted(folder.glob("[!.]*.json"))]
def _table_number(doc: Any) -> int:
found = _TABLE_NUMBER.match(str(doc.get("표번") or "")) if isinstance(doc, dict) else None
return int(found.group(1)) if found else 0
def ensure_basis(column: str, old: Any = None) -> bool:
"""산출근거 파일이 없으면 만듦(식 없는 새 문서라 `계산값` 없음 · 첫 [저장] 때 서버가 적음)."""
"""산출근거 파일이 없으면 만듦(식 없는 새 문서라 `계산값` 없음 · 첫 [저장] 때 서버가 적음).
표번 = 도번과 같은 규칙(있는 것 중 가장 큰 번호 + 1) · 만든 뒤로는 안 바뀜."""
path = _path("basis", column)
with _LOCK:
if path.is_file():
return False
atomic_write_json(path, basis_doc(column, old))
number = max((_table_number(doc) for _name, doc in _basis_docs()), default=0) + 1
doc = {**basis_doc(column, old), "표번": f"표-{number:02d}"}
atomic_write_json(path, doc)
return True
def basis_numbers() -> dict[str, str | None]:
"""`{열 id: 표번}` — 좌측 산출근거 줄이 한 번에 받음.
표번이 없는 옛 파일은 여기서 만나는 순서로 채움(도번 규칙과 같음)."""
docs = _basis_docs()
missing = [name for name, doc in docs if not (isinstance(doc, dict) and doc.get("표번"))]
if missing:
with _LOCK:
docs = _basis_docs() # 잠금 안에서 다시 읽음 — 그 사이 누가 채웠을 수 있음
number = max((_table_number(doc) for _name, doc in docs), default=0)
for name, doc in docs:
if isinstance(doc, dict) and not doc.get("표번"):
number += 1
doc["표번"] = f"표-{number:02d}"
atomic_write_json(_path("basis", name), doc)
docs = _basis_docs()
return {name: (doc.get("표번") if isinstance(doc, dict) else None) for name, doc in docs}
def _server_values(doc: Any) -> dict[str, Any]:
try:
return spreadsheet.with_server_values(doc)