"""M02 실무 호표 만든 일위대가 ↔ 엑셀 호표 단가 대조(PLAN 705-3 · 723-1 · 742-1 · 777-1 `M02_Hopyo_Build` 에서 떼어 냄). 참고 · 판정 아님 — 우리 = 엑셀 끝수 꼴(EST 비목 합 내림 · STmate 줄 반올림 · 계 = 줄 계 합 내림) 중 엑셀과 같은 쪽 · 수량 = 엑셀 수량(`본뜬.실무수량` · 하위 사전조합까지 `excel_given`). """ from __future__ import annotations from decimal import ROUND_FLOOR, ROUND_HALF_UP, Decimal from typing import Any from M01_MasterData import M01_MasterData_Store as m01 from M02_MasterTemplete import M02_Labor_Doc as labor from M02_MasterTemplete import M02_Labor_Ref as refs # 엑셀 끝수 꼴(705-3) — (줄 금액 0.1 · 합 원 자름, 계 = 줄 계 합에서) · EST = 비목 합 내림 · # STmate = 줄 반올림 · 계 = 줄 계 합 내림(비목 머리 값과 따로) CUTS = ((ROUND_FLOOR, False), (ROUND_HALF_UP, True), (ROUND_HALF_UP, False)) def _excel_round(got: dict[str, Any], how: str, by_line: bool) -> Decimal | None: """엑셀 끝수(705-3) — 줄 금액 비목마다 0.1(`how`) · 하위 호표 단가 = 원 · 비율 줄 = 기준 × 율 ÷ 100 · 합 = 비목마다 원(`how`) 더함 · `by_line` = 줄 계 합 원 내림 · 못 셈 줄 있으면 None.""" parts = dict.fromkeys(labor.COSTS, Decimal(0)) for one in got.get("안") or []: if one.get("기준") is None: return None cut = 100 if (one.get("고정") or {}).get("비율") else 1 for k in labor.COSTS: unit = Decimal(one["기준"][k]) unit = unit.to_integral_value(how) if "참조" in one else unit parts[k] += (unit * one["수량"] / cut).quantize(Decimal("0.1"), how) if by_line: return sum(parts.values()).to_integral_value(ROUND_FLOOR) return sum(v.to_integral_value(how) for v in parts.values()) def excel_given(ref: dict[str, Any], depth: int = 1) -> dict[str, Any]: """`본뜬.실무수량` → 안 줄 알약 값(723-1 · 하위 사전조합까지) — 엑셀 대조 전용.""" doc = refs.read_ref(ref) if depth <= refs.DEPTH else None if not isinstance(doc, dict): return {} rid, hands = refs.ref_id(ref), (doc.get("본뜬") or {}).get("실무수량") or {} out = {f"{rid}#{i}": v for i, v in (hands.get("줄") or {}).items()} out |= {f"{rid}#자재|{code}": v for code, v in (hands.get("자재") or {}).items()} for one in refs.refs_in(doc): out |= excel_given(one, depth + 1) return out def compare(ref: dict[str, Any], hopyo: dict[str, Any]) -> dict[str, Any]: """만든 사전조합 1 단위 값 ↔ 엑셀 호표 단가(참고 · 판정 아님) — 우리 = 엑셀 끝수 규칙(내림 · 반올림 중 엑셀과 같은 쪽 · `_excel_round`) · 수량 = 엑셀 수량(`excel_given` · 723-1).""" head = {k: ref[k] for k in ("층", "주인", "키")} got = refs.Expand(m01.loaded()[1], given=excel_given(head))(head) base = got.get("기준") or {} excel = (hopyo.get("금액") or {}).get("계") cuts = [_excel_round(got, *one) for one in CUTS] if base else [] cuts = [c for c in cuts if c is not None] cut = next( (c for c in cuts if abs(c - Decimal(str(excel or 0))) < 1), cuts[0] if cuts else None ) ours = float(cut if cut is not None else base["계"]) if "계" in base else None gap = round(ours - excel, 1) if ours is not None and excel is not None else None same = ours is not None and abs(ours - (excel or 0)) < 1 # 705-3 — 엑셀 금액 없음 = 0 return {"엑셀": excel, "우리": ours, "차이": gap, "같음": same, "까닭": got.get("까닭")}