- 새 M02_Hopyo_SkUnit: 줄 판정(Units 잇기 + 매개값 · 사용자 고름 {차례: 노임|기계|자재|기준|뺌} · 꼬리표 「내가 고름」) · 단가 = M01 지금 값으로 다시 셈(우리금액) · 엑셀 금액 차 = 참고 · 블록 판정 「옮길 수 있음」 | 「고를 줄 n」 · 단위 SK 더한줄 = 인력 · 기계경비 로직 줄 · 재료(손료율 든 수량 · 별도계상) · 율 줄(778-4 꼴 · 줄 목록 = 그 행 add:#k) · 하위 참조 · 값 고정 줄 · 저장 꼴로 다듬어 같은 내용 SK 지문 같음
- Sangeun: 기초 · SK · 고름 대기 갈림(SkPatch.plan) 걷고 단위 SK 하나 · 확인 [{번, 고름?}] 에 든 블록만 만듦 · 하위 단산 먼저 · 고를 줄 남으면 못만듦 · 일위대가 만들기가 부르는 Maker.plan(번) = 「확인 전」 고름 대기(자동 없음) · 별칭 제안 걷음(바탕 기초 없음)
- Router: sangeun 몸 바탕 → 확인(줄 수 그대로)
- 시험 새 뜻: test_729(기초 · SK · 고름 대기 · 계수 · 못옮김 잇기 · 729-5 차이 SK 바탕 길 = 바탕 없는 단위 SK 로 사라짐 → 확인 블록 단위 SK · 줄 고름 · 뺌으로 갈음) · test_718_1 · test_572 · test_779_2 · test_m02_hopyo_flow(확인 목록을 보내야 SK 생김 · 같은 파일 안 같은 내용 단위 SK 없을 수 있음 · 바탕 없는 SK = SC 에 참조 그대로)
- 새 test_778_5: 작업반장 = 건설업 · 규격 차례 무관 · 글자 규격 = 후보 · 손료율 · 설치비 · 제경비 율 줄 · 여덟 파일 줄 연결 표 · 확인 안 한 블록 안 만듦 · 고름 · 400 · 404 · 다시 올림 같은 SK 재사용 · 일위대가 확인 전 못 이음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
173 lines
9.7 KiB
Python
173 lines
9.7 KiB
Python
"""778-5 줄 잇기 한 벌 · 블록 = 줄 판정 모음 · 단위 SK(D-2 · D-3 · 요구 622 · 623 · 624).
|
||
|
||
- 줄 잇기(`M02_Hopyo_Units`) — 작업반장 = 건설업 · 숫자 규격 치수 차례 · 구분자 무관 · 글자 규격 = 고를 것 ·
|
||
손료율 = 자재 줄 방법 · 우리에 없는 율 항목(설치비 · 제경비) = 율 줄 · 여덟 파일 묶음별 수 굳힘
|
||
- 단위 SK(`M02_Hopyo_SkUnit` · `M02_Hopyo_Sangeun`) — 확인 안 한 블록 안 만듦 · 확인 블록 = 바탕 없는 SK +
|
||
근거 꼬리표 · 고를 줄 남으면 못만듦 · 고르면 만듦 · 다시 올려도 같은 SK 재사용 · 일위대가 = 확인 전 못 이음
|
||
층 = 임시 폴더 · M01 · 마스터 읽기만.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
|
||
import pytest
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Fit as fit
|
||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||
from M02_MasterTemplete import M02_Hopyo_Sangeun as sangeun
|
||
from M02_MasterTemplete import M02_Hopyo_SkUnit as skunit
|
||
from M02_MasterTemplete import M02_Hopyo_Store as hs
|
||
from M02_MasterTemplete import M02_Hopyo_Units as units
|
||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||
from resources.tester import helper_m02_hopyo_golden as golden
|
||
from resources.tester.test_m02_hopyo_excel import URL, _post, client, master_hash # noqa: F401
|
||
|
||
BUILD = "/api/m02/layers/personal/hopyo-build"
|
||
# 여덟 파일 불리는 블록 줄 연결 표(778-5 D-2 굳힘 · 바뀌면 까닭 커밋 글)
|
||
TABLE = {
|
||
"L": {"이어짐": 366, "고를 것": 0, "못 이음": 0},
|
||
"X": {"이어짐": 180, "고를 것": 10, "못 이음": 0},
|
||
"M": {"이어짐": 71, "고를 것": 55, "못 이음": 4},
|
||
"R": {"이어짐": 150, "고를 것": 2, "못 이음": 0},
|
||
"S": {"이어짐": 3, "고를 것": 0, "못 이음": 0},
|
||
"E": {"이어짐": 1, "고를 것": 0, "못 이음": 0},
|
||
}
|
||
ALL_LINKED = 156
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def us():
|
||
return units.Units()
|
||
|
||
|
||
def test_작업반장_건설업_기본(us):
|
||
got = us.line({"묶음": "L", "이름": "작업반장", "직종": "작업반장", "품": 0.1})
|
||
assert got["상태"] == units.LINKED and got["단위"] == {"노임": "LB000001"} # 건설업
|
||
assert got["꼬리표"] == ["기본 — 건설업 작업반장"] and got["후보"][0]["키"] == "LB000261"
|
||
one = us.line({"묶음": "L", "이름": "보통인부", "직종": "보통인부", "품": 0.5})
|
||
assert one["단위"] == {"노임": "LB000002"} and not one["꼬리표"]
|
||
|
||
|
||
def test_숫자_규격_차례_구분자_무관_글자_규격_후보(us):
|
||
assert hmat.dims("600×1200") == hmat.dims("1200x600 mm") == hmat.dims("1200*600㎜")
|
||
assert hmat.dims("1종2류(철재)") is None and hmat.lettered("1종2류(철재)")
|
||
assert not hmat.lettered("600*1200mm") and not hmat.lettered("#20, 0.9mm")
|
||
for spec in ("600×1200", "1200x600", "1200 X 600mm"):
|
||
got = us.line({"묶음": "M", "이름": "유로폼패널", "규격": spec, "단위": "매", "품": 1})
|
||
assert got["상태"] == units.LINKED and got["단위"] == {"자재": "MT000467"}, spec
|
||
named = us.line({"묶음": "M", "이름": "유로폼(패널)(600×1200)", "단위": None, "품": 1})
|
||
assert named["단위"] == {"자재": "MT000467"} # 「유로폼(패널)」 = 유로폼패널 · 단위 없는 EST 줄
|
||
paint = {"묶음": "M", "이름": "광명단조합페인트", "규격": "1종2류(철재)", "단위": "L", "품": 1}
|
||
got = us.line(paint)
|
||
assert got["상태"] == units.PICK and got["글"] == units.LETTERS and got["후보"]
|
||
|
||
|
||
def test_손료율_자재_줄_율_항목_율_줄(us):
|
||
wear = us.line({"묶음": "M", "이름": "유로폼패널", "규격": "600×1200", "품": 0.267,
|
||
"손료율": 0.267}) # fmt: skip
|
||
assert wear["방법"] == units.WEAR and wear["상태"] == units.LINKED
|
||
setup = {"묶음": "R", "이름": "설치비(재료비의 5%)", "품": 0.05, "기준꼴": "자재"}
|
||
assert us.line(setup)["단위"] == {"기준": "재료", "비목": "재료비"} # 우리에 없는 율 항목
|
||
rest = {"묶음": "R", "이름": "제경비(노무비의 60%)", "품": 0.6, "기준꼴": "노무비"}
|
||
assert us.line(rest)["단위"] == {"기준": "노무비", "비목": "경비"}
|
||
lost = us.line({"묶음": "R", "이름": "기구손료(노무비의 9%)", "기준": "노무비", "품": 0.09})
|
||
assert lost["상태"] == units.PICK and lost["후보"] == [{"기준": "노무비", "비목": "경비"}]
|
||
# 단위 SK 셈 — 설치비 = 재료 줄 × 5 % · 제경비 = 노무비 × 60 %
|
||
block = {"항목": [
|
||
{"묶음": "L", "이름": "보통인부", "직종": "보통인부", "품": 0.5, "행": 1},
|
||
{"묶음": "M", "이름": "유로폼패널", "규격": "600×1200", "단위": "매", "품": 2, "행": 2},
|
||
{**setup, "행": 3}, {**rest, "행": 4},
|
||
]} # fmt: skip
|
||
got = skunit.judge(block, us, lambda sub: None, None, "㎡")
|
||
assert got["판정"] == skunit.GOOD and [r["상태"] for r in got["줄"]] == [units.LINKED] * 4
|
||
labor_, mat = (got["줄"][0]["우리금액"], got["줄"][1]["우리금액"])
|
||
assert got["줄"][2]["우리금액"] == pytest.approx(mat * 0.05, abs=0.1)
|
||
assert got["줄"][3]["우리금액"] == pytest.approx(labor_ * 0.6, abs=0.1)
|
||
doc = {"키": "SK-000001", "이름": "t", "단위": "㎡", "바탕": None, "더한줄": got["더한줄"]}
|
||
whole = us.whole
|
||
ran = labor._json(calc.run(sd.clean(doc, None, whole), whole))
|
||
assert ran["1단위"]["계"] == pytest.approx(got["우리금액"])
|
||
|
||
|
||
def test_여덟_파일_줄_연결_표(us):
|
||
rows, full, blocks = [], 0, 0
|
||
for tag in golden.FILES:
|
||
hp = golden.read(tag)
|
||
_, first = sangeun.calls(hp)
|
||
for b in hp["단산"]:
|
||
if int(b["번"]) not in first:
|
||
continue
|
||
got = [(o["묶음"], us.line(o)["상태"]) for o in b.get("항목") or [] if not o.get("밖")]
|
||
rows += got
|
||
blocks += 1
|
||
full += all(s == units.LINKED for _, s in got)
|
||
assert units.summary(rows) == TABLE and (full, blocks) == (ALL_LINKED, 206)
|
||
|
||
|
||
def _sks(folder) -> dict[str, dict]:
|
||
return {p.stem: json.loads(p.read_bytes()) for p in (folder / "sangeun").glob("SK-*.json")}
|
||
|
||
|
||
def test_확인한_블록만_단위_SK_고름_재사용(client):
|
||
before = master_hash()
|
||
folder = layers.personal_dir(7, 42)
|
||
key = _post(client).json()["키"]
|
||
url = f"{URL.format('personal')}/{key}/sangeun"
|
||
fit.run_job(folder, key)
|
||
got = client.post(url, json={}).json()
|
||
assert got["만듦"] == [] and not _sks(folder) # 확인 안 한 블록 = 안 만듦
|
||
assert len(got["판정"]) == got["블록"] == 49 # 불리는 블록
|
||
rows = {r["번"]: r for r in got["판정"]}
|
||
good = [n for n, r in rows.items() if r["판정"] == skunit.GOOD and r["근거"]]
|
||
pick = next(n for n, r in rows.items() if r["고를줄"])
|
||
assert rows[pick]["판정"] == f"고를 줄 {rows[pick]['고를줄']}"
|
||
one = rows[good[0]]
|
||
assert all("우리금액" in x and "차" in x for x in one["줄"] if x["상태"] == units.LINKED)
|
||
# 확인 = 그 블록만 · 고를 줄 남은 블록 = 못만듦
|
||
made = client.post(url, json={"확인": [{"번": good[0]}, {"번": pick}]}).json()
|
||
assert [x["번"] for x in made["만듦"]] == [good[0]] and made["못만듦"][0]["번"] == pick
|
||
docs = _sks(folder)
|
||
doc = docs[made["만듦"][0]["키"]]
|
||
assert doc["바탕"] is None and doc["본뜬"]["갈래"] == skunit.UNIT and doc["더한줄"]
|
||
assert doc["본뜬"]["근거"]["로직"] == one["근거"]["로직"] and doc["본뜬"]["못옮김"] == []
|
||
# 고르면 만듦(못 이은 줄마다 첫 후보 · 후보 없으면 뺌)
|
||
picks = {}
|
||
for x in rows[pick]["줄"]:
|
||
if x["상태"] in (units.PICK, units.NONE):
|
||
c = (x["후보"] or [{}])[0]
|
||
k = c.get("키") or c.get("자재")
|
||
field = {"L": "노임", "X": "기계", "M": "자재"}.get(x["묶음"])
|
||
picks[str(x["차례"])] = {field: k} if k and field else c or {"뺌": "현장"}
|
||
again = client.post(url, json={"확인": [{"번": pick, "고름": picks}]}).json()
|
||
assert [x["번"] for x in again["만듦"]] == [pick] and again["그대로"] == 1
|
||
mine = next(r for r in again["판정"] if r["번"] == pick)
|
||
assert all("내가 고름" in x["꼬리표"] for x in mine["줄"] if str(x["차례"]) in picks)
|
||
bad = {"확인": [{"번": pick, "고름": {"0": {"노임": "XX"}}}]}
|
||
assert client.post(url, json=bad).status_code == 400
|
||
assert client.post(url, json={"확인": [{"번": 9999}]}).status_code == 404
|
||
# 일위대가 만들기 — 확인 안 한 단가산출 줄 = 못 이음(확인 전)
|
||
calls = {}
|
||
for h in hs.read(folder, key)["호표"]:
|
||
for x in h.get("줄") or []:
|
||
ref = x.get("참조") or {}
|
||
if ref.get("종류") == "단산":
|
||
calls.setdefault(int(ref["번"]), h["번"])
|
||
wait = next(n for n in sorted(calls) if n not in (good[0], pick))
|
||
res = client.post(BUILD, json={"층": "personal", "키": key, "번": calls[wait], "줄": []})
|
||
left = [x for x in res.json()["detail"]["남은줄"] if x.get("까닭") == sangeun.WAIT_WHY]
|
||
assert res.status_code == 409 and left
|
||
# 다시 올림(끝내기 뒤) = 같은 내용 SK 재사용
|
||
n = len(_sks(folder))
|
||
client.delete(f"{URL.format('personal')}/{key}")
|
||
key2 = _post(client).json()["키"]
|
||
fit.run_job(folder, key2)
|
||
url2 = f"{URL.format('personal')}/{key2}/sangeun"
|
||
re_ = client.post(url2, json={"확인": [{"번": good[0]}]}).json()
|
||
assert re_["같은것씀"] == [{"번": good[0], "키": made["만듦"][0]["키"]}]
|
||
assert len(_sks(folder)) == n
|
||
assert master_hash() == before
|