feat(797-1): 엑셀 불러오기 요소 연결 — 기계 이음 [우리 단가로] 셈 · 유가 · 환율 이음 · 산근 속 산근 형제 참조 · 읽기 불가 사유 응답
- 기계(EQ) 이음 줄 [우리 단가로] = 시간당 기계경비의 그 비목(옛 Decimal × dict 실패 387 블록 풀림) · 시간당 자료 없는 EQ = 엑셀 단가 그대로 - 내보내기 `_m01` = 유가(MO) · 환율(FX) 유일 짝만 자동 · 여럿 = 표지 · 화폐는 이름 그대로일 때만(「유로폼」 오판 막음) - 산근 속 산근 = 같은 엑셀 안쪽 블록을 형제 가져온 식 SK 로 먼저 만들어 `참조`(비목 한 줄 부름 = `참조비목`) · 단가기준 따라감 · 못 잇는 줄 = 얼림 + 표지 - 쓰는 곳 · 층 복사 · 프로젝트 굳힘이 가져온 호표 참조를 봄(`Labor_Ref.refs_in` · `_move_doc`) - 올림 응답 `읽기불가` + 화면 토스트 - 시험 test_797_1_import_links 9 · 낡은 시험 없음 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -271,11 +271,12 @@ def _upload(name: str, data: bytes, who: dict[str, Any]) -> dict[str, Any]:
|
||||
mark = hopyo_store.fingerprint(data)
|
||||
hopyo_forget.sweep(store.root()) # 하루 지난 작업 중 HP 끝냄(743-1)
|
||||
if same := next((r for r in hopyo_store.rows(store.root()) if r.get("지문") == mark), None):
|
||||
cannot = None
|
||||
if not from_hopyo.has(store.root(), same["키"]): # 793-3b 전에 올린 HP
|
||||
from_hopyo.keep(store.root(), same["키"], name, data)
|
||||
cannot = from_hopyo.keep(store.root(), same["키"], name, data)
|
||||
hopyo_judge.ensure(store.root(), same["키"])
|
||||
san_basis.ensure(store.root(), same["키"])
|
||||
return {**same, "이어서": True}
|
||||
return {**same, "이어서": True, **({"읽기불가": cannot} if cannot else {})}
|
||||
try:
|
||||
parsed = hopyo_excel.read_book(name, data)
|
||||
except hopyo_excel.ExcelError as error:
|
||||
@@ -288,10 +289,14 @@ def _upload(name: str, data: bytes, who: dict[str, Any]) -> dict[str, Any]:
|
||||
parsed["사전"] = hopyo_row.lexicon(sheets) # 엑셀 사전(785-3 · 1차 줄 판정)
|
||||
parsed["숨은식"] = san_basis.hidden(parsed, sheets) # STmate 숨은 G 식(793-3a 근거 제안)
|
||||
row = hopyo_store.save(store.root(), name, parsed, who, grid, mark)
|
||||
from_hopyo.keep(store.root(), row["키"], name, data) # 산근 시트(793-3b 가져온 식 열기)
|
||||
cannot = from_hopyo.keep(
|
||||
store.root(), row["키"], name, data
|
||||
) # 산근 시트(793-3b 가져온 식 열기)
|
||||
hopyo_judge.ensure(store.root(), row["키"]) # 블록 풀이 뒤 셈(파생 저장본 · 화면 안 기다림)
|
||||
san_basis.ensure(store.root(), row["키"]) # 근거 제안 뒤 셈(793-3a)
|
||||
return row
|
||||
return (
|
||||
{**row, "읽기불가": cannot} if cannot else row
|
||||
) # 797-1 가져온 식 못 씀 사유 → 화면 토스트
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo")
|
||||
|
||||
@@ -54,24 +54,30 @@ class Expand:
|
||||
return got
|
||||
return labor._number(qty.get(refs.SAVED)) if self.hands else hand
|
||||
|
||||
def __call__(self, ref: Ref, trail: tuple[str, ...] = ()) -> dict[str, Any]:
|
||||
def __call__(
|
||||
self, ref: Ref, trail: tuple[str, ...] = (), basis: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""`basis` = 가져온 식 SK(797-1 산근 속 산근)를 부르는 쪽 단가기준(엑셀 | 우리)으로 셈."""
|
||||
rid = refs.ref_id(ref)
|
||||
if rid in trail:
|
||||
keys = [t.rsplit("|", 1)[-1] for t in (*trail, rid)]
|
||||
return self._stop(ref, "순환 — " + " → ".join(keys))
|
||||
if len(trail) >= refs.DEPTH:
|
||||
return self._stop(ref, f"참조가 {refs.DEPTH}단을 넘음")
|
||||
if rid not in self.memo:
|
||||
memo = f"{rid}|{basis}" if basis else rid
|
||||
if memo not in self.memo:
|
||||
doc = self.read(ref)
|
||||
if not isinstance(doc, dict):
|
||||
return self._stop(ref, f"없는 참조 「{ref['키']}」")
|
||||
if refs.kind_of(ref["키"]) == "promote":
|
||||
self.memo[rid] = self._promote(doc)
|
||||
self.memo[memo] = self._promote(doc)
|
||||
elif refs.kind_of(ref["키"]) == "sangeun": # 단가산출(715-2) = 고친 줄 셈 1 단위
|
||||
self.memo[rid] = calc.unit(doc, self.whole, self, (*trail, rid))
|
||||
if basis and isinstance((doc.get("가져온") or {}).get("로직"), dict):
|
||||
doc = {**doc, "가져온": {**doc["가져온"], "단가기준": basis}}
|
||||
self.memo[memo] = calc.unit(doc, self.whole, self, (*trail, rid))
|
||||
else: # 사전조합 = 그 SC 로직 사본(795-2)
|
||||
self.memo[rid] = lc.combo(self, doc, (*trail, rid))
|
||||
return self.memo[rid]
|
||||
self.memo[memo] = lc.combo(self, doc, (*trail, rid))
|
||||
return self.memo[memo]
|
||||
|
||||
@staticmethod
|
||||
def _stop(ref: Ref, why: str) -> dict[str, Any]:
|
||||
|
||||
@@ -60,6 +60,8 @@ def refs_in(doc: Any) -> list[Ref]:
|
||||
return []
|
||||
base = doc.get("바탕")
|
||||
lines = [*(doc.get("담은로직") or []), *(doc.get("더한줄") or []), base]
|
||||
own = ((doc.get("가져온") or {}).get("로직") or {}).get("호표") # 가져온 식 산근 속 산근(797-1)
|
||||
lines += own if isinstance(own, list) else []
|
||||
return [
|
||||
one["참조"] for one in lines if isinstance(one, dict) and isinstance(one.get("참조"), dict)
|
||||
]
|
||||
@@ -500,6 +502,12 @@ def _move_doc(doc: dict[str, Any], move: Callable[[Ref, int], Ref], depth: int)
|
||||
out = {**doc, "더한줄": _moved(doc.get("더한줄"), move, depth)}
|
||||
if isinstance(base, dict) and isinstance(base.get("참조"), dict):
|
||||
out["바탕"] = {"참조": move(base["참조"], depth)}
|
||||
own = (doc.get("가져온") or {}).get("로직")
|
||||
if isinstance(own, dict): # 가져온 식 산근 속 산근(797-1)
|
||||
out["가져온"] = {
|
||||
**doc["가져온"],
|
||||
"로직": {**own, "호표": _moved(own.get("호표"), move, depth)},
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface HopyoFile {
|
||||
시트: string;
|
||||
호표수: number;
|
||||
어긋남: number;
|
||||
/** 올림 응답만 — 산근 시트를 못 읽은 사유(빈 파일 · DRM 암호 · 형식 오류 · 797-1) */
|
||||
읽기불가?: string;
|
||||
}
|
||||
|
||||
export type Money = Record<"계" | "노무비" | "재료비" | "경비", number | null>;
|
||||
|
||||
@@ -148,6 +148,7 @@ export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection {
|
||||
.then((got: HopyoFile & { 이어서?: boolean }) => {
|
||||
if (got.이어서) showToast(`같은 엑셀 작업 중 — ${got.키} 이어서`, "info");
|
||||
else showToast(`올림 — 호표 ${got.호표수}`, got.어긋남 ? "warning" : "success");
|
||||
if (got.읽기불가) showToast(`가져온 식으로 열 수 없음 — ${got.읽기불가}`, "warning");
|
||||
return show(pick, got, true);
|
||||
})
|
||||
.catch((error) => showToast(why(error), "error"))
|
||||
|
||||
@@ -176,15 +176,40 @@ def reprice(logic: dict, where: dict, price: dict[str, float]) -> int:
|
||||
return n
|
||||
|
||||
|
||||
def _m01(kind: str, src: dict) -> str | None:
|
||||
"""1차 줄 판정 유일 짝 M01 키 — 없거나 여럿 = None."""
|
||||
def _m01(kind: str, src: dict) -> tuple[str | None, str | None]:
|
||||
"""1차 줄 판정 유일 짝 M01 키 → (키, 표지) — 없음 = (None, None) · 여럿 = (None, 표지 · 유가 ·
|
||||
환율만 — 재료 · 인력 · 기계는 지금처럼 조용히 이음 없음). 재료 줄 이름이 M01 유가전력(MO)이면
|
||||
유가 · 화폐 이름 그대로(달러 · USD …)면 환율(FX)로 판정(797-1 · 「유로폼」 같은 글 속 낱말은 아님)."""
|
||||
if kind not in ("인력", "재료", "기계"):
|
||||
return None
|
||||
return None, None
|
||||
from M02_MasterTemplete import M02_Hopyo_Elem as elem
|
||||
from M02_MasterTemplete import M02_Hopyo_Row as hrow
|
||||
|
||||
got = hrow.m01_picks(kind, {k: src.get(k) or "" for k in ("이름", "규격", "단위")})
|
||||
line = {k: src.get(k) or "" for k in ("이름", "규격", "단위")}
|
||||
if kind == "재료":
|
||||
if elem.oil_rows(line["이름"]):
|
||||
kind = "유가"
|
||||
elif line["이름"].strip().upper() in {x.upper() for pair in hrow.CURRENCY for x in pair}:
|
||||
kind = "환율"
|
||||
if kind == "유가": # MO 전부 → 줄 규격이 상세구분(전국 · 지역)이면 그것만
|
||||
got = [
|
||||
{"키": r["키"], "규격": str(r.get("상세구분") or "")}
|
||||
for r in elem.oil_rows(line["이름"])
|
||||
]
|
||||
spec = line["규격"].replace(" ", "")
|
||||
got = (
|
||||
[x for x in got if x["규격"] == spec]
|
||||
or [x for x in got if spec and spec in x["규격"]]
|
||||
or got
|
||||
)
|
||||
else:
|
||||
got = hrow.m01_picks(kind, line)
|
||||
keys = {x.get("키") for x in got if x.get("키")}
|
||||
return keys.pop() if len(keys) == 1 else None
|
||||
if len(keys) == 1:
|
||||
return keys.pop(), None
|
||||
if len(keys) > 1 and kind in ("유가", "환율"):
|
||||
return None, f"{NOTE}{kind} 후보 여럿({len(keys)}) · 사람 확인"
|
||||
return None, None
|
||||
|
||||
|
||||
# ── 내보낼 로직 틀 ─────────────────────────────────────────────────────
|
||||
@@ -371,9 +396,11 @@ class _Stm:
|
||||
if not one_ and len(sole) == 1: # 비목 꼬리 없어도 값 든 비목 하나면 단가 줄(셈 같음)
|
||||
one_, to = sole[0], None
|
||||
res |= {"비목": one_, "옮김": to} if one_ else {}
|
||||
link = _m01(res["갈래"], res) if self.keys else None
|
||||
link, many = _m01(res["갈래"], res) if self.keys else (None, None)
|
||||
one = resource(o.names.item(name), pr.get("단위"), cf, res, link)
|
||||
self.line(one, node, True, src)
|
||||
if many:
|
||||
o.glyph(src["행"], src["글"], mark=many)
|
||||
|
||||
def rate(self, node: dict, src: dict, cf: str) -> None:
|
||||
rate, o = node["율"], self.o
|
||||
@@ -558,11 +585,13 @@ def est(block: dict, lines: list[dict], want: dict, keys: bool = False, file: st
|
||||
i, role = next((i, r) for i, r in L["역할"].items() if r["갈래"] == "단가")
|
||||
price = next(n[1] for n in ex.numbers(L["식"]) if n[2] == i)
|
||||
res = _est_res(role, price, comp)
|
||||
link = _m01(res["갈래"], res) if keys else None
|
||||
link, many = _m01(res["갈래"], res) if keys else (None, None)
|
||||
qty = text(L["식"], {}, {i: "1"})
|
||||
one = resource(o.names.item(L.get("라벨") or ""), None, qty, res, link)
|
||||
one |= {**head, **({"자리": place} if place else {})}
|
||||
o.add_line(one, True, at, glyph, L["값"])
|
||||
if many:
|
||||
o.glyph(at, glyph, mark=many)
|
||||
if role.get("모호"): # 임의 선택 아님 — 후보를 사람이 고름
|
||||
o.glyph(at, glyph, mark=f"{NOTE}단가 후보 여럿({role['남은']}) · 사람 확인")
|
||||
elif role.get("갈림") in ("문맥", "기계 세 줄"):
|
||||
|
||||
@@ -125,6 +125,11 @@ def _base_of(doc: dict, whole: Any, read: Any, trail: tuple[str, ...]) -> dict[s
|
||||
row = skc.over(doc, whole).logic(base["로직"]) if base else sd.empty_row(doc)
|
||||
except Exception as e: # noqa: BLE001 — 엔진 FormulaError
|
||||
raise sd.DocError(f"바탕 로직 · {e}") from e
|
||||
if any(x.get("참조") for x in row.get("호표") or []): # 가져온 식 안쪽 산근(797-1) 값 채움
|
||||
from M02_MasterTemplete.M02_Labor_Expand import Expand
|
||||
|
||||
row = {**row, "호표": [dict(x) for x in row["호표"]]}
|
||||
_bundles(row, whole, Expand(whole, read), trail)
|
||||
row, lost = eqm.on(row, doc, skc.over(doc, whole))
|
||||
return {**empty, "줄": row, "짝잃음": lost}
|
||||
ref = base["참조"]
|
||||
@@ -323,7 +328,12 @@ def _bundles(row: dict, whole: Any, expand: Any, trail: tuple) -> None:
|
||||
for at, item in enumerate(row["호표"]):
|
||||
if item.get("값묶음") is not None or not ("참조" in item or "고정" in item):
|
||||
continue
|
||||
sub = expand(item["참조"], trail) if "참조" in item else fixed.line(whole, item["고정"])
|
||||
if "참조" in item and item.get(
|
||||
"기준"
|
||||
): # 가져온 식 산근 속 산근(797-1) — 부르는 쪽 단가기준
|
||||
sub = expand(item["참조"], trail, item["기준"])
|
||||
else:
|
||||
sub = expand(item["참조"], trail) if "참조" in item else fixed.line(whole, item["고정"])
|
||||
item["이름"] = item.get("이름") or sub.get("이름") or ""
|
||||
item["단위"] = item.get("단위") or sub.get("단위") or ""
|
||||
if sub.get("기준") is None:
|
||||
@@ -331,6 +341,10 @@ def _bundles(row: dict, whole: Any, expand: Any, trail: tuple) -> None:
|
||||
row["호표"][at] = _gap(item, NO_QTY_WHY if NO_QTY in why else why)
|
||||
continue
|
||||
item["값묶음"] = {k: Decimal(str(sub["기준"][k])) for k in COSTS}
|
||||
if item.get("참조비목") in COSTS: # 안쪽 산근의 그 비목만(엑셀이 비목마다 한 줄로 부른 때)
|
||||
item["값묶음"] = {
|
||||
k: v if k == item["참조비목"] else Decimal(0) for k, v in item["값묶음"].items()
|
||||
}
|
||||
|
||||
|
||||
def run(
|
||||
|
||||
@@ -14,16 +14,19 @@ STmate 블록 끝전(비목버림 | 합버림_큰나머지)은 파일 설정 —
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_San_Emit as em
|
||||
|
||||
DIRNAME = "산근"
|
||||
RELOAD = "가져온 식 자료 없음 — 엑셀을 다시 올림"
|
||||
RULES = ("비목버림", "합버림_큰나머지")
|
||||
MAX_INNER = 6 # 안쪽 산근 깊이 상한
|
||||
|
||||
|
||||
def _path(folder: str | Path, key: str) -> Path:
|
||||
@@ -105,12 +108,70 @@ def emit(folder: str | Path, key: str, no: int) -> dict[str, Any]:
|
||||
return em.est(blk, est.parse_block(blk["글"], prices), total[blk["번"]], True, name)
|
||||
|
||||
|
||||
def make(source: Path, key: str, no: int, name: str = "") -> dict[str, Any]:
|
||||
"""지금 층(만들 층)에 가져온 식 SK — 같은 내용이 있으면 그 SK(`다시씀`)."""
|
||||
def _inner(source: Path, key: str, logic: dict) -> list[tuple[dict, int | None, str | None]]:
|
||||
"""안쪽 산근 줄 `[(호표 줄, 같은 엑셀 안쪽 블록 번, 못 잇는 까닭)]` — STmate = 줄 코드가 블록 코드 ·
|
||||
EST = 줄 이름 「단산N」(N 은 총괄표에 있는 블록). 비목 하나만 부른 줄은 그 비목이 그대로(옮김 없음)일 때만."""
|
||||
from M02_MasterTemplete import M02_San_Book as book
|
||||
|
||||
sheets = json.loads(_path(source, key).read_bytes())["시트"]
|
||||
stmate = book.kind_of(sheets) == "STmate"
|
||||
have = {b["코드"]: int(b["코드"][1:]) for b in book.stmate_blocks(sheets)} if stmate else {}
|
||||
total = set() if stmate else set(book.est_prices(sheets)[1])
|
||||
out = []
|
||||
for line in logic["호표"]:
|
||||
res = line.get("자원") or {}
|
||||
if res.get("갈래") != "단가산출":
|
||||
continue
|
||||
if stmate:
|
||||
n = have.get(str(res.get("코드")))
|
||||
else:
|
||||
hit = re.fullmatch(r"단산(\d+)", str(res.get("이름")))
|
||||
n = int(hit.group(1)) if hit and int(hit.group(1)) in total else None
|
||||
if n is None:
|
||||
out.append((line, None, "같은 엑셀에 안쪽 블록 없음"))
|
||||
elif res.get("옮김") or (res.get("꼴") == "EST" and res.get("열쇠비목") not in em.COMP):
|
||||
out.append((line, n, "비목이 바뀐 부름"))
|
||||
else:
|
||||
out.append((line, n, None))
|
||||
return out
|
||||
|
||||
|
||||
def _link_inner(source: Path, key: str, no: int, out: dict, trail: tuple) -> None:
|
||||
"""산근 속 산근(797-1) — 안쪽 블록을 먼저 같은 길로 만들어 `참조` 로 이음(안쪽이 바뀌면 따라감) ·
|
||||
못 잇는 줄 = 엑셀 값묶음 얼림 + 표지."""
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
|
||||
made: dict[int, dict | None] = {}
|
||||
for line, n, why in _inner(source, key, out["로직"]):
|
||||
if n is not None and not why and (n == no or n in trail or len(trail) >= MAX_INNER):
|
||||
why = "안쪽 블록이 자기 자신을 부름"
|
||||
if n is not None and not why and n not in made:
|
||||
try:
|
||||
made[n] = make(source, key, n, "", (*trail, no))
|
||||
except Exception as e: # noqa: BLE001 — 안쪽을 못 만듦 = 얼림(바깥은 만듦)
|
||||
made[n], why = None, f"안쪽 블록을 못 만듦 — {str(getattr(e, 'detail', e))[:60]}"
|
||||
if n is not None and not why and made[n] is None:
|
||||
why = "안쪽 블록을 못 만듦"
|
||||
if why:
|
||||
out["원본"]["표지"].append(
|
||||
f"{em.NOTE}안쪽 산근 「{line['이름']}」 이음 못 함({why}) · 엑셀 값 얼림"
|
||||
)
|
||||
continue
|
||||
layer, owner = refs.here()
|
||||
line["참조"] = {"층": layer, "주인": owner, "키": made[n]["이름"]}
|
||||
res = line["자원"]
|
||||
if res.get("비목"):
|
||||
line["참조비목"] = em.COMP[res["비목"]]
|
||||
|
||||
|
||||
def make(source: Path, key: str, no: int, name: str = "", _trail: tuple = ()) -> dict[str, Any]:
|
||||
"""지금 층(만들 층)에 가져온 식 SK — 같은 내용이 있으면 그 SK(`다시씀`) ·
|
||||
안쪽 산근 블록은 형제 SK 를 먼저 만들어 참조(797-1)."""
|
||||
from M02_MasterTemplete import M02_San_Basis_Store as san_basis
|
||||
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
||||
|
||||
out = emit(source, key, no)
|
||||
_link_inner(source, key, no, out, _trail)
|
||||
title = str(name or "").strip() or str(out["로직"].get("이름") or f"단산{no}")
|
||||
try:
|
||||
basis = san_basis.block(source, key, no).get("근거후보") or None
|
||||
|
||||
@@ -51,6 +51,7 @@ KINDS = ("인력", "재료", "기계", "로직")
|
||||
ROW = ("이름", "결과단위", "입력", "중간", "호표", "덧줄", "단계", "끝전", "조정", "끝수")
|
||||
LINE = ("종류", "이름", "규격", "단위", "수량", "비목", "단가", "값묶음", "이음", "번지", "합밖")
|
||||
LINE += ("자리", "출처", "비고", "자원") # 자원 = 경쟁사 코드 · 엑셀 단가 원본(793-2a · 셈 안 씀)
|
||||
LINE += ("참조", "참조비목") # 산근 속 산근 = 같은 엑셀의 형제 가져온 식 SK(797-1 · 로직 줄만)
|
||||
EXTRA = ("이름", "식", "비목", "번지", "합밖", "자리", "출처", "비고")
|
||||
META = ("원본", "검산", "근거후보", "바꿈") # 검산 = 저장 때 서버가 다시 적음(`stamp`)
|
||||
MANY = 300 # 호표 · 덧줄 · 중간 줄 수
|
||||
@@ -75,6 +76,29 @@ def is_imported(doc: Any) -> bool:
|
||||
|
||||
|
||||
# ── 바탕 줄 ───────────────────────────────────────────────────────────
|
||||
_HOURLY: dict[str, Any] = {"whole": None, "rows": {}}
|
||||
|
||||
|
||||
def hourly_of(key: str) -> dict:
|
||||
"""이음 기계(EQ) 시간당 노 · 재 · 경 — M01 판이 바뀌면 다시(캐시 = M01 묶음 하나에 매임)."""
|
||||
from M02_MasterTemplete import M02_Machine_Hourly as machine
|
||||
|
||||
whole = _whole()
|
||||
if _HOURLY["whole"] is not whole:
|
||||
_HOURLY.update(whole=whole, rows={})
|
||||
if key not in _HOURLY["rows"]:
|
||||
_HOURLY["rows"][key] = machine.hourly(key)
|
||||
return _HOURLY["rows"][key]
|
||||
|
||||
|
||||
def machine_price(link: str, part: Any, excel: Any) -> Any:
|
||||
"""[우리 단가로] 기계 이음 줄 값 — 시간당 경비의 그 비목 · 시간당 자료 없으면 엑셀 단가 그대로."""
|
||||
got = hourly_of(link)
|
||||
if got.get("ok") and part in COSTS:
|
||||
return sd.plain(Decimal(str(got[part])))
|
||||
return excel
|
||||
|
||||
|
||||
def row_of(doc: Any) -> dict | None:
|
||||
"""가져온 식 바탕 줄(엔진 로직 줄) — 단가기준 우리 = 이음 있는 줄의 단가 · 값묶음 뗌 · 요소 =
|
||||
이음 | {이름}(로직 줄 = 이음 | None) · 가져온 식 아님 = None."""
|
||||
@@ -87,9 +111,17 @@ def row_of(doc: Any) -> dict | None:
|
||||
link = one.get("이음")
|
||||
item = {k: v for k, v in one.items() if k != "이음"}
|
||||
item["요소"] = link or (None if one.get("종류") == "로직" else {"이름": one.get("이름")})
|
||||
if one.get(
|
||||
"참조"
|
||||
): # 안쪽 산근 = 형제 SK 1 단위 값(셈이 채움 · 엑셀 값묶음은 얼린 사본으로 둠)
|
||||
item |= {"요소": None, "값묶음": None, "기준": "우리" if ours else "엑셀"}
|
||||
if ours and link:
|
||||
item.pop("단가", None)
|
||||
item.pop("값묶음", None)
|
||||
if str(link).startswith(
|
||||
"EQ"
|
||||
): # EQ = 기계 제원 묶음 — 줄 값은 시간당 기계경비의 그 비목
|
||||
item["단가"] = machine_price(link, one.get("비목"), one.get("단가"))
|
||||
items.append(item)
|
||||
row = {"입력": [], "중간": [], "덧줄": [], **meta["로직"]}
|
||||
return {**row, "키": doc.get("키"), "호표": items}
|
||||
@@ -150,6 +182,17 @@ def _line(raw: Any, where: str, whole: Any) -> dict:
|
||||
out["단위"] = str(raw.get("단위") or "")
|
||||
out["수량"] = _formula(raw.get("수량"), f"{where} 수량")
|
||||
out["이음"] = _link(raw.get("이음"), kind, where, whole)
|
||||
if "참조" in out:
|
||||
ref = out["참조"]
|
||||
if kind != "로직" or not isinstance(ref, dict):
|
||||
raise sd.DocError(f"{where} · 참조는 로직 줄의 {{층, 주인, 키}}")
|
||||
if ref.get("층") not in ("system", "company", "personal") or not re.fullmatch(
|
||||
r"SK-\d{6}", str(ref.get("키") or "")
|
||||
):
|
||||
raise sd.DocError(f"{where} · 참조는 단가산출(SK)만")
|
||||
out["참조"] = {k: ref.get(k) for k in ("층", "주인", "키")}
|
||||
if out.get("참조비목") not in (*COSTS, None):
|
||||
raise sd.DocError(f"{where} · 참조비목은 노무비 · 재료비 · 경비")
|
||||
if kind != "로직" and out.get("비목") not in sd.PARTS.values():
|
||||
raise sd.DocError(f"{where} · 비목은 노무비 · 재료비 · 경비")
|
||||
if "단가" in out:
|
||||
@@ -159,7 +202,7 @@ def _line(raw: Any, where: str, whole: Any) -> dict:
|
||||
out["값묶음"] = {k: sd.plain(sd._num(pack.get(k, 0), f"{where} {k}")) for k in COSTS}
|
||||
if kind == "로직" and "단가" in out:
|
||||
raise sd.DocError(f"{where} · 로직 줄 엑셀 값은 값묶음")
|
||||
if not any(k in out for k in ("단가", "값묶음")) and not out["이음"]:
|
||||
if not any(k in out for k in ("단가", "값묶음", "참조")) and not out["이음"]:
|
||||
raise sd.DocError(f"{where} · 엑셀 단가 · 이음 둘 다 없음")
|
||||
return out
|
||||
|
||||
@@ -261,7 +304,7 @@ def unlinked(doc: dict) -> list[dict]:
|
||||
return [
|
||||
{"자리": ids[n], "이름": one.get("이름") or ""}
|
||||
for n, one in enumerate(doc[KEY]["로직"].get("호표") or [])
|
||||
if not one.get("이음")
|
||||
if not one.get("이음") and not one.get("참조")
|
||||
]
|
||||
|
||||
|
||||
@@ -321,7 +364,13 @@ def _inner(doc: dict, whole: Any) -> dict:
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
|
||||
m01, key = calc.labor.m01, str(doc.get("키") or "SK-000000")
|
||||
over = calc._Over(whole, key, {**row_of(doc), "키": key})
|
||||
row = {**row_of(doc), "키": key}
|
||||
if any(x.get("참조") for x in row["호표"]): # 안쪽 산근 값 채움(797-1 · 셈 길과 같음)
|
||||
from M02_MasterTemplete.M02_Labor_Expand import Expand
|
||||
|
||||
row["호표"] = [dict(x) for x in row["호표"]]
|
||||
calc._bundles(row, whole, Expand(whole), ())
|
||||
over = calc._Over(whole, key, row)
|
||||
try:
|
||||
auto = {k: v for k, v in m01.mau.values(over, over.logic(key)).items() if v is not None}
|
||||
given = {**auto, **m01._dec(doc.get("입력값") or {})}
|
||||
|
||||
Reference in New Issue
Block a user