Merge remote-tracking branch 'origin/sub_desktop_1' into main_laptop_1 — 166-2 설계내역서 손질(Store docstring · import 양쪽 살림)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
— [단가 갱신] 때만 M01 로 새로 ·
|
||||
[저장] 때 책에 없는 새 줄만 더함(있는 줄 안 바꿈)
|
||||
user_values.json 사용자 값 {"materials": {자재 키: {applied?, supply?, quote?, quote_page?,
|
||||
pick?}}, "estimate": {…}}
|
||||
pick?}}, "estimate": {…},
|
||||
"bill": {"qty": {줄 키: 수}, "rows": [손 줄]}}(166-2 설계내역서 손질)
|
||||
— 고친 칸만(키가 있으면 그 값 · null = 빈칸 · 없으면 기본)
|
||||
|
||||
quotes/<자재 키>/ 견적서 파일(166-4 · 여러 개) — 폴더가 목록(키의 못 쓰는 글자는 %XX) ·
|
||||
@@ -25,6 +26,7 @@ from typing import Any
|
||||
from urllib.parse import unquote
|
||||
|
||||
from B09_Estimation.B09_Estimation_Calc_Base import SOURCES, SUPPLY
|
||||
from B09_Estimation.B09_Estimation_Calc_Bill import SECTIONS
|
||||
|
||||
FOLDER = ("B09_Estimation", "v1")
|
||||
BOOK = "price_book.json"
|
||||
@@ -86,6 +88,56 @@ def _num(value: Any) -> float | None:
|
||||
return float(value) if math.isfinite(value) else None
|
||||
|
||||
|
||||
def _text(value: Any) -> str:
|
||||
return str(value)[:200] if isinstance(value, (str, int, float)) else ""
|
||||
|
||||
|
||||
def _clean_ref(ref: Any) -> dict[str, Any] | None:
|
||||
"""손 줄 연결 — dg · sg = 호표 키 · logic = M01 로직(입력값 · 켜쌓기율 · 고름)."""
|
||||
if not isinstance(ref, dict):
|
||||
return None
|
||||
tab = ref.get("tab")
|
||||
if tab in ("dg", "sg") and isinstance(ref.get("key"), str) and ref["key"]:
|
||||
return {"tab": tab, "key": ref["key"]}
|
||||
if tab == "logic" and isinstance(ref.get("code"), str) and ref["code"]:
|
||||
given = ref.get("given") if isinstance(ref.get("given"), dict) else {}
|
||||
picks = ref.get("picks") if isinstance(ref.get("picks"), dict) else None
|
||||
return {"tab": "logic", "code": ref["code"], "name": _text(ref.get("name")),
|
||||
"given": {str(k): v for k, v in given.items() if isinstance(v, (str, int, float))},
|
||||
"rate": _num(ref.get("rate")), "picks": picks} # fmt: skip
|
||||
return None
|
||||
|
||||
|
||||
def clean_bill(raw: Any) -> dict[str, Any]:
|
||||
"""설계내역서 손질(166-2) — 수량 덮어쓰기(수만) · 손 줄(`hand:` 키 · 아는 공종만)."""
|
||||
raw = raw if isinstance(raw, dict) else {}
|
||||
qty = raw.get("qty") if isinstance(raw.get("qty"), dict) else {}
|
||||
codes = {code for code, _name, _parent in SECTIONS}
|
||||
rows = []
|
||||
for row in raw.get("rows") if isinstance(raw.get("rows"), list) else []:
|
||||
if not isinstance(row, dict) or not str(row.get("key") or "").startswith("hand:"):
|
||||
continue
|
||||
if row.get("section") not in codes:
|
||||
continue
|
||||
rows.append(
|
||||
{
|
||||
"key": str(row["key"]),
|
||||
"section": row["section"],
|
||||
"after": _text(row.get("after")),
|
||||
**{k: _text(row.get(k)) for k in ("name", "spec", "unit")},
|
||||
"qty": _num(row.get("qty")),
|
||||
"ref": _clean_ref(row.get("ref")),
|
||||
}
|
||||
)
|
||||
out: dict[str, Any] = {}
|
||||
kept = {str(k): _num(v) for k, v in qty.items() if _num(v) is not None}
|
||||
if kept:
|
||||
out["qty"] = kept
|
||||
if rows:
|
||||
out["rows"] = rows
|
||||
return out
|
||||
|
||||
|
||||
def clean_user(raw: Any) -> dict[str, Any]:
|
||||
"""사용자 값 다듬기 — 모르는 칸 버림 · 수 칸은 수 · null(빈칸)만."""
|
||||
raw = raw if isinstance(raw, dict) else {}
|
||||
@@ -116,7 +168,8 @@ def clean_user(raw: Any) -> dict[str, Any]:
|
||||
rates = given.get("rates")
|
||||
if isinstance(rates, dict):
|
||||
estimate["rates"] = {str(k): _num(v) for k, v in rates.items()}
|
||||
return {"materials": materials, "estimate": estimate}
|
||||
bill = clean_bill(raw.get("bill"))
|
||||
return {"materials": materials, "estimate": estimate, **({"bill": bill} if bill else {})}
|
||||
|
||||
|
||||
def read_user(project_root: str | Path) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user