feat(일위대가실무): 324-2 맞춤 — 엑셀 구성 줄 → 돈 로직 · 우리 자재 · 규격 입력값 · 환산 · 층 기억
- M02_Hopyo_Match: 정규화(master_material.plain · plain_spec · plain_unit) · 차례 = 층 기억(같은 층 → 위 층) → 이름 같음 · 일위대가조합.json 메모 씨앗(읽기만) → 정리 이름 · 후보 5(두 글자 묶음 + 단위 + 산림품셈 · 규격 같은 자재 덤) · 후보는 확정 뒤에만 들어감
- 규격 → 입력값: 고르기 글(품명 괄호 포함 · 낱말 머리) · 기계 고르기이름 · D= H= H: B= T= L= L3= · 천 단위 쉼표 · 못 읽음 표시
- 환산: 같은 단위 1 · 단위표 · ㎡→㎥ T · m→㎡ B · m→㎥ B×T = 추정 · 두 단위 로직 · 그 밖 입력
- 길: GET /hopyo/{키}(호표마다 맞춤 n/m) · GET /hopyo/{키}/{번}(줄 맞춤 · 후보) · POST …/guess(미리 채움) · POST /hopyo-memory(확정 · 빼기 · 지움 = 층 쓰기)
- 시험 test_m02_hopyo_match 15 통과(+ 324-1 15)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
"""일위대가 실무자료 맞춤(PLAN 324-2 · `M02_Hopyo_Match`) — 규격 → 입력값 · 단위 · 환산 ·
|
||||
맞춤 차례 · 기억 우선 · 길(보기 · 미리 채움 · 확정) · master_data 원본 그대로. 층은 임시 폴더.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
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 == ["뒷길이"] # 고르기 밖 수 = 빈 값 + 표시
|
||||
|
||||
|
||||
# ── 맞춤 차례 ────────────────────────────────────────────────────────
|
||||
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 test_이름_같음_후보_호표_퍼센트(client):
|
||||
before = master_hash()
|
||||
assert _post(client).status_code == 200
|
||||
first = _hopyo(client, 1)
|
||||
plywood = first["합판거푸집"]
|
||||
assert plywood["상태"] == "같음" and plywood["대상"]["로직"] == "GF000160"
|
||||
assert plywood["입력값"] == {"횟수": "4회사용시"} and plywood["들어감"] is True
|
||||
remicon = first["레미콘(25-21-120)"]
|
||||
assert remicon["상태"] == "후보" and remicon["들어감"] is False
|
||||
assert all(c["규격"] == "25-21-120" for c in remicon["후보"][:3]) # 규격 같은 자재 먼저
|
||||
dig = _hopyo(client, 3)["구조물터파기(인력100%)"]
|
||||
assert dig["상태"] == "후보" and len(dig["후보"]) == 5
|
||||
assert dig["후보"][0]["이름"].startswith("구조물터파기")
|
||||
ref = _hopyo(client, 45)["고화매트재료비"]
|
||||
assert ref["상태"] == "호표" and ref["대상"] == {"호표": 46, "단위": "㎡"} and ref["들어감"]
|
||||
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
|
||||
assert next(h for h in whole["호표"] if h["번"] == 61)["맞춤"] == [0, 0]
|
||||
assert master_hash() == before
|
||||
|
||||
|
||||
def test_미리_채움_확정_기억_우선(client):
|
||||
assert _post(client).status_code == 200
|
||||
url = f"{URL.format('personal')}/HP-000001/3/guess"
|
||||
got = client.post(url, json={"차례": 1, "대상": {"로직": "GF000101"}}).json()
|
||||
assert got["환산"] == {"배": 1, "갈래": "같음"} and got["대상"]["단위"] == "㎥"
|
||||
assert client.post(url, json={"차례": 1, "대상": {"로직": "XX"}}).status_code == 400
|
||||
key = _hopyo(client, 3)["구조물터파기(인력100%)"]["열쇠"]
|
||||
assert key == "구조물터파기(인력100%)|토사|m3"
|
||||
body = {"줄": {key: {"로직": "GF000101", "입력값": {"지역": "서울"}, "환산": 1}}}
|
||||
assert client.post("/api/m02/layers/personal/hopyo-memory", json=body).status_code == 200
|
||||
dig = _hopyo(client, 3)["구조물터파기(인력100%)"]
|
||||
assert (dig["상태"], dig["대상"]["로직"], dig["입력값"], dig["들어감"]) == (
|
||||
"기억",
|
||||
"GF000101",
|
||||
{"지역": "서울"},
|
||||
True,
|
||||
)
|
||||
# 이름 같음보다 기억이 앞섬 · 빼기
|
||||
plywood = _hopyo(client, 1)["합판거푸집"]["열쇠"]
|
||||
out = {"줄": {plywood: {"빼기": True}}}
|
||||
assert client.post("/api/m02/layers/personal/hopyo-memory", json=out).status_code == 200
|
||||
assert _hopyo(client, 1)["합판거푸집"]["상태"] == "빼기"
|
||||
clear = {"줄": {plywood: None}}
|
||||
assert client.post("/api/m02/layers/personal/hopyo-memory", json=clear).status_code == 200
|
||||
assert _hopyo(client, 1)["합판거푸집"]["상태"] == "같음"
|
||||
|
||||
|
||||
def test_확정_검사와_위_층_기억(client):
|
||||
bad = [
|
||||
{"로직": "GC000422", "환산": 1}, # 돈 아닌 로직
|
||||
{"로직": "GF000101", "환산": 0},
|
||||
{"로직": "GF000101", "자재": "MT004258", "환산": 1},
|
||||
{"환산": 1},
|
||||
]
|
||||
for one in bad:
|
||||
res = client.post("/api/m02/layers/personal/hopyo-memory", json={"줄": {"a|b|c": one}})
|
||||
assert res.status_code == 400, one
|
||||
assert client.post("/api/m02/layers/system/hopyo-memory", json={"줄": {}}).status_code == 403
|
||||
client.who.update(role="SYSTEM_ADMIN")
|
||||
key = "구조물터파기(인력100%)|토사|m3"
|
||||
body = {"줄": {key: {"로직": "GF000103", "환산": 1}}}
|
||||
assert client.post("/api/m02/layers/system/hopyo-memory", json=body).status_code == 200
|
||||
client.who.update(role="USER")
|
||||
assert _post(client).status_code == 200
|
||||
assert _hopyo(client, 3)["구조물터파기(인력100%)"]["대상"]["로직"] == "GF000103" # 위 층
|
||||
mine = {"줄": {key: {"로직": "GF000101", "환산": 1}}}
|
||||
assert client.post("/api/m02/layers/personal/hopyo-memory", json=mine).status_code == 200
|
||||
assert _hopyo(client, 3)["구조물터파기(인력100%)"]["대상"]["로직"] == "GF000101" # 같은 층 먼저
|
||||
assert hm.chain(layers.personal_dir(7, 42))[1] == layers.company_dir(7)
|
||||
Reference in New Issue
Block a user