Files
Aislo/B09_Estimation/B09_Estimation_Refresh.py
T
eomsangdonandClaude Opus 5.5 ccabbad6fb 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>
2026-11-01 16:09:53 +09:00

125 lines
5.9 KiB
Python

"""B09 [업데이트] 한 곳(PLAN 703-3) — 단가 책 다시 굳힘 + 원본이 바뀐 구조물 사본 [재적용]을 함께.
미리 보기(`preview`) = 바뀌는 단가 줄(새 줄 + 바뀐 줄 · 지금 수요) · 다시 가져올 구조물 사본
(연결된 것 중 원본이 바뀐 것만 · 원본 없음 = 그대로) · 지킬 고친 칸(덮개 칸 수 · 702-4 —
재적용 · 새 판 뒤에도 남음) · 노임 판(굳힌 판 → 지금 M01 판 · 옛 책 = 굳힌 날)
적용(`reapply_changed` → 부르는 쪽이 수요 다시 → `prices.refresh_book`) — 사본 먼저(수요가 바뀜) ·
덮개(사용자 값)는 안 건드림 · 사본 하나가 막혀도 나머지는 함(막힌 것 = 알림).
M02 단가산출(SK · 719-2) — 구조물 · 서고 사본의 굳힌 SK 를 원본 층에서 다시 풀어 굳힘(같은 굳힌 키 ·
같은 cover → B09 덮개 · 입력값 · 고른 자재 그대로) · 서고 사본도 원본판이 다르면 다시 굳힘 ·
미리 보기 `단가산출` = 바뀌는 SK 줄 [{사본, 이름, 키, 판(새 · 바뀜 · 빠짐)}].
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
from B09_Estimation import B09_Estimation_Bill_Library as library
from B09_Estimation import B09_Estimation_Prices as prices
from B09_Estimation.B09_Estimation_Calc_Labor import COPY
from M02_MasterTemplete import M02_Item_Copy as item_copy
from M02_MasterTemplete import M02_Item_Sangeun as item_sk
from M02_MasterTemplete.M02_Item_Snapshot import MANIFEST_NAME, linked_snapshots, structures_dir
from M02_MasterTemplete.M02_Item_Source import LABOR_FILE, build_source, read_json
def changed_copies(root: str | Path) -> list[dict[str, Any]]:
"""연결된 구조물 사본 중 원본(층 · 마스터)이 바뀐 것 `[{폴더, 이름}]`."""
linked = {one["폴더"] for one in linked_snapshots(root).values()}
return [
{"폴더": it["폴더"], "이름": it["이름"] or it["폴더"]}
for it in item_copy.list_items(root)
if it["폴더"] in linked and it["마스터바뀜"]
]
def _sks(labor: Any) -> dict[str, dict]:
copies = (labor or {}).get("참조사본") or {}
return {rid: doc for rid, doc in copies.items() if item_sk.is_sangeun(doc)}
def sk_lines(old: Any, new: Any, where: str) -> list[dict[str, str]]:
"""사본 하나 전후 일위대가.json → 바뀌는 SK 줄(굳힌 줄 · 원본 SK 문서가 다름)."""
was, now = _sks(old), _sks(new)
out = []
for rid in sorted(set(was) | set(now)):
a, b = was.get(rid) or {}, now.get(rid) or {}
if all(a.get(k) == b.get(k) for k in (item_sk.ROW, "원본")):
continue
mark = "새" if not a else "빠짐" if not b else "바뀜"
out.append({"사본": where, "이름": (b or a).get("이름") or "", "키": rid.rsplit("|")[-1],
"판": mark}) # fmt: skip
return out
def sk_changes(root: str | Path, copies: list[dict], libs: list[tuple]) -> list[dict[str, str]]:
"""다시 가져올 구조물 사본(`changed_copies`) · 서고 사본(`library.changed`)의 바뀌는 SK 줄."""
out = []
for one in copies:
folder = structures_dir(root) / one["폴더"]
try:
fresh = build_source((read_json(folder / MANIFEST_NAME) or {})["원본"])
except (LookupError, ValueError, KeyError):
continue
out += sk_lines(read_json(folder / LABOR_FILE), fresh.get(LABOR_FILE), one["이름"])
for folder, manifest, files, origin in libs:
name = str(origin.get("name") or folder.name)
out += sk_lines(read_json(folder / LABOR_FILE), files.get(LABOR_FILE), name)
return out
def cover_cells(cover: dict[str, Any] | None) -> int:
"""고친 칸 수 — 덮개 칸(품 · 수량 · 단가) · 복사본 표지 뺌."""
return sum(
len(cell) for lines in (cover or {}).values() for cid, cell in lines.items() if cid != COPY
)
def edition_text(editions: dict[str, str] | None) -> str:
return " · ".join(f"{kind} {day[:7]}" for kind, day in sorted((editions or {}).items()))
def preview(
root: str | Path, book: dict[str, Any] | None, keys: list[str], cover: dict | None
) -> dict[str, Any]:
diff = prices.book_diff(book, prices.refresh_book(keys, book))
copies, libs = changed_copies(root), library.changed(root)
lib_names = [{"폴더": f.name, "이름": str(o.get("name") or f.name)} for f, _m, _f, o in libs]
before = (book or {}).get("labor_edition")
made = str((book or {}).get("made") or "")[:10]
return {
"단가줄": diff["new"] + diff["changed"],
"새줄": diff["new"],
"바뀐줄": diff["changed"],
"계수": diff["factor"],
"환율": diff["fx"],
"사본": copies + lib_names,
"단가산출": sk_changes(root, copies, libs),
"고친칸": cover_cells(cover),
"노임판": {
"전": edition_text(before) if before else (f"굳힌 날 {made}" if made else "안 굳힘"),
"후": edition_text(prices.labor_editions(keys)),
},
}
def reapply_changed(root: str | Path, who: dict[str, Any] | None = None) -> dict[str, list]:
"""원본이 바뀐 연결 사본 재적용 · 서고 사본 다시 굳힘 —
`{다시: [이름], 막힘: [이름 — 까닭]}`."""
done, stuck = [], []
for one in library.changed(root):
name = str(one[3].get("name") or one[0].name)
try:
library.refresh_copy(root, one)
done.append(name)
except (LookupError, ValueError, KeyError, OSError) as error:
stuck.append(f"{name} — {error}")
for one in changed_copies(root):
try:
item_copy.reapply(root, one["폴더"], who)
done.append(one["이름"])
except (LookupError, ValueError, KeyError, OSError) as error:
stuck.append(f"{one['이름']} — {error}")
return {"다시": done, "막힘": stuck}