Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
잇기 길 `{노임|기계|자재: 키}`).
|
||||
자재 = `M02_Hopyo_Material.candidates` 점수(이름 글자 묶음 · 규격) · 단가 = M01 자재 값(엑셀 줄 단위)
|
||||
노임 = LB 이름 글자 묶음 닮음 · 단가 = LB 값
|
||||
기계 = EQ 갈래 겹침 · 규격 수(같음 1 · 겹침 0.5) · 이름 낱말 덤 · 단가 없음(기계경비는 로직 셈)
|
||||
기계 = EQ 갈래 겹침 · 규격 수(같음 1 · 겹침 0.5) · 이름 낱말 덤 · 괄호 낱말 어긋남 깎음 ·
|
||||
단가 없음(기계경비는 로직 셈) · `machine` = 하나로 가름(778-1)
|
||||
차례 = 점수(0.1 단위로 접음) › 엑셀 단가에 가까운 것(엑셀 단가 = 블록 글 「이름 단가 × 식 = 금액」 · 모르면 점수만).
|
||||
서버 단독 · 읽기만.
|
||||
"""
|
||||
@@ -19,10 +20,18 @@ from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Hopyo_Finger as finger
|
||||
from M02_MasterTemplete import M02_Hopyo_Match as hm
|
||||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||||
from M02_MasterTemplete.M02_Hopyo_Words import mtypes, parse_rhs, specnums
|
||||
from M02_MasterTemplete.M02_Hopyo_Words import mtypes, nfkc, parse_rhs, specnums
|
||||
|
||||
TOP = 3
|
||||
POOL = 5
|
||||
# 괄호 낱말(778-1) — 글에 있으면 EQ 이름에도 있어야 함 · 부속 = 글에 없으면 본 장비 아님
|
||||
FORMS = (
|
||||
("타이어", ("타이어",)),
|
||||
("무한", ("무한궤도",)),
|
||||
("습지", ("습지",)),
|
||||
("트럭탑재", ("트럭탑재",)),
|
||||
)
|
||||
PARTS = ("자동덮개", "부착용", "콤팩터", "습지")
|
||||
|
||||
|
||||
def prices(block: dict[str, Any] | None) -> dict[str, float]:
|
||||
@@ -90,8 +99,36 @@ def labor(name: str, want: float | None = None) -> list[dict[str, Any]]:
|
||||
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 후보 — 갈래 · 규격 수가 없으면 글에서 읽음."""
|
||||
"""기계 글(이름 규격) → EQ 후보 — 갈래 · 규격 수 없으면 글에서 읽음 · 괄호 낱말 어긋남 깎음."""
|
||||
whole = m01.loaded()[1]
|
||||
kinds = set(kinds or ()) or mtypes(text)
|
||||
nums = set(nums or ()) or set(specnums(text))
|
||||
@@ -105,6 +142,8 @@ def machines(text: str, kinds: set | None = None, nums: set | None = None) -> li
|
||||
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(
|
||||
|
||||
@@ -20,6 +20,7 @@ from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Hopyo_Elem as elem
|
||||
from M02_MasterTemplete import M02_Hopyo_Finger as finger
|
||||
from M02_MasterTemplete import M02_Hopyo_Match as hm
|
||||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||||
@@ -86,6 +87,9 @@ class Linker:
|
||||
set(specnums(row.get("규격"))), mm_plain(row.get("이름")), mm_plain(row.get("규격")))
|
||||
for row in self.whole.numbers.get("EQ", {}).values()
|
||||
] # fmt: skip
|
||||
self.eq_names = {
|
||||
str(r.get("키")): r.get("이름") for r in self.whole.numbers.get("EQ", {}).values()
|
||||
}
|
||||
self.near = 0 # 못 이은 이음 후보 수
|
||||
|
||||
def link(self, line: dict[str, Any], price: dict[str, Decimal]) -> dict[str, str | None]:
|
||||
@@ -103,13 +107,14 @@ class Linker:
|
||||
return out
|
||||
|
||||
def _machine(self, line: dict[str, Any]) -> str | None:
|
||||
"""기계 줄 → EQ 하나 — 이름 · 규격 글 같음 › 갈래 겹침 + 규격 수 같음(하나일 때만)."""
|
||||
"""기계 줄 → EQ 하나 — 이름 · 규격 글 같음 › 갈래 + 규격 수 · 괄호 낱말(하나일 때만)."""
|
||||
name, spec = mm_plain(line.get("이름")), mm_plain(line.get("규격"))
|
||||
same = [c for c, _, _, n, s in self.machines if n == name and s == spec]
|
||||
if len(same) != 1:
|
||||
text = f"{line.get('이름') or ''} {line.get('규격') or ''}"
|
||||
kinds, nums = mtypes(text), set(specnums(text))
|
||||
same = [c for c, t, s, _, _ in self.machines if kinds & t and nums and nums == s]
|
||||
same = elem.narrow(text, [(c, self.eq_names.get(c)) for c in same])
|
||||
if len(same) == 1:
|
||||
return same[0]
|
||||
self.near += bool(same)
|
||||
|
||||
@@ -54,10 +54,32 @@ LINK = {
|
||||
} # 잇기 칸 → 더한줄 · 키 머리
|
||||
|
||||
|
||||
MACH_IN = "기계" # 기계경비 기초 기계 입력 칸
|
||||
|
||||
|
||||
class LinkError(ValueError):
|
||||
"""잇기 틀림(없는 id · 갈래 다름 · 없는 키) — 부르는 쪽이 400."""
|
||||
|
||||
|
||||
def _mach_logics(whole: Any, kinds: set, eq: dict | None = None) -> list[str]:
|
||||
"""기계경비 기초 차례 — 갈래 첫 기초(굴착기 = 무한궤도) › 그 밖 · `eq` 가 있으면
|
||||
그 기계(또는 같은 이름 기계)를 고르기에 둔 기초가 앞(778-1)."""
|
||||
first = next((c for k, c in combine.MACHINE.items() if k in kinds), combine.MACHINE_ANY)
|
||||
order = [first, *sorted(combine.MACHINE_SET - {first})]
|
||||
if not eq:
|
||||
return order
|
||||
eqs = whole.numbers.get("EQ", {})
|
||||
|
||||
def takes(code: str) -> bool:
|
||||
ins = whole.logic(code).get("입력") or []
|
||||
opts = next((s.get("고르기") or [] for s in ins if s.get("이름") == MACH_IN), [])
|
||||
same = any((eqs.get(o) or {}).get("이름") == eq.get("이름") for o in opts)
|
||||
return eq.get("원문번호") in opts or same
|
||||
|
||||
hit = [c for c in order if takes(c)]
|
||||
return hit + [c for c in order if c not in hit]
|
||||
|
||||
|
||||
def _near(a: float, b: float) -> bool:
|
||||
return abs(a - b) <= TINY or option.near(a, b, fit.TOL[0], option.ru(a))
|
||||
|
||||
@@ -293,16 +315,18 @@ class _Plan:
|
||||
self.note("장비", "·".join(sorted(m["갈래"])), excel, mine, 규격=spec, 단위="시간")
|
||||
|
||||
def _add_machine(self, m: dict[str, Any], line: dict[str, Any]) -> None:
|
||||
"""엑셀만 장비 → 기계경비 로직 줄(기계 = 그 갈래 · 규격 옵션 거꾸로 찾기)."""
|
||||
"""엑셀만 장비 → 기계경비 로직 줄(기계 = 그 갈래 · 규격 옵션 거꾸로 찾기 ·
|
||||
괄호 낱말로 하나면 그 기계 · 로직 = 그 기계를 고르기에 둔 기초 먼저(778-1))."""
|
||||
fp = finger.empty(line)
|
||||
fp["장비"] = [{**m, "시간": 1.0, "밑시간": 1.0, "율": []}]
|
||||
kinds = set(m["갈래"])
|
||||
first = next((c for k, c in combine.MACHINE.items() if k in kinds), combine.MACHINE_ANY)
|
||||
for code in [first, *sorted(combine.MACHINE_SET - {first})]:
|
||||
eq = elem.machine(str(m.get("글") or ""), kinds, set(m.get("규격") or []))
|
||||
for code in _mach_logics(self.whole, kinds, eq):
|
||||
got = option.find(code, fp, [])
|
||||
if got["수준"] <= 1:
|
||||
name = " ".join([*m["갈래"], *(f"{x:g}" for x in m.get("규격") or [])])
|
||||
self.add({"종류": "로직", "로직": code, "입력값": got["입력"], "이름": name,
|
||||
given = {**got["입력"], **({MACH_IN: eq["원문번호"]} if eq else {})}
|
||||
self.add({"종류": "로직", "로직": code, "입력값": given, "이름": name,
|
||||
"단위": "시간", "수량": _num(m["시간"] * self.tb)}) # fmt: skip
|
||||
return
|
||||
spec = " ".join(f"{x:g}" for x in m.get("규격") or [])
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""장비 → EQ 괄호 낱말(PLAN 778-1 · 분석 621-B 2-3) — 「굴착기(타이어형)」 = 타이어 기계 · 부속 뺌.
|
||||
|
||||
같은 갈래 · 규격에 이름 여럿(타이어 · 무한궤도 · 습지 · 부착용)인 EQ 를 글의 괄호 낱말로 가름 —
|
||||
불러오기 이음(`Linker._machine`) · 후보 1위(`Elem.machines`) ·
|
||||
엑셀만 장비 기계경비 기초(`SkPatch._add_machine`) 모두 같은 기계. master 읽기만.
|
||||
"""
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Hopyo_Combine as combine
|
||||
from M02_MasterTemplete import M02_Hopyo_Elem as elem
|
||||
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
|
||||
from M02_MasterTemplete import M02_Hopyo_SkPatch as skpatch
|
||||
|
||||
TIRE = "굴착기(타이어형)(0.6㎥)"
|
||||
|
||||
|
||||
def _key(code: str) -> str:
|
||||
"""기계 번호(0211-0060) → EQ 키."""
|
||||
return str(m01.loaded()[1].numbers["EQ"][code]["키"])
|
||||
|
||||
|
||||
def test_굴착기_타이어형_EQ000026_세_길_같음():
|
||||
linker = hfix.Linker(by_name=True)
|
||||
assert linker._machine({"이름": "굴착기(타이어형)", "규격": "0.6㎥"}) == "EQ000026"
|
||||
assert elem.machines(TIRE)[0]["키"] == "EQ000026"
|
||||
assert elem.machine(TIRE)["키"] == "EQ000026"
|
||||
# 부속 · 형식 낱말 — 덤프트럭 ≠ 자동덮개 · 굴착기 0.7 ≠ 습지 · 콤팩터(부착용) · 무한궤도 그대로
|
||||
assert linker._machine({"이름": "덤프트럭", "규격": "15톤"}) == "EQ000074"
|
||||
assert linker._machine({"이름": "굴착기", "규격": "0.7㎥"}) == "EQ000020"
|
||||
assert linker._machine({"이름": "굴착기(무한궤도)", "규격": "0.6㎥"}) == "EQ000019"
|
||||
assert linker._machine({"이름": "굴착기", "규격": "0.6㎥"}) is None # 타이어 · 무한궤도 못 가름
|
||||
|
||||
class Fake:
|
||||
tb, whole, got = 2.0, m01.loaded()[1], None
|
||||
|
||||
def add(self, one):
|
||||
self.got = one
|
||||
|
||||
def miss(self, *a, **k):
|
||||
raise AssertionError("기계 못 찾음")
|
||||
|
||||
m = {"갈래": ["굴착기"], "규격": [0.6], "시간": 0.25, "글": TIRE}
|
||||
fake = Fake()
|
||||
skpatch._Plan._add_machine(fake, m, {"이름": "x", "단위": "㎥"})
|
||||
assert fake.got["로직"] != combine.MACHINE["굴착기"] # 굴착기(무한궤도) 기계경비 아님
|
||||
assert _key(fake.got["입력값"]["기계"]) == "EQ000026" and fake.got["수량"] == 0.5
|
||||
Reference in New Issue
Block a user