Files
Aislo/resources/tester/test_812_9_machine_link.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

216 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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["브레이카"]["기호"]