"""M02 단가산출(SK) 보기 넷 = 셈 답 하나(PLAN 750-1 · 요구 588-1) — 화면 셈 길 답 덧칸. - `글` = 읽는 식(M01 `/text` 와 같은 꼴 · `master_text.lines` + `master_sheet_line.annotate`) · SK 고친 줄(`_Over`) · 같은 입력(자동값 + 입력값) · 같은 고름(`choosing`) 위에서 지음. 줄마다 `cid` = SK 줄 cid(`annotate` 와 같은 차례 — 호표 줄 × `_shares` 비목 · 뒤 덧줄). 금액 · 소계 · 계 = SK `줄` · `기준` 그대로(`finish` — 별도계상 뺌 · 켜쌓기율 · 단위 환산 ÷배 · 못 셈 줄 까닭) · 식 글(`글` · `이름글`)은 바탕 단위 셈 그대로 + 머리 `나눔`(배 ≠ 1). - `중간` = 시험 계산 중간값(수 · 글만) — 시험 계산 = {lines: `줄`, sums: `기준`, middle: `중간`}. 계산 자리 = 서버 단독(CLAUDE.md 5장 ③) · 화면은 그리기만. """ from __future__ import annotations from decimal import Decimal from typing import Any from M02_MasterTemplete import M02_Labor_Doc as labor mf = labor.m01.cm.mf import master_sheet_line as msl # noqa: E402 — scripts 길은 `m01` 이 둠 import master_text as mtx # noqa: E402 _ERRORS = (mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError) def text_of(over: Any, key: str, values: dict, picks: Any, ids: list, eng: list) -> dict: """`{글, 중간}` — `eng` = `run_line` 줄(별도계상 빼기 전 · 차례 맞춤용) · 못 지음 = 까닭.""" row = over.logic(key) auto = {k: v for k, v in labor.m01.mau.values(over, row).items() if v is not None} given = {**auto, **labor.m01._dec(values or {})} try: with mf.mp.choosing(picks or None): text = msl.annotate(over, key, given, mtx.lines(over, key, given)) mid = mf.run(over, key, given).get("중간") or {} except _ERRORS as e: return {"글": {"ok": False, "reason": str(e) or type(e).__name__}, "중간": {}} if text.get("ok") and "groups" in text and "결과" not in row: _tag(row, ids, eng, text) keep = (Decimal, int, float, str, bool) return {"글": text, "중간": {k: v for k, v in mid.items() if isinstance(v, keep)}} def _tag(row: dict, ids: list, eng: list, text: dict) -> None: """글 줄마다 `cid` — `annotate` 와 같은 차례(호표 줄 × 몫 비목 → 덧줄).""" slots = {g["비목"]: iter(g["줄"]) for g in text["groups"]} items, adds = row.get("호표") or [], row.get("덧줄") or [] for n, line in enumerate(eng): if n < len(items): parts = [p for p, _ in mtx._shares(line)] if line.get("비목") else [] else: parts = [adds[n - len(items)]["비목"]] if n - len(items) < len(adds) else [] for part in parts: one = next(slots.get(part, iter(())), None) if one is not None and n < len(ids): one["cid"] = ids[n] def finish(ans: dict) -> dict: """화면 답(`per_unit` 뒤) — 글 금액 · 소계 · 계 = SK 줄 · 기준 · SK 에 없는 줄(별도계상) 뺌.""" text = ans.get("글") or {} base = ans.get("기준") if not text.get("ok") or "groups" not in text or not base: return ans sk = {x["cid"]: x for x in ans.get("줄") or [] if x.get("cid")} groups = [] for g in text["groups"]: kept = [] for one in g["줄"]: line = sk.get(one.get("cid")) if line is None and one.get("cid"): continue # SK 에서 빠진 줄(별도계상) if line is not None: parts = line.get("비목") or {} money = parts.get(g["비목"]) if len(parts) > 1 else line.get("금액") one = {**one, "금액": money} if line.get("까닭"): one["까닭"] = line["까닭"] kept.append(one) if kept: groups.append({**g, "줄": kept, "소계": base.get(g["비목"])}) times = ans.get("화면배") head = {"나눔": times} if times not in (None, 1) else {} return {**ans, "글": {**text, "groups": groups, "계": base.get("계"), **head}}