Files
Aislo/M02_MasterTemplete/M02_Hopyo_Elem.py
T
eomsangdonandClaude Opus 5.5 3d4b2ec1d1 feat(812-9): 가져온 기계 잇기 다시 — 기계표 전체 · 표기 정규화 · 괄호 낱말 · 범위 규격 · 별칭 먼저 · 조합 기계 둘 · 조회어 기종만
- 새 `M02_Machine_Find` — 기종 = 기계표 이름 · 괄호 붙임 · 상세구분(낱말표 24 안 씀) · 소리 표기 한 글(로우더 = 로더 · 로울러 = 롤러 · 브레이카 = 브레이커 · 굴삭기 = 굴착기 · 램 머 = 래머 …) · 기계표 괄호 낱말로 좁힘(표에 없는 낱말 발파 · 리핑 = 안 봄) · 규격 = 수 + 단위 전부(범위 · 복수 · 인치 = ㎝) · 하나 = 이음 · 여럿 = 「기계 후보 n — 고르기」 · 0 = 「기계 못 찾음」 표지(가까운 규격으로 대신 잇지 않음)
- 별칭 먼저 — 마스터 층 + 지금 층 별칭 장부 기계 짝(이름 + 규격 · 이름만 · 규격 속 할증 글 뺌) · `M02_Doc_Match_Note._ALIAS` 여섯 쌍 걷음(소리 표기 = 정규화 · 같은 기종 = 기계표 · 별칭 장부)
- 조합 기계 = 기계마다 항목 하나(요구 738-3 · 제목 = 그 기계 · 줄 쌓기 차림 `M02_Doc_NativeLayout`) · 줄 둘(「+」 · 괄호 속 다른 기종 · 「…조합」 · 덤프트럭(자동덮개) = 덤프트럭 + 자동덮개시설) — 같은 수량 식 · 엑셀 값은 첫 줄 · 다음 줄 0 + 표지 「조합 기계 — 경비는 한 덩이」 · 우리 단가로 = 각자 시간당 경비
- 원파일 EST · STmate · 엑셀 읽기기 · 일위대가 호표(`Hopyo_Elem` · `Hopyo_Fixed._eqs` · `Hopyo_Row.m01_picks`)가 같은 찾기 · STmate 묶음 기계 줄도 시간당 경비 이음
- 문서 머리 `가져온.기계`(기호마다 판정 · 후보 · 찾기어) · 못 이은 기계 줄도 기계 줄(이름 단추 · `owned_of` 파이썬 · TS 짝) · 이름 단추 조회어 = 기종 이름만(규격 뺌) · 고르면 엑셀 값 기억 그대로(금액 안 바뀜) · 문서 [저장] = 고른 EQ → 이 층 별칭 장부 줄 → M01 별칭 장부 후보(승인 흐름 그대로) · 장부 짝 글 = 「기계 EQ…」
- 마스터 층 별칭 장부(resources/master_template/실무자료/맞춤.json) = 사용자 승인 다섯(요구 735-5 · 736) — 아스팔트페이버(피니셔) = 아스팔트 피니셔 · 디젤 엔진 = 엔진(디젤) · 워터제트 = 워터젯트 · 버킷준설기 = 버킷식준설기 · 바이브레이트 봉상후렉시블 = 콘크리트 진동기 엔진식 ø45(2.6㎾)
- 시험 — 새 test_812_9(예 전부 · 정규화 · 괄호 · 범위 · 조합 둘 · 별칭 먼저 · 표지 · 고르면 별칭 줄 · 금액 전후) · test_812_3 전석쌓기 · 측구터파기 연암 항목을 기계마다 항목으로 고침(조합 기계 둘로 가름 · 요구 738-3 이 바꾼 까닭)
- 전수 목록 docs/raw/요구_1104/_목록_812-9_기계이음.xlsx

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:02:29 +09:00

153 lines
6.4 KiB
Python

