- 규격 다른 별칭(품명|단위 느슨 열쇠) = 꼬리표 「별칭 · 규격 다름」 · 짝은 후보 끝 덤 · 상태 · 옵션은 줄 숫자 맞춤 - 품 비교 같은 갈래 장비 여러 줄은 합쳐 견줌(서버 _qty · 화면 compareText) - 조합 덧 로직 = 단위 맞음 + 조항 · 직종 · 이름 하나 닿을 때만 · 아니면 덧 후보 · 섞인 줄 그대로 · 맞춤셈 판 2 - 골든 사람 126 1순위 118 · 다섯 안 122 · 맞음 57 틀림 0 · 조합 62/83 그대로 · 338 대상 시험 142 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
248 lines
13 KiB
Python
248 lines
13 KiB
Python
"""일위대가 실무자료 맞춤(PLAN 324-2 · `M02_Hopyo_Match`) — 규격 → 입력값 · 단위 · 환산 ·
|
||
맞춤 차례 · 기억 우선 · 길(보기 · 미리 채움 · 확정) · master_data 원본 그대로. 층은 임시 폴더.
|
||
"""
|
||
|
||
import pytest
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Fit as fit
|
||
from M02_MasterTemplete import M02_Hopyo_Match as hm
|
||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||
|
||
# 올리기 길 시험의 `client` 마당을 같이 씀
|
||
# ruff: noqa: F811
|
||
from resources.tester.test_m02_hopyo_excel import URL, _post, client, master_hash # noqa: F401
|
||
|
||
mm = hm.mm
|
||
|
||
|
||
# ── 규격 · 단위 · 환산 ─────────────────────────────────────────────────
|
||
def test_규격_이름_붙은_수():
|
||
assert hm.numbers("D=1,000mm,일반형") == {"D": (1000, "mm")}
|
||
assert hm.numbers("H:1.2m") == {"H": (hm.Decimal("1.2"), "m")}
|
||
assert {k: float(v) for k, (v, _) in hm.numbers("B=2.3,H=0.4,야면석").items()} == {
|
||
"B": 2.3,
|
||
"H": 0.4,
|
||
}
|
||
assert hm.numbers("L3=35cm적용,장비사용") == {"L3": (35, "cm")}
|
||
assert hm.numbers("Φ40×200") == {} and mm.plain_spec("Φ40×200") == "ø40x200"
|
||
|
||
|
||
def test_단위_정규화():
|
||
got = [mm.plain_unit(u) for u in ("㎥", "M", "Ton", "TON", "EA", "㎡")]
|
||
assert got == ["m3", "m", "톤", "톤", "개", "m2"]
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("have", "want", "spec", "times", "kind"),
|
||
[
|
||
("㎥", "㎥", "", 1, "같음"),
|
||
("M", "m", "", 1, "같음"),
|
||
("kg", "ton", "", 0.001, "단위표"),
|
||
("㎡", "㎥", "T=150mm", 0.15, "추정"),
|
||
("M", "㎡", "B=2.3,H=0.4", 2.3, "추정"),
|
||
("M", "㎥", "B=2.3,T=0.2", 0.46, "추정"),
|
||
("개소", "㎥", "", None, "입력"),
|
||
("㎥", "㎡ · ㎥", "", 1, "두 단위"),
|
||
("M", "㎡ · ㎥", "", None, "입력"),
|
||
],
|
||
)
|
||
def test_환산(have, want, spec, times, kind):
|
||
assert hm.factor(have, want, spec) == {"배": times, "갈래": kind}
|
||
|
||
|
||
SPECS = [
|
||
{"이름": "뒷길이", "단위": "㎝", "고르기": [25, 35, 45], "자동값": None},
|
||
{"이름": "관경", "단위": "mm", "자동값": None},
|
||
{"이름": "설치높이", "단위": "m", "자동값": None},
|
||
{
|
||
"이름": "기계",
|
||
"고르기": ["0201-0020", "0201-0060"],
|
||
"고르기이름": {
|
||
"0201-0020": "굴착기(무한궤도) 0.2㎥",
|
||
"0201-0060": "굴착기(무한궤도) 0.6㎥",
|
||
},
|
||
"자동값": None,
|
||
},
|
||
{"이름": "횟수", "고르기": ["3회사용시", "4회사용시"], "자동값": None},
|
||
{"이름": "단위당", "고르기": ["㎡당", "㎥당"], "자동값": None},
|
||
{"이름": "지역", "고르기": ["전국평균", "서울"], "자동값": "전국평균"},
|
||
{"이름": "보정작업", "고르기": ["해당 없음", "전석섞인토사"], "자동값": None},
|
||
]
|
||
|
||
|
||
def test_규격에서_입력값():
|
||
line = {"이름": "찰붙임(4회)", "규격": "L3=35cm적용,굴삭기0.6㎥,D=1,000mm,H:1.2m", "단위": "㎥"}
|
||
values, unread = hm.spec_inputs(line, SPECS)
|
||
assert values == {
|
||
"뒷길이": 35,
|
||
"관경": 1000,
|
||
"설치높이": 1.2,
|
||
"기계": "0201-0060",
|
||
"횟수": "4회사용시",
|
||
"단위당": "㎥당",
|
||
}
|
||
assert unread == ["보정작업"] # 지역 = 자동값 · 못 읽은 것만 표시
|
||
other = {"이름": "찰붙임", "규격": "L3=40cm", "단위": "㎡"}
|
||
values, unread = hm.spec_inputs(other, SPECS[:1])
|
||
assert values == {} and unread == ["뒷길이"] # 고르기 밖 수 = 빈 값 + 표시
|
||
|
||
|
||
# ── 맞춤 응답(338 · 설계 3-3) ─────────────────────────────────────────
|
||
def _hopyo(client, no, layer="personal"):
|
||
res = client.get(f"{URL.format(layer)}/HP-000001/{no}")
|
||
assert res.status_code == 200, res.text
|
||
return {one["이름"]: one["맞춤"] for one in res.json()["호표"]["줄"]}
|
||
|
||
|
||
def _ready(client, layer="personal"):
|
||
assert _post(client, layer).status_code == 200
|
||
folder = layers.personal_dir(7, 42) if layer == "personal" else client.system
|
||
before = _hopyo(client, 1, layer)["레미콘타설(장비사용)"]
|
||
assert before["상태"] == "후보" and "셈 중" in before["꼬리표"] # 셈 전 = 이름 · 근거 후보
|
||
fit.run_job(folder, "HP-000001")
|
||
return folder
|
||
|
||
|
||
def test_새_상태_체크_제안(client):
|
||
before = master_hash()
|
||
_ready(client)
|
||
first = _hopyo(client, 1)
|
||
remicon = first["레미콘타설(장비사용)"]
|
||
assert remicon["상태"] == "맞음" and remicon["체크"] is True and "들어감" not in remicon
|
||
target = remicon["대상"][0]
|
||
assert target["로직"] == "GC000198" and target["입력값"]["소량감률"] == 50
|
||
assert target["출처"]["소량감률"] == "블록" and target["배"] == 1
|
||
assert remicon["품"]["같음"]["콘크리트공"] and remicon["품"]["엑셀"]["인력"]["콘크리트공"]
|
||
plywood = first["합판거푸집"]
|
||
assert plywood["상태"] == "맞음" and set(plywood["갈리는칸"]) == {"측압", "폼타이규격"}
|
||
mat = first["레미콘(25-21-120)"]
|
||
assert mat["상태"] == "후보" and mat["체크"] is False
|
||
assert all(c["규격"] == "25-21-120" for c in mat["후보"][:3]) # 규격 같은 자재 먼저
|
||
dig = _hopyo(client, 3)["구조물터파기(인력100%)"]
|
||
assert dig["상태"] == "다른 점" and dig["체크"] is False
|
||
assert dig["대상"][0]["로직"] == "GC000083" and len(dig["후보"]) == 5
|
||
assert "특별인부 엑셀 0.3542 · 우리 0.3704" in dig["꼬리표"]
|
||
ref = _hopyo(client, 45)["고화매트재료비"]
|
||
assert ref["상태"] == "호표" and ref["체크"] is False and ref["글"] == "제46호표 먼저 확인"
|
||
assert ref["대상"][0]["호표"] == 46 and ref["대상"][0]["단위"] == "㎡"
|
||
pct = _hopyo(client, 46)["잡재료비"]
|
||
assert pct["상태"] == "%" and pct["체크"] is False
|
||
whole = client.get(f"{URL.format('personal')}/HP-000001").json()
|
||
assert whole["맞춤"][1] == 415 and 0 < whole["맞춤"][0] < 415 and whole["셈"]["끝"]
|
||
assert next(h for h in whole["호표"] if h["번"] == 61)["맞춤"] == [0, 0]
|
||
one = client.get(f"{URL.format('personal')}/HP-000001/1").json()["호표"]
|
||
assert one["셈"]["끝"] and one["만든"] is None and one["맞춤"][0] >= 2
|
||
assert master_hash() == before
|
||
|
||
|
||
def _ledger(client, rows, layer="personal"):
|
||
res = client.put(f"/api/m02/layers/{layer}/hopyo-ledger", json={"줄": rows})
|
||
assert res.status_code == 200, res.text
|
||
|
||
|
||
def test_별칭_체크_규칙(client):
|
||
_ready(client)
|
||
dig = "구조물터파기(인력100%)|토사|m3"
|
||
remicon = "레미콘타설(장비사용)|철근구조물-소량타설|m3"
|
||
_ledger(
|
||
client,
|
||
{
|
||
dig: {"갈래": "짝", "짝": [{"로직": "GC000083", "배": 1}]},
|
||
remicon: {"갈래": "짝", "짝": [{"로직": "GC000199", "배": 1}]},
|
||
"합판거푸집|6회|m2": {"갈래": "짝", "짝": [{"로직": "GC000211", "배": 1}]},
|
||
"면목|구멍|개": {"갈래": "우리에 없음"},
|
||
},
|
||
)
|
||
got = _hopyo(client, 3)["구조물터파기(인력100%)"]
|
||
assert got["상태"] == "별칭" and got["체크"] is True and got["대상"][0]["출처"] == {}
|
||
assert got["별칭"]["층"] == "personal" and not got["별칭"]["근거다름"]
|
||
first = _hopyo(client, 1)
|
||
other = first["레미콘타설(장비사용)"] # 숫자 판정 맞음 = GC000198 → 체크 안 함
|
||
assert other["상태"] == "별칭" and other["체크"] is False
|
||
assert "숫자 판정 맞음 = GC000198" in other["꼬리표"]
|
||
loose = first["합판거푸집"] # 규격 4회 ↔ 장부 6회 — 느슨 열쇠 = 꼬리표 · 후보 덤만(229-4 ①)
|
||
assert loose["상태"] == "맞음" and "별칭" not in loose and "별칭 · 규격 다름" in loose["꼬리표"]
|
||
assert all("별칭" not in t["출처"].values() for t in loose["대상"])
|
||
assert "GC000211" in {c["로직"] for c in loose["후보"]} | {t["로직"] for t in loose["대상"]}
|
||
_ledger(
|
||
client, {remicon: {"갈래": "짝", "짝": [{"로직": "GC000198", "입력값": {"소량감률": 50}}]}}
|
||
)
|
||
plain = _hopyo(client, 7)["레미콘타설(장비사용)"] # 무근구조물 ↔ 장부 철근구조물-소량타설
|
||
assert plain["상태"] == "맞음" and plain["체크"] is True and "별칭" not in plain
|
||
assert plain["대상"][0]["로직"] == "GC000198" and plain["대상"][0]["입력값"]["소량감률"] == 0
|
||
assert "별칭 · 규격 다름" in plain["꼬리표"]
|
||
_ledger(client, {dig: {"갈래": "짝", "짝": [{"로직": "GF000101", "배": 1}]}})
|
||
off = _hopyo(client, 3)["구조물터파기(인력100%)"] # 산림 로직 ↔ 엑셀 건설 3-2-1
|
||
assert off["별칭"]["근거다름"] and off["체크"] is False and "근거 다름" in off["꼬리표"]
|
||
|
||
|
||
def test_품_비교_같은_장비_여러_줄():
|
||
from M02_MasterTemplete import M02_Hopyo_Line as lines
|
||
|
||
ex = {"인력": {}, "장비": [{"갈래": ["굴착기"], "시간": t} for t in (0.0334, 0.0371, 0.0889)]}
|
||
one = {"인력": {}, "장비": [{"갈래": ["굴착기"], "시간": 0.0889}], "자재": []}
|
||
assert not lines._qty({"품": ex}, {"우리품": one})["같음"]["굴착기"] # 값이 달라도 ✓ 이던 흠
|
||
many = {**one, "장비": ex["장비"]}
|
||
assert lines._qty({"품": ex}, {"우리품": many})["같음"]["굴착기"]
|
||
|
||
|
||
def test_장부_뺌_위_층(client):
|
||
client.who.update(role="SYSTEM_ADMIN")
|
||
key = "구조물터파기(인력100%)|토사|m3"
|
||
_ledger(client, {key: {"갈래": "안 씀"}}, "system")
|
||
client.who.update(role="USER")
|
||
_ready(client)
|
||
got = _hopyo(client, 3)["구조물터파기(인력100%)"]
|
||
assert got["상태"] == "뺌" and got["뺌"] == "안 씀" and got["별칭"]["층"] == "system"
|
||
assert hm.chain(layers.personal_dir(7, 42))[1] == layers.company_dir(7)
|
||
|
||
|
||
def test_미리_셈_목록_낱개(client):
|
||
folder = _ready(client)
|
||
url = f"{URL.format('personal')}/HP-000001/1/guess"
|
||
one = client.post(url, json={"차례": 1, "대상": {"로직": "GC000198"}})
|
||
assert one.status_code == 200, one.text
|
||
assert one.json()["대상"][0]["환산"] == {"배": 1, "갈래": "같음"}
|
||
assert client.post(url, json={"차례": 1, "대상": {"로직": "XX"}}).status_code == 400
|
||
body = [{"로직": "GC000198", "입력값": {"타설방법": "장비사용타설", "소량감률": 50}}]
|
||
got = hm.guess(folder, "HP-000001", 1, 1, body)
|
||
assert got["대상"][0]["출처"]["소량감률"] == "고름"
|
||
assert got["품"]["같음"]["콘크리트공"] and got["품"]["엑셀"]["인력"]["콘크리트공"]
|
||
plain = hm.guess(folder, "HP-000001", 1, 1, [{"로직": "GC000198", "입력값": {"소량감률": 0}}])
|
||
assert not plain["품"]["같음"]["콘크리트공"] # 옵션을 바꾸면 우리 품이 바뀜
|
||
|
||
|
||
def test_로직_추가_고르기_줄은_이_층과_위_층(client):
|
||
client.who.update(role="SYSTEM_ADMIN")
|
||
assert _post(client, "system").status_code == 200
|
||
client.who.update(role="USER")
|
||
assert _post(client).status_code == 200
|
||
rows = client.get("/api/m02/layers/personal/hopyo-picks").json()["줄"]
|
||
assert [r["층"] for r in rows].count("personal") == 68
|
||
assert [r["층"] for r in rows].count("system") == 68
|
||
three = next(r for r in rows if r["층"] == "system" and r["번"] == 3)
|
||
assert {k: three[k] for k in ("주인", "키", "이름", "단위")} == {
|
||
"주인": None,
|
||
"키": "HP-000001",
|
||
"이름": "임도표지석",
|
||
"단위": "개소",
|
||
}
|
||
assert three["맞춤"][1] == 4
|
||
system = client.get("/api/m02/layers/system/hopyo-picks").json()["줄"]
|
||
assert {r["층"] for r in system} == {"system"} # 마스터 = 마스터만
|
||
|
||
|
||
def test_뜻_낱말이_안_겹치면_후보_아님():
|
||
assert hm.words("면 목") == {"면목"} and hm.words("구조물되메우기(인력100%)") == {
|
||
"구조물되메우기"
|
||
}
|
||
assert hm.words("되메우기 및 다짐(소형장비)") == {"되메우기", "다짐"}
|
||
soil = {"이름": "잔토처리(인력100%)", "규격": "", "단위": "㎥", "갈래": "단가산출"}
|
||
assert hm.candidates(soil, "로직") == [] # 「처리」 두 글자만 겹침(소금처리 · 바탕처리)
|
||
stone = {"이름": "임도표지석(받침,글새김포함)", "규격": "", "단위": "㎥", "갈래": "자재"}
|
||
assert hm.candidates(stone, "자재") == [] # 속도표지판 · 도로표지용페인트
|
||
got = hm.b_order(soil)
|
||
assert got["상태"] == "없음" and got["top"] == []
|
||
fill = {"이름": "구조물되메우기(인력100%)", "규격": "", "단위": "㎥", "갈래": "단가산출"}
|
||
assert hm.candidates(fill, "로직")[0]["이름"] == "되메우기" # 한쪽 낱말에 듦 = 겹침
|