- 329-1 새 M02_Hopyo_Grid(xls formatting_info 값 · 병합 · 열 폭 · 행 높이 · 서식표 · 틀고정 / xlsx 값만) · read_book 호표 · 줄 「행」 · Store 격자 저장 · 읽기 · 지움(실무자료/격자/<키>.json) · GET hopyo-grid/{key}(옛 파일 404) · Match 요약 .get
- 330-1 새 M02_Hopyo_Basis.read — 단산N 블록 이름 · 단위 · 행 · 출처(책 · 절) · 직종(우리 인력 이름 · 옛이름) · 금액 · 단산 줄 「근거」
- 330-2 Basis.ours · candidates(조항 1.0 · 앞 마디 0.5 · 건설 공통 · 토목 0.1 · 직종 0.5 · 단위 0.15 · 까닭) · match_line 이름 후보와 로직 키로 합침 · 상태 후보 그대로
- 시험 test_m02_hopyo_grid · test_m02_hopyo_basis 새로 · test_m02_hopyo_match 제3호표 구조물터파기 1순위 → 굴착(인력/토사)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
"""M02 일위대가 실무자료 저장(PLAN 324-1) — 층 폴더 `실무자료/<키>.json` ·
|
|
맞춤 기억 `실무자료/맞춤.json` · 엑셀꼴 격자 `실무자료/격자/<키>.json`(329-1 · 파일과 같이 지움).
|
|
|
|
파일 = 읽은 결과만(원본 엑셀 안 둠) `{키, 파일, 올린때, 올린이, 시트, 호표, 검산}` · 키 `HP-######`
|
|
(층마다 다음 번호) · 맞춤 기억 = `{줄: {줄 열쇠: {로직?|자재?|빼기?, 입력값, 환산, 확정}}}`.
|
|
층 종류(`layers.KINDS`) 밖 폴더 — 양식 목록 · 복사 · 프로젝트 씨앗에 안 섞임 · 권한은 길 몫.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import threading
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from common_util.common_util_json import atomic_write_json
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
|
|
DIRNAME = "실무자료"
|
|
MEMORY = "맞춤"
|
|
GRID = "격자"
|
|
NO_GRID = "격자 없음 — 다시 올리면 보임"
|
|
_KEY = re.compile(r"^HP-(\d{6})$")
|
|
# ponytail: 프로세스 안 잠금 — 서버를 여럿 띄우면 파일 잠금으로
|
|
_LOCK = threading.Lock()
|
|
|
|
|
|
def _dir(folder: str | Path) -> Path:
|
|
return Path(folder) / DIRNAME
|
|
|
|
|
|
def _path(folder: str | Path, key: str) -> Path:
|
|
if not _KEY.match(str(key)):
|
|
raise store.StoreError(404, f"없는 실무자료 「{key}」")
|
|
return _dir(folder) / f"{key}.json"
|
|
|
|
|
|
def _load(path: Path) -> Any:
|
|
try:
|
|
return json.loads(path.read_bytes())
|
|
except (OSError, ValueError):
|
|
return None
|
|
|
|
|
|
def _grid_path(folder: str | Path, key: str) -> Path:
|
|
return _dir(folder) / GRID / _path(folder, key).name
|
|
|
|
|
|
def brief(doc: dict[str, Any]) -> dict[str, Any]:
|
|
"""목록 한 줄 — `{키, 파일, 올린때, 올린이, 시트, 호표수, 어긋남}`."""
|
|
keep = ("키", "파일", "올린때", "올린이", "시트")
|
|
return {
|
|
**{k: doc.get(k) for k in keep},
|
|
"호표수": len(doc.get("호표") or []),
|
|
"어긋남": len((doc.get("검산") or {}).get("어긋남") or []),
|
|
}
|
|
|
|
|
|
def rows(folder: str | Path) -> list[dict[str, Any]]:
|
|
"""층 실무자료 목록(키 차례)."""
|
|
out = []
|
|
for path in sorted(_dir(folder).glob("HP-*.json")):
|
|
doc = _load(path)
|
|
if isinstance(doc, dict) and _KEY.match(path.stem):
|
|
out.append(brief(doc))
|
|
return out
|
|
|
|
|
|
def read(folder: str | Path, key: str) -> dict[str, Any]:
|
|
doc = _load(_path(folder, key))
|
|
if not isinstance(doc, dict):
|
|
raise store.StoreError(404, f"없는 실무자료 「{key}」")
|
|
return doc
|
|
|
|
|
|
def save(
|
|
folder: str | Path,
|
|
name: str,
|
|
parsed: dict[str, Any],
|
|
who: Any,
|
|
grid: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""읽은 결과(+ 격자) → 새 키로 씀 → 목록 한 줄."""
|
|
with _LOCK:
|
|
have = [
|
|
int(m.group(1)) for p in _dir(folder).glob("HP-*.json") if (m := _KEY.match(p.stem))
|
|
]
|
|
key = f"HP-{max(have, default=0) + 1:06d}"
|
|
doc = {
|
|
"키": key,
|
|
"파일": name,
|
|
"올린때": datetime.now().isoformat(timespec="seconds"),
|
|
"올린이": who,
|
|
**parsed,
|
|
}
|
|
if grid is not None:
|
|
book = {**grid["책"], "열": key}
|
|
atomic_write_json(_grid_path(folder, key), {**grid, "책": book})
|
|
atomic_write_json(_path(folder, key), doc)
|
|
return brief(doc)
|
|
|
|
|
|
def grid(folder: str | Path, key: str) -> dict[str, Any]:
|
|
"""엑셀꼴 격자 `{책, 호표, 단산}` — 없으면(옛 파일) 404."""
|
|
doc = _load(_grid_path(folder, key))
|
|
if not isinstance(doc, dict):
|
|
raise store.StoreError(404, NO_GRID)
|
|
return doc
|
|
|
|
|
|
def delete(folder: str | Path, key: str) -> None:
|
|
path = _path(folder, key)
|
|
with _LOCK:
|
|
if not path.is_file():
|
|
raise store.StoreError(404, f"없는 실무자료 「{key}」")
|
|
path.unlink()
|
|
_grid_path(folder, key).unlink(missing_ok=True)
|
|
|
|
|
|
# ── 맞춤 기억 ─────────────────────────────────────────────────────────
|
|
def memory(folder: str | Path) -> dict[str, dict[str, Any]]:
|
|
"""층 맞춤 기억 `{줄 열쇠: 확정}` — 없으면 빈 것."""
|
|
doc = _load(_dir(folder) / f"{MEMORY}.json")
|
|
got = doc.get("줄") if isinstance(doc, dict) else None
|
|
return got if isinstance(got, dict) else {}
|
|
|
|
|
|
def remember(folder: str | Path, entries: dict[str, dict[str, Any] | None]) -> dict[str, Any]:
|
|
"""기억 더함 · 바꿈(값 None = 지움) → 새 기억 전부."""
|
|
with _LOCK:
|
|
got = memory(folder)
|
|
for key, value in entries.items():
|
|
if value is None:
|
|
got.pop(key, None)
|
|
else:
|
|
got[key] = value
|
|
atomic_write_json(_dir(folder) / f"{MEMORY}.json", {"줄": got})
|
|
return got
|