feat(B09 일위대가): 고친 칸 덮개 셈 — 사용자 값 daega_cover · 블록 cover · 줄 cid · 파이썬·TS 짝 (PLAN 702-2)

- 덮개 자리 = B09 사용자 값(user_values.json · 화면 캐시와 같은 user) {블록 cover: {줄 cid: {qty · 노 · 재 · 경 단가}}} · 원본 층 · 사본 파일 안 건드림(브레인 승인)
- 블록 cover = 조합 S:사본 폴더 · 서고 L:폴더 · 사전조합 C:참조 · 품셈 P:로직+입력 해시 · 줄 cid = 줄 정체(코드 · 산근 로직 · 부른 대가 로직) + n번째 — 내용 해시 밖(호표 id 그대로)
- 셈 짝 Calc_Labor.py · .ts — 고친 칸만 셈 값 대신 · 줄 base(품셈 값) · covered · 짝 잃은 덮개 daega_orphans(지우지 않음) · 데이터 점검 알림
- [저장] = 서버가 덮개 입력값만 받아 다시 셈(Store.clean_cover 수만) · 노임 새 판 · [재적용] 뒤에도 남음
- B08 구조물도 카드용 고친 칸 수 cover_count
- 시험 test_702_b09_daega_cover(거울 · 판 바꿈 · 재적용 꼴 · 다듬기) · test_145_2 _ts 덮개 인자

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 12:41:22 +09:00
co-authored by Claude Opus 5.5
parent 935bd7d04e
commit 969d7f74f3
7 changed files with 401 additions and 23 deletions
+54 -5
View File
@@ -12,6 +12,9 @@
(155-3 · 사용자 뜻 = 일위대가화 줄 = 대가 · 단가산출 줄 = 산근 · 145 정함 뒤집음)
같은 블록 = 같은 꼴 → 한 호표(id = 내용 해시 · 같은 원본 + 같은 셈 단가 = 하나) ·
덮개 열쇠(702-2 · 내용 해시 밖 · 수량 · 단가가 바뀌어도 그대로) = 블록 `cover` + 줄 `cid` —
cover = 조합 `S:사본 폴더` · 서고 `L:폴더` · 사전조합 `C:참조` · 품셈 `P:로직 + 입력 해시`
cid = 줄 정체(비목 : 노임 · 자재 · 중기 코드 · 산근 · 부른 대가 로직 · 참조) + `#n번째`
줄 수량 = 사본 셈 수량(손 · 면적 알약 칸) — 대가 줄만 ÷ 배(부르는 호표 1 단위 몫 ·
산근 단가는 이미 1 단위).
단가 · 금액 · 끝수는 셈 짝(`B09_Estimation_Calc_Labor`)이 B09 프로젝트 한 벌 단가로 냄.
@@ -78,12 +81,35 @@ class Builder:
self.blocks: dict[str, dict[str, Any]] = {}
self.gaps: list[dict[str, str]] = []
def put(self, block: dict[str, Any]) -> str:
def put(self, block: dict[str, Any], cover: str | None = None) -> str:
bid = f"DG:{_digest([block['kind'], block['origin'], block['lines']])}"
if bid not in self.blocks:
self.blocks[bid] = {"id": bid, **block}
mark = {"품셈": "P", "사전조합": "C"}.get(block["kind"], "S")
if cover is None:
origin = block["origin"]
cover = f"C:{origin['참조']}" if mark == "C" else f"{mark}:{_digest(origin)}"
lines = [{**ln, "cid": cid} for ln, cid in zip(block["lines"], self._cids(block))]
self.blocks[bid] = {"id": bid, "cover": cover, **block, "lines": lines}
return bid
def _cids(self, block: dict[str, Any]) -> list[str]:
"""줄 정체(702-2) — 같은 정체 n번째 = `#n` · 재적용으로 줄이 늘거나 바뀌어도 맞는 줄에."""
seen: dict[str, int] = {}
out = []
for line in block["lines"]:
kind, key = line["kind"], str(line.get("key") or "")
if kind == "daega" and key in self.blocks:
origin = self.blocks[key]["origin"]
key = str(origin.get("로직") or origin.get("참조") or key)
elif kind == "sangeun":
key = key.rsplit(":", 1)[0] # SG:로직:입력 해시 → 로직만
elif kind == "pct":
key = str(line.get("base") or "")
head = f"{kind}:{key or line.get('name') or ''}"
seen[head] = seen.get(head, -1) + 1
out.append(f"{head}#{seen[head]}")
return out
# ── 품셈(로직 하나) ─────────────────────────────────────────────────
def poomsem(self, whole: Any, one: dict[str, Any], title: str = "") -> str | None:
code = str(one.get("로직") or "")
@@ -245,7 +271,7 @@ class Builder:
)
# ── 조합(구조물 사본) ───────────────────────────────────────────────
def combo(self, folder: Path) -> str | None:
def combo(self, folder: Path, cover: str | None = None) -> str | None:
labor = read_json(folder / LABOR_FILE)
if not isinstance(labor, dict):
return None
@@ -280,13 +306,14 @@ class Builder:
"cite": "",
"origin": {k: origin.get(k) for k in ("layer", "owner", "key")},
"lines": lines,
}
},
cover or f"S:{folder.name}",
)
def library(self, folder: Path, manifest: dict[str, Any]) -> str | None:
"""설계내역서 서고 사본(204-2) — 구조물조합 = 조합 블록 · 사전조합 = 겹 대가 블록 그대로."""
if manifest.get("종류") != "precombo":
return self.combo(folder)
return self.combo(folder, f"L:{folder.name}")
labor = read_json(folder / LABOR_FILE)
ref = (manifest.get("원본") or {}).get("참조")
copies = (labor or {}).get("참조사본") or {}
@@ -340,6 +367,28 @@ def _plain(value: Any) -> Any:
return value
def cover_count(project_root: str | Path, folder: str) -> int:
"""702-4 B08 구조물도 카드 — 그 사본 블록 + 부른 블록에 붙은 B09 고친 칸 수(카드 = 사본 셈)."""
from B09_Estimation.B09_Estimation_Store import read_user
cover = read_user(project_root).get("daega_cover") or {}
if not cover:
return 0
got = daega_inputs(
project_root
) # ponytail: 프로젝트 한 벌 셈 — 카드 열 때만 · 느리면 사본 하나로
by_id = {b["id"]: b for b in got["blocks"]}
owners = {linked["폴더"]: o for o, linked in linked_snapshots(project_root).items()}
todo, seen = [got["owners"].get(owners.get(folder, ""))], set()
while todo:
block = by_id.get(todo.pop())
if block is None or block["id"] in seen:
continue
seen.add(block["id"])
todo += [ln.get("key") for ln in block["lines"] if ln["kind"] == "daega"]
return sum(len(cover.get(by_id[bid]["cover"]) or {}) for bid in seen)
def daega_inputs(project_root: str | Path) -> dict[str, Any]:
"""대가 셈 입력 — `{blocks: [...], owners: {구조물 id: 블록 id}}` · 사본 없으면 빈 것 ·
설계내역서 서고 사본(204-2)이 있으면 `libs` {폴더: 블록 id}.