- Finger — 풀이 두 벌(_text · _stmate) 걷고 블록 단위 줄 투영 하나(_units) · 글은 조건 · 옛 판 · 계수 · 「…의 N %」 만 · 셈 밖 줄 안 씀 - Shares — 몫 = 단위 줄을 절(제목 · 윗절)로 묶음 한 벌 · 식 다시 풀기(qty_of · sections_stm) 걷음 · trade = ReadEst.trade - Elem.prices = 단위 줄 단가(글 다시 안 풂 · machines 갈래 안 건드림) - Match.clause = Basis.clause 만 · 대체 몸 · _TYPO_CLAUSE 걷음(오타는 읽기 ReadEst.sources) · fix_typo = 사본(Line 이 부름) - Fit.cache_path — HP 지문 칸(올린 엑셀 sha256 + 판) · 파일 그대로면 다시 안 읽음(lru) · 붙여넣기 HP 만 파일 해시 - 지문 전후 = 셈 단위 275 중 150 같음 · 다름 = 장비 절 · 글(쓰는 곳 없음) 100 · 고침(STmate 기계 두 묶음 · 거창 자재 · 끝 점 직종 · 옛 몫 금액 ÷ 단가 엉터리 · 제외금액 줄) — 골든 1위 줄마다 · 불리는 블록 206 갈래 · 1위 · 상태 전후 같음(162/177 · 기초 80 · SK 37 · 대기 89) - S1 다시 굳힘 = 봉화 · 울진대흥 · 울진소광 자원(같은 코드 두 묶음 따로) · S2 다시 굳힘 125/275(위 까닭) - 시험 고침 — finger(블록 = ReadEst.items · STmate 단위 줄 · 셈 밖) · fit 옛 HP(항목 · 글 없음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
186 lines
8.7 KiB
Python
186 lines
8.7 KiB
Python
"""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"(?<![A-Za-z가-힣])(q|qs|q1|K|k|f|f1|f2|E|Es|Cm|L|V1|V2|T|rt|n|t1|t2|t3|t4)\s*=\s*"
|
||
r"(\d+(?:\.\d+)?)(?![\d.]*\s*[×x*÷/])"
|
||
)
|
||
_SECTION = re.compile(r"^\s*(\d+\.|[가-하]\.)")
|
||
OLD = re.compile(
|
||
r"((?<![\d,.])20(0\d|1\d|20)(?![\d,.])|(?<!\d)(1\d|20)년|임도품셈|임도표준품셈|사방품셈|사방사업표준품셈)"
|
||
)
|
||
_HOURS = re.compile(r"(hr|시간)", re.I)
|
||
TRADE_UNITS = ("인",)
|
||
rates_in = hopyo_est.rates_in
|
||
|
||
|
||
def _flat(s: Any) -> 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
|