- 새 `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>
216 lines
13 KiB
Python
216 lines
13 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||
"""812-9 지킴 — 가져온 기계 잇기(`M02_Machine_Find` · 요구 718 · 719).
|
||
|
||
(a) 기계표 전체에서 찾음 — 낱말표 밖 기종(로우더 · 공기 압축기 · 램 머 · 라인마커 …) · 후보 여럿이던 것
|
||
(물탱크 · 크레인(무한궤도) · 머캐덤 · 콘크리트 펌프차 …) · 지어 맞추기 없음(1.0Ton = 우리 표에 없음)
|
||
(b) 표기 정규화 · 괄호 낱말(무한궤도 · 타이어 · 일반용 · 표에 없는 낱말 발파 = 안 봄) · 범위 · 복수 규격
|
||
(c) 조합 기계 = 둘(대형브레이카 +굴착기 · 0.7+브레이카 · +부착용집게 · +진동콤팩터 · 덤프트럭(자동덮개))
|
||
(d) 별칭 먼저 — 지금 층 장부 기계 짝 = 그 EQ · 이름만 열쇠 = 그 기종으로 · 후보 여럿 표지 ·
|
||
[저장] 때 고른 EQ = 지금 층 장부 줄(`learn`)
|
||
(e) 금액 전후 같음 — 조합 줄 둘(첫 줄 엑셀 값 · 다음 줄 0) · 원파일 저장 금액 그대로
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
HERE = Path(__file__).resolve().parent
|
||
for p in (ROOT, HERE):
|
||
if str(p) not in sys.path:
|
||
sys.path.insert(0, str(p))
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Ledger as ledger # noqa: E402
|
||
from M02_MasterTemplete import M02_Machine_Find as mf # noqa: E402
|
||
from M02_MasterTemplete import M02_MasterTemplete_Store as store # noqa: E402
|
||
from resources.tester.test_m02_labor_ref import world # noqa: E402, F401 — 같은 임시 층
|
||
|
||
|
||
def one(name: str, spec: str = "") -> tuple[str, str, str]:
|
||
got = mf.find(name, spec)
|
||
first = got["후보"][0] if got["후보"] else {}
|
||
return got["판정"], str(first.get("이름") or ""), str(first.get("규격") or "")
|
||
|
||
|
||
LINKED = [ # 낱말 모름 · 후보 여럿이던 예(812-8) → 하나
|
||
("로우더(타이어)", "0.57㎥", "로더(타이어)", "0.57㎥"),
|
||
("로우더(타이어)", "1.72㎥", "로더(타이어)", "1.72㎥"),
|
||
("공기 압축기", "21.0㎥/min", "공기압축기(이동식)", "21.0㎥/min"),
|
||
("공기 압축기", "7.1㎥/min", "공기압축기(이동식)", "7.1㎥/min"),
|
||
("라인마커", "10Km/hr", "라인 마커", "10km/hr"),
|
||
("램 머", "80kg", "래 머", "80㎏"),
|
||
("진동파일해머", "60kW", "진동파일 해머(전동식)", "60㎾"),
|
||
("진동파일해머", "45kW", "진동파일 해머(전동식)", "45㎾"),
|
||
("진동파일해머", "40kW", "진동파일 해머(전동식)", "40㎾"),
|
||
("노면파쇄기", "2.0m", "노면파쇄기", "2.0m"),
|
||
("아스팔트 디스트리뷰터", "3800ℓ", "아스팔트 디스트리뷰터", "3,800ℓ"),
|
||
("고소작업차", "3Ton", "고소작업차", "3ton"),
|
||
("고소작업차", "5Ton", "고소작업차", "5ton"),
|
||
("취부기(녹생토)", "18.65kW", "취부기(녹생토 암절개면 보호식재용)", "18.65㎾"),
|
||
("아스팔트 스프레이어", "400ℓ", "아스팔트 스프레이어", "400ℓ"),
|
||
("커 터", "320~400mm", "커터(콘크리트 및 아스팔트용)", "320~400"),
|
||
("경운기", "1000kg", "경운기", "1,000㎏"),
|
||
("건설용펌프(자흡식)", "50mm", "건설용펌프(자흡식)", "50(1.49×10)"),
|
||
("강연선인장기", "120Ton", "강연선인장기", "120ton"),
|
||
("디젤 엔진", "11.19kW", "엔진(디젤)", "기종 디젤 엔진 · 규격 11.19㎾"),
|
||
("물탱크", "5500ℓ", "물탱크(살수차)", "5,500ℓ"),
|
||
("물탱크", "16000ℓ", "물탱크(살수차)", "16,000ℓ"),
|
||
("콘크리트 펌프차", "32m,80~95㎥/hr", "콘크리트 펌프차", "32 [80∼95]"),
|
||
("머캐덤 로울러(자주식)", "10~12Ton", "머캐덤 롤러(자주식)", "10∼12"),
|
||
("타이어 로울러(자주식)", "8~15Ton", "타이어 롤러(자주식)", "8∼15"),
|
||
("크레인(무한궤도)", "40Ton", "크레인(무한궤도)", "40 (1.53)"),
|
||
("크레인(무한궤도)", "35Ton", "크레인(무한궤도)", "35 (1.33)"),
|
||
("크로울러 드릴(공기식)", "17㎥/min", "크롤러드릴(공기식)", "17 (120㎜)"),
|
||
("모터 그레이더", "3.6m(일반용)", "모터그레이더(일반용)", "3.6m"),
|
||
("콘크리트 믹서", "0.3㎥", "콘크리트 믹서", "0.30㎥"),
|
||
("진동 로울러(자주식)", "12Ton", "진동롤러(자주식)", "12ton"),
|
||
("굴삭기(무한궤도)", "0.7㎥", "굴착기(무한궤도)", "0.7㎥"),
|
||
("굴착기(타이어형)", "0.6㎥", "굴착기(타이어)", "0.6㎥"),
|
||
("굴착기(무한궤도)", "0.4㎥(발파)", "굴착기(무한궤도)", "0.4㎥"),
|
||
("불도우저(무한궤도)", "32Ton", "불도저(무한궤도)", "32ton"),
|
||
]
|
||
|
||
|
||
@pytest.mark.parametrize("name,spec,our,size", LINKED)
|
||
def test_linked(name: str, spec: str, our: str, size: str) -> None:
|
||
kind, *got = one(name, spec)
|
||
assert kind in (mf.LINKED, mf.ALIAS) and got == [our, size] # 승인 별칭(디젤 엔진) = 별칭
|
||
|
||
|
||
MASTER = [ # 마스터 층 승인 별칭(요구 735-5 · 736) — 이름 별칭 + 규격은 수로 맞댐
|
||
("아스팔트페이버(피니셔)", "3.0m", "EQ000193"),
|
||
("아스팔트페이버(피니셔)", "1.7m", "EQ000192"),
|
||
("디젤 엔진", "11.19kW", "EQ000500"),
|
||
("디젤 엔진", "26.11kW", "EQ000503"),
|
||
("디젤 엔진", "52.22kW", "EQ000504"),
|
||
("워터제트", "96kW", "EQ000400"),
|
||
("버킷준설기", "7.46kW", "EQ000425"),
|
||
("바이브레이트", "봉상후렉시블", "EQ000248"),
|
||
]
|
||
|
||
|
||
@pytest.mark.parametrize("name,spec,eq", MASTER)
|
||
def test_master_alias(name: str, spec: str, eq: str) -> None:
|
||
got = mf.find(name, spec)
|
||
assert got["키"] == eq and got["판정"] in (mf.LINKED, mf.ALIAS)
|
||
|
||
|
||
def test_no_guess() -> None:
|
||
"""우리 표에 없는 규격 = 못 찾음(가까운 규격으로 안 이음) · 기종 모름 = 별칭 몫 · 여럿 = 여럿."""
|
||
got = mf.find("덤프트럭", "1.0Ton")
|
||
assert got["판정"] == mf.NONE and got["까닭"] == "우리 표에 없음(규격)" and got["키"] is None
|
||
assert got["후보"][0]["규격"] == "2.5ton" # 보여주기만 — 가까운 규격 먼저
|
||
for name, spec in (("에어 호스", "1.91cm"), ("모노펌프", "5-20㎥/hr")): # 승인 전 = 별칭 몫
|
||
got = mf.find(name, spec)
|
||
assert (got["판정"], got["까닭"]) == (mf.NONE, "기종 모름 — 별칭")
|
||
got = mf.find("크레인(트럭)", "10Ton") # 「트럭」 = 기계표 괄호에 없는 낱말 → 안 봄
|
||
assert got["판정"] == mf.MANY and got["수"] == 2
|
||
assert mf.mark(got, "크레인(트럭)", "10Ton").startswith("참고 · 기계 후보 2 — 고르기")
|
||
assert "기계 못 찾음" in mf.mark(mf.find("덤프트럭", "1.0Ton"), "덤프트럭", "1.0Ton")
|
||
assert mf.mark(mf.find("물탱크", "5500ℓ"), "물탱크", "5500ℓ") is None
|
||
|
||
|
||
def test_norm_paren_spec() -> None:
|
||
assert mf.norm("로우더") == mf.norm("로더") and mf.norm("로울러") == mf.norm("롤러")
|
||
assert mf.norm("브레이카") == mf.norm("브레이커") and mf.norm("굴삭기") == mf.norm("굴착기")
|
||
assert mf.norm("불도우저") == mf.norm("불도저") and mf.norm("램 머") == mf.norm("래 머")
|
||
assert mf.tokens("10~12Ton") == [(10.0, 12.0, "ton")]
|
||
assert mf.tokens("32m,80~95㎥/hr") == [(32.0, 32.0, "m"), (80.0, 95.0, "m3/hr")]
|
||
assert mf.tokens("5,500ℓ") == [(5500.0, 5500.0, "l")]
|
||
assert mf.spec_fit(mf.tokens("0.7㎥"), mf.tokens("0.6∼0.8")) == "안"
|
||
assert one("굴착기(타이어)", "0.6㎥")[1] == "굴착기(타이어)" # 괄호 낱말이 가름
|
||
assert one("굴착기(무한궤도)", "0.6㎥")[1] == "굴착기(무한궤도)"
|
||
|
||
|
||
COMBO = [ # 조합 기계 = 둘 · 규격 없는 쪽 = 앞 규격
|
||
("굴삭기(무한궤도)", "0.7+브레이카", [("굴착기(무한궤도)", "0.7㎥"), ("대형 브레이커", "0.7㎥")]),
|
||
("대형브레이카", "+굴착기(0.7㎥)", [("대형 브레이커", "0.7㎥"), ("굴착기(무한궤도)", "0.7㎥")]),
|
||
("대형브레이카", "+굴착기(0.4㎥)", [("대형 브레이커", "0.4㎥"), ("굴착기(무한궤도)", "0.4㎥")]),
|
||
("굴삭기+진동콤팩터", "0.7㎥", [("굴착기(무한궤도)", "0.7㎥"),
|
||
("유압식 진동콤팩터(굴착기 부착용)", "0.7㎥")]),
|
||
("굴삭기(무한궤도)", "0.2㎥+부착용집게", [("굴착기(무한궤도)", "0.2㎥"), ("부착용 집게", "0.2㎥")]),
|
||
("굴삭기(무한궤도)", "0.6㎥+부착용집게", [("굴착기(무한궤도)", "0.6㎥"), ("부착용 집게", "0.6∼0.8")]),
|
||
("로우더(타이어)+소형노면파쇄기", "0.95㎥", [("로더(타이어)", "0.95㎥"), ("소형노면파쇄기", "0.95㎥")]),
|
||
("덤프트럭(자동덮개)", "15Ton", [("덤프트럭", "15ton"), ("덤프트럭 자동덮개시설", "15톤용")]),
|
||
("굴삭기(무한궤도)", "0.7㎥,(브레이커조합)", [("굴착기(무한궤도)", "0.7㎥"), ("대형 브레이커", "0.7㎥")]),
|
||
] # fmt: skip
|
||
|
||
|
||
@pytest.mark.parametrize("name,spec,want", COMBO)
|
||
def test_combo_two(name: str, spec: str, want: list) -> None:
|
||
got = mf.find_all(name, spec)
|
||
assert [g["판정"] for g in got] == [mf.LINKED, mf.LINKED]
|
||
assert [(g["후보"][0]["이름"], g["후보"][0]["규격"]) for g in got] == want
|
||
|
||
|
||
def test_combo_many_part() -> None:
|
||
"""대형브레이카 +굴착기(1.0㎥) — 굴착기 무한궤도 · 타이어 둘 다 1.0 = 그 쪽만 후보 여럿(사용자 고름)."""
|
||
got = mf.find_all("대형브레이카", "+굴착기(1.0㎥)")
|
||
assert [g["판정"] for g in got] == [mf.LINKED, mf.MANY]
|
||
assert mf.find_all("덤프트럭", "15Ton") == [{**mf.find("덤프트럭", "15Ton"), "이름": "덤프트럭", "규격": "15Ton"}] # fmt: skip
|
||
|
||
|
||
def _pair(eq: str, name: str, spec: str, unit: str = "hr") -> dict:
|
||
full = ledger.key({"이름": name, "규격": spec, "단위": unit})
|
||
row = {"원래": {"품명": name, "규격": spec, "단위": unit}, "갈래": "짝"}
|
||
return {full: row | {"짝": [{"갈래": "기계", "키": eq, "입력값": {}, "배": 1}]}}
|
||
|
||
|
||
def test_alias_first_and_learn(world) -> None: # noqa: F811
|
||
me = world["personal"]
|
||
me.mkdir(parents=True, exist_ok=True)
|
||
with store.at(me):
|
||
assert mf.find("아스팔트페이버(피니셔)", "3.0m")["판정"] == mf.NONE
|
||
ledger.write(me, _pair("EQ000193", "아스팔트페이버(피니셔)", ""), {"id": "sub3임시"})
|
||
got = mf.find("아스팔트페이버(피니셔)", "3.0m") # 이름만 열쇠 = 그 기종 → 규격으로 하나
|
||
assert (got["판정"], got["후보"][0]["이름"], got["후보"][0]["규격"]) == (
|
||
mf.ALIAS, "아스팔트 피니셔", "3m") # fmt: skip
|
||
ledger.write(me, _pair("EQ000129", "크레인(트럭)", "15Ton"), {"id": "sub3임시"})
|
||
got = mf.find("크레인(트럭)", "15Ton") # 이름 + 규격 열쇠 = 그 EQ(찾기보다 먼저)
|
||
assert (got["판정"], got["키"]) == (mf.ALIAS, "EQ000129")
|
||
assert (
|
||
mf.find("크레인(트럭)", "15Ton(할증125%)")["키"] == "EQ000129"
|
||
) # 규격 속 할증 글 = 안 봄
|
||
assert mf.same_kind("아스팔트페이버(피니셔)", "아스팔트 피니셔") is True
|
||
doc = {
|
||
"변수": [{"열쇠": "P_크", "기호": "P", "아래": "크", "연결": {
|
||
"갈래": "단가산출", "키": "GC000270", "입력": {"기계": "'2104-0010'"}}}],
|
||
"가져온": {"기계": [{"기호": "P_크", "판정": mf.MANY, "키": None,
|
||
"원래": {"이름": "크레인(트럭)", "규격": "10Ton", "단위": "hr"}}]},
|
||
} # fmt: skip
|
||
assert mf.learn(doc, {"id": "sub3임시"}) == 1
|
||
got = mf.find("크레인(트럭)", "10Ton")
|
||
assert got["판정"] == mf.ALIAS and got["후보"][0]["원문번호"] == "2104-0010"
|
||
assert mf.learn(doc, {"id": "sub3임시"}) == 0 # 이미 같은 EQ = 안 씀
|
||
assert mf.find("크레인(트럭)", "10Ton")["판정"] == mf.MANY # 다른 층(마스터) = 안 봄
|
||
|
||
|
||
def test_combo_money_same() -> None:
|
||
"""원파일 거창 측구터파기 연암 — 「굴삭기(무한궤도) 0.7+브레이카」 = 줄 둘 · 금액 = 원파일 저장 그대로."""
|
||
from test_810_2_native_doc import doc_and_money, native
|
||
|
||
from M02_MasterTemplete import M02_Doc_FromNative as fn
|
||
|
||
saved = native("EST", "거창")
|
||
blk = fn.numbers(saved)[2]
|
||
sk, doc, money = doc_and_money(fn.emit(saved, 2))
|
||
names = [(m["이름"], m.get("규격")) for m in doc["금액"]]
|
||
assert ("굴삭기(무한궤도)", "0.7+브레이카") in names and ("대형 브레이커", "0.7㎥") in names
|
||
assert float(money["계"]) == pytest.approx(float(blk["저장"]["T"]), abs=1)
|
||
found = {x["원래"]["이름"]: x for x in doc["가져온"]["기계"]}
|
||
assert found["브레이카"]["조합"] is True and found["브레이카"]["찾기어"] == "대형 브레이커"
|
||
assert any("조합 기계 — 경비는 한 덩이" in m for m in doc["가져온"]["표지"])
|
||
from M02_MasterTemplete import M02_Doc_NativeLayout as lay
|
||
|
||
from test_812_3_native_layout import _show
|
||
|
||
groups = [(g[0], g[2]) for g in _show(doc)] # 조합 기계 = 기계마다 항목(요구 738-3)
|
||
assert ("굴착기(무한궤도) 0.7㎥", ["=Q", "굴삭기(무한궤도)"]) in groups
|
||
assert ("대형 브레이커 0.7㎥", ["대형 브레이커"]) in groups
|
||
owned = lay.owned_of(doc) # 못 이은 기계 줄(시간당 자료 없음 = 숫자 묶음)도 기계 = 이름 단추
|
||
at = next(i for i, m in enumerate(doc["금액"]) if m["이름"] == "대형 브레이커")
|
||
assert owned.get(at) == found["브레이카"]["기호"]
|