"""M02 일위대가 불러오기 — 못옮김 · 안 이은 기계 줄 → M01 요소 후보 셋(PLAN 746-1 · 요구 585).
후보 = `[{키, 갈래 노임|기계|자재|유가, 이름, 규격, 단위, 단가}]` 셋까지 — 보여주기만(자동 이음 없음 ·
고르면 지금 잇기 길 `{노임|기계|자재|유가: 키}`).
자재 = `M02_Hopyo_Material.candidates` 점수(이름 글자 묶음 · 규격) · 단가 = M01 자재 값(엑셀 줄 단위)
노임 = LB 이름 글자 묶음 닮음 · 단가 = LB 값
기계 = 기계 찾기(812-9 `M02_Machine_Find` — 기계표 전체 · 표기 정규화 · 괄호 낱말 · 규격) 후보 ·
단가 없음(기계경비는 로직 셈) · `machine` = 하나로 정할 때만
유가(785-3) = MO 이름에 글 이름이 듦(경유 ⊂ 자동차용경유) · 전국 먼저 · 단가 = MO 값
차례 = 점수(0.1 단위로 접음) › 엑셀 단가에 가까운 것(엑셀 단가 = 블록 단위 줄 단가 · 모르면 점수만).
서버 단독 · 읽기만.
"""
from __future__ import annotations
import math
import re
from typing import Any
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Hopyo_Match as hm
from M02_MasterTemplete import M02_Hopyo_Material as hmat
from M02_MasterTemplete import M02_Hopyo_UnitLine as unit_line
from M02_MasterTemplete import M02_Machine_Find as mfind
from M02_MasterTemplete.M02_Hopyo_Words import nfkc
TOP = 3
POOL = 5
def prices(block: dict[str, Any] | None) -> dict[str, float]:
"""블록 단위 줄(노무 · 자재 · 값) → `{이름: 엑셀 단가}`(첫 것 · 776-3 — 글 다시 안 풂)."""
out: dict[str, float] = {}
for one in (block or {}).get("항목") or []:
price = unit_line.money(one.get("단가")) if one.get("묶음") in ("L", "M", "E") else None
if price:
out.setdefault(re.sub(r"^[0-9가-힣]\.", "", str(one.get("이름") or "")).strip(), price)
return out
def _gap(price: Any, want: float | None) -> float:
try:
p = float(price)
except (TypeError, ValueError):
return math.inf
return abs(math.log(p / want)) if want and p > 0 else math.inf
def _order(rows: list[tuple[float, dict]], want: float | None) -> list[dict]:
rows.sort(key=lambda x: -x[0])
pool = rows[:POOL]
pool.sort(key=lambda x: (-round(x[0], 1), _gap(x[1].get("단가"), want)))
return [one for _, one in pool[:TOP]]
def _num(x: Any) -> int | float | None:
if x is None:
return None
f = float(x)
return int(f) if f == int(f) else round(f, 4)
def materials(name: str, unit: str = "", want: float | None = None) -> list[dict[str, Any]]:
whole = m01.loaded()[1]
rows = []
for c in hmat.candidates({"이름": name, "규격": "", "단위": unit}, POOL):
code = c.get("자재")
if not code:
continue
try:
price = hmat.mm.element(whole, code, {}, unit or None)[0]
except Exception: # noqa: BLE001 — 값 못 읽음 = 단가 없음
price = None
one = {"키": code, "갈래": "자재", "이름": c.get("이름"), "규격": c.get("규격") or ""}
rows.append((c.get("점수") or 0.0, {**one, "단위": c.get("단위"), "단가": _num(price)}))
return _order(rows, want)
def labor(name: str, want: float | None = None) -> list[dict[str, Any]]:
whole = m01.loaded()[1]
me = hm._grams(re.sub(r"\s", "", name))
rows = []
for row in whole.numbers.get("LB", {}).values():
them = hm._grams(re.sub(r"\s", "", str(row.get("이름") or "")))
score = 2 * len(me & them) / (len(me) + len(them))
if score > 0:
one = {"키": str(row.get("키")), "갈래": "노임", "이름": row.get("이름"), "규격": ""}
rows.append((score, {**one, "단위": "인", "단가": _num(row.get("값"))}))
return _order(rows, want)
def oil_rows(name: Any) -> list[dict[str, Any]]:
"""유가전력 MO 줄 — 정리 이름(빈칸 뺌)이 MO 이름에 듦 · 전국 먼저 · 키 차례."""
me = re.sub(r"\s", "", nfkc(name))
if len(me) < 2:
return []
rows = [
r
for r in m01.loaded()[1].numbers.get("MO", {}).values()
if me in re.sub(r"\s", "", nfkc(r.get("이름")))
]
rows.sort(key=lambda r: (not str(r.get("상세구분") or "").startswith("전국"), str(r["키"])))
return rows
def oils(name: str) -> list[dict[str, Any]]:
"""유가전력 후보 셋 — `{키, 갈래 유가, 이름, 규격 = 상세구분, 단위, 단가}`."""
return [
{
"키": str(r["키"]),
"갈래": "유가",
"이름": r.get("이름"),
"규격": r.get("상세구분") or "",
"단위": r.get("단위"),
"단가": _num(r.get("값")),
} # fmt: skip
for r in oil_rows(name)[:TOP]
]
def _eq(one: dict[str, Any]) -> dict[str, Any]:
raw = m01.loaded()[1].numbers.get("EQ", {}).get(str(one.get("원문번호")), {})
unit = (raw.get("단위") or {}).get("내용시간") if isinstance(raw.get("단위"), dict) else ""
return {"키": one["키"], "갈래": "기계", "이름": one["이름"], "규격": one.get("규격") or "",
"단위": unit or "시간", "단가": None} # fmt: skip
def machine(text: str, *_: Any) -> dict | None:
"""기계 글(이름 규격) → EQ 한 줄 — 기계 찾기(812-9 `M02_Machine_Find`)가 하나로 정할 때만."""
got = mfind.find(text)
if got["판정"] not in (mfind.LINKED, mfind.ALIAS):
return None
return m01.loaded()[1].numbers.get("EQ", {}).get(str(got["후보"][0]["원문번호"]))
def machines(text: str, *_: Any) -> list[dict]:
"""기계 글 → EQ 후보(보여주기만 · 812-9) — 이음 · 후보 여럿 = 그것 · 못 찾음 = 같은 기종(가까운 규격 먼저)."""
return [_eq(one) for one in mfind.find(text)["후보"][:TOP]]
def lost(one: dict[str, Any], want: float | None = None) -> list[dict[str, Any]]:
"""못옮김 한 줄(`M02_Hopyo_SkPatch` 꼴) → 후보."""
kind, name = one.get("갈래"), str(one.get("이름") or "")
if kind == "자재":
return materials(name, str(one.get("단위") or ""), want)
if kind == "유가":
return oils(name)
if kind == "노임":
return labor(name, want)
if kind == "기계":
return machines(f"{name} {one.get('규격') or ''}", set(name.split("·")))
return []