Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -47,7 +47,7 @@ from B09_Estimation.B09_Estimation_Calc_Base import (
|
||||
)
|
||||
|
||||
PARTS = ("labor", "material", "expense")
|
||||
COVER_KEYS = ("qty", *PARTS)
|
||||
COVER_KEYS = ("qty",) # 841-1 단가 고친 칸 걷음 — 품(수량)만
|
||||
TAB = "dg"
|
||||
NO_LINES = "구성 줄 없음" # 줄 0 개 일위대가의 빈 줄 이름
|
||||
COST = {"노무비": "labor", "재료비": "material", "경비": "expense"}
|
||||
@@ -55,9 +55,9 @@ WAYS = {"버림": rounddown, "올림": roundup, "반올림": round_half}
|
||||
COPY = "@copy"
|
||||
ADD = "@add"
|
||||
WHY = {
|
||||
"labor": "노임 단가 없음",
|
||||
"material": "자재 적용 단가 없음",
|
||||
"machine": "중기 시간 단가 없음",
|
||||
"labor": "노임 단가 없음(M02 엔진)",
|
||||
"material": "자재 단가 없음(M02 엔진)",
|
||||
"machine": "기계 시간당 없음(M02 엔진)",
|
||||
"daega": "부른 대가 합계 없음",
|
||||
"sangeun": "산근 단가 없음(145-1)",
|
||||
}
|
||||
@@ -96,16 +96,18 @@ def _prices(line: dict[str, Any], books: dict[str, Any], sums: dict) -> dict[str
|
||||
"""줄 비목 단가 — 그 비목이 없는 줄은 0 · 원천이 없으면 None."""
|
||||
kind, key = line["kind"], line.get("key")
|
||||
out: dict[str, float | None] = dict.fromkeys(PARTS, 0.0)
|
||||
if kind == "labor":
|
||||
out["labor"] = num((books["labor"].get(key) or {}).get("price"))
|
||||
elif kind == "material":
|
||||
row = books["material"].get(key) or {}
|
||||
# 관급 · 별도계상 자재 = 대가 안 0원(자재대 · 관급 단락에서 셈 — 두 번 안 셈)
|
||||
out["material"] = 0.0 if row.get("supply") in SUPPLY else num(row.get("price"))
|
||||
elif kind in ("machine", "daega", "sangeun"):
|
||||
row = {"machine": books["machine"], "daega": sums, "sangeun": books["sangeun"]}[kind].get(
|
||||
key
|
||||
) or {}
|
||||
if kind in (
|
||||
"labor",
|
||||
"material",
|
||||
"machine",
|
||||
): # 841-1 = M02 엔진 단가(`own`) · B09 단가 책 안 읽음
|
||||
own = line.get("own")
|
||||
out = {p: num(own.get(p)) if p in own else 0.0 for p in PARTS} if own else _none()
|
||||
row = (books["material"].get(key) or {}) if kind == "material" else {}
|
||||
if row.get("supply") in SUPPLY: # 관급 · 별도계상 자재 = 대가 안 0원(자재대 · 관급 단락)
|
||||
out["material"] = 0.0
|
||||
elif kind in ("daega", "sangeun"):
|
||||
row = {"daega": sums, "sangeun": books["sangeun"]}[kind].get(key) or {}
|
||||
for part in PARTS:
|
||||
out[part] = None if row.get("blank") else num(row.get(part)) # 빈 줄 대가 = 빈칸
|
||||
elif kind == "fixed":
|
||||
@@ -115,21 +117,18 @@ def _prices(line: dict[str, Any], books: dict[str, Any], sums: dict) -> dict[str
|
||||
return out
|
||||
|
||||
|
||||
def _none() -> dict[str, float | None]:
|
||||
return dict.fromkeys(PARTS, None)
|
||||
|
||||
|
||||
def _fixed(line: dict[str, Any], books: dict[str, Any]) -> dict[str, float | None]:
|
||||
"""고정 줄 단가(705-1) — 줄 단가 위에 이은 비목만 단가 책 값."""
|
||||
"""고정 줄 단가(705-1) — 줄 단가 그대로 · 이은 자재가 관급 · 별도계상이면 재료 0(841-1 단가 책
|
||||
값으로 다시 안 덮음)."""
|
||||
own, link = line.get("own") or {}, line.get("link") or {}
|
||||
out = {part: num(own.get(part)) or 0.0 for part in PARTS}
|
||||
labor = books["labor"].get(link.get("labor")) if link.get("labor") else None
|
||||
if labor and num(labor.get("price")) is not None:
|
||||
out["labor"] = num(labor["price"])
|
||||
row = books["material"].get(link.get("material")) if link.get("material") else None
|
||||
if row and (row.get("supply") in SUPPLY or num(row.get("price")) is not None):
|
||||
out["material"] = 0.0 if row.get("supply") in SUPPLY else num(row["price"])
|
||||
machine = books["machine"].get(link.get("machine")) if link.get("machine") else None
|
||||
if machine and not machine.get("blank"):
|
||||
for part in PARTS:
|
||||
if num(machine.get(part)) is not None:
|
||||
out[part] = num(machine[part])
|
||||
if row and row.get("supply") in SUPPLY:
|
||||
out["material"] = 0.0
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -39,16 +39,16 @@ import {
|
||||
} from "./B09_Estimation_Calc_Base";
|
||||
|
||||
const PARTS = ["labor", "material", "expense"] as const;
|
||||
const COVER_KEYS = ["qty", ...PARTS] as const;
|
||||
const COVER_KEYS = ["qty"] as const; // 841-1 단가 고친 칸 걷음 — 품(수량)만
|
||||
const TAB = "dg";
|
||||
const NO_LINES = "구성 줄 없음"; // 줄 0 개 일위대가의 빈 줄 이름(파이썬 짝)
|
||||
export const COPY = "@copy";
|
||||
/** 붙인 줄 덮개 열쇠(705-2) */
|
||||
export const ADD = "@add";
|
||||
const WHY: Record<string, string> = {
|
||||
labor: "노임 단가 없음",
|
||||
material: "자재 적용 단가 없음",
|
||||
machine: "중기 시간 단가 없음",
|
||||
labor: "노임 단가 없음(M02 엔진)",
|
||||
material: "자재 단가 없음(M02 엔진)",
|
||||
machine: "기계 시간당 없음(M02 엔진)",
|
||||
daega: "부른 대가 합계 없음",
|
||||
sangeun: "산근 단가 없음(145-1)",
|
||||
};
|
||||
@@ -103,14 +103,17 @@ const blanks = (): Parts => ({ labor: null, material: null, expense: null });
|
||||
|
||||
function linePrices(line: Row, books: Row, sums: Record<string, Row>): Parts {
|
||||
const { kind, key } = line;
|
||||
const out = zeros();
|
||||
if (kind === "labor") out.labor = num((books.labor[key] || {}).price);
|
||||
else if (kind === "material") {
|
||||
const row: Row = books.material[key] || {};
|
||||
// 관급 · 별도계상 자재 = 대가 안 0원(자재대 · 관급 단락에서 셈 — 두 번 안 셈)
|
||||
out.material = (SUPPLY as readonly string[]).includes(row.supply) ? 0 : num(row.price);
|
||||
} else if (kind === "machine" || kind === "daega" || kind === "sangeun") {
|
||||
const src: Row = ({ machine: books.machine, daega: sums, sangeun: books.sangeun } as Row)[kind];
|
||||
let out = zeros();
|
||||
if (kind === "labor" || kind === "material" || kind === "machine") {
|
||||
// 841-1 = M02 엔진 단가(`own`) · B09 단가 책 안 읽음
|
||||
const own: Row | undefined = line.own;
|
||||
out = blanks();
|
||||
if (own) for (const part of PARTS) out[part] = part in own ? num(own[part]) : 0;
|
||||
const row: Row = kind === "material" ? books.material[key] || {} : {};
|
||||
// 관급 · 별도계상 자재 = 대가 안 0원(자재대 · 관급 단락)
|
||||
if ((SUPPLY as readonly string[]).includes(row.supply)) out.material = 0;
|
||||
} else if (kind === "daega" || kind === "sangeun") {
|
||||
const src: Row = ({ daega: sums, sangeun: books.sangeun } as Row)[kind];
|
||||
const row: Row = src[key] || {};
|
||||
for (const part of PARTS) out[part] = row.blank ? null : num(row[part]); // 빈 줄 대가 = 빈칸
|
||||
} else if (kind === "fixed") return fixedPrices(line, books);
|
||||
@@ -118,20 +121,14 @@ function linePrices(line: Row, books: Row, sums: Record<string, Row>): Parts {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 고정 줄 단가(705-1) — 줄 단가 위에 이은 비목만 단가 책 값(짝 `_fixed`) */
|
||||
/** 고정 줄 단가(705-1) — 줄 단가 그대로 · 이은 자재가 관급 · 별도계상이면 재료 0(짝 `_fixed`) */
|
||||
function fixedPrices(line: Row, books: Row): Parts {
|
||||
const own: Row = line.own || {};
|
||||
const link: Row = line.link || {};
|
||||
const out = zeros();
|
||||
for (const part of PARTS) out[part] = num(own[part]) ?? 0;
|
||||
const labor: Row | undefined = link.labor ? books.labor[link.labor] : undefined;
|
||||
if (labor && num(labor.price) !== null) out.labor = num(labor.price);
|
||||
const row: Row | undefined = link.material ? books.material[link.material] : undefined;
|
||||
const supplied = !!row && (SUPPLY as readonly string[]).includes(row.supply);
|
||||
if (row && (supplied || num(row.price) !== null)) out.material = supplied ? 0 : num(row.price);
|
||||
const machine: Row | undefined = link.machine ? books.machine[link.machine] : undefined;
|
||||
if (machine && !machine.blank)
|
||||
for (const part of PARTS) if (num(machine[part]) !== null) out[part] = num(machine[part]);
|
||||
if (row && (SUPPLY as readonly string[]).includes(row.supply)) out.material = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -243,18 +243,18 @@ class Builder:
|
||||
part = PART.get(str(add.get("비목")), "expense")
|
||||
return {**head, "kind": "pct", "part": part, "base": base, "spec": f"{label}의 %",
|
||||
"unit": "%", "qty": round(value / under * 100, 6)} # fmt: skip
|
||||
kind = item.get("종류")
|
||||
kind, own = item.get("종류"), _own(x, qty)
|
||||
if kind == "인력":
|
||||
key = goods.get("키") or str(item.get("요소") or "")
|
||||
return {**head, "kind": "labor", "key": key, "qty": qty}
|
||||
return {**head, "kind": "labor", "key": key, "qty": qty, "own": own}
|
||||
if kind == "재료":
|
||||
if not goods.get("키"):
|
||||
return {**head, "kind": "blank", "qty": qty, "why": x.get("까닭") or "품목 없음"}
|
||||
return {**head, "kind": "material", "key": goods["키"], "spec": goods.get("규격") or "",
|
||||
"qty": qty} # fmt: skip
|
||||
"qty": qty, "own": own} # fmt: skip
|
||||
# 로직 줄 — `Sangeun.call_of` 판정: 기계경비 로직 = 중기 · 그 밖 = 하위 산근
|
||||
if call and call.get("machine"):
|
||||
return {**head, "kind": "machine", "key": call["machine"], "qty": qty}
|
||||
if call and call.get("machine"): # 기계 시간당 = M02 엔진 값(841-1)
|
||||
return {**head, "kind": "machine", "key": call["machine"], "qty": qty, "own": own}
|
||||
if call and call.get("code"):
|
||||
key = sg.sangeun_key(call["code"], call["given"])
|
||||
return {**head, "kind": "sangeun", "key": key, "qty": qty}
|
||||
@@ -559,11 +559,24 @@ def _row_of(key: str, where: dict) -> str:
|
||||
return f"{where[line]}@{PART[cost]}" if cost else str(where[line])
|
||||
|
||||
|
||||
def _own(x: dict, qty: float | None) -> dict[str, float] | None:
|
||||
"""엔진 줄 비목 단가(841-1) = 엔진 금액 ÷ 엔진 수량 · 수량 0 = 0 · 못 셈 = None(빈칸)."""
|
||||
money = x.get("비목") or {}
|
||||
if not qty:
|
||||
return dict.fromkeys(PART.values(), 0.0) if qty == 0 else None
|
||||
return {PART[k]: (_f(v) or 0.0) / qty for k, v in money.items() if k in PART}
|
||||
|
||||
|
||||
def _material(whole: Any, mat: dict, qty: Any) -> dict[str, Any]:
|
||||
"""자재 줄 — 조합 사용자 자재 · 사전조합 자재(175-2) 한 꼴 · 이름 · 규격 · 단위 = 사본 단가."""
|
||||
"""자재 줄 — 조합 사용자 자재 · 사전조합 자재(175-2) 한 꼴 · 이름 · 규격 · 단위 = 사본 단가 ·
|
||||
단가 = M02 엔진 자재 기준단가(841-1 · 조합 셈 답 `기준단가` · 없으면 사본 단가 묶음으로)."""
|
||||
row = (whole.get(mat.get("코드")) if mat.get("코드") else None) or {}
|
||||
names = {k: row.get(v) or "" for k, v in (("name", "이름"), ("spec", "규격"), ("unit", "단위"))}
|
||||
return {"kind": "material", "key": mat.get("코드"), **names, "qty": _f(qty)}
|
||||
price = mat["기준단가"] if "기준단가" in mat else None
|
||||
if price is None and mat.get("코드"):
|
||||
price = labor_doc.material_price(whole, mat["코드"], mat.get("단위"))["기준단가"]
|
||||
own = None if _f(price) is None else {"material": _f(price)}
|
||||
return {"kind": "material", "key": mat.get("코드"), **names, "qty": _f(qty), "own": own}
|
||||
|
||||
|
||||
def _engine_bases(engine: list[dict], items: list[dict]) -> dict[str, float]:
|
||||
|
||||
@@ -160,7 +160,7 @@ def compute_sheets(
|
||||
화면 짝 = `B09_Estimation_UI_Shell.computeSheets`(산근만 서버 답 그대로) ·
|
||||
memo = 두 번 셈의 앞 산근 풀이(`Sangeun.collect`)."""
|
||||
base = compute_base(inputs, user)
|
||||
sg = sangeun.build(root, base, user, memo) # 145-1 — 굳힌 단가로 풀이(+ 166-2 손 줄 로직)
|
||||
sg = sangeun.build(root, user, memo) # 145-1 — M02 엔진 값으로 풀이(841-1 · + 166-2 손 줄 로직)
|
||||
sg = sg_cover.cover_sangeun(sg, user.get("sg_cover")) # 719-3 이 공사 산근 고친 칸
|
||||
by_key = {row["key"]: row for row in sg["sg_list"]}
|
||||
labor = compute_labor(
|
||||
@@ -423,14 +423,11 @@ class PreviewBody(BaseModel):
|
||||
|
||||
|
||||
def logic_preview(root: Path, body: PreviewBody) -> dict[str, Any]:
|
||||
"""로직 한 산근 미리보기 — 단가 = 굳힌 책(없는 줄 = 지금 M01) · 사용자 대비표 고름."""
|
||||
"""로직 한 산근 미리보기 — 단가 = M02 엔진(841-1 · 그 로직 닫힌 묶음 · B09 단가 책 안 읽음)."""
|
||||
src = {"code": body.code, "given": sangeun._clean_given(body.given), "rate": body.rate,
|
||||
"picks": body.picks} # fmt: skip
|
||||
hands = [("preview", "미리보기", src)]
|
||||
keys = sangeun.hand_resource_keys({"haul_refs": {"preview": {"tab": "logic", **src}}})
|
||||
user = store.read_user(root)
|
||||
base = compute_base(prices.build_inputs(store.read_book(root), keys), user)
|
||||
solver = sangeun.Solver(base)
|
||||
solver = sangeun.Solver()
|
||||
block = solver.add(sangeun.hand_whole(hands), src, None)
|
||||
text = [
|
||||
*block["head"],
|
||||
|
||||
@@ -62,6 +62,7 @@ import master_sheet_line as msl # noqa: E402 — 계산서 줄 덧칸(714-1) ·
|
||||
|
||||
PARTS = ("labor", "material", "expense")
|
||||
COST_PART = {"노무비": "labor", "재료비": "material", "경비": "expense"}
|
||||
PART_COST = {v: k for k, v in COST_PART.items()}
|
||||
PCT_OF = {**COST_PART, "인건비": "people"} # % 덧줄 밑수 → 덮개 다시 셈(719-3)
|
||||
DEPTH = 4 # 하위 산근 깊이 상한
|
||||
NO_SOURCE = "조합 밖 토공 · 운반 · 사토 · 준비공은 산근 없음"
|
||||
@@ -165,49 +166,15 @@ def rows_of(folder: Path, whole: Any = None) -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
# ── 풀이 ──────────────────────────────────────────────────────────────
|
||||
class _Seen(dict):
|
||||
"""읽은 키를 적는 단가 표 — 두 번 셈(183-1)에서 첫 풀이가 본 값만 견줌."""
|
||||
|
||||
def __init__(self, *args: Any) -> None:
|
||||
super().__init__(*args)
|
||||
self.seen: set[Any] = set()
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
self.seen.add(key)
|
||||
return super().__contains__(key)
|
||||
|
||||
def __getitem__(self, key: Any) -> Any:
|
||||
self.seen.add(key)
|
||||
return super().__getitem__(key)
|
||||
|
||||
def get(self, key: Any, default: Any = None) -> Any:
|
||||
self.seen.add(key)
|
||||
return super().get(key, default)
|
||||
|
||||
def same(self, other: dict) -> bool:
|
||||
"""본 키마다 있음 · 값이 `other` 와 같음."""
|
||||
return all(
|
||||
dict.__contains__(self, k) == dict.__contains__(other, k)
|
||||
and dict.get(self, k) == dict.get(other, k)
|
||||
for k in self.seen
|
||||
)
|
||||
|
||||
|
||||
class _Prices:
|
||||
"""B09 표 단가 — 노무 · 중기(노 · 재 · 경) · 자재 적용 단가(키 → 값)."""
|
||||
"""엔진 단가(841-1) — M02 엔진이 사본 단가 묶음으로 낸 줄 금액 ÷ 수량 · 텍스트 수식 글이 읽음
|
||||
(노무 · 자재 = 수 · 중기 = 노 · 재 · 경). B09 단가 책 · 바탕 표는 안 읽음."""
|
||||
|
||||
def __init__(self, sheets: dict[str, Any]) -> None:
|
||||
self.labor = _Seen({r["key"]: num(r.get("price")) for r in sheets.get("labor") or []})
|
||||
self.machine = _Seen({r["key"]: r for r in sheets.get("machine") or []})
|
||||
self.material = _Seen(
|
||||
{r["key"]: num(r.get("applied")) for r in sheets.get("compare") or []}
|
||||
)
|
||||
|
||||
def same(self, other: _Prices) -> bool:
|
||||
"""첫 풀이가 본 단가가 `other` 에서도 같음 — 같으면 풀이도 같음."""
|
||||
return all(
|
||||
getattr(self, k).same(getattr(other, k)) for k in ("labor", "machine", "material")
|
||||
)
|
||||
def __init__(self) -> None:
|
||||
# ponytail: 키 하나 = 단가 하나(사본마다 기준 시점이 달라도 마지막 본 값) — 글 짓기 전용
|
||||
self.labor: dict[str, float | None] = {}
|
||||
self.material: dict[str, float | None] = {}
|
||||
self.machine: dict[str, dict[str, float | None]] = {}
|
||||
|
||||
|
||||
def _unit(row: dict) -> tuple[str, Decimal]:
|
||||
@@ -323,6 +290,10 @@ def spec_text(whole: Any, given: dict[str, Any]) -> tuple[str, str]:
|
||||
return ", ".join(f"{k}={shown(k, v)}" for k, v in given.items()), raw
|
||||
|
||||
|
||||
def _float(value: Any) -> Any:
|
||||
return float(value) if isinstance(value, Decimal) else value
|
||||
|
||||
|
||||
def _money(price: float | None, qty: float | None) -> float | None:
|
||||
"""줄 금액 = ROUNDDOWN(단가 × 수량, 1)(엑셀 자리)."""
|
||||
return None if price is None or qty is None else rounddown(price * qty, 1)
|
||||
@@ -340,8 +311,9 @@ def block_totals(lines: list[dict[str, Any]]) -> dict[str, float | None]:
|
||||
class Solver:
|
||||
"""한 번 셈 — 산근 블록을 첫 등장 차례로 모음(하위 산근 포함)."""
|
||||
|
||||
def __init__(self, sheets: dict[str, Any]) -> None:
|
||||
self.prices = _Prices(sheets)
|
||||
def __init__(self) -> None:
|
||||
self.prices = _Prices()
|
||||
self.eng: list[dict[str, Any]] = [] # 지금 푸는 로직의 엔진 줄(호표 차례 · 841-1)
|
||||
self.blocks: dict[str, dict[str, Any]] = {}
|
||||
|
||||
def add(
|
||||
@@ -367,10 +339,13 @@ class Solver:
|
||||
unit, times = _unit(row)
|
||||
rate = Decimal(str(src["rate"])) / 100 if src.get("rate") not in (None, 100) else None
|
||||
lines: list[dict[str, Any]] = []
|
||||
# 841-1 단가 = M02 엔진 줄 금액 ÷ 수량(켜쌓기율 없이 · 날 값 · B09 단가 책 안 읽음)
|
||||
eng = labor_doc.run_line(whole, code, src["given"], None, src.get("picks"), raw=True)
|
||||
try:
|
||||
env = _env(whole, row, src["given"])
|
||||
with mf.mp.choosing(src.get("picks")):
|
||||
for n, item in enumerate(row.get("호표", [])):
|
||||
self.eng = eng.get("줄") or [] # 하위 산근 풀이가 바꿔 놓아도 되돌림
|
||||
lines.append(self._line(whole, item, n, env, times, rate, depth))
|
||||
ss.places(row, lines) # 줄 자리(792-3)
|
||||
self._extras(whole, row, env, lines)
|
||||
@@ -434,6 +409,14 @@ class Solver:
|
||||
at += n
|
||||
return text[:start], text[-1]
|
||||
|
||||
def _each(self, n: int, part: str, q: float) -> float | None:
|
||||
"""엔진 줄 n 비목 단가 = 엔진 금액 ÷ 엔진 수량 · 안 쓰는 줄(수량 0) = 0 · 못 셈 = None."""
|
||||
x = self.eng[n] if n < len(self.eng) else {}
|
||||
qty, money = num(_float(x.get("수량"))), (x.get("비목") or {}).get(PART_COST[part])
|
||||
if not qty:
|
||||
return 0.0 if q == 0 else None
|
||||
return None if money is None else float(money) / qty
|
||||
|
||||
def _blank(self, item: dict, why: str) -> dict[str, Any]:
|
||||
part = COST_PART.get(item.get("비목"), "material")
|
||||
return {"kind": "blank", "name": item.get("이름", ""), "spec": "", "qty": None,
|
||||
@@ -451,11 +434,12 @@ class Solver:
|
||||
zero = dict.fromkeys(PARTS, 0.0)
|
||||
kind = item.get("종류")
|
||||
if kind == "로직":
|
||||
return self._logic_line(whole, item, env, q, base, zero, depth)
|
||||
return self._logic_line(whole, item, n, env, q, base, zero, depth)
|
||||
if kind == "인력":
|
||||
key = mf.fill(item["요소"], env) if isinstance(item["요소"], str) else ""
|
||||
price = self.prices.labor.get(key)
|
||||
why = None if price is not None else f"노무비목록표에 없음 「{key}」"
|
||||
price = self._each(n, "labor", q)
|
||||
self.prices.labor[key] = price
|
||||
why = None if price is not None else f"노임 단가 없음(M02 엔진) 「{key}」"
|
||||
return {**base, **zero, "kind": "labor", "ref": {"tab": "labor", "key": key},
|
||||
"price": {"labor": price}, "labor": _money(price, q), "why": why} # fmt: skip
|
||||
ref = (
|
||||
@@ -468,29 +452,33 @@ class Solver:
|
||||
key = str(
|
||||
goods.get("키") or (source or "").split(".")[0] or (ref if isinstance(ref, str) else "")
|
||||
)
|
||||
if key not in self.prices.material: # 자리표시 글(「MO:경유:전국평균」) = 대비표 키로
|
||||
try:
|
||||
key = str((whole.get(key) or {}).get("키") or key)
|
||||
except (mf.FormulaError, KeyError, TypeError, ValueError):
|
||||
pass
|
||||
try: # 자리표시 글(「MO:경유:전국평균」) = 그 줄 키로
|
||||
key = str((whole.get(key) or {}).get("키") or key)
|
||||
except (mf.FormulaError, KeyError, TypeError, ValueError):
|
||||
pass
|
||||
part = COST_PART.get(item.get("비목"), "material")
|
||||
price = self.prices.material.get(key)
|
||||
if q == 0:
|
||||
price = price if price is not None else 0.0 # 안 쓰는 재료 줄 — 금액 0
|
||||
why = None if price is not None else f"자재단가대비표에 없음 「{key}」"
|
||||
price = self._each(n, part, q)
|
||||
self.prices.material[key] = price
|
||||
why = None if price is not None else f"자재 단가 없음(M02 엔진) 「{key}」"
|
||||
spec = goods.get("규격") or ""
|
||||
ref = {"tab": "compare", "key": key}
|
||||
return {**base, **zero, "spec": spec, "kind": "material", "ref": ref,
|
||||
"price": {part: price}, part: _money(price, q), "why": why,
|
||||
"_goods": goods} # fmt: skip
|
||||
|
||||
def _logic_line(self, whole, item, env, q, base, zero, depth) -> dict[str, Any]:
|
||||
def _logic_line(self, whole, item, n, env, q, base, zero, depth) -> dict[str, Any]:
|
||||
call = call_of(whole, item, env)
|
||||
if call and "machine" in call:
|
||||
keys = [call["machine"]] if call["machine"] else []
|
||||
m = self.prices.machine.get(keys[0]) if keys else None
|
||||
price = {p: num((m or {}).get(p)) for p in PARTS}
|
||||
why = None if m else f"중기목록표에 없음 「{keys[0] if keys else item.get('이름')}」"
|
||||
price = {p: self._each(n, p, q) for p in PARTS} # 기계 시간당 = M02 엔진 값(841-1)
|
||||
m = None not in price.values()
|
||||
if keys:
|
||||
self.prices.machine[keys[0]] = price
|
||||
why = (
|
||||
None
|
||||
if m
|
||||
else f"기계 시간당 없음(M02 엔진) 「{keys[0] if keys else item.get('이름')}」"
|
||||
)
|
||||
amounts = {p: _money(price[p], q) if m else None for p in PARTS}
|
||||
ref = {"tab": "machine", "key": keys[0] if keys else ""}
|
||||
return {**base, "kind": "machine", "ref": ref,
|
||||
@@ -618,16 +606,15 @@ def hand_resource_keys(user: dict[str, Any] | None) -> list[str]:
|
||||
|
||||
def collect(
|
||||
entries: list[tuple[str, Path]],
|
||||
sheets: dict[str, Any],
|
||||
hands: list[tuple[str, str, dict[str, Any]]] | None = None,
|
||||
memo: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""`entries` = [(구조물 id, 사본 폴더)] → 블록 · 목록 · 「연결 없음」 ·
|
||||
`hands` = 손 줄 로직(166-2 · 사본 뒤 번호 · 마스터 로직) ·
|
||||
memo = 같은 사본 · 손 줄의 앞 풀이(두 번 셈) — 앞 풀이가 본 단가가 같으면 그 풀이 사본."""
|
||||
solver = Solver(sheets)
|
||||
if memo and memo["prices"].same(solver.prices):
|
||||
if memo and "sg" in memo: # 841-1 풀이 = 사본 · 엔진만(바탕 표 안 읽음) → 앞 풀이 그대로
|
||||
return copy.deepcopy(memo["sg"])
|
||||
solver = Solver()
|
||||
for owner, folder in entries:
|
||||
whole = item_prices.load_prices(read_json(folder / PRICE_FILE) or {})
|
||||
for src in rows_of(folder, whole):
|
||||
@@ -652,13 +639,12 @@ def collect(
|
||||
"sg_hand": hand_keys,
|
||||
}
|
||||
if memo is not None:
|
||||
memo.update(prices=solver.prices, sg=copy.deepcopy(out))
|
||||
memo.update(sg=copy.deepcopy(out))
|
||||
return out
|
||||
|
||||
|
||||
def build(
|
||||
project_root: str | Path,
|
||||
sheets: dict[str, Any],
|
||||
user: dict[str, Any] | None = None,
|
||||
memo: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
@@ -676,4 +662,4 @@ def build(
|
||||
entries += [
|
||||
(f"서고 {(m.get('원본') or {}).get('name') or f.name}", f) for f, m in copies(project_root)
|
||||
]
|
||||
return collect(entries, sheets, hand_sources(user), memo)
|
||||
return collect(entries, hand_sources(user), memo)
|
||||
|
||||
@@ -1412,3 +1412,11 @@ GC000089의 단가품셈 기초의 항목을 내가 하나 삭제했음. 되돌
|
||||
4. 우리 단가로가 기본이 될필요는 없음.
|
||||
5. 문제없음. 너의 제안 선택
|
||||
7. 경쟁사와 동일하게 반영
|
||||
|
||||
## 요구 865 (11-15 · 사용자 · B09 원가는 확인할 필요 없음 — M02 정보를 가져가 쓰기 때문 · B09 에 별도 로직이 있었다면 그것이 문제)
|
||||
|
||||
b09원가에서는 확인할필요 없음. 어차피 m02의정보를 가져가 사용하기때문. 만약b09에 별도의로직이 있었다면 문제임.
|
||||
|
||||
## 요구 866 (11-15 · 사용자 · B09 역할 = M02 에서 복사해 온 단가산출 · 일위대가 · 구조물 조합을 분류 · 합산해 보여 줌 · B08 · B09 에서 단가산출 · 일위대가를 개별로 계산하는 로직 삭제 · 합산 로직은 둠 · 기본값 설정은 사용자가 시점 잡음(잊음) · 코드 작업 진행)
|
||||
|
||||
우리 프로그램은 m02의 단가산출, 일위대가, 구조물조합을 미리 만들어두고 프로젝트 내부에는 복사해서 가져다 씀 이때. 복사해간 데이터들을 분류하고 합산하여 사용자에게 보여주는게 m09의 역할임. 단가산출과 일위대가를 계산하는 로직은 일단 걷어내면 됨. 이후 아직 내가 설명하지 기본값 설정을 구현하고 들어갈 생각임. 아직 기본값 설정에 대해서는 내가 판단하여 시점을 잡을 생각이니 잊고 일단은 b08, b09에서 개별로 계산하는 로직은 삭제. 합산 로직은 그냥 둬둬됨. 코드 작업진행해.
|
||||
|
||||
Reference in New Issue
Block a user