- 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>
161 lines
7.1 KiB
Python
161 lines
7.1 KiB
Python
"""M02 일위대가 불러오기 — 못옮김 · 안 이은 기계 줄 → M01 요소 후보 셋(PLAN 746-1 · 요구 585).
|
|
|
|
후보 = `[{키, 갈래 노임|기계|자재, 이름, 규격, 단위, 단가}]` 셋까지 — 보여주기만(자동 이음 없음 · 고르면 지금
|
|
잇기 길 `{노임|기계|자재: 키}`).
|
|
자재 = `M02_Hopyo_Material.candidates` 점수(이름 글자 묶음 · 규격) · 단가 = M01 자재 값(엑셀 줄 단위)
|
|
노임 = LB 이름 글자 묶음 닮음 · 단가 = LB 값
|
|
기계 = EQ 갈래 겹침 · 규격 수(같음 1 · 겹침 0.5) · 이름 낱말 덤 · 괄호 낱말 어긋남 깎음 ·
|
|
단가 없음(기계경비는 로직 셈) · `machine` = 하나로 가름(778-1)
|
|
차례 = 점수(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.M02_Hopyo_Words import mtypes, nfkc, specnums
|
|
|
|
TOP = 3
|
|
POOL = 5
|
|
# 괄호 낱말(778-1) — 글에 있으면 EQ 이름에도 있어야 함 · 부속 = 글에 없으면 본 장비 아님
|
|
FORMS = (
|
|
("타이어", ("타이어",)),
|
|
("무한", ("무한궤도",)),
|
|
("습지", ("습지",)),
|
|
("트럭탑재", ("트럭탑재",)),
|
|
)
|
|
PARTS = ("자동덮개", "부착용", "콤팩터", "습지")
|
|
|
|
|
|
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 _form(text: str, name: Any) -> tuple[bool, bool]:
|
|
"""괄호 낱말(778-1) — (글 형식 낱말이 EQ 이름에 다 있음, EQ 이름에 글에 없는 부속 있음)."""
|
|
t, n = nfkc(text), nfkc(name)
|
|
ok = all(any(w in n for w in ws) for k, ws in FORMS if k in t)
|
|
return ok, any(w in n and w not in t for w in PARTS)
|
|
|
|
|
|
def narrow(text: str, rows: list[tuple[str, Any]]) -> list[str]:
|
|
"""EQ `[(키, 이름)]` → 괄호 낱말로 가른 키 — 형식 다름 뺌 · 부속은 남는 것 있을 때만 뺌."""
|
|
keep = [(c, _form(text, n)[1]) for c, n in rows if _form(text, n)[0]]
|
|
return [c for c, part in keep if not part] or [c for c, _ in keep]
|
|
|
|
|
|
def machine(text: str, kinds: set | None = None, nums: set | None = None) -> dict | None:
|
|
"""기계 글 → EQ 한 줄 — 갈래 겹침 + 규격 수 같음 · 괄호 낱말로 하나일 때만."""
|
|
kinds = set(kinds or ()) or mtypes(text)
|
|
nums = set(nums or ()) or set(specnums(text))
|
|
rows = {
|
|
str(r.get("키")): r
|
|
for r in m01.loaded()[1].numbers.get("EQ", {}).values()
|
|
if nums
|
|
and kinds & mtypes(f"{r.get('상세구분')} {r.get('이름')}")
|
|
and nums == set(specnums(r.get("규격")))
|
|
}
|
|
got = narrow(text, [(k, r.get("이름")) for k, r in rows.items()])
|
|
return rows[got[0]] if len(got) == 1 else None
|
|
|
|
|
|
def machines(text: str, kinds: set | None = None, nums: set | None = None) -> list[dict]:
|
|
"""기계 글(이름 규격) → EQ 후보 — 갈래 · 규격 수 없으면 글에서 읽음 · 괄호 낱말 어긋남 깎음."""
|
|
whole = m01.loaded()[1]
|
|
kinds = set(kinds or ()) or mtypes(text)
|
|
nums = set(nums or ()) or set(specnums(text))
|
|
mine = hm.words(text)
|
|
rows = []
|
|
for row in whole.numbers.get("EQ", {}).values():
|
|
types = mtypes(f"{row.get('상세구분')} {row.get('이름')}")
|
|
if not kinds & types:
|
|
continue
|
|
spec = set(specnums(row.get("규격")))
|
|
score = len(kinds & types) / len(kinds | types)
|
|
score += 1.0 if nums and nums == spec else 0.5 if nums & spec else 0.0
|
|
score += 0.3 * len(mine & hm.words(row.get("이름")))
|
|
ok, part = _form(text, row.get("이름"))
|
|
score -= (0.0 if ok else 1.0) + (0.5 if part else 0.0)
|
|
one = {"키": str(row.get("키")), "갈래": "기계", "이름": row.get("이름")}
|
|
unit = (row.get("단위") or {}).get("내용시간") if isinstance(row.get("단위"), dict) else ""
|
|
rows.append(
|
|
(score, {**one, "규격": row.get("규격") or "", "단위": unit or "시간", "단가": None})
|
|
)
|
|
return _order(rows, None)
|
|
|
|
|
|
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 labor(name, want)
|
|
if kind == "기계":
|
|
return machines(f"{name} {one.get('규격') or ''}", set(name.split("·")))
|
|
return []
|