Files
Aislo/resources/tester/test_812_9_machine_link.py
T
eomsangdonandClaude Opus 5.5 030a843f07 feat(815-1 K1·K2·K3·K5): 불러오기 이음 — EST 코드 · 별칭 칸 · 표기 맞추기 · 견적 줄
- K1 EST 원파일 자재 = 우리 자재 출처 칸 EST 코드로 바로 이음(새 M02_Elem_Alias · 자재 자원 24,219 중 코드 같음 23,981 모두 그 줄)
- K2 M01 요소 줄 「별칭」 칸 읽기 — 기계 찾기(옛 별칭 장부 읽기 걷음) · 재료 · 인력 · 유가 · 일위대가 호표 줄(Units · Hopyo_Sangeun 장부 import 뗌)
- K3 재료 표기 정규화 정확 일치(M/M · 곱셈표 · 지름 · 괄호 · 빈칸 · 전각 · 지역 꼬리) — 바탕 이름 길보다 먼저(치즐(0.7m3) → 0.2㎥ 줄로 잘못 잇던 것 바로잡음)
- K5 견적 · 운임 · 수수료 줄 = 우리 자재면 재료 줄(우리 값) · 아니면 그대로 + 「견적」 표지 · 문서 설명 · 화면 「견적 n」 배지(못 이음 수에서 뺌)
- 시험 test_815_1_import_link 새로 · test_812_9 = 옛 장부 별칭 대신 기계 줄 별칭 칸(마스터 다섯은 칸이 비면 시험 동안만 넣음) · 「디젤 엔진」 은 마스터 별칭 묶음으로 옮김
- test_778_5 기계 표(162 · 3 · 25)는 고치기 전 판과 같음 — 812-9 뒤부터 굳힌 수와 다름(이번 고침 무관 · 안 고침)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 14:36:58 +09:00

217 lines
14 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) 별칭 먼저 — M01 기계 줄 「별칭」 칸(815-1 · 시험 안 임시 별칭) · 이름 + 규격 통째 = 그 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_Elem_Alias as ea # noqa: E402
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 — 같은 임시 층
from test_815_1_import_link import aliases # noqa: E402 — M01 별칭 칸 임시
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"),
("물탱크", "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"),
]
# 마스터 기계 별칭 다섯(815-2 가 기계 줄 「별칭」 칸으로 옮김) — 칸이 비었으면 시험 동안만 넣음
FIVE = {"EQ000193": "아스팔트페이버(피니셔)", "EQ000500": "디젤 엔진", "EQ000400": "워터제트",
"EQ000425": "버킷준설기", "EQ000248": '"바이브레이트 봉상후렉시블"'} # fmt: skip
@pytest.mark.parametrize("name,spec,eq", MASTER)
def test_master_alias(name: str, spec: str, eq: str) -> None:
rows = ea.book()["줄"]["EQ"]
with aliases("EQ", {k: v for k, v in FIVE.items() if not rows[k].get("별칭")}):
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 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
pairs = {"EQ000193": "아스팔트페이버(피니셔)", "EQ000129": '"크레인(트럭) 15Ton"'}
with aliases("EQ", pairs):
got = mf.find("아스팔트페이버(피니셔)", "3.0m") # 이름만 별칭 = 그 기종 → 규격으로 하나
assert (got["판정"], got["후보"][0]["이름"], got["후보"][0]["규격"]) == (
mf.ALIAS, "아스팔트 피니셔", "3m") # fmt: skip
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 # 옛 층 장부 줄(이음은 별칭 칸만 봄)
assert any(r.get("원래", {}).get("규격") == "10Ton" for r in ledger.read(me).values())
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 # 첫 줄 규격 = 그 기계(814-1)
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["브레이카"]["기호"]