Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1

This commit is contained in:
2026-10-16 21:29:22 +09:00
11 changed files with 504 additions and 51 deletions
+23
View File
@@ -123,6 +123,29 @@ def test_로직_여럿_같은_별칭_감쌈_자재_몫_덮음(client):
assert sorted(p.name for p in (mine / "precombo").glob("*.json")) == files
def test_자재_세_칸_로직_자재_두_번_막음(client):
"""358-2 — 별도계상 줄 자재 = 세 칸(별도계상 · 관급) · 로직 자재와 같은 품목 = 400 아니고
사용자 자재에서 빼고 그 로직 `자재표시` 로 + 빠진 줄 까닭."""
assert _post(client).status_code == 200
apart = {"차례": 0, "대상": [{"자재": "MT004258", "배": 1}]} # 레미콘(25-21-120) 별도계상 줄
got = _build(client, 1, [apart])
mine = layers.personal_dir(7, 42)
mats = _doc(mine, got["참조"]["키"])["자재"]
assert [{k: m.get(k) for k in ("코드", "별도계상", "자재구분", "운반비")} for m in mats] == [
{"코드": "MT004258", "별도계상": "별도계상", "자재구분": "사급", "운반비": None}
] # 자재구분 = 고른 자재 이름(-사급) 먼저
twice = [
{"차례": 1, "대상": [{"로직": "GC000207", "배": 1}]}, # 철근 현장조립 = 로직 자재 결속선
{"차례": 0, "대상": [{"자재": "MT000209", "배": 1}]}, # 같은 결속선을 별도계상 줄에
]
got = _build(client, 1, twice)
doc = _doc(mine, got["참조"]["키"])
assert doc["자재"] == []
line = next(x for x in doc["담은로직"] if x.get("로직") == "GC000207")
assert {"별도계상": "별도계상", "자재구분": "관급"} in line["자재표시"].values()
assert any(m["까닭"] == hb.TWICE and m["차례"] == 0 for m in got["빠진줄"])
def test_몸_없음_재검사_400(client):
assert _post(client).status_code == 200
one = {"로직": "GF000101", "배": 1}
+2 -1
View File
@@ -82,7 +82,8 @@ def test_끝까지_확인한_줄만_별칭(client):
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
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
+94 -6
View File
@@ -115,9 +115,11 @@ def test_새_상태_체크_제안(client):
assert remicon["품"]["같음"]["콘크리트공"] and remicon["품"]["엑셀"]["인력"]["콘크리트공"]
plywood = first["합판거푸집"]
assert plywood["상태"] == "맞음" and set(plywood["갈리는칸"]) == {"측압", "폼타이규격"}
mat = first["레미콘(25-21-120)"]
assert mat["상태"] == "후보" and mat["체크"] is False
assert all(c["규격"] == "25-21-120" for c in mat["후보"][:3]) # 규격 같은 자재 먼저
mat = first["레미콘(25-21-120)"] # 358-2 — 「별도계상」 글 = 표시 · 지역 변형 = 첫째 + 꼬리표
assert mat["상태"] == "맞음" and mat["체크"] is True and "별도계상" in mat["꼬리표"]
assert mat["대상"][0]["규격"].endswith("25-21-120") and "레미콘" in mat["대상"][0]["이름"]
assert any(t.startswith("지역 · 관급/사급 못 가림") for t in mat["꼬리표"])
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
@@ -157,9 +159,10 @@ def test_별칭_체크_규칙(client):
assert got["상태"] == "별칭" and got["체크"] is True and got["대상"][0]["출처"] == {}
assert got["별칭"]["층"] == "personal" and not got["별칭"]["근거다름"]
first = _hopyo(client, 1)
other = first["레미콘타설(장비사용)"] # 숫자 판정 맞음 = GC000198 → 체크 안 함
assert other["상태"] == "별칭" and other["체크"] is False
assert "숫자 판정 맞음 = GC000198" in other["꼬리표"]
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["대상"])
@@ -268,3 +271,88 @@ def test_이름후보_뜻겹침_출처_표시(client):
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"
# ── 단계 대조 보임(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"]