Files
eomsangdonandClaude Sonnet 5.5 e23fec8c9c feat(786-1): 별도계상 줄 품목 고르기 없앰 — 후보 고정 줄 하나 · SC 합 밖 고정 줄
- Row.judge: 별도계상 줄은 M01 · 사전 · 장부 후보 안 찾고 고정 줄 하나(품목 「프로젝트에서」 표지) · 갈래 수 그대로
- Make: 별도계상 고정 줄 = 단가 0 · 메모 「별도계상 · 품목은 프로젝트에서」(합 밖)
- Picks.ts: apartPick 표지 셈
- 시험 642_3 · 642_5 · 642_6 Picks 몫을 새 꼴로 고침(옛 단언: 레미콘 자재 목록 별도계상 칸)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 07:17:50 +09:00

211 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""642-5(PLAN 785-5) 만들기 · 길 지킴 시험(설계 642-A 5-3 넷째 줄) — 임시 storage(`client` 마당).
- 판정 길(목록 · 호표 · 블록) · 검산 길(입력값 = 엔진이 쓴 칸 모두 · 지역 · 자재지역 · 조종원직종 비움)
- 옮기기 → SC 줄(고른 것마다 · 같은 별칭 · 본뜬 {호표, 차례, 행} · 수량 = 엑셀 × 몫) · 같은 기초 둘째 = 사본 ·
별도계상 자재 · 입력 필요 = 그 줄만 막힘(고정 줄로 남음) · SK 더한줄(바탕 없음) · 장부 · 다시 옮김 = SC 안 늚
- 금액 · 품 칸 막음 · M01 원본 그대로
"""
import json
from M02_MasterTemplete import M02_Hopyo_Bind as hb
from M02_MasterTemplete import M02_Hopyo_Build as hbuild
from M02_MasterTemplete import M02_Hopyo_Judge as judge
from M02_MasterTemplete import M02_Hopyo_Ledger as ledger
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Template_Layers as layers
# 올리기 길 시험의 `client` 마당을 같이 씀
# ruff: noqa: F811
from resources.tester.test_m02_hopyo_excel import _post, client, master_hash # noqa: F401
API = "/api/m02/layers/personal"
KEY = "HP-000001"
def _mine():
return layers.personal_dir(7, 42)
def _doc(kind, key):
return json.loads((_mine() / kind / f"{key}.json").read_bytes())
def _at(unit, no):
"""호표 줄 판에서 단산 N 을 부르는 줄."""
return next(r for r in unit["줄"] if r.get("번") == no and "단가산출" in r["갈래"])
def _base(row, sec=0):
"""그 줄 풀이 절의 고른 기초 · 입력값."""
part = row["풀이"]["절"][sec]
cand = next(c for c in part["후보"] if c["키"] == part["고름"])
return {"갈래": "기초", "키": cand["키"], "입력값": cand["바인딩"]["입력값"]}
def _specs(code):
m = hb.m01.cm.master()
return {s["이름"] for s in hb.specs_of(m.logic(code))} - set(hb.SKIP)
def test_판정_검산_옮기기(client):
before = master_hash()
assert _post(client).status_code == 200
got = client.get(f"{API}/hopyo-judge/{KEY}").json()
assert got["셈중"] is True and got["블록"][9]["판정"] is None # 뒤 셈 전
assert judge.judged(_mine(), KEY) is None
judge.solve_all(_mine(), KEY) # 올림 뒤 뒤 셈(시험은 바로)
got = client.get(f"{API}/hopyo-judge/{KEY}").json()
assert got["셈중"] is False and len(got["호표"]) == 68 and len(got["블록"]) == 95
assert got["블록"][9]["번"] == 10 and got["블록"][9]["판정"] == "기초로 풂"
assert judge.judged(_mine(), KEY)[10] == {"판정": "기초로 풂", "고를수": 0}
assert got["호표"][0]["판정"] == {"제안 있음": 2, "2차 필요": 4}
two = client.get(f"{API}/hopyo-judge/{KEY}/2").json()
ten = _at(two, 10) # 예시 1 야면석소운반 단산10
assert ten["이름"] and "단가" in ten and ten["풀이"]["판정"] == "기초로 풂"
ex1 = _base(ten)
assert ex1["키"] == "GC001140" and ex1["입력값"]["운반무게"] == 575
blk = client.get(f"{API}/hopyo-judge/{KEY}/block/10").json()
assert blk["부름"] and "몫" in blk["절"][0] and "금액" in blk
check = client.post(
f"{API}/hopyo/{KEY}/check", json={"호표": 2, "차례": ten["차례"], "고른": [ex1]}
).json()
one = check["고른"][0]
assert one["판정"] == "품 같음" and one["입력필요"] == []
assert set(one["입력값"]) == _specs("GC001140")
assert not set(one["입력값"]) & set(hb.SKIP) # 지역 · 자재지역 · 조종원직종 비움
assert int(one["금액"]["계"]) == 94227 # 엑셀 94,227 (원 미만 버림)
assert check["수량"]["우리"] == ten["수량"]["엑셀"]
first = client.get(f"{API}/hopyo-judge/{KEY}/1").json()
rebar, form = _at(first, 47), _at(first, 24) # 예시 2 철근 · 예시 3 거푸집
a, b = _base(rebar, 0), _base(rebar, 1)
b["입력값"] = {**b["입력값"], "구분": "토목"} # 엇갈림 = 사용자 고름
a["몫"] = 0.5
mat = first["줄"][0] # 레미콘 별도계상 — 786-1 후보 = 고정 줄 하나(품목은 프로젝트에서)
assert mat["판정"] == "제안 있음" and len(mat["후보"]) == 1
cement = mat["후보"][0]
assert (cement["갈래"], cement["키"], cement["별도계상"]) == ("고정", None, True)
assert cement["품목"] == "프로젝트에서"
stuck = {"갈래": "기초", "키": "GC000211", "입력값": {"유형": "보통"}}
ex3 = _base(form) # 예시 3 GC000211 보통 · 4회 · 높이감률 · 구멍땜 = 입력 필요
url = f"{API}/hopyo/{KEY}/check"
need = client.post(url, json={"호표": 1, "차례": form["차례"], "고른": [ex3]}).json()
assert (
need["고른"][0]["입력필요"] == ["높이감률", "구멍땜"]
and need["고른"][0]["금액"]["계"] is None
)
ex3["입력값"] |= {"높이감률": 0, "구멍땜": 0}
got3 = client.post(url, json={"호표": 1, "차례": form["차례"], "고른": [ex3]}).json()["고른"][0]
assert (
got3["판정"] == "품 같음" and abs(got3["금액"]["노무비"] - 38291) <= 1
) # 엑셀 노무 38,291
other = next(r for r in two["줄"] if r["차례"] > ten["차례"]) # 같은 기초 둘째 줄
twice = {**ex1, "입력값": {**ex1["입력값"], "운반거리": 12}}
body = {
"층": "personal",
"호표": {
"1": {
str(rebar["차례"]): [a, b],
str(form["차례"]): [stuck],
"0": [cement],
},
"2": {str(ten["차례"]): [ex1], str(other["차례"]): [twice]},
},
"블록": {"10": [{**ex1, "몫": 1}]},
}
res = client.post(f"{API}/hopyo/{KEY}/move", json=body)
assert res.status_code == 200, res.text
out = res.json()
assert [x["번"] for x in out["만듦"]] == [1, 2] and out["장부"]["씀"] >= 4
assert out["막힘"] == [
{
"호표": 1,
"차례": form["차례"],
"까닭": out["막힘"][0]["까닭"],
"칸": out["막힘"][0]["칸"],
}
]
assert out["막힘"][0]["까닭"].startswith("입력 필요") and out["막힘"][0]["칸"]
sc2 = _doc("precombo", out["만듦"][1]["키"])
lines = sc2["담은로직"]
mine = next(x for x in lines if x.get("로직") == "GC001140")
assert mine["별칭"] == ten["이름"][: len(mine["별칭"])]
assert mine["본뜬"] == {"호표": 2, "차례": ten["차례"], "행": ten["행"]}
assert mine["수량"]["손"] == ten["수량"]["엑셀"]
assert set(mine["입력값"]) == _specs("GC001140")
wrap = next(x for x in lines if x.get("별칭") == other["이름"][: len(x.get("별칭") or "")])
inner = wrap["참조"]["몸"]["담은로직"][
0
] # 같은 기초 둘째 = 한 줄 SC 사본(본뜬 = 사본 출처 740-1)
assert inner["로직"] == "GC001140" and inner["입력값"]["운반거리"] == 12
assert len(lines) == len(two["줄"]) # 확인 안 한 줄 = 고정(빠짐 0)
sc1 = _doc("precombo", out["만듦"][0]["키"])
pair = [x for x in sc1["담은로직"] if (x.get("본뜬") or {}).get("차례") == rebar["차례"]]
assert [x["로직"] for x in pair] == ["GC000206", "GC000207"]
assert pair[0]["별칭"] == pair[1]["별칭"] # 한 엑셀 줄 = 우리 두 줄 · 같은 별칭
excel = rebar["수량"]["엑셀"]
assert pair[0]["수량"]["손"] == excel * 0.5 and pair[1]["수량"]["손"] == excel
assert pair[1]["입력값"]["구분"] == "토목"
held = next(
x
for x in sc1["담은로직"]
if (x.get("고정") or {}).get("출처", {}).get("줄") == form["차례"]
)
assert "본뜬" not in held # 막힌 줄 = 고정 줄(엑셀 숫자)
held_apart = next(
x for x in sc1["담은로직"] if (x.get("본뜬") or {}).get("차례") == mat["차례"]
)
fx = held_apart["고정"] # 별도계상 줄 = 합 밖 고정 줄(단가 0 · 자재 목록에 안 넣음)
assert fx["이름"] == mat["이름"] and fx["규격"] == "별도계상" and not any(fx["단가"].values())
assert held_apart["수량"]["손"] == mat["수량"]["엑셀"]
assert held_apart["메모"].startswith("별도계상 · 품목은 프로젝트에서")
without = {**sc1, "담은로직": [x for x in sc1["담은로직"] if x is not held_apart]}
got_sum, base_sum = labor.server(sc1)["계산값"], labor.server(without)["계산값"]
assert got_sum["반영합"] == base_sum["반영합"] # 서버 정본 셈 — 별도계상 줄은 합에 안 듦
sk = _doc("sangeun", out["단가산출"][0]["키"])
assert sk["바탕"] is None and [x["로직"] for x in sk["더한줄"]] == ["GC001140"]
assert sk["더한줄"][0]["입력값"]["운반무게"] == 575
book = ledger.read(_mine())
assert any(
p.get("갈래") == "기초" and p.get("키") == "GC000207"
for row in book.values()
for p in row.get("짝") or []
)
scs = sorted(p.stem for p in (_mine() / "precombo").glob("SC-*.json"))
stop = client.post(f"{API}/hopyo/{KEY}/move", json=body) # 이미 옮긴 것 = 덮음 없이 409
assert stop.status_code == 409 and [x["번"] for x in stop.json()["detail"]["호표"]] == [1, 2]
assert sorted(p.stem for p in (_mine() / "precombo").glob("SC-*.json")) == scs
again = client.post(f"{API}/hopyo/{KEY}/move", json={**body, "덮음": True})
assert again.status_code == 200, again.text
again = again.json()
assert [x["키"] for x in again["만듦"]] == [x["키"] for x in out["만듦"]]
assert sorted(p.stem for p in (_mine() / "precombo").glob("SC-*.json")) == scs
assert hbuild.confirmed(_mine())[hbuild.mark(KEY, 2)] == out["만듦"][1]["키"]
assert client.delete(f"{API}/hopyo/{KEY}").status_code == 200 # [끝내기] = 저장본도 지움
assert not list((_mine() / "실무자료" / "_derived").glob(f"*{KEY}*"))
assert master_hash() == before
def test_막는_길(client):
assert _post(client).status_code == 200
pick = {"갈래": "기초", "키": "GC001140", "입력값": {}, "금액": 1}
bad = client.post(
f"{API}/hopyo/{KEY}/move", json={"층": "personal", "호표": {"2": {"0": [pick]}}}
)
assert bad.status_code == 422 # 금액 · 품 칸 = 서버가 셈
none = client.post(f"{API}/hopyo/{KEY}/move", json={"층": "personal", "호표": {"99": {}}})
assert none.status_code == 404
wrong = {"갈래": "인력", "키": "LB999999"}
res = client.post(
f"{API}/hopyo/{KEY}/move", json={"층": "personal", "호표": {"2": {"0": [wrong]}}}
)
assert res.status_code == 400