Files
Aislo/resources/tester/test_m02_hopyo_match.py
T
eomsangdonandClaude Opus 5.5 6466904997 feat(M02 일위대가): 361-2 전국 자재 줄 — 상세 자재목록 아래 보기 전용 표 · 확인 창 세 칸 = 만들기에 적힐 값
- 새 파일 `M02_MasterTemplete_Labor_National`: 사전조합 `본뜬.전국자재` 를 자재목록 아래 표로(코드 「전국」 · 이름 · 규격 · 수량 · 단위 · 세 칸 글 · 「지역은 프로젝트에서」 · 금액 칸 비움 · 단추 없음) · 없으면 안 그림 · 셈 · 문서 꼴 · 저장 길 그대로
- 상세(`Labor_Detail`) 자재 칸에 부르는 줄 하나 · `Labor_Api` 본뜬 타입에 `전국자재` 칸
- 확인 창 자재 표 세 칸: 서버가 대상마다 `세칸`(`Material.marks` · 만들기와 같은 값)을 실음 → 전국-관급 줄 = 관급(옛 기본값 「사급」)
- 시험: 전국 자재 줄 있는 것 · 없는 것 · 단추 없음 · 글 명사형 · 읽기 길 · 세 칸

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
2026-10-16 23:42:18 +09:00

429 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""일위대가 실무자료 맞춤(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)"] # 358-2 · 361-1 — 「별도계상」 표시 · 지역 변형만 = 전국
assert mat["상태"] == "맞음" and mat["체크"] is True and "별도계상" in mat["꼬리표"]
assert mat["대상"][0] | {"환산": None, "세칸": None} == {
"전국": True,
"이름": "레미콘(전국)-관급",
"규격": "25-21-120",
"단위": "㎥",
"입력값": {},
"못읽음": [],
"출처": {},
"환산": None,
"세칸": None,
"몫": None,
"배": 1,
} # 특정 지역 키 없음
assert "전국 — 지역은 프로젝트에서" in mat["꼬리표"]
assert not any("못 가림" in t for t in mat["꼬리표"])
assert mat["후보"][0]["전국"] and len(mat["후보"]) == 129 # 전국 + 지역 변형 128(접어 고름)
assert all(c["변형"] and c["자재"] for c in mat["후보"][1:])
assert all(c["규격"].endswith("25-21-120") for c in mat["후보"])
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 ≠ 장부 짝 → 판정 먼저(358-3)
assert other["상태"] == "맞음" and other["체크"] is True and "별칭" not in other
assert other["대상"][0]["로직"] == "GC000198" and "장부와 다름" in other["꼬리표"]
assert "GC000199" in {c["로직"] for c 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]["이름"] == "되메우기" # 한쪽 낱말에 듦 = 겹침
# ── 이름 닮은 로직(358-1 · 옛 324 데스크탑 다섯 그대로) ─────────────────────
def test_이름후보_옛_데스크탑_다섯():
fill = {"이름": "구조물되메우기(인력100%)", "규격": "", "단위": "㎥", "갈래": "단가산출"}
got = [c["로직"] for c in hm.name_cands(fill)]
assert got == ["GF000113", "GF000194", "GC000108", "GC000109", "GC000451"] # 되메우기 다섯
dowel = {"이름": "다웰바설치", "규격": "Φ32×1000", "단위": "EA", "갈래": "단가산출"}
assert [c["로직"] for c in hm.name_cands(dowel)][:3] == ["GC000226", "GF000201", "GF000202"]
soil = {"이름": "잔토처리(인력100%)", "규격": "", "단위": "㎥", "갈래": "단가산출"}
assert hm.name_cands(soil) == [] # 뜻 낱말 안 겹치면 후보 아님 그대로
same = {"이름": "되메우기", "규격": "", "단위": "㎥", "갈래": "단가산출"}
assert hm.name_cands(same)[0] == {**hm.name_cands(same)[0], "로직": "GF000113", "점수": 9}
def test_이름후보_뜻겹침_출처_표시(client):
_ready(client)
dig = _hopyo(client, 3)["구조물터파기(인력100%)"]
assert dig["이름후보"] and all({"뜻겹침", "출처같음", "책"} <= set(c) for c in dig["이름후보"])
assert dig["후보"][0]["출처같음"] is True and dig["후보"][0]["책"] == "건설품셈" # 3-2-1
assert [c["로직"] for c in dig["후보"]][0] == "GC000083" # 차례 · 판정 그대로(표시만)
mat = _hopyo(client, 1)["레미콘(25-21-120)"]
assert "이름후보" not in mat # 자재 줄은 로직 이름후보 없음
# ── 자재 · 별도계상 줄(358-2 · `M02_Hopyo_Material`) ─────────────────────────
MATERIAL_ANSWER = { # 북부청 임도 자재 · 별도계상 줄 → 우리 자재(정답 표 · 사람 확인)
("면목", "0.03×0.03×0.0424"): "MT007567", # 면 목 42, 30×30(0.03 m = 30 mm)
("자물쇠", "50m/m"): "MT008011",
("열연강판", "3.0mm"): "MT000884", # 3.0≤t<4.5 kg(같은 단위 먼저)
("원형돌망태(몸통)", "#8,45×45"): "MT006349",
("원형돌망태(뚜껑)", "#8,45×45"): "MT006350",
}
def test_자재_다듬기_규격_수():
from M02_MasterTemplete import M02_Hopyo_Material as hmat
line, apart = hmat.clean({"이름": "조약돌(별도계상)", "규격": "Ø100~200mm내외", "갈래": "자재"})
assert apart and line["이름"] == "조약돌"
assert hmat.clean({"이름": "레미콘(25-21-120)", "규격": "별도계상"})[0]["규격"] == ""
assert hmat.base("레미콘(관급)-강원도(홍천군(내면 제외))") == hmat.base("레미콘(서울)-사급")
assert (
hmat.base("와이어메쉬(용접철망)관급")
== hmat.base("와이어메쉬(용접철망),서울")
== "와이어메쉬"
)
row = {"spec": "", "nums": [42.0, 30.0, 30.0], "ranges": []}
assert hmat.spec_fit({"이름": "면목", "규격": "0.03×0.03×0.0424"}, row) == 1.0
rng = {"spec": "", "nums": [], "ranges": hmat._ranges("2.0≤T<3.0")}
assert hmat.spec_fit({"이름": "열연강판", "규격": "3.0mm"}, rng) == 0.0 # 끝 3.0 은 안 듦
got = hmat.marks(
{"이름": "레미콘", "규격": "별도계상", "갈래": "별도계상", "비고": "운반비 별도"}
)
assert got == {"별도계상": "별도계상", "자재구분": "관급", "운반비": "미포함"}
assert hmat.marks({"이름": "면목", "단가": {"재료비": 360}}, "면 목") == {
"별도계상": "직접계산",
"자재구분": "사급",
}
def test_북부청_자재_별도계상_맞음_늚(client):
_ready(client)
seen, sure = {}, 0
for no in range(1, 69):
res = client.get(f"{URL.format('personal')}/HP-000001/{no}")
if res.status_code != 200:
continue
for one in res.json()["호표"]["줄"]:
key = (one["이름"], one["규격"])
if one["갈래"] in ("자재", "별도계상") and key not in seen:
seen[key] = one["맞춤"]
sure = sum(m["상태"] == "맞음" for m in seen.values())
assert len(seen) == 30 and sure >= 8 # 옛 2 → 8(면목 · 레미콘 둘 · 와이어메쉬 · 열연강판 …)
for key, code in MATERIAL_ANSWER.items():
assert seen[key]["상태"] == "맞음" and seen[key]["대상"][0]["자재"] == code, key
pipe = seen[("탄소강관(백)", "D=125mm(외경139.8mm)")] # 이름 달라도 규격 수로 후보
assert pipe["상태"] == "후보" and "139.8" in pipe["후보"][0]["규격"]
mortar = seen[("드라이모르터", "조적용레미탈")] # 이름 오타 · 규격 같음 = 1위 후보
assert mortar["후보"][0]["자재"] == "MT004251"
# ── 지역만 다른 자재 = 전국(361-1) ─────────────────────────────────────
REMICON = {"이름": "레미콘(25-21-120)", "규격": "별도계상", "단위": "㎥", "갈래": "별도계상"}
H_BEAM = {"이름": "H 형강", "규격": "SS275, 100*100*6*8mm", "단위": "Ton", "갈래": "자재"}
def test_전국_지역_줄글_전국품목():
from M02_MasterTemplete import M02_Hopyo_Material as hmat
rows, tags = hmat.sure(hmat.clean(REMICON)[0]) # 지역 낱말 없음 · (전국) 품목 없음
assert rows[0]["one"] == {
"전국": True,
"이름": "레미콘(전국)-관급",
"규격": "25-21-120",
"단위": "㎥",
}
assert tags == [hmat.NATIONAL_TAG] and all("자재" in r["one"] for r in rows[1:])
paid = {**REMICON, "규격": "", "갈래": "자재", "단가": {"재료비": 90000}}
assert hmat.national(paid)["이름"] == "레미콘(전국)-사급" # 재료비 줄 = 사급 꼬리
named = {**REMICON, "비고": "강원도 강릉시"} # 줄 글에 지역 = 그 지역(원문)
rows, tags = hmat.sure(hmat.clean(named)[0])
assert rows[0]["one"]["이름"] == "레미콘(관급)-강원도(강릉시)" and tags == [hmat.REGION_TAG]
assert hmat.national(named) is None
rows, tags = hmat.sure(hmat.clean(H_BEAM)[0]) # (전국) 품목 있음 = 그것
assert rows[0]["one"]["자재"] == "MT001694" and tags == []
assert hmat.national(H_BEAM) is None
seoul = hmat.sure(hmat.clean({**H_BEAM, "비고": "서울"})[0])[0][0]["one"]
assert seoul["이름"] == "H 형강(서울)"
def test_전국_고르기_지역_되돌림(client):
_ready(client)
no, at = (
1,
next(
x["차례"]
for x in client.get(f"{URL.format('personal')}/HP-000001/1").json()["호표"]["줄"]
if x["이름"] == "레미콘(25-21-120)"
),
)
path = f"{URL.format('personal')}/HP-000001/{no}/guess"
seoul = client.post(path, json={"차례": at, "대상": [{"자재": "MT004258", "배": 1}]})
assert seoul.status_code == 200 and seoul.json()["대상"][0]["자재"] == "MT004258"
back = client.post(path, json={"차례": at, "대상": [{"전국": True, "배": 1}]})
assert back.status_code == 200, back.text
got = back.json()["대상"][0]
assert got["전국"] and "자재" not in got and got["이름"] == "레미콘(전국)-관급"
other = next( # 전국 자리표 없는 줄(로직 줄) = 400
x["차례"]
for x in client.get(f"{URL.format('personal')}/HP-000001/1").json()["호표"]["줄"]
if x["이름"] == "레미콘타설(장비사용)"
)
bad = client.post(path, json={"차례": other, "대상": [{"전국": True, "배": 1}]})
assert bad.status_code == 400
# ── 단계 대조 보임(358-3) — 목록 수 = 확인 창 수 · 연결 두 칸 · 목록 열 때 뒤 셈 ─────────────
def test_목록_수_확인_창_수_한_규칙(client):
_ready(client)
whole = client.get(f"{URL.format('personal')}/HP-000001").json()
need = done = 0
for brief in whole["호표"]:
one = client.get(f"{URL.format('personal')}/HP-000001/{brief['번']}").json()["호표"]
lines = one["줄"]
assert brief["맞춤"] == [sum(x["맞춤"]["체크"] for x in lines), len(lines)], brief["번"]
inside = [x for x in lines if x["맞춤"]["상태"] not in ("%", "노임", "뺌")]
assert (brief["연결할"], brief["연결된"]) == (one["연결할"], one["연결된"])
assert one["연결할"] == len(inside) and one["연결된"] == sum(
x["맞춤"]["체크"] for x in inside
)
need, done = need + one["연결할"], done + one["연결된"]
assert (whole["연결할"], whole["연결된"]) == (need, done) and 0 < done <= need < 415
pct = next(h for h in whole["호표"] if h["번"] == 46) # 잡재료비 % 줄 = 셈 밖
assert pct["연결할"] < pct["맞춤"][1]
def test_목록_열_때_뒤_셈_예약(client, monkeypatch):
assert _post(client).status_code == 200
asked = []
monkeypatch.setattr(fit, "ensure", lambda folder, key: asked.append(key) or {"끝": False})
rows = client.get(URL.format("personal")).json()["줄"]
assert [r["키"] for r in rows] == asked == ["HP-000001"]