Files
Aislo/resources/tester/test_m02_hopyo_flow.py
T
eomsangdonandClaude Opus 5.5 4cdb41f80b feat(778-5): 블록 = 줄 판정 모음 · 확인한 블록만 단위 SK(바탕 없음 · 근거 꼬리표) · 확인 목록 받는 길(D-3 · 요구 623 · 624)
- 새 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>
2026-11-04 14:16:12 +09:00

188 lines
9.6 KiB
Python

"""338-4 이음매 — 끝까지(시험 클라이언트 · 임시 층): 북부청 임도 올림 → 셈 끝 → 호표 보기(맞음 ·
다른 점 ·
후보) → 화면 도우미(`helper_m02_hopyo_check.cjs` · 실물 응답)가 만든 몸(체크한 줄만) →
[확인하고 사전조합 만들기] → 사전조합 줄(로직 여럿 같은 별칭 · 배 · 몫) · 별칭 장부 → 다시 올림 =
별칭 체크 ·
몸 없는 만들기 400 · 옛 hopyo-memory 404 · STmate 영월 올림 → 상태 · master_data 원본 그대로.
"""
import json
import os
import subprocess
from M02_MasterTemplete import M02_Hopyo_Fit as fit
from M02_MasterTemplete import M02_Template_Layers as layers
from resources.tester.helper_m02_hopyo_golden import FILES
from resources.tester.test_m02_hopyo_excel import URL, _post, client, master_hash # noqa: F401
# ruff: noqa: F811
HERE = os.path.dirname(os.path.abspath(__file__))
FOLDER = (7, 42) # USER 의 개인 층
def _node(payload: dict) -> dict:
proc = subprocess.run(
["node", os.path.join(HERE, "helper_m02_hopyo_check.cjs")],
input=json.dumps(payload, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=False,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def _up(client, key: str, name: str | None = None, data: bytes | None = None) -> None:
res = _post(client, data=data, **({"name": name} if name else {}))
assert res.status_code == 200, res.text
assert res.json()["키"] == key
fit.run_job(layers.personal_dir(*FOLDER), key) # 셈 끝(뒤 셈 대신 바로)
def _one(client, key: str, no: int) -> dict:
res = client.get(f"{URL.format('personal')}/{key}/{no}")
assert res.status_code == 200, res.text
return res.json()["호표"]
def _find(client, key: str, name: str) -> tuple[int, dict]:
whole = client.get(f"{URL.format('personal')}/{key}").json()
assert whole["셈"]["끝"]
for h in whole["호표"]:
one = _one(client, key, h["번"])
if any(x["이름"] == name for x in one["줄"]):
return h["번"], one
raise KeyError(name)
def _build(client, key: str, no: int, lines: list, decide: bool = False, **extra) -> dict:
"""`decide` = 남은 줄(729-2)을 「우리에 없음」으로 고른 뒤 만듦."""
body = {"층": "personal", "키": key, "번": no, "줄": lines, **extra}
if decide:
left = client.post("/api/m02/layers/personal/hopyo-unlinked", json=body).json()["남은줄"]
body["줄"] = [*lines, *({"차례": x["차례"], "뺌": "우리에 없음"} for x in left)]
res = client.post("/api/m02/layers/personal/hopyo-build", json=body)
assert res.status_code == 200, res.text
return res.json()
def _sure(client, key: str, hopyo: dict, body: list) -> None:
"""778-5 — 체크 안 한 단가산출 줄의 블록을 [단가산출]에서 확인(자동 없음 · 옮길 수 있는 것만)."""
picked = {x["차례"] for x in body}
want = {int(x["참조"]["번"]) for x in hopyo["줄"]
if (x.get("참조") or {}).get("종류") == "단산" and x["차례"] not in picked} # fmt: skip
url = f"{URL.format('personal')}/{key}/sangeun"
rows = client.post(url).json()["판정"]
sure = [{"번": r["번"]} for r in rows if r["번"] in want and not r["고를줄"]]
assert client.post(url, json={"확인": sure}).status_code == 200
def _doc(ref: dict) -> dict:
path = layers.personal_dir(*FOLDER) / "precombo" / f"{ref['키']}.json"
return json.loads(path.read_text(encoding="utf-8"))
def test_끝까지_확인한_줄만_별칭(client):
before = master_hash()
_up(client, "HP-000001")
no, hopyo = _find(client, "HP-000001", "레미콘타설(장비사용)")
by = {x["이름"]: x for x in hopyo["줄"]}
assert by["레미콘타설(장비사용)"]["맞춤"]["상태"] == "맞음"
screen = _node({"real": hopyo["줄"]})
seen = {x["이름"]: x for x in screen["lines"]}
remicon = seen["레미콘타설(장비사용)"]
assert remicon["checked"] and remicon["checkable"]
assert remicon["compare"].startswith("콘크리트공 0.1091 → 0.1091 ✓")
assert "굴착기 0.29 → 0.2909 ✓" in remicon["compare"]
assert "소량감률=50(블록)" in remicon["inputs"] and remicon["target"].endswith("[㎥]")
states = {x["상태"] for x in screen["lines"]}
assert {"맞음", "다른 점"} <= states # 358-2 — 레미콘 · 면목 자재 줄도 맞음(후보 남지 않음)
assert seen["면목"]["상태"] == "맞음" and seen["면목"]["checked"]
body = screen["body"] # 화면이 만든 몸 = 서버 체크 제안 줄만
suggested = [x["차례"] for x in hopyo["줄"] if x["맞춤"]["체크"]]
assert [x["차례"] for x in body] == suggested and screen["leftOut"]["확인 안 함"] > 0
rebar = next(x for x in hopyo["줄"] if x["이름"] == "철근가공조립(현장)")
two = [{k: p[k] for k in ("로직", "입력값", "배")} for p in rebar["맞춤"]["대상"]]
assert {p["로직"] for p in two} == {"GC000206", "GC000207"} # 다른 점 → 사용자가 체크
body = body + [{"차례": rebar["차례"], "대상": two}]
_sure(client, "HP-000001", hopyo, body)
made = _build(client, "HP-000001", no, body)
national = [x for x in body if any(t.get("전국") for t in x.get("대상", []))] # 361-1
assert national and all(x["대상"] == [{"전국": True, "배": 1}] for x in national)
assert made["다시씀"] is False and made["장부"]["씀"] == len(body) - len(national)
assert all(m["까닭"] for m in made["빠진줄"]) # 확인 안 함 · % 줄
doc = _doc(made["참조"])
logics = [x for x in doc["담은로직"] if x.get("로직")]
alias = {x["로직"]: x["별칭"] for x in logics}
assert alias["GC000206"] == alias["GC000207"] == "철근가공조립(현장)" # 로직 여럿 · 같은 별칭
assert doc["본뜬"]["확인"] and doc["본뜬"]["키"] == f"HP-000001 제{no}호표"
rows = client.get("/api/m02/layers/personal/hopyo-ledger").json()["줄"]
keys = {r["열쇠"]: r for r in rows}
assert {p["로직"] for p in keys[rebar["맞춤"]["열쇠"]]["짝"]} == {"GC000206", "GC000207"}
# 다시 올림 = 확인한 줄은 별칭 · 체크 · 만든 것이 보임
assert _post(client).json()["이어서"] is True # 718-1 — 같은 엑셀 = 그 파일 이어서(새 키 없음)
again = {x["이름"]: x["맞춤"] for x in _one(client, "HP-000001", no)["줄"]}
assert again["레미콘타설(장비사용)"]["상태"] == "별칭" and again["레미콘타설(장비사용)"]["체크"]
assert again["철근가공조립(현장)"]["상태"] == "별칭"
assert again["철근가공조립(현장)"]["대상"][0]["출처"] == {
k: "별칭" for k in again["철근가공조립(현장)"]["대상"][0]["입력값"]
}
first = _one(client, "HP-000001", no)
assert {k: first["만든"][k] for k in ("키", "확인")} == {"키": made["참조"]["키"], "확인": True}
assert first["만든"]["품셈"] == doc["본뜬"]["품셈"] # 705-3 — 품셈 줄 차례(첫 체크)
assert master_hash() == before
def test_섞인_줄_몫_배(client):
_up(client, "HP-000001")
no, hopyo = _find(client, "HP-000001", "고임돌 채집 및 운반")
line = next(x for x in hopyo["줄"] if x["이름"] == "고임돌 채집 및 운반")
part = line["맞춤"]["대상"][0]
assert (part["로직"], part["몫"], part["배"]) == ("GF000211", 0.1, 0.1)
screen = _node({"real": [line]})
assert screen["lines"][0]["checkable"] and not screen["lines"][0]["checked"]
one = {k: part[k] for k in ("로직", "입력값", "배", "몫")}
made = _build(client, "HP-000001", no, [{"차례": line["차례"], "대상": [one]}], decide=True)
doc = _doc(made["참조"])
at = next(i for i, x in enumerate(doc["담은로직"]) if x.get("로직") == "GF000211")
want = round(line["수량"] * 0.1, 6) # 742-1 — 엑셀 수량(몫) = 저장 수량 · 출처 기록도 같은 값
assert doc["담은로직"][at]["수량"] == {"손": want}
assert doc["본뜬"]["실무수량"]["줄"][str(at)] == want
def test_막는_길(client):
_up(client, "HP-000001")
empty = client.post(
"/api/m02/layers/personal/hopyo-build",
json={"층": "personal", "키": "HP-000001", "번": 1, "줄": []},
)
left = empty.json()["detail"]["남은줄"] # 729-2 — 안 이은 줄 남음 = 안 만듦
assert empty.status_code == 409 and left
peek = client.post(
"/api/m02/layers/personal/hopyo-unlinked",
json={"층": "personal", "키": "HP-000001", "번": 1, "줄": []},
).json()
assert peek == {"남은줄": left, "수": len(left)}
money = client.post(
"/api/m02/layers/personal/hopyo-build",
json={"층": "personal", "키": "HP-000001", "번": 1, "줄": [{"차례": 1, "금액": 1}]},
)
assert money.status_code in (400, 422)
gone = client.post("/api/m02/layers/personal/hopyo-memory", json={"줄": {}})
assert gone.status_code in (404, 405)
url = f"{URL.format('personal')}/HP-000001/1/guess"
many = client.post(url, json={"차례": 1, "대상": [{"로직": "GC000198", "입력값": {}}]})
assert many.status_code == 200 and many.json()["대상"][0]["로직"] == "GC000198"
def test_STmate_영월(client):
path = FILES["동부청영월"]
_up(client, "HP-000001", path.name, path.read_bytes())
whole = client.get(f"{URL.format('personal')}/HP-000001").json()
assert whole["셈"]["끝"] and len(whole["호표"]) == 23 and whole["맞춤"][0] > 0
states = set()
for h in whole["호표"]:
states |= {x["맞춤"]["상태"] for x in _one(client, "HP-000001", h["번"])["줄"]}
assert {"맞음", "후보"} <= states