Files

352 lines
14 KiB
Python
Raw Permalink 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.
# -*- coding: utf-8 -*-
"""642-6(PLAN 785-6) 판정 화면 지킴 — 줄 판 · 블록 판 · 고름 캐시(설계 642-A 5-3 다섯째 줄).
- 부품 상자만(날 `<button>` · `<input>` 0 · 색은 공통 값) · 700 줄.
- 고름 캐시 순수 셈(`logic/Hopyo_Picks` · cjs 도우미) — 북부청 실제 줄 판(785-3 `Row.rows`) +
블록 풀이(785-2 `Solve.block`)를 그대로 넣어 봄: 제안 = 미리 체크만(빈 캐시 = 옮길 것 0) ·
[호표 제안 모두 확인] = 다 풀린 제안만 · 예시 1(단산10 GC001140 입력 필요 0) · 예시 2(단산47 기초 둘 ·
구분 엇갈림 → 토목) · 예시 3(단산24 GC000211 높이감률 · 구멍땜 입력 필요 → 0) · 옮기기 몸 = 확인한 것만 ·
막는 칸 남은 줄 막음 · `필요` 걷음 · 배 얹음.
읽기만(엑셀 · M01) · storage 안 씀.
"""
import json
import re
import subprocess
from pathlib import Path
import pytest
from M02_MasterTemplete import M02_Hopyo_Bind as hb
from M02_MasterTemplete import M02_Hopyo_Clause as hc
from M02_MasterTemplete import M02_Hopyo_Excel as hx
from M02_MasterTemplete import M02_Hopyo_Row as hr
from M02_MasterTemplete import M02_Hopyo_Solve as hs
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
M02 = ROOT / "M02_MasterTemplete"
NORTH = (
ROOT
/ "resources/knowledge/original/실무문서/2026_실무일위대가 모음"
/ "2026년_북부지방산림청_임도사업_설계내역서(공통단가).xls"
)
NEW = [
M02 / "M02_MasterTemplete_Hopyo_Rows.ts",
M02 / "M02_MasterTemplete_Hopyo_Block.ts",
M02 / "M02_MasterTemplete_Hopyo_JudgeApi.ts",
M02 / "logic/M02_MasterTemplete_Hopyo_Picks.ts",
]
FILE = {"layer": "personal", "owner": None}
def _run(steps: list[dict]) -> list:
proc = subprocess.run(
["node", str(HERE / "helper_642_6_picks.cjs")],
input=json.dumps({"steps": steps}, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=False,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@pytest.fixture(scope="module")
def judged():
"""호표 1 · 2 줄 판 + 단산 줄 풀이(예시 셋) — 785-5 `hopyo-judge/{키}/{번}` 꼴."""
book = hx.read_book(NORTH.name, NORTH.read_bytes())
master = hb.m01.cm.master()
rows = hr.rows(book, None)
blocks = {b["번"]: b for b in book["단산"]}
out = {}
for no in (1, 2):
hopyo = next(h for h in book["호표"] if h["번"] == no)
for row in rows[no]:
if row.get("번") not in (10, 24, 47):
continue
blk = blocks[row["번"]]
cands = [{"키": c["키"], "절": c.get("절")} for c in hc.basis(blk)["후보"]]
line = next(x for x in hopyo["줄"] if (x.get("참조") or {}).get("번") == row["번"])
same = [x for x in hopyo["줄"] if x is not line]
row["풀이"] = hs.block(master, blk, hs.parts(blk, cands), {"줄": line, "같은줄": same})
out[no] = rows[no]
return out
def _row(judged, no, sk):
return next(r for r in judged[no] if r.get("번") == sk)
def test_부품상자만_700줄():
for f in NEW:
text = f.read_text(encoding="utf-8")
assert len(text.splitlines()) <= 700, f.name
assert not re.search(r"""createElement\(\s*["'](button|input)["']""", text), f.name
assert not re.search(r"""\bel\(\s*["'](button|input|select)["']""", text), f.name
assert "<button" not in text and "<input" not in text, f.name
css = (M02 / "M02_MasterTemplete_Hopyo_Judge.css").read_text(encoding="utf-8")
assert not re.search(r"#[0-9a-fA-F]{3,8}\b|rgba?\(", css)
picks = NEW[3].read_text(encoding="utf-8")
assert not re.search(r"sessionStorage|fetch\(|document\.", picks.split("*/", 1)[1])
def test_제안은_미리체크만_자동확인없음(judged):
rows = judged[1]
got = _run(
[
{"fn": "emptyDraft", "args": []},
{"fn": "moveBody", "args": [{"$": 0}, FILE]},
*[{"fn": "rowDefaults", "args": [r]} for r in rows],
]
)
assert got[1]["수"] == 0 and got[1]["body"]["호표"] == {}
defaults = dict(zip([r["차례"] for r in rows], got[2:]))
first = rows[0]
assert first["판정"] == "제안 있음" and defaults[0][0]["키"] == first["후보"][0]["키"]
assert defaults[4][0]["갈래"] == "고정" # 후보 없음 = 고정 금액 제안
assert [p["키"] for p in defaults[5]] == ["GC000206", "GC000207"] # 예시 2 절마다 기초
def test_호표_제안_모두_확인은_다풀린_제안만(judged):
rows = judged[1]
got = _run(
[
{"fn": "emptyDraft", "args": []},
{"fn": "confirmSuggested", "args": [{"$": 0}, 1, rows]},
{"fn": "moveBody", "args": [{"$": 1}, FILE]},
]
)
on = {int(k) for k, v in got[1]["호표"]["1"].items() if v["확인"]}
assert on == {
0
} # 레미콘 별도계상 — 단산24 · 47 = 입력 필요 · 엇갈림 · 46 · 33 = 풀이 없음 · 면목 = 후보 없음
assert got[2]["수"] == 1 and list(got[2]["body"]["호표"]["1"]) == ["0"]
def test_별도계상_줄은_고름없이_확인_옮김(judged):
"""786-1 — 별도계상 줄 = 고정 줄 하나 · 막는 칸 없음 · [확인] 하면 옮기기 몸에 들어감."""
row = judged[1][0] # 레미콘 별도계상
got = _run(
[
{"fn": "rowDefaults", "args": [row]},
{"fn": "suggestReady", "args": [row]},
{"fn": "confirmable", "args": [{"$": 0}]},
{"fn": "blockedOf", "args": [{"$": 0}]},
{"fn": "apartPick", "args": [row["후보"][0]]},
{"fn": "emptyDraft", "args": []},
{"fn": "putLine", "args": [{"$": 5}, 1, 0, {"고른": {"$": 0}, "확인": True}]},
{"fn": "moveBody", "args": [{"$": 6}, FILE]},
]
)
(p,) = got[0]
assert p["갈래"] == "고정" and p["별도계상"] is True and p["품목"] == "프로젝트에서"
assert got[1] is True and got[2] is True and got[3] == [] and got[4] is True
assert got[7]["막힘"] == [] and got[7]["수"] == 1
(moved,) = got[7]["body"]["호표"]["1"]["0"]
assert moved["갈래"] == "고정" and moved["별도계상"] is True and "필요" not in moved
def test_예시1_단산10_입력필요0(judged):
row = _row(judged, 2, 10)
got = _run(
[
{"fn": "rowDefaults", "args": [row]},
{"fn": "suggestReady", "args": [row]},
{"fn": "confirmable", "args": [{"$": 0}]},
]
)
(p,) = got[0]
assert p["키"] == "GC001140" and p["필요"] == []
assert p["입력값"] == {
"운반거리": 6,
"경사각도": 35,
"운반회수": 42,
"운반무게": 575,
"회당운반량": 25,
}
assert {k: v["길"] for k, v in p["출처"].items()}["운반무게"] == "B4"
assert got[1] is True and got[2] is True
money = row["풀이"]["절"][0]["후보"][0]["금액"]["계"]
assert int(money) == 94227
def test_예시2_단산47_기초둘_구분엇갈림(judged):
row = _row(judged, 1, 47)
got = _run(
[
{"fn": "rowDefaults", "args": [row]},
{"fn": "blockedOf", "args": [{"$": 0}]},
]
)
assert [p["키"] for p in got[0]] == ["GC000206", "GC000207"]
assert got[1] == ["구분"]
fixed = _run([{"fn": "setInput", "args": [got[0][1], "구분", "토목", "B5"]}])[0]
assert fixed["입력값"]["구분"] == "토목" and fixed["출처"]["구분"] == {"길": "B5"}
assert _run([{"fn": "confirmable", "args": [[got[0][0], fixed]]}])[0] is True
secs = [s["제목"] for s in row["풀이"]["절"]]
assert len(secs) == 2 # 맡은 절 둘
def test_예시3_단산24_입력필요_0넣으면_풀림(judged):
row = _row(judged, 1, 24)
(p,) = _run([{"fn": "rowDefaults", "args": [row]}])[0]
assert p["키"] == "GC000211" and set(p["필요"]) == {"높이감률", "구멍땜"}
got = _run(
[
{"fn": "missing", "args": [p]},
{"fn": "setInput", "args": [p, "높이감률", "0"]},
{"fn": "setInput", "args": [{"$": 1}, "구멍땜", "0"]},
{"fn": "missing", "args": [{"$": 2}]},
]
)
assert set(got[0]) == {"높이감률", "구멍땜"} and got[3] == []
assert got[2]["입력값"]["높이감률"] == 0 and got[2]["출처"]["구멍땜"] == {"길": "사용자"}
assert got[2]["입력값"]["유형"] == "보통" and got[2]["입력값"]["사용횟수"] == "4회"
def test_옮기기몸_확인한것만_막힘_필요걷음_배(judged):
r24, r47 = _row(judged, 1, 24), _row(judged, 1, 47)
steps = [
{"fn": "emptyDraft", "args": []},
{"fn": "rowDefaults", "args": [r24]},
# 단산24 = 막는 칸 남은 채 확인 → 막힘
{"fn": "putLine", "args": [{"$": 0}, 1, r24["차례"], {"고른": {"$": 1}, "확인": True}]},
{"fn": "rowDefaults", "args": [r47]},
{"fn": "setInput", "args": [{"$": 3}, "구분", "토목", "B5"]},
# 단산47 은 확인 안 함 → 몸에 없음
{"fn": "putLine", "args": [{"$": 2}, 1, r47["차례"], {"고른": [], "확인": False}]},
{"fn": "rowDefaults", "args": [judged[1][0]]},
{"fn": "putLine", "args": [{"$": 5}, 1, 0, {"고른": {"$": 6}, "확인": True, "배": 1.5}]},
{"fn": "moveBody", "args": [{"$": 7}, {"layer": "personal", "owner": "u1"}]},
]
body = _run(steps)[-1]
assert body["수"] == 1
assert body["body"]["층"] == "personal" and body["body"]["주인"] == "u1"
(moved,) = body["body"]["호표"]["1"]["0"]
assert moved["배"] == 1.5 and "필요" not in moved
assert body["막힘"] == [
{"호표": 1, "차례": r24["차례"], "까닭": "입력 필요", "칸": ["높이감률", "구멍땜"]}
]
def test_고름바꾸면_확인풀림_칠(judged):
row = judged[1][0]
got = _run(
[
{"fn": "emptyDraft", "args": []},
{"fn": "rowDefaults", "args": [row]},
{"fn": "putLine", "args": [{"$": 0}, 1, 0, {"고른": {"$": 1}, "확인": True}]},
{"fn": "rowTone", "args": [row, {"고른": {"$": 1}, "확인": True}]},
{"fn": "editLine", "args": [{"$": 2}, 1, row, []]},
{"fn": "rowTone", "args": [judged[1][4], None]},
{"fn": "rowTone", "args": [judged[1][1], None]},
]
)
assert got[3] == "success"
assert got[4]["호표"]["1"]["0"]["확인"] is False
assert got[5] == "danger" and got[6] == "info"
def test_블록칠_새판정(monkeypatch):
"""SheetMap 블록 칠 = 새 판정(제안 칠) · Marks = `Judge.judged` 저장본 › 셈 중이면 옛 판정."""
from M02_MasterTemplete import M02_Hopyo_Marks as mk
from resources.tester.test_779_4_sheet_map import _node
def mark(v, n=None, call=(1,)):
return {
"번": 1,
"판정": v,
"고를수": n,
"부름": list(call),
"SK": None,
"갈래": None,
"못옮김": [],
}
got = _node(
{
"blockTones": [
mark("기초로 풂"),
mark("고를 것", 3),
mark("줄 조합", call=()),
{**mark("고를 것", 2), "SK": "SK-1", "갈래": "기초"},
mark("맞음"),
]
}
)["blocks"]
assert [b["tone"] for b in got] == ["success", "info", "info", "success", "success"]
assert got[1]["글"] == "제안 — 고를 것 3" and got[2]["글"] == "제안 — 줄 조합 · 일위대가 밖"
src = Path(mk.__file__).read_text(
encoding="utf-8"
) # 칠 = 블록 풀이 저장본만(785-7 옛 맞춤셈 걷음)
assert "hjudge.judged(folder, key)" in src and "Hopyo_Fit" not in src
def test_엑셀보기_이음_가운데페이지없음():
"""[수정] = 모달 「확인 — 제N호표」(786-2) · [옮기기] = 오른쪽 판 아래 · 단가산출 목록 = 새 블록 판정."""
assert not (M02 / "M02_MasterTemplete_Hopyo_Wizard.ts").exists()
sheet = (M02 / "M02_MasterTemplete_Hopyo_Sheet.ts").read_text(encoding="utf-8")
assert "openHopyoCheck({" in sheet and "createJudgeMove({" in sheet
right = sheet.split('className: "m02-hopyo-sheet__side"')[1].split("});")[0]
assert "move.root" in right
sk = (M02 / "M02_MasterTemplete_Hopyo_SheetSk.ts").read_text(encoding="utf-8")
assert "createJudgeBlock({" in sk and "sangeunStep" not in sk
def test_장부짝_줄은_고른것_모두(judged):
row = {**_row(judged, 1, 47), "판정": "제안 있음", "장부": {"갈래": "짝"}}
row["후보"] = [
{
"갈래": "기초",
"키": "GC000206",
"이름": "가공",
"입력값": {"Type": "Type-Ⅰ"},
"출처": {},
"몫": None,
},
{
"갈래": "기초",
"키": "GC000207",
"이름": "조립",
"입력값": {"구분": "토목"},
"출처": {},
"몫": None,
},
]
got = _run([{"fn": "rowDefaults", "args": [row]}, {"fn": "suggestReady", "args": [row]}])
assert [p["키"] for p in got[0]] == ["GC000206", "GC000207"] and got[1] is True
def test_줄칠_확인함_만듦_초록():
grid = {
"호표": [{"번": 1, "행": [5, 8]}, {"번": 2, "행": [9, 10]}],
"단산": [],
"보기": {"일위대가표": "s1"},
"줄": [
{"번": 1, "차례": 0, "행": 6},
{"번": 1, "차례": 1, "행": 7},
{"번": 1, "차례": 2, "행": 8},
{"번": 2, "차례": 0, "행": 10},
],
}
briefs = [
{"번": 1, "줄상태": [[0, "후보", False], [1, "없음", False], [2, "%", False]]},
{"번": 2, "줄상태": [[0, "후보", False]], "만든": "SC-1"},
]
got = _run(
[
{"fn": "indexOf", "args": [grid], "mod": "SheetMap"},
{
"fn": "paintOf",
"args": [{"$": 0}, briefs, [], {"$none": 1}, {"$set": [[1, 0], [1, 2]]}],
"mod": "SheetMap",
},
]
)[1]["s1"]
assert got["6"]["tone"] == "success" and "확인함" in got["6"]["글"] # 판정 화면 확인
assert got["7"]["tone"] == "danger" # 확인 안 한 못 이음 그대로
assert got["8"]["tone"] == "muted" # 밖 줄 = 확인해도 흐림
assert got["10"]["tone"] == "success" and "만듦" in got["10"]["글"] # 만든 SC 호표