"""M02 일위대가 실무자료 엑셀 지문(PLAN 338-1 · 설계 2-2 · 776-3) — 호표 줄 + 이어진 단가산출 블록 → 직종 품 · 장비 시간 · 자재 수량 · 율 · 계수 · 조건 · 조항 · 몫. 금액 안 씀. 형식 모름 — 블록 단위 줄(`항목` · `M02_Hopyo_UnitLine`)의 투영: L 노무 = 직종 품 · 식이 있으면 맨 인자가 섞음 비율 후보(호표 이름 · 규격 · 절 제목의 (N%) · 8:2)와 같을 때 율로 가름 · 식 없는 줄(QTY)은 호표 이름 비율 둘 이상이면 작은 쪽(인력) X 기계 = 장비 {갈래, 규격, 시간, 밑시간, 율, 절, 글} · 식 없는 줄은 큰 비율(장비)로 가름 M 자재 = 식(수량) · 별도계상(품 없음) · E 값 줄에 수량이 있으면 장비 글이면 장비 · 아니면 자재 셈 밖 · 율 · 하위 줄 = 안 씀(율 % 는 글에서). 글(블록 머리 「N.이름 [단위]」 + 몸 글) = 조건 · 옛 판 · 계수(q · K · f …) · 글 속 「…의 N %」. 몫(조합용 · 설계 2-7) = 절마다 노임 · 장비 벡터(`M02_Hopyo_Shares.sections`). 블록 없는 기계(hr) 줄 = `machine` · 노임 · 장비를 바로 적은 호표 = `direct`. """ from __future__ import annotations import re from typing import Any from M02_MasterTemplete import M02_Hopyo_ReadEst as hopyo_est from M02_MasterTemplete import M02_Hopyo_Shares as shares from M02_MasterTemplete.M02_Hopyo_Shares import trade from M02_MasterTemplete.M02_Hopyo_Words import conds, mtypes, specnums _PCT = re.compile(r"([가-힣]+)의\s*([\d.]+)\s*%") _COEF = re.compile( r"(? str: return re.sub(r"\s+", "", str(s or "")) def empty(line: dict[str, Any], block: dict[str, Any] | None = None) -> dict[str, Any]: block = block or {} basis = line.get("근거") or {} name, spec = str(line.get("이름") or ""), str(line.get("규격") or "") return { "이름": name, "규격": spec, "단위": str(line.get("단위") or ""), "출처": [list(x) for x in block.get("출처") or basis.get("출처") or []], "인력": {}, # 직종: {품, 밑품, 율, 어떻게} "장비": [], # {갈래, 규격, 시간, 밑시간, 율, 절, 글} "자재": [], # {이름, 품, 밑품} "율": [], # [바탕, %] "계수": {}, "조건": {}, "이름조건": {k: sorted(v) for k, v in conds(f"{name} {spec}").items()}, "옛판": False, "몫": [], } def _labor(fp: dict, name: str, q: float, base: float, rates: list, how: str) -> None: one = fp["인력"].setdefault(name, {"품": 0.0, "밑품": 0.0, "율": [], "어떻게": []}) one["품"] += q one["밑품"] += base one["율"] += rates one["어떻게"].append(how) def _round(fp: dict[str, Any]) -> dict[str, Any]: for one in fp["인력"].values(): one["품"], one["밑품"] = round(one["품"], 6), round(one["밑품"], 6) return fp def excel(block: dict[str, Any] | None, line: dict[str, Any]) -> dict[str, Any]: """HP 블록(설계 3-1) + 그 줄(이름 · 규격 · 단위) → 지문 · 블록이 없거나 단위 줄 · 글이 없으면 기계(hr) 줄 지문(장비 1 시간) 또는 빈 지문.""" if block and (block.get("항목") or block.get("글")): return _round(_units(block, line)) return machine(line) def machine(line: dict[str, Any]) -> dict[str, Any]: """블록 없는 기계(hr) 줄 → 장비 [{갈래, 규격, 시간 1}].""" fp = empty(line) text = f"{fp['이름']} {fp['규격']}" types = mtypes(text) if types: hours = 1.0 if _HOURS.search(fp["단위"]) else None one = {"갈래": sorted(types), "규격": sorted(specnums(text)), "시간": hours} fp["장비"] = [{**one, "밑시간": None, "율": [], "절": "0", "글": fp["이름"]}] fp["조건"] = fp["이름조건"] return fp def _text(fp: dict[str, Any], block: dict[str, Any]) -> None: """블록 글 → 조건(머리 + 앞 넉 줄 + ※ · 작업조건 줄) · 옛 판 · 계수 · 「…의 N %」.""" head = f"{block.get('번', '')}.{block.get('이름', '')} [{block.get('단위', '')}]" text = [head, *[str(t) for t in block.get("글") or []]] front = text[:5] + [t for t in text if "※" in t or "작업조건" in t] got = conds(" ".join([fp["이름"], fp["규격"], *front])) fp["조건"] = {k: sorted(v) for k, v in got.items()} fp["옛판"] = bool(OLD.search(" ".join(text[:6]))) for row in text[1:]: fp["율"] += [[k, float(v)] for k, v in _PCT.findall(row)] if not _SECTION.match(row): for k, v in _COEF.findall(row): fp["계수"].setdefault(k, float(v)) def _unmix(one: dict[str, Any], rates: set[float]) -> tuple[float, list]: """(밑품, 율) — 율 메모가 없는데 맨 인자가 섞음 비율 후보와 같으면 율로 가름.""" if not one.get("율"): for v in one.get("인자") or []: if round(v, 4) in rates and 0 < v < 1: return one["밑품"] / v, [[round(v, 4), "섞음"]] return one["밑품"], one.get("율") or [] def _units(block: dict[str, Any], line: dict[str, Any]) -> dict[str, Any]: fp = empty(line, block) _text(fp, block) item_rates = rates_in(f"{fp['이름']} {fp['규격']}") pair = sorted(item_rates) if len(item_rates) >= 2 else [] lab_rate, mach_rate = (pair[0], pair[-1]) if pair else (None, None) for one in block.get("항목") or []: kind, q, how = one.get("묶음"), one.get("품"), one.get("어떻게") if one.get("밖") or (q is None and how != "별도"): continue text = f"{one.get('이름') or ''} {one.get('규격') or ''}".strip() rates = item_rates | set(one.get("절율") or []) if kind == "L" and (who := one.get("직종") or trade(one.get("이름"))): if how == "QTY": base, r = (q / lab_rate, [[lab_rate, "섞음"]]) if lab_rate else (q, []) else: base, r = _unmix(one, rates) if how == "식" else (one["밑품"], one["율"]) _labor(fp, who, q, base, r, how) elif kind == "X" or (kind == "E" and mtypes(text)): if how == "QTY": base, r = (q / mach_rate if mach_rate else q), [] else: base, r = _unmix(one, rates) sizes = specnums(one.get("규격") or "") | specnums(one.get("이름") or "") got = {"갈래": sorted(mtypes(text)), "규격": sorted(sizes), "시간": q} fp["장비"].append({**got, "밑시간": base, "율": r, "절": one.get("절"), "글": text}) elif kind in ("M", "E") and (how in ("식", "별도", "QTY")): fp["자재"].append({"이름": text, "품": q, "밑품": one.get("밑품", q)}) fp["몫"] = shares.sections(block.get("항목") or []) return fp def direct(hopyo: dict[str, Any]) -> dict[str, Any] | None: """노임 · 장비를 바로 적은 호표 → 줄 수량 합 지문(호표 1 단위) · 근거글 출처 — 없으면 None.""" lines = hopyo.get("줄") or [] fp = empty({k: hopyo.get(k) for k in ("이름", "규격", "단위")}) texts = [str(t) for t in hopyo.get("근거글") or []] texts += [f"{x.get('이름')} {x.get('규격')}" for x in lines if x.get("갈래") == "근거글"] fp["출처"] = hopyo_est.sources(" ".join(texts)) hit = False for x in lines: q = x.get("수량") if not isinstance(q, (int, float)) or q <= 0: continue if kind_of(x) == "노임" and (who := trade(x.get("이름"))): _labor(fp, who, float(q), float(q), [], "QTY") hit = True elif kind_of(x) == "기계": text = f"{x.get('이름')} {x.get('규격')}" if ts := mtypes(text): one = {"갈래": sorted(ts), "규격": sorted(specnums(text)), "시간": float(q)} fp["장비"].append({**one, "밑시간": float(q), "율": [], "절": "0", "글": text}) hit = True return _round(fp) if hit and fp["인력"] else None def kind_of(line: dict[str, Any]) -> str: """줄 갈래 + 「노임」(단위 인 + 우리 직종 · 나 창 읽기 전에도).""" kind = str(line.get("갈래") or "") unit = _flat(line.get("단위")) if kind in ("기타", "자재", "노임") and unit in TRADE_UNITS and trade(line.get("이름")): return "노임" return kind