feat(719-2): 공사 쪽 M02 단가산출(SK) — B09 대가 블록 · 산근 · [업데이트] · 내 자료집 SK

- Labor_Source: 굳힌 SK = 품셈 꼴 대가 블록(사본 단가.json 굳힌 키 줄 · M01 안 읽음) · 단가산출 속 일위대가 · SK 줄 = 그 대가 블록 부르는 줄 · 고정 줄 = fixed · 단가 고친 줄 = fixed(M02 셈 단가) · cover = P:+SK 이름표(SK 고쳐도 B09 덮개 그대로) · 줄 sk · poom(품셈 값) · 부르는 줄 수량 ÷ 굳힌 줄 배 · 사전조합 알약 = 굳힌 알약 · 서고 sangeun 사본
- Sangeun: SK 하위 로직 = 산근 · 단가산출 속 참조 걸음 · 결과단위 배 = 사본 줄에서(_unit(row) · unit_of(whole, code))
- Linked: 굳힌 SK 자원 키(굳힌 키 로직 줄 · 고정 이음)
- Refresh: 미리 보기 단가산출(바뀌는 SK 줄) · 서고 사본 원본판 다르면 다시 굳힘
- Bill_Library: 종류 sangeun(내 자료집 SK 불러오기) · changed · refresh_copy(같은 폴더 · 값 그대로 · 원본판 없는 옛 사본 그대로)
- Daega_Personal: 품셈 블록(굳힌 SK 포함) → 개인 층 SK(B09 품 · 단가 덮개 = SK 덮개 · 더한줄 수량 · 단가)
- M02_Item_Sangeun: 굳힌 SK 정체(굳힌 줄 차례 → 원본 cid)
- 화면: 일위대가 펼침 SK 줄 되돌림 [단가산출 값으로] · 곁 글 품셈 값 · 내 자료집 단가산출 글 · 업데이트 미리 보기 SK 줄 · 단가연결 내 것 단가산출(Labor_Api fetchLibrary 종류 넓힘)
- 시험 test_719_2_sk_project 5 통과 + 1 건너뜀(715-1b 엔진 단가 · 값묶음 뒤 자동 실행)
- test_155_5: _unit 인자 = 로직 줄로 바뀜(사본 줄에서 배 읽음) — 부르는 줄 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 16:09:53 +09:00
co-authored by Claude Opus 5.5
parent 1124cf26f6
commit ccabbad6fb
15 changed files with 637 additions and 60 deletions
+42 -6
View File
@@ -6,6 +6,9 @@
도면 · 면적 · 일위대가 · 단가 · 입력값 · 고른자재 첫 값)
precombo 사전조합 = `일위대가.json` {줄: {담은로직: [{참조, 수량: {손: 1}}]}, 참조사본} ·
`단가.json`(`M02_Item_Source.price_bundle` — 사본 단가 묶음 한 벌)
sangeun 단가산출(SK · 내 자료집 · 719-2) = precombo 와 한 꼴(참조사본 = 풀어 굳힌 SK)
[업데이트](719-2) = 원본판이 다른 사본만 같은 폴더에 다시 굳힘(`changed` · `refresh_copy` ·
값 그대로 · 원본판 없는 옛 사본 = 그대로).
사본 = 원본이 바뀌거나 지워져도 그대로(굳힘) ·
대가 블록 = `Labor_Source.daega_inputs`(`libs` {폴더: 블록 id}) ·
단가 수요 = `Linked.linked_resource_keys` · 줄 연결 = {tab: dg, key: 블록 id, lib: 폴더} ·
@@ -58,11 +61,11 @@ from M02_MasterTemplete.M02_Item_Source import (
)
LIBRARY = "library"
KINDS = ("combo", "precombo")
KINDS = ("combo", "precombo", "sangeun")
#: 폴더 이름 — 종류 · 층(project = 불러온 공사 일위대가 · 707-7) · 주인 ·
#: 키(영문 · 숫자 · `-` `_`) · 값 해시(263-2 · 없을 수 있음)
NAME = re.compile(
r"^(combo|precombo)@(system|company|personal|project)@[0-9-]+@[A-Za-z0-9_-]{1,40}(~[0-9a-f]{8})?$"
r"^(combo|precombo|sangeun)@(system|company|personal|project)@[0-9-]+@[A-Za-z0-9_-]{1,40}(~[0-9a-f]{8})?$"
)
@@ -87,11 +90,11 @@ def folder_name(
return name
def _precombo_files(layer: str, owner: str | None, key: str) -> dict[str, Any]:
def _precombo_files(layer: str, owner: str | None, key: str, kind: str = "precombo") -> dict:
ref = {"층": layer, "주인": owner, "키": key}
doc = refs.read_ref(ref)
if doc is None or doc.get("종류") != refs.TITLE["precombo"]:
raise LookupError(f"사전조합 {key} 이 {layer} 층에 없음")
if doc is None or doc.get("종류") != refs.TITLE[kind]:
raise LookupError(f"{refs.TITLE[kind]} {key} 이 {layer} 층에 없음")
line = {"참조": ref, "수량": {"손": 1}}
labor = labor_doc_rows({"이름": doc.get("이름"), "단위": doc.get("단위"), "담은로직": [line]})
return {
@@ -116,10 +119,43 @@ def _build(kind: str, layer: str, owner: str | None, key: str) -> tuple[dict, di
item = {"layer": layer, "owner": owner, "key": key}
item["name"] = item_name(item)
return build_snapshot(item), origin_of(item)
files = _precombo_files(layer, owner, key)
files = _precombo_files(layer, owner, key, kind)
return files, files.pop("_원본")
def _source_of(manifest: dict[str, Any]) -> tuple[str, str, str | None, str]:
"""manifest → (종류, 층, 주인, 키)."""
kind, origin = str(manifest.get("종류")), manifest.get("원본") or {}
if kind == "combo":
return kind, origin.get("layer"), origin.get("owner"), origin.get("key")
ref = origin.get("참조") or {}
return kind, ref.get("층"), ref.get("주인"), ref.get("키")
def changed(project_root: str | Path) -> list[tuple[Path, dict[str, Any], dict, dict]]:
"""원본판이 지금 원본과 다른 사본 `(폴더, manifest, 새 파일들, 새 원본 표지)` — 원본판 없는 옛
사본 · 원본 없음 = 뺌(그대로)."""
out = []
for folder, manifest in copies(project_root):
if not manifest.get("원본판"):
continue
try:
files, origin = _build(*_source_of(manifest))
except (LookupError, ValueError, KeyError):
continue
if source_version(files) != manifest["원본판"]:
out.append((folder, manifest, files, origin))
return out
def refresh_copy(project_root: str | Path, one: tuple) -> None:
"""`changed` 한 줄 → 같은 폴더에 다시 굳힘(값 · 가져온이 그대로 · 원본판 새로)."""
folder, manifest, files, origin = one
keep = {k: v for k, v in manifest.items() if k not in ("값", "원값")}
fresh = {**keep, "원본": origin, "원본판": source_version(files), "가져온날": now_text()}
_write(project_root, folder.name, files, fresh, manifest.get("값") or {})
# ── 운반 값 사본(263-2) ────────────────────────────────────────────────
def _logic(code: str) -> dict[str, Any]:
try: