feat(778-5): 줄 잇기 한 벌 — 단위 줄 → 우리 계산 단위 + 상태(이어짐 · 고를 것 · 못 이음) · 일위대가 자재 맞춤 같은 함수(D-2 · 요구 622 · 623 · 624)
- 새 M02_Hopyo_Units: 노무 LB(같은 이름 여럿 = 건설업 먼저 · 작업반장) · 기계 EQ(Fixed.Linker) → 기계경비 기초 로직 줄(778-2) · 없는 규격 = 가까운 후보만 · 자재 MT(별칭 장부 › 이름 · 규격 › 바탕 이름 · 숫자 규격 · 이름에 붙은 규격 · 글자 규격 = 고를 것) · 손료율 방법 · 율 778-4 꼴(기준꼴 → 묶음 · 비목 · 줄 목록) · 하위 · 값 - Material: 숫자 규격 치수 차례 · 구분자 무관 견줌(dims) · 글자 규격 판별(lettered) · 단위 없는 줄(EST 블록) = 단위 안 봄 - Line._material = Units.material — 일위대가 자재 체크 제안 147 → 146(봉화 광명단조합페인트 「1종2류(철재)」 = 글자 규격 → 후보 · 요구 624-2) - 여덟 파일 불리는 블록 줄 842: 노무 366/366 · 기계 180/190 · 자재 71/130 · 율 150/152 · 하위 3/3 · 값 1/1 · 줄 다 이어진 블록 156/206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||||
from M02_MasterTemplete import M02_Hopyo_Option as option
|
||||
from M02_MasterTemplete import M02_Hopyo_Ours as ours
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_Hopyo_Units as units
|
||||
from M02_MasterTemplete import M02_Hopyo_Verdict as verdict
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
@@ -208,13 +209,14 @@ def _material(line: dict, ctx: Ctx, full: bool) -> dict:
|
||||
"""자재 · 별도계상 줄(358-2 · `M02_Hopyo_Material`) — 「별도계상」 글 = 꼬리표 · 맞음 =
|
||||
이름 · 규격 같음 하나 · 또는 바탕 이름 · 규격 수 같은 한 품목(줄 글 지역 · 지역 없는 것 ·
|
||||
「전국」 자리표 · 361-1) · 후보 = 그 품목 변형 모두(`변형` · 고르기 창이 접음)."""
|
||||
me, apart = hmat.clean(line)
|
||||
tags = [hmat.APART] if apart else []
|
||||
found = hm._exact(me, "자재", ctx.got)
|
||||
text = "이름 · 규격 같음"
|
||||
if len(found) != 1:
|
||||
sure, more = hmat.sure(me)
|
||||
found, text, tags = [r["one"] for r in sure], "바탕 이름 · 규격 같음", tags + more
|
||||
me, _ = hmat.clean(line)
|
||||
hit = units.material(line, ctx.got, full) # 줄 잇기 한 벌(778-5 · 단가산출 블록 자재와 같음)
|
||||
found, text, tags = hit["찾음"], hit["글"], hit["꼬리표"]
|
||||
if hit["상태"] == units.PICK and hit["후보"] and not found: # 글자 규격 = 후보로만(요구 624-2)
|
||||
cands = hit["후보"] if hit["글"] == units.LETTERS else []
|
||||
cands = cands or (hmat.candidates(me) if full else [])
|
||||
return {"상태": "후보", "체크": False, "대상": [], "후보": cands if full else [], "글": text,
|
||||
"꼬리표": tags} # fmt: skip
|
||||
if found:
|
||||
target = _mat_target(found[0], line)
|
||||
conv = target["환산"]
|
||||
|
||||
@@ -45,6 +45,8 @@ _NUM = re.compile(r"\d+(?:\.\d+)?")
|
||||
_RANGE = re.compile(r"(\d+(?:\.\d+)?)\s*(<=|≤|<)\s*[A-Za-z]\s*(<=|≤|<)\s*(\d+(?:\.\d+)?)")
|
||||
_TAIL = re.compile(r"[\s,\-]*([가-힣]+)$")
|
||||
SCALES = (1, 10, 100, 1000, 0.1, 0.01, 0.001)
|
||||
_N = r"\d+(?:\.\d+)?"
|
||||
_DIMS = re.compile(rf"(?i)((?:{_N})(?:\s*[×xX*]\s*{_N})*)\s*(?:mm|cm|m|㎜|㎝|㎡|㎥|kg|t|ton)?")
|
||||
|
||||
|
||||
def _nfkc(text: Any) -> str:
|
||||
@@ -148,11 +150,27 @@ def _near(x: float, y: float) -> bool:
|
||||
return any(abs(x * s - y) <= 0.02 * max(abs(y), 1e-9) for s in SCALES)
|
||||
|
||||
|
||||
def dims(spec: Any) -> tuple[float, ...] | None:
|
||||
"""숫자 규격(778-5 · 요구 624-2) → 치수 차례 무관 묶음 — 「600×1200」 = 「1200x600 mm」 · 구분자
|
||||
× x X * · 끝 단위 하나 · 글 섞이면 None."""
|
||||
hit = _DIMS.fullmatch(_nfkc(spec).strip())
|
||||
return tuple(sorted(_nums(hit.group(1)))) if hit else None
|
||||
|
||||
|
||||
def lettered(spec: Any) -> bool:
|
||||
"""글자 든 규격(한글 낱말 · 지역 · 관급/사급 · 별도계상 뺌) — 같은 규격 판정 대신 사용자 확인."""
|
||||
return bool(_words(_APART.sub("", _nfkc(spec))))
|
||||
|
||||
|
||||
def spec_fit(line: dict[str, Any], row: dict[str, Any]) -> float:
|
||||
"""규격 맞음 0~1 — 같음 · 범위에 듦 · 줄 규격 수가 자재 규격 수에 있는 몫."""
|
||||
"""규격 맞음 0~1 — 같음 · 숫자 규격 치수 같음(차례 · 구분자 무관) · 범위에 듦 · 줄 규격 수가 자재
|
||||
규격 수에 있는 몫."""
|
||||
text = _nfkc(f"{line.get('규격') or ''} {' '.join(hm._PAREN.findall(str(line.get('이름'))))}")
|
||||
if mm.plain_spec(line.get("규격")) == row["spec"]:
|
||||
return 1.0
|
||||
a = dims(line.get("규격"))
|
||||
if a and a == dims((row.get("one") or {}).get("규격")):
|
||||
return 1.0
|
||||
mine = _nums(text)
|
||||
if row["ranges"]:
|
||||
inside = any(
|
||||
@@ -204,13 +222,14 @@ def _paren_ok(line: dict[str, Any], row: dict[str, Any]) -> bool:
|
||||
|
||||
|
||||
def sure(line: dict[str, Any]) -> tuple[list[dict[str, Any]], list[str]]:
|
||||
"""맞음 ② — 바탕 이름 같음 · 단위 · 괄호 낱말 · 규격 맞음 1 이 한 품목 → (변형 차례, 꼬리표) ·
|
||||
아니면 ([], [])."""
|
||||
"""맞음 ② — 바탕 이름 같음 · 단위(줄에 없으면 안 봄 · EST 블록 줄) · 괄호 낱말 · 규격 맞음 1 이 한
|
||||
품목 → (변형 차례, 꼬리표) · 아니면 ([], [])."""
|
||||
_, index = pool()
|
||||
rows = [
|
||||
r
|
||||
for r in index.get(base(line.get("이름")), [])
|
||||
if hm.fits(line.get("단위"), r["one"]["단위"]) and _paren_ok(line, r)
|
||||
if (not line.get("단위") or hm.fits(line.get("단위"), r["one"]["단위"]))
|
||||
and _paren_ok(line, r)
|
||||
]
|
||||
full = [r for r in rows if spec_fit(line, r) == 1.0]
|
||||
unit = mm.plain_unit(line.get("단위"))
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
"""M02 일위대가 실무자료 줄 잇기 한 벌(PLAN 778-5 · D-2 · 요구 622 · 623 · 624) — 단위 줄
|
||||
(`M02_Hopyo_UnitLine`) → 우리 계산 단위 + 상태. 형식(EST · STmate) · 대상(단가산출 블록 · 일위대가 줄)을
|
||||
모름 — 일위대가 자재 줄(`M02_Hopyo_Line._material`)도 `material` 을 씀.
|
||||
|
||||
잇기 = `{상태 이어짐|고를 것|못 이음, 방법, 단위, 후보, 꼬리표, 글}` · 자동 없음(제안만 · 사용자가 확인).
|
||||
L 노무 = 직종 LB(이름 · 옛이름) · 같은 이름 여럿 = 건설업 먼저(작업반장 · 요구 624-1) · 단위 `{노임}`
|
||||
X 기계 = EQ(`Fixed.Linker._machine` · 778-1 괄호 낱말) → 기계경비 기초 로직(`SkPatch._mach_logics` ·
|
||||
778-2) · 단위 `{기계, 로직, 할증?}` · 없는 규격 = 가까운 규격 후보만(`Elem.machines` · 자동 고름 없음)
|
||||
M 자재 = MT(`material` — 별칭 장부 › 이름 · 규격 같음 › 바탕 이름 · 숫자 규격 같음(치수 차례 · 구분자
|
||||
무관) · 글자 든 규격 = 후보로만) · 손료율 = 방법 「수량 × 손료율 × 단가」 · 별도 = 단위 `{자재?,
|
||||
별도계상}` · 단위 `{자재}`
|
||||
R 율 = 778-4 꼴 `{기준, 비목}` — 기준 = `기준꼴`(인력 · 재료 묶음 · 노무비 · 줄 목록 `{행: [...]}`) ·
|
||||
우리에 없는 율 항목(설치비 · 제경비)도 율 줄 · 기준 못 찾음 = 고를 것(글 기준 제안)
|
||||
S 하위 = `{하위 {종류 단산|일위, 번}}`(SK · SC 참조) · E 값 = `{값: 금액}`(고정 금액 줄)
|
||||
별칭 장부 짝(자재) = 숫자보다 먼저(`Line.match_line` 규칙). 서버 단독 · 읽기만.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from M02_MasterTemplete import M02_Hopyo_Elem as elem
|
||||
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
|
||||
from M02_MasterTemplete import M02_Hopyo_Ledger as ledger
|
||||
from M02_MasterTemplete import M02_Hopyo_Match as hm
|
||||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||||
from M02_MasterTemplete import M02_Hopyo_Shares as shares
|
||||
from M02_MasterTemplete import M02_Hopyo_SkPatch as skpatch
|
||||
from M02_MasterTemplete import M02_Hopyo_UnitLine as unit_line
|
||||
from M02_MasterTemplete.M02_Hopyo_Words import mtypes
|
||||
|
||||
LINKED, PICK, NONE = "이어짐", "고를 것", "못 이음"
|
||||
HOW = {
|
||||
"L": "노무 = 직종 × 품 × 노임",
|
||||
"X": "기계 = 시간 × 시간당 기계경비",
|
||||
"M": "자재 = 수량 × 단가",
|
||||
"R": "율 = 기준 × 율",
|
||||
"S": "하위 = 하위 값 × 수량",
|
||||
"E": "값 = 고정 금액",
|
||||
}
|
||||
WEAR = "자재 = 수량 × 손료율 × 단가"
|
||||
APART = "자재 = 별도계상(0 원)"
|
||||
TRADE_FIRST = "건설업" # 같은 이름 노임 여럿 = 건설업 먼저(임도 = 토목 · 요구 624-1)
|
||||
LETTERS = "글자 규격 — 확인"
|
||||
# 율 기준꼴(`UnitLine.base_rows`) → 778-4 기준(묶음 · 비목) · 그 밖 = 줄 목록
|
||||
BASES = {"인력": "인력", "인력(블록)": "인력", "자재": "재료", "자재(블록)": "재료"}
|
||||
BASES |= {k: k for k in ("노무비", "재료비", "경비")} # 일위대가 % 줄 기준(규격 글)
|
||||
_PART_WORDS = (("공구손료", "경비"), ("재료", "재료비"), ("자재", "재료비"), ("소모", "재료비"))
|
||||
|
||||
|
||||
def _cands(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return [{**hm._public(r), "점수": r.get("점수"), "판정": "후보"} for r in rows]
|
||||
|
||||
|
||||
def _split(me: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""이름에 붙은 규격 · 괄호 이름 — 「이름 규격」 · 「이름(규격)」(끝 괄호에 수) · 「유로폼(패널)」 =
|
||||
유로폼패널 · 「패널(유로폼)」 = 유로폼패널 · 끝 「[㎥]」 뗌."""
|
||||
name = re.sub(r"\[[^\]]*\]\s*$", "", str(me.get("이름") or "")).strip()
|
||||
spec = str(me.get("규격") or "")
|
||||
pairs = []
|
||||
if not spec:
|
||||
head, _, rest = name.partition(" ")
|
||||
pairs += [(head, rest)] if rest else []
|
||||
if hit := re.match(r"^(.+?)\s*[((]([^()()]*\d[^()()]*)[))]\s*$", name):
|
||||
pairs.append((hit.group(1), hit.group(2)))
|
||||
out = []
|
||||
for n, sp in [*pairs, (name, spec)]:
|
||||
if hit := re.fullmatch(r"([^()]+)\(([^()\d]+)\)", n.strip()):
|
||||
a, b = hit.group(1).strip(), hit.group(2).strip()
|
||||
out += [{**me, "이름": a + b, "규격": sp}, {**me, "이름": b + a, "규격": sp}]
|
||||
elif (n, sp) != (me.get("이름"), spec):
|
||||
out.append({**me, "이름": n, "규격": sp})
|
||||
return out
|
||||
|
||||
|
||||
def material(line: dict[str, Any], got: dict[str, Any], full: bool = True) -> dict[str, Any]:
|
||||
"""자재 줄(별도계상 글 뗌) → `{상태, 찾음 [M01 자재], 글, 꼬리표, 후보}` — 이름 · 규격 같음 하나 ›
|
||||
바탕 이름 · 숫자 규격 같음(이름에 붙은 규격 「이름 규격」 · 「이름(규격)」도) · 글자 규격 = 고를 것."""
|
||||
me, apart = hmat.clean(line)
|
||||
tags = [hmat.APART] if apart else []
|
||||
found = hm._exact(me, "자재", got)
|
||||
if len(found) == 1:
|
||||
return {"상태": LINKED, "찾음": found, "글": "이름 · 규격 같음", "꼬리표": tags, "후보": []}
|
||||
tries = [me, *_split(me)]
|
||||
for one in tries:
|
||||
sure, more = hmat.sure(one)
|
||||
if not sure:
|
||||
continue
|
||||
rows = [r["one"] for r in sure]
|
||||
if hmat.lettered(one.get("규격")):
|
||||
cands = [{**hm._public(r), "점수": None, "판정": "후보", "글": LETTERS} for r in rows]
|
||||
return {"상태": PICK, "찾음": [], "글": LETTERS, "꼬리표": tags + more, "후보": cands}
|
||||
return {"상태": LINKED, "찾음": rows, "글": "바탕 이름 · 규격 같음", "꼬리표": tags + more,
|
||||
"후보": []} # fmt: skip
|
||||
cands = hmat.candidates(me) if full else []
|
||||
state = PICK if cands or not full else NONE
|
||||
return {
|
||||
"상태": state,
|
||||
"찾음": [],
|
||||
"글": "후보만" if cands else "없음",
|
||||
"꼬리표": tags,
|
||||
"후보": cands,
|
||||
}
|
||||
|
||||
|
||||
def rate_part(name: Any) -> str:
|
||||
"""율 금액이 드는 비목 — 공구손료 = 경비 · 재료 · 자재 · 소모 = 재료비 · 그 밖 경비."""
|
||||
flat = re.sub(r"\s", "", str(name or ""))
|
||||
return next((part for word, part in _PART_WORDS if word in flat), "경비")
|
||||
|
||||
|
||||
def _text_base(one: dict[str, Any]) -> str:
|
||||
"""율 줄 기준 글(「노무비의」 · 「주재료의」 · STmate 표지) → 778-4 기준 제안."""
|
||||
text = re.sub(r"\s", "", f"{one.get('기준') or ''} {one.get('기준글') or ''} {one.get('이름')}")
|
||||
if "주재료" in text or "주자재" in text or "M:" in text:
|
||||
return "재료"
|
||||
if "재료비" in text:
|
||||
return "재료비"
|
||||
if "경비" in text and "노무" not in text:
|
||||
return "경비"
|
||||
return "노무비"
|
||||
|
||||
|
||||
class Units:
|
||||
"""한 번 잇기 — M01 · 목록 · 장부를 한 번 읽음(`books` = `Ledger.books` · 없으면 장부 안 봄)."""
|
||||
|
||||
def __init__(self, books: list | None = None, doc: str | None = None) -> None:
|
||||
self.linker = hfix.Linker(by_name=True)
|
||||
self.whole, self.got = self.linker.whole, self.linker.got
|
||||
self.books, self.doc = books, doc
|
||||
self.eqs = {str(r.get("키")): r for r in self.whole.numbers.get("EQ", {}).values()}
|
||||
self.lb: dict[str, list[dict]] = {}
|
||||
for row in self.whole.numbers.get("LB", {}).values():
|
||||
self.lb.setdefault(str(row.get("이름") or ""), []).append(row)
|
||||
|
||||
def line(self, one: dict[str, Any]) -> dict[str, Any]:
|
||||
kind = one.get("묶음")
|
||||
how = HOW.get(str(kind), HOW["E"])
|
||||
got = getattr(self, f"_{kind}", self._E)(one)
|
||||
return {"방법": how, "후보": [], "꼬리표": [], "글": "", **got}
|
||||
|
||||
def _L(self, one: dict[str, Any]) -> dict[str, Any]:
|
||||
name = shares.trade(one.get("직종") or one.get("이름")) or ""
|
||||
rows = self.lb.get(name, [])
|
||||
cands = [{"키": str(r.get("키")), "갈래": "노임", "이름": r.get("이름"),
|
||||
"구분": r.get("구분"), "단가": elem._num(r.get("값"))} for r in rows] # fmt: skip
|
||||
if len(rows) == 1:
|
||||
return {"상태": LINKED, "단위": {"노임": cands[0]["키"]}}
|
||||
first = [c for c in cands if c["구분"] == TRADE_FIRST]
|
||||
if len(first) == 1:
|
||||
rest = [c for c in cands if c is not first[0]]
|
||||
tag = f"기본 — {TRADE_FIRST} {name}"
|
||||
return {"상태": LINKED, "단위": {"노임": first[0]["키"]}, "후보": rest, "꼬리표": [tag]}
|
||||
named = elem.labor(str(one.get("이름") or ""))
|
||||
return {"상태": PICK if named or cands else NONE, "단위": None, "후보": cands or named}
|
||||
|
||||
def _X(self, one: dict[str, Any]) -> dict[str, Any]:
|
||||
line = {"이름": one.get("이름"), "규격": one.get("규격") or ""}
|
||||
code = self.linker._machine(line)
|
||||
if code is None:
|
||||
cands = elem.machines(f"{line['이름']} {line['규격']}")
|
||||
return {"상태": PICK if cands else NONE, "단위": None, "후보": cands,
|
||||
"글": "가까운 규격 — 고름" if cands else "같은 기계 없음"} # fmt: skip
|
||||
eq = self.eqs.get(code) or {}
|
||||
kinds = mtypes(f"{eq.get('상세구분')} {eq.get('이름')}")
|
||||
unit = {"기계": code, "로직": skpatch._mach_logics(self.whole, kinds, eq)[0]}
|
||||
unit |= {"할증": one["할증"]} if one.get("할증") else {}
|
||||
return {
|
||||
"상태": LINKED,
|
||||
"단위": unit,
|
||||
"글": f"{eq.get('이름')} {eq.get('규격') or ''}".strip(),
|
||||
}
|
||||
|
||||
def _M(self, one: dict[str, Any]) -> dict[str, Any]:
|
||||
how = WEAR if one.get("손료율") else HOW["M"]
|
||||
apart = one.get("어떻게") == "별도" or one.get("별도")
|
||||
line = {
|
||||
"이름": one.get("이름"),
|
||||
"규격": one.get("규격") or "",
|
||||
"단위": one.get("단위") or "",
|
||||
}
|
||||
line |= {"갈래": hmat.APART} if apart else {}
|
||||
pair = self._alias(line)
|
||||
if pair:
|
||||
unit = {"자재": pair} | ({"별도계상": True} if apart else {})
|
||||
return {"상태": LINKED, "방법": how, "단위": unit, "꼬리표": ["별칭 장부 짝"]}
|
||||
got = material(line, self.got)
|
||||
apart = apart or hmat.APART in got["꼬리표"]
|
||||
found = [r for r in got["찾음"] if r.get("자재")]
|
||||
if got["상태"] == LINKED and found:
|
||||
unit = {"자재": found[0]["자재"]} | ({"별도계상": True} if apart else {})
|
||||
return {
|
||||
"상태": LINKED,
|
||||
"방법": how,
|
||||
"단위": unit,
|
||||
"글": got["글"],
|
||||
"꼬리표": got["꼬리표"],
|
||||
}
|
||||
if apart: # 별도계상 = 0 원 줄(우리 자재 못 찾아도 셈 같음)
|
||||
return {"상태": LINKED, "방법": APART, "단위": {"별도계상": True}, "후보": got["후보"]}
|
||||
state = got["상태"] if got["상태"] != LINKED else PICK # 「전국」 자리표 = 지역 고름
|
||||
return {"상태": state, "방법": how, "단위": None, "후보": got["후보"] or _cands(got["찾음"]),
|
||||
"글": got["글"], "꼬리표": got["꼬리표"]} # fmt: skip
|
||||
|
||||
def _alias(self, line: dict[str, Any]) -> str | None:
|
||||
"""별칭 장부 자재 짝(자동 정확 열쇠 · 하나) — 숫자보다 먼저."""
|
||||
if self.books is None:
|
||||
return None
|
||||
got = ledger.recall(line, "", self.books, doc=self.doc)
|
||||
if not got or got.get("제안") or got.get("규격다름") or got.get("갈래") != "짝":
|
||||
return None
|
||||
pairs = [p.get("자재") for p in got.get("짝") or []]
|
||||
return pairs[0] if len(pairs) == 1 and pairs[0] else None
|
||||
|
||||
def _R(self, one: dict[str, Any]) -> dict[str, Any]:
|
||||
part = rate_part(one.get("이름"))
|
||||
shape = one.get("기준꼴")
|
||||
tags = ["이름 없음"] if one.get("이름없음") else []
|
||||
if shape in BASES:
|
||||
return {"상태": LINKED, "단위": {"기준": BASES[shape], "비목": part}, "꼬리표": tags}
|
||||
if shape and one.get("기준줄"):
|
||||
unit = {"기준": {"행": list(one["기준줄"])}, "비목": part}
|
||||
return {"상태": LINKED, "단위": unit, "글": shape, "꼬리표": tags}
|
||||
guess = {"기준": _text_base(one), "비목": part}
|
||||
return {"상태": PICK, "단위": None, "후보": [guess], "글": "기준 못 찾음 — 기준 고름",
|
||||
"꼬리표": tags} # fmt: skip
|
||||
|
||||
def _S(self, one: dict[str, Any]) -> dict[str, Any]:
|
||||
sub = one.get("하위") or {}
|
||||
if not sub.get("번"):
|
||||
return {"상태": NONE, "단위": None, "글": "하위 번 없음"}
|
||||
return {"상태": LINKED, "단위": {"하위": {"종류": sub.get("종류"), "번": int(sub["번"])}}}
|
||||
|
||||
def _E(self, one: dict[str, Any]) -> dict[str, Any]:
|
||||
got = one.get("금액")
|
||||
if got is None:
|
||||
got = unit_line.money(one.get("단가"))
|
||||
if got is None:
|
||||
return {"상태": NONE, "단위": None, "글": "금액 없음"}
|
||||
return {"상태": LINKED, "방법": HOW["E"], "단위": {"값": float(got)}}
|
||||
|
||||
|
||||
def hopyo_unit(line: dict[str, Any]) -> dict[str, Any]:
|
||||
"""일위대가 줄(`UnitLine.check_hopyo` 투영 뒤) → 단위 줄 꼴 — 이름 · 규격 · 단위 · 품 = 수량 · 별도."""
|
||||
one = {k: line.get(k) for k in ("묶음", "이름", "규격", "단위", "할증")}
|
||||
one.update(품=line.get("수량"), 금액=unit_line.money(line.get("금액")), 직종=None)
|
||||
if line.get("갈래") == "별도계상":
|
||||
one["어떻게"] = "별도"
|
||||
if one["묶음"] == "R":
|
||||
base = str(line.get("기준") or "노무비")
|
||||
one.update(기준=base, 기준꼴={"주재료비": "자재"}.get(base, base))
|
||||
one["품"] = float(line["수량"]) / 100 if line.get("수량") is not None else None
|
||||
ref = line.get("참조") or {}
|
||||
if one["묶음"] == "S" and ref.get("번"):
|
||||
one["하위"] = {"종류": ref.get("종류"), "번": ref.get("번")}
|
||||
return one
|
||||
|
||||
|
||||
def summary(rows: list[dict[str, Any]]) -> dict[str, dict[str, int]]:
|
||||
"""묶음별 `{묶음: {이어짐, 고를 것, 못 이음}}` — 줄 연결 표."""
|
||||
out: dict[str, dict[str, int]] = {}
|
||||
for kind, state in rows:
|
||||
one = out.setdefault(kind, dict.fromkeys((LINKED, PICK, NONE), 0))
|
||||
one[state] += 1
|
||||
return out
|
||||
Reference in New Issue
Block a user