- 새 `M02_Machine_Find` — 기종 = 기계표 이름 · 괄호 붙임 · 상세구분(낱말표 24 안 씀) · 소리 표기 한 글(로우더 = 로더 · 로울러 = 롤러 · 브레이카 = 브레이커 · 굴삭기 = 굴착기 · 램 머 = 래머 …) · 기계표 괄호 낱말로 좁힘(표에 없는 낱말 발파 · 리핑 = 안 봄) · 규격 = 수 + 단위 전부(범위 · 복수 · 인치 = ㎝) · 하나 = 이음 · 여럿 = 「기계 후보 n — 고르기」 · 0 = 「기계 못 찾음」 표지(가까운 규격으로 대신 잇지 않음) - 별칭 먼저 — 마스터 층 + 지금 층 별칭 장부 기계 짝(이름 + 규격 · 이름만 · 규격 속 할증 글 뺌) · `M02_Doc_Match_Note._ALIAS` 여섯 쌍 걷음(소리 표기 = 정규화 · 같은 기종 = 기계표 · 별칭 장부) - 조합 기계 = 기계마다 항목 하나(요구 738-3 · 제목 = 그 기계 · 줄 쌓기 차림 `M02_Doc_NativeLayout`) · 줄 둘(「+」 · 괄호 속 다른 기종 · 「…조합」 · 덤프트럭(자동덮개) = 덤프트럭 + 자동덮개시설) — 같은 수량 식 · 엑셀 값은 첫 줄 · 다음 줄 0 + 표지 「조합 기계 — 경비는 한 덩이」 · 우리 단가로 = 각자 시간당 경비 - 원파일 EST · STmate · 엑셀 읽기기 · 일위대가 호표(`Hopyo_Elem` · `Hopyo_Fixed._eqs` · `Hopyo_Row.m01_picks`)가 같은 찾기 · STmate 묶음 기계 줄도 시간당 경비 이음 - 문서 머리 `가져온.기계`(기호마다 판정 · 후보 · 찾기어) · 못 이은 기계 줄도 기계 줄(이름 단추 · `owned_of` 파이썬 · TS 짝) · 이름 단추 조회어 = 기종 이름만(규격 뺌) · 고르면 엑셀 값 기억 그대로(금액 안 바뀜) · 문서 [저장] = 고른 EQ → 이 층 별칭 장부 줄 → M01 별칭 장부 후보(승인 흐름 그대로) · 장부 짝 글 = 「기계 EQ…」 - 마스터 층 별칭 장부(resources/master_template/실무자료/맞춤.json) = 사용자 승인 다섯(요구 735-5 · 736) — 아스팔트페이버(피니셔) = 아스팔트 피니셔 · 디젤 엔진 = 엔진(디젤) · 워터제트 = 워터젯트 · 버킷준설기 = 버킷식준설기 · 바이브레이트 봉상후렉시블 = 콘크리트 진동기 엔진식 ø45(2.6㎾) - 시험 — 새 test_812_9(예 전부 · 정규화 · 괄호 · 범위 · 조합 둘 · 별칭 먼저 · 표지 · 고르면 별칭 줄 · 금액 전후) · test_812_3 전석쌓기 · 측구터파기 연암 항목을 기계마다 항목으로 고침(조합 기계 둘로 가름 · 요구 738-3 이 바꾼 까닭) - 전수 목록 docs/raw/요구_1104/_목록_812-9_기계이음.xlsx Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
482 lines
21 KiB
Python
482 lines
21 KiB
Python
"""M02 가져온 식(엑셀 불러오기) 단가산출 = 수식 문서(PLAN 802-4b-1 · 요구 664 · 665) — 서버 단독.
|
|
|
|
경쟁사 산근 블록도 같은 꼴(식 + 값)로 들어옴 — 엑셀 단가 그대로 엔진 줄(`가져온.로직` + SK 고침)을
|
|
`master_doc_convert` 로 문서로 옮기고(손 값 = 숫자 연결 · 중간 = 식 줄 · 덧줄 = 금액 줄 식 · 끝수 ·
|
|
단계 · 조정 · 합밖 · 자리 그대로) 이은 것을 연결로 얹음 · 연결마다 엑셀 값을 기억(`엑셀` 칸):
|
|
인력 · 재료 · 유가 · 전력 · 환율 이음 = 요소 {키} · 기계 이음(EQ) = 기계경비 단가산출
|
|
{키 GC, 입력 기계 …, 칸 = 그 줄 비목}(시간당 자료 없음 = 숫자 그대로 · 설명) ·
|
|
로직 이음 「로직(GC…)」 = 단가산출 {키, 입력} · 산근 속 산근(797-1 참조) = 단가산출
|
|
{키 형제 SK, 참조 {층, 주인}, 칸?} ·
|
|
소요량 · 계수 고른 것(797-2 `가져온.연결.고름`) = 그 수 자리에 표 연결 기호(pct · kgt = ÷ 배).
|
|
문서 머리 `가져온` = {파일, 시트, 행, 갈래, 코드?, 표지(797-1a · 못 읽음 까닭 그대로), 알림?}.
|
|
셈(`run`) — 단가기준 엑셀(기본) = 엑셀 값 기억 연결을 숫자로 보고 셈 · 우리 = 연결 값으로 셈 ·
|
|
형제 SK 는 늘 연결(부르는 쪽 단가기준으로 · 안쪽이 바뀌면 따라감).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy as cp
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
from M02_MasterTemplete import M02_Doc_Copy as dc
|
|
from M02_MasterTemplete import M02_Doc_Import_Shape as shape
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
|
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
|
|
|
XL = "엑셀" # 연결 칸 — 엑셀 값 기억
|
|
HEAD = (
|
|
"파일",
|
|
"시트",
|
|
"행",
|
|
"갈래",
|
|
"코드",
|
|
"원파일",
|
|
"원코드",
|
|
"규격",
|
|
"확정",
|
|
) # 뒤 셋 = 원파일(810-3)
|
|
GUESS = "추정" # 원파일 품셈 조건 글 표지(810-2 `M02_Doc_NativeEst.GUESS`)
|
|
SUB = "원파일 소계" # 원파일 소계 덧줄(810-2 `M02_Doc_NativeEst`) — 합 밖 · 비목 칸 비움
|
|
PICK = "소요량" # 고른 표 칸 기호 이름 머리
|
|
_SCALE = {"eq": (1,), "pct": (100, 0.01), "kgt": (1000, 0.001)}
|
|
|
|
|
|
def is_imported(sk: Any) -> bool:
|
|
return imp.is_imported(sk)
|
|
|
|
|
|
def basis_of(sk: dict) -> str:
|
|
return "우리" if (sk.get(imp.KEY) or {}).get("단가기준") == "우리" else "엑셀"
|
|
|
|
|
|
# ── 엑셀 단가 그대로 엔진 줄(옛 셈 길 · SK 고침 얹은 것) ───────────────────
|
|
def _ctx(sk: dict, whole: Any) -> tuple:
|
|
"""엑셀 단가 기준 고친 줄 — 옛 셈 길(`OLD`)로 돌려 엔진에 넣은 (색인, 키, 입력, 답, 재료)."""
|
|
from M02_MasterTemplete import M02_Doc_Sk as dsk
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
|
|
raw = {**sk, imp.KEY: {**sk[imp.KEY], "단가기준": "엑셀"}}
|
|
box: list = []
|
|
token = dsk.OLD.set(True)
|
|
try:
|
|
calc.run(sd.clean(raw, None, whole), whole, catch=box)
|
|
finally:
|
|
dsk.OLD.reset(token)
|
|
return box[-1]
|
|
|
|
|
|
# ── 소요량 · 계수 고른 칸 → 수 자리에 기호 ──────────────────────────────
|
|
def _swap_nth(node: Any, n: int, new: tuple, seen: list) -> Any:
|
|
"""`M02_Sangeun_QtyLinks._literals` 차례의 n 번째 수 → `new` 마디(같은 걸음 규칙)."""
|
|
mf = dc.cv.mf
|
|
if not isinstance(node, tuple):
|
|
return node
|
|
kind = node[0]
|
|
if kind == "num":
|
|
seen[0] += 1
|
|
return new if seen[0] - 1 == n else node
|
|
if kind == "bin":
|
|
cmp_ = node[1] in mf._OPS
|
|
sides = [
|
|
side
|
|
if cmp_ and isinstance(side, tuple) and side[0] == "num"
|
|
else _swap_nth(side, n, new, seen)
|
|
for side in node[2:]
|
|
]
|
|
return (kind, node[1], *sides)
|
|
if kind in ("neg", "field"):
|
|
return (kind, _swap_nth(node[1], n, new, seen), *node[2:])
|
|
if kind == "call":
|
|
return ("call", node[1], [_swap_nth(a, n, new, seen) for a in node[2]])
|
|
return node
|
|
|
|
|
|
def _conds(table: dict, at: int) -> dict | None:
|
|
"""표 줄 차례 → 그 줄 하나만 맞는 조건 {조건: 값}(범위 = 위 끝 › 아래 끝) · 못 가림 = None."""
|
|
rows = table.get("줄") or []
|
|
if not (0 <= at < len(rows)):
|
|
return None
|
|
row, out = rows[at], {}
|
|
for cond, how in (table.get("조건") or {}).items():
|
|
if cond not in row:
|
|
continue
|
|
have = row[cond]
|
|
if how == "범위":
|
|
low, high = have
|
|
have = high if high is not None else low
|
|
if have is None:
|
|
return None
|
|
out[cond] = have
|
|
try:
|
|
hit = dc.ml.find(table, out)
|
|
except Exception: # noqa: BLE001 — 못 가림 = 수 그대로
|
|
return None
|
|
return out if hit is row else None
|
|
|
|
|
|
def _picks(row: dict, ids: list[str], base: list[str], meta: dict, master: Any) -> dict:
|
|
"""고른 칸(`가져온.연결.고름`) → 중간 줄(표 찾기 하나 · 연결 기호) · 수 자리에 그 기호 ÷ 배 ·
|
|
돌려줌 = {중간 이름: 엑셀 칸 값}. 자리 줄이 SK 고침으로 바뀌었으면 건너뜀."""
|
|
mf, mee = dc.cv.mf, dc.cv.mee
|
|
src = meta["로직"]
|
|
picked = ((meta.get("연결") or {}).get("고름")) or []
|
|
out: dict[str, Any] = {}
|
|
mids = row.setdefault("중간", [])
|
|
taken = {str(x.get("이름")) for x in [*(row.get("입력") or []), *mids]}
|
|
for one in picked:
|
|
part, i, n = str(one.get("자리") or "::").split(":")[:3]
|
|
field = "수량" if part == "호표" else "식"
|
|
lines = src.get(part) or []
|
|
if not (i.isdigit() and n.isdigit() and int(i) < len(lines)):
|
|
continue
|
|
cid = base[int(i) + (len(src.get("호표") or []) if part == "덧줄" else 0)]
|
|
if cid not in ids:
|
|
continue
|
|
at = ids.index(cid)
|
|
mine = (row["호표"] if at < len(row["호표"]) else row.get("덧줄") or [])[
|
|
at if at < len(row["호표"]) else at - len(row["호표"])
|
|
]
|
|
if str(mine.get(field)) != str(lines[int(i)].get(field)):
|
|
continue # SK 덮개가 그 식을 바꿈 — 수 자리 어긋남
|
|
try:
|
|
table = master.get(str(one.get("표")))
|
|
conds = _conds(table, int(one.get("줄")))
|
|
node = mf.parse(str(mine[field]))
|
|
except Exception: # noqa: BLE001 — 표 · 식 못 읽음 = 수 그대로
|
|
continue
|
|
if conds is None:
|
|
continue
|
|
name = next(f"{PICK}{k}" for k in range(1, 999) if f"{PICK}{k}" not in taken)
|
|
taken.add(name)
|
|
scale = next(
|
|
(m for m in _SCALE.get(str(one.get("꼴")), (1,)) if _near(one, m)), None
|
|
) # 엑셀 수 = 칸 값 ÷ 배
|
|
if scale is None:
|
|
continue
|
|
ref = (
|
|
("name", name)
|
|
if scale == 1
|
|
else ("bin", "/", ("name", name), ("num", Decimal(str(scale))))
|
|
)
|
|
mine[field] = mee.raw_text(_swap_nth(node, int(n), ref, [0]))
|
|
args = ", ".join(f"{k}={_lit(v)}" for k, v in conds.items())
|
|
mids.append(
|
|
{"이름": name, "식": f"찾기({one['표']}{', ' if args else ''}{args}).{_q(one['칸'])}"}
|
|
)
|
|
out[name] = Decimal(str(one.get("값")))
|
|
return out
|
|
|
|
|
|
def _near(one: dict, m: float) -> bool:
|
|
cell, v = float(one.get("값") or 0), float(one.get("수") or 0)
|
|
return abs(cell - v * m) <= 1e-9 * max(1.0, abs(cell))
|
|
|
|
|
|
def _lit(v: Any) -> str:
|
|
if isinstance(v, (int, float, Decimal)) and not isinstance(v, bool):
|
|
return dc.cv.mf._plain(Decimal(str(v)))
|
|
return "'" + str(v).replace("'", "") + "'"
|
|
|
|
|
|
def _q(name: Any) -> str:
|
|
return dc.cv.mee._q(str(name))
|
|
|
|
|
|
# ── 이음 → 연결 ────────────────────────────────────────────────────────
|
|
def hourly_link(eq: str, part: str) -> dict | None:
|
|
"""기계(EQ) 시간당 그 비목 = 기계경비 단가산출 부름(`M02_Machine_Hourly` 와 같은 짝 고름) ·
|
|
자료 없음 = None."""
|
|
from M02_MasterTemplete import M02_Machine_Hourly as machine
|
|
|
|
if part not in imp.COSTS or not imp.hourly_of(eq).get("ok"):
|
|
return None
|
|
code = str(imp._whole().get(eq).get("원문번호") or "")
|
|
fit = [r for r in machine._family() if code in [str(c) for c in machine._codes(r)]]
|
|
if not fit:
|
|
return None
|
|
logic = min(fit, key=machine._rank)
|
|
given = {"기계": _lit(code)}
|
|
for spec in logic.get("입력") or []:
|
|
if spec.get("이름") != "기계":
|
|
given[spec["이름"]] = _lit((spec.get("고르기") or [""])[0])
|
|
return {"갈래": "단가산출", "키": str(logic["키"]), "입력": given, "칸": part}
|
|
|
|
|
|
def _logic_link(text: str) -> dict | None:
|
|
"""「로직(GC…, 이름='값')」 → 단가산출 연결 · 인자가 수 · 글 아니면 None."""
|
|
try:
|
|
node = dc.cv.mf.parse(text)
|
|
except Exception: # noqa: BLE001
|
|
return None
|
|
if node[0] != "logic" or any(v[0] not in ("num", "str") for _, v in node[2]):
|
|
return None
|
|
return {"갈래": "단가산출", "키": node[1], "입력": {k: _lit(v[1]) for k, v in node[2]}}
|
|
|
|
|
|
def _link_of(item: dict) -> tuple[dict | None, str]:
|
|
"""가져온 줄 하나의 연결(엑셀 값 기억 없이) · 설명(못 이은 까닭)."""
|
|
if item.get("참조"):
|
|
ref = item["참조"]
|
|
link = {
|
|
"갈래": "단가산출",
|
|
"키": ref["키"],
|
|
"참조": {"층": ref["층"], "주인": ref.get("주인")},
|
|
}
|
|
return link | ({"칸": item["참조비목"]} if item.get("참조비목") else {}), ""
|
|
key = str(item.get("이음") or "")
|
|
if not key:
|
|
return None, ""
|
|
if item.get("종류") == "로직":
|
|
return _logic_link(key), ""
|
|
if key.startswith("EQ"):
|
|
got = hourly_link(key, str(item.get("비목") or ""))
|
|
return got, "" if got else f"기계 {key} 시간당 경비 자료 없음 — 엑셀 단가 그대로"
|
|
link = {"갈래": "요소", "키": key}
|
|
if key[:2] == dc.cv.mm.LINKED: # 품셈재료 = 줄 단위로 값이 갈림(M01 변환과 같음)
|
|
link["단위"] = str(item.get("단위") or "")
|
|
return link, ""
|
|
|
|
|
|
MACHINES = "_기계" # 문서 셈 동안 잠깐 — `derive` 가 머리 `가져온.기계` 로 옮김
|
|
|
|
|
|
def _machine_entry(item: dict, sym: str) -> dict:
|
|
"""기계 줄 찾기 판정 한 칸 — {기호, 원래 {이름, 규격, 단위}, 판정, 키, 후보, 찾기어, 까닭, 조합?}."""
|
|
res = item["자원"]
|
|
got = res["찾기"]
|
|
one = {
|
|
"기호": sym,
|
|
"원래": {"이름": got["이름"], "규격": got["규격"], "단위": item.get("단위")},
|
|
}
|
|
one |= {k: got.get(k) for k in ("판정", "키", "후보", "찾기어", "까닭")}
|
|
return one | {k: True for k in ("조합", "조합첫") if res.get(k)}
|
|
|
|
|
|
def _excel(item: dict) -> Any:
|
|
if item.get("값묶음"):
|
|
return {k: Decimal(str(item["값묶음"].get(k) or 0)) for k in imp.COSTS}
|
|
return Decimal(str(item["단가"])) if item.get("단가") is not None else None
|
|
|
|
|
|
def _attach(doc: dict, row: dict, ids: list[str], src: dict[str, dict]) -> None:
|
|
"""금액 줄 단가 숫자(엑셀 값) → 이은 연결 + 엑셀 값 기억(SK 고침으로 바뀐 값 = 숫자 그대로) ·
|
|
기계 찾기 판정(812-9) = 문서 `가져온.기계`(단가 기호마다 · 화면 고르기 · 저장 때 별칭)."""
|
|
by_id = {dc.md.var_id(v): v for v in doc.get("변수") or []}
|
|
found: list[dict] = doc.setdefault(MACHINES, [])
|
|
for at, line in enumerate((doc.get("금액") or [])[: len(row["호표"])]):
|
|
item = src.get(ids[at]) if at < len(ids) else None
|
|
price = str(line.get("단가") or "")
|
|
var = by_id.get(price.removesuffix(".계"))
|
|
if item is not None and var is not None and (item.get("자원") or {}).get("찾기"):
|
|
found.append(_machine_entry(item, dc.md.var_id(var)))
|
|
if item is None or var is None or (var.get("연결") or {}).get("갈래") != "숫자":
|
|
continue
|
|
excel = _excel(item)
|
|
if not item.get("참조") and var["연결"].get("값") != excel:
|
|
continue # SK 덮개 단가 = 사용자 값
|
|
link, note = _link_of(item)
|
|
if note:
|
|
var["설명"] = note
|
|
if link is None:
|
|
continue
|
|
if item.get("참조비목") and isinstance(excel, dict):
|
|
excel = excel[item["참조비목"]]
|
|
line["단가"], line["비목"] = dc.md.var_id(var), item["참조비목"]
|
|
var["연결"] = {**link, XL: excel}
|
|
|
|
|
|
def _numbers(doc: dict, keep: dict) -> None:
|
|
"""수 하나뿐인 식 줄(EST 얼린 변수) → 숫자 연결 · 고른 칸 중간 → 엑셀 칸 값 기억."""
|
|
by_id = {dc.md.var_id(v): v for v in doc.get("변수") or []}
|
|
lines = []
|
|
for one in doc.get("식") or []:
|
|
var = by_id.get(str(one.get("기호")))
|
|
try:
|
|
node = dc.md.parse(one.get("식"))
|
|
except dc.md.DocError:
|
|
node = None
|
|
if var is not None and node is not None and node[0] == "num":
|
|
var["연결"] = {"갈래": "숫자", "값": node[1]}
|
|
continue
|
|
lines.append(one)
|
|
doc["식"] = lines
|
|
for var in doc.get("변수") or []:
|
|
if var.get("열쇠") in keep and (var.get("연결") or {}).get("갈래") == "표":
|
|
var["연결"][XL] = keep[var["열쇠"]]
|
|
|
|
|
|
def derive(sk: dict, ctx: tuple | None = None, whole: Any = None) -> dict:
|
|
"""가져온 식 SK → 수식 문서(키 = SK 키) — `ctx` = 엑셀 단가 기준 옛 셈 길이 엔진에 넣은 것."""
|
|
meta = sk[imp.KEY]
|
|
if ctx is None:
|
|
ctx = _ctx(sk, whole or imp._whole())
|
|
master, key, values, got, _ = ctx
|
|
row = cp.deepcopy(master.logic(key))
|
|
ids = list(got.get("정체") or [])
|
|
bare = imp.row_of({**sk, imp.KEY: {**meta, "단가기준": "엑셀"}}) or {}
|
|
base = sd.line_ids(bare)
|
|
src = dict(zip(base, meta["로직"].get("호표") or []))
|
|
keep = _picks(row, ids, base, meta, master)
|
|
given = dc.given_of(master, key, values)
|
|
doc, notes = dc.cv.convert(_Row(master, key, row), row, given)
|
|
_attach(doc, row, ids, src)
|
|
_numbers(doc, keep)
|
|
native = bool((meta.get("원본") or {}).get("원파일"))
|
|
if native or (meta.get("원본") or {}).get(
|
|
"엑셀"
|
|
): # 810-2 · 812-1 — 소계 비목 칸 비움 · 값 변수 출처
|
|
_native(doc, native)
|
|
head = {k: (meta.get("원본") or {}).get(k) for k in HEAD if (meta.get("원본") or {}).get(k)}
|
|
marks = list((meta.get("원본") or {}).get("표지") or [])
|
|
if native or (meta.get("원본") or {}).get(
|
|
"엑셀"
|
|
): # 810-3 — 품셈 조건 글(값 없음) = 「추정」 칸
|
|
head["추정"] = [
|
|
{"행": x.get("행"), "글": " ".join(str(x.get("글") or "").split())}
|
|
for x in meta["원본"].get("글줄") or []
|
|
if GUESS in str(x.get("표지") or "")
|
|
]
|
|
if not (doc.get("금액") or doc.get("결과") or row["호표"] or row.get("덧줄")):
|
|
_empty(doc) # 빈 산근 = 엑셀 0 원(줄 없음 합) — 「—」 아님
|
|
marks.append(EMPTY)
|
|
found = doc.pop(MACHINES, None)
|
|
head |= {"기계": found} if found else {} # 812-9 기계 찾기 판정(줄 쌓기 고르기 · 저장 때 별칭)
|
|
doc["가져온"] = head | {"표지": marks} | ({"알림": notes} if notes else {})
|
|
shape.shape(doc, meta) # 809-1 · 요구 704 — 지은 이름 → 엑셀 글 머리 · EST 기계 세 줄 = 한 줄
|
|
if found: # 세 줄을 한 줄로 합쳐 사라진 기호의 기계 판정은 뺌
|
|
alive = {dc.md.var_id(v) for v in doc.get("변수") or []}
|
|
doc["가져온"]["기계"] = [x for x in found if x["기호"] in alive]
|
|
return {**doc, "키": str(sk.get("키") or key), "이름": str(sk.get("이름") or doc.get("이름"))}
|
|
|
|
|
|
def _native(doc: dict, native: bool = True) -> None:
|
|
"""원파일 · 엑셀 읽기기 문서 — 소계 덧줄(합 밖) 비목 칸 비움(금액만) · 수 하나 변수(옵션 칸) 출처
|
|
「원파일」(엑셀 = 출처 없음 → 화면 꼬리표 「엑셀」)."""
|
|
for line in doc.get("금액") or []:
|
|
if line.get("출처") == SUB and line.get("합밖"):
|
|
line.pop("비목", None)
|
|
for var in doc.get("변수") or []:
|
|
if native and (var.get("연결") or {}).get("갈래") == "숫자" and XL not in var["연결"]:
|
|
var["출처"] = "원파일"
|
|
|
|
|
|
EMPTY = "참고 · 빈 산근 — 엑셀 줄 없음 · 금액 0 그대로"
|
|
|
|
|
|
def _empty(doc: dict) -> None:
|
|
"""줄 없는 엑셀 산근 — 0 묶음 줄 하나(엑셀 금액 0 = 줄 없는 합 · 지은 값 아님)."""
|
|
doc["변수"].append(
|
|
{"열쇠": "P_빈", "기호": "P", "아래": "빈", "위": "", "이름": "빈 산근", "단위": "",
|
|
"연결": {"갈래": "숫자", "값": dict.fromkeys(imp.COSTS, Decimal(0))}}
|
|
) # fmt: skip
|
|
doc["금액"] = [{"이름": "빈 산근", "단위": "식", "비목": "묶음", "단가": "P_빈"}]
|
|
|
|
|
|
class _Row:
|
|
"""변환 색인 — 이 SK 줄만 고친 줄(소요량 중간 더함)로 · 그 밖 그대로."""
|
|
|
|
def __init__(self, master: Any, key: str, row: dict) -> None:
|
|
self._m, self._k, self._r = master, key, row
|
|
|
|
def logic(self, ref: str) -> dict:
|
|
return self._r if ref == self._k else self._m.logic(ref)
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
return getattr(self._m, name)
|
|
|
|
|
|
# ── 셈 ────────────────────────────────────────────────────────────────
|
|
def view(doc: dict, basis: str) -> dict:
|
|
"""단가기준 엑셀 = 엑셀 값 기억 연결(형제 SK 빼고)을 숫자로 · 우리 = 그대로 ·
|
|
TS 짝 = `common_util/common_util_formula_doc_import.ts` `importView`(거울 시험 test_802_4b1)."""
|
|
if basis == "우리":
|
|
return doc
|
|
out = cp.deepcopy(doc)
|
|
for var in out.get("변수") or []:
|
|
link = var.get("연결") or {}
|
|
if XL in link and not str(link.get("키") or "").startswith("SK-"):
|
|
var["연결"] = {"갈래": "숫자", "값": link[XL]}
|
|
return out
|
|
|
|
|
|
class Links(dc.ml.Links):
|
|
"""M01 연결(지금 M01 · 수식 문서 폴더) + 형제 가져온 식 SK(부르는 쪽 단가기준)."""
|
|
|
|
def __init__(self, master: Any, basis: str, trail: tuple = ()) -> None:
|
|
super().__init__(master, dc.folder())
|
|
self.basis, self.trail, self.mine = basis, trail, {}
|
|
|
|
def doc(self, key: str) -> dict | None:
|
|
if not str(key).startswith("SK-"):
|
|
return super().doc(key)
|
|
return self.mine.get(key)
|
|
|
|
def learn(self, doc: dict) -> None:
|
|
"""문서의 형제 SK 연결 → 그 SK 문서를 읽어 둠(깊이 · 순환 = 못 읽음 · 표지는 계산기)."""
|
|
for var in doc.get("변수") or []:
|
|
link = var.get("연결") or {}
|
|
key, ref = str(link.get("키") or ""), link.get("참조")
|
|
if not key.startswith("SK-") or key in self.mine or not isinstance(ref, dict):
|
|
continue
|
|
full = {**ref, "키": key}
|
|
rid = refs.ref_id(full)
|
|
if rid in self.trail or len(self.trail) >= refs.DEPTH:
|
|
continue
|
|
sib = refs.read_ref(full)
|
|
mine = ((sib or {}).get(dc.KEY) or {}).get(
|
|
"문서"
|
|
) # 우리 기초 사본으로 바꾼 형제(810-4)
|
|
if not is_imported(sib) and not isinstance(mine, dict):
|
|
continue
|
|
self.mine[key] = None # 순환 막음
|
|
try:
|
|
sub = view(of(sib), self.basis) if is_imported(sib) else {**mine, "키": key}
|
|
except Exception: # noqa: BLE001 — 못 지음 = 없는 단가산출(연결 끊김 표지)
|
|
continue
|
|
self.mine[key] = sub
|
|
self.trail = (*self.trail, rid)
|
|
self.learn(sub)
|
|
|
|
|
|
def of(sk: dict) -> dict:
|
|
"""SK 의 문서 — 수식 길로 고친 것(따로) = 저장본 · 아니면 지금 줄에서 지음."""
|
|
got = sk.get(dc.KEY) if dc.has(sk) else None
|
|
if got and got.get("따로") and isinstance(got.get("문서"), dict):
|
|
return got["문서"]
|
|
return derive(sk)
|
|
|
|
|
|
def record(doc: dict, sk: dict) -> dict[str, Any]:
|
|
"""수식 탭 화면 캐시(`M02_Doc_Screen.record` 짝) — 단가기준 보기 문서 셈이 쓴 연결 · 형제 SK 문서."""
|
|
import master_doc_record as mr # `M02_Doc_Copy` 가 scripts 를 길에 둔 뒤
|
|
|
|
basis = basis_of(sk)
|
|
links = Links(imp._whole(), basis)
|
|
mine = view(doc, basis)
|
|
rec = mr.Rec(links)
|
|
try:
|
|
links.learn(mine)
|
|
rec.prefetch(mine)
|
|
dc.mc.calc(mine, rec)
|
|
except Exception: # noqa: BLE001 — 셈 틀림 = 셈 답 표지 · 기록은 모은 데까지
|
|
pass
|
|
return rec.out()
|
|
|
|
|
|
def run(doc: dict, sk: dict, master: Any = None) -> dict:
|
|
"""가져온 식 문서 셈(SK 단가기준) — 계산기 = M01 과 같은 `master_doc_calc`."""
|
|
basis = basis_of(sk)
|
|
links = Links(master or imp._whole(), basis)
|
|
mine = view(doc, basis)
|
|
try:
|
|
links.learn(mine)
|
|
return dc.mc.calc(mine, links)
|
|
except Exception as e: # noqa: BLE001 — 계산기 예외 없음이 원칙 · 남은 틈도 「—」
|
|
return {
|
|
"셈됨": False,
|
|
"표지": [{"갈래": "셈 틀림", "글": str(e)}],
|
|
**dict.fromkeys(dc.COSTS),
|
|
}
|