"""779-4 엑셀 보기 창 칠 · 뛸 곳(`logic/M02_MasterTemplete_Hopyo_SheetMap.ts` · Node) — 줄 칠 꼴 표(「이어짐」 = 확인 창 체크 규칙 · 캐시가 이김 · 「없음」 = 빨강) · 블록 칠(만든 SK › 판정 「예상」) · 실물 두 형식(북부청 임도 EST · 동부청 영월 STmate): 칠 행 수 = 줄 수 · 머리 행 = 가장 나쁜 칠 · 단산 줄 → 단가산출 상세(역할 id · STmate 「단가산출근거」) · 목록 행 · 부르는 줄 차례.""" import json import os import subprocess import warnings import pytest from M02_MasterTemplete import M02_Hopyo_Excel as hx from M02_MasterTemplete import M02_Hopyo_Grid as hg from resources.tester.helper_m02_hopyo_golden import FILES warnings.filterwarnings("ignore", category=UserWarning, module="openpyxl") HERE = os.path.dirname(os.path.abspath(__file__)) STATES = ("맞음", "다른 점", "후보", "없음", "%", "노임", "별칭", "뺌") def _node(payload: dict) -> dict: proc = subprocess.run( ["node", os.path.join(HERE, "helper_779_4_sheet_map.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 _d(체크=None, 대상=None, 뺌=None) -> dict: return {"체크": 체크 or {}, "대상": 대상 or {}, "뺌": 뺌 or {}} def test_줄_칠_꼴표(): rows = [ (("맞음", True, None, 0), ("success", False)), (("별칭", True, None, 0), ("success", False)), (("호표", True, None, 0), ("success", False)), (("다른 점", True, None, 0), ("warning", False)), (("다른 점", False, None, 0), ("info", False)), (("후보", False, None, 0), ("info", False)), (("호표", False, None, 0), ("info", False)), (("없음", False, None, 0), ("danger", False)), # 확인 창 「없음」 = 못 이음 (("노임", False, None, 0), ("danger", False)), (("%", False, None, 0), ("muted", False)), (("뺌", False, None, 0), ("muted", False)), # 캐시가 이김 — 끈 맞음 = 고를 것 · 고른 대상 + 체크 = 이어짐(내가 고름 띠) (("맞음", True, _d({"0": False}), 0), ("info", False)), (("없음", False, _d({"0": True}, {"0": [{"배": 1}]}), 0), ("success", True)), (("다른 점", False, _d({"0": True}, {"0": [{"배": 2}]}), 0), ("success", True)), (("없음", False, _d({"0": True}, {"0": [{"배": None}]}), 0), ("danger", True)), # 배 없음 (("후보", False, _d({"3": True}, {"3": []}), 3), ("info", True)), # 대상 없음 = 못 체크 (("맞음", True, _d(뺌={"0": "로직없음"}), 0), ("muted", True)), (("%", False, _d({"0": True}, {"0": [{"배": 1, "비율": "x"}]}), 0), ("success", True)), (("%", False, _d({"0": True}, {"0": [{"배": 1}]}), 0), ("muted", True)), (("맞음", True, _d({"5": False}), 0), ("success", False)), # 다른 줄 캐시 = 서버 제안 ] got = _node({"tones": [list(a) for a, _ in rows]})["tones"] assert [(x["tone"], x["mine"]) for x in got] == [b for _, b in rows] def test_블록_칠(): marks = [ None, # 안 불림 {"번": 1, "판정": None, "SK": None, "갈래": None, "못옮김": []}, {"번": 2, "판정": "맞음", "SK": None, "갈래": None, "못옮김": []}, {"번": 3, "판정": "다른 점", "SK": None, "갈래": None, "못옮김": []}, {"번": 4, "판정": "후보", "SK": None, "갈래": None, "못옮김": []}, {"번": 5, "판정": "후보", "SK": "SK-000009", "갈래": "기초", "못옮김": []}, {"번": 6, "판정": "맞음", "SK": "SK-000010", "갈래": "SK", "못옮김": []}, {"번": 7, "판정": "맞음", "SK": "SK-000011", "갈래": "SK", "못옮김": [{"이름": "x"}]}, ] got = _node({"blockTones": marks})["blocks"] assert [x["tone"] for x in got] == [ "muted", "muted", "success", "warning", "info", "success", "warning", "danger", ] assert got[2]["글"].startswith("예상") and got[5]["글"].startswith("SK-000009") assert "못옮김 1" in got[7]["글"] @pytest.fixture(scope="module", params=["북부청임도", "동부청영월"]) def real(request): path = FILES[request.param] data = path.read_bytes() parsed = hx.read_book(path.name, data) grid = hg.read(path.name, data, parsed) grid.pop("책") return parsed, grid def test_실물_칠_행_수_머리_행(real): parsed, grid = real briefs, n = [], 0 for h in parsed["호표"]: states = [] for x in h["줄"]: states.append([x["차례"], STATES[n % len(STATES)], n % 3 == 0]) n += 1 briefs.append({"번": h["번"], "줄상태": states}) got = _node({"grid": grid, "briefs": briefs, "blocks": []}) ilwi = grid["보기"]["일위대가표"] paint = {int(k): v for k, v in got["paint"][ilwi].items()} line_rows = {x["행"] for x in grid["줄"]} assert len(line_rows) == n # 줄 행 1:1 assert line_rows <= set(paint) # 칠 칸 행 수 = 줄 상태 수 heads = {h["행"][0] for h in grid["호표"]} - line_rows assert set(paint) == line_rows | heads rank = ["muted", "success", "warning", "info", "danger"] for h in grid["호표"]: mine = [paint[x["행"]] for x in grid["줄"] if x["번"] == h["번"]] if mine and h["행"][0] not in line_rows: assert paint[h["행"][0]] == max(mine, key=rank.index), h["번"] nothing = [x for b in briefs for x in b["줄상태"] if x[1] == "없음" and not x[2]] assert nothing and all( paint[r] == "danger" for b in briefs for at, s, c in b["줄상태"] if s == "없음" and not c for r in [next(x["행"] for x in grid["줄"] if (x["번"], x["차례"]) == (b["번"], at))] ) # 블록 = 재료 없으면 밖(muted) · 상세 블록 행 · 목록 행 모두 칠 detail = {int(k) for k in got["paint"][grid["보기"]["단가산출상세"]]} assert detail == {r for b in grid["단산"] for r in range(b["행"][0], b["행"][1] + 1)} listed = {int(k) for k in got["paint"][grid["보기"]["단가산출표"]]} assert listed == { x["행"] for x in grid["목록"]["단산"] if x["번"] in {b["번"] for b in grid["단산"]} } def test_실물_뛸_곳(real): parsed, grid = real ids = grid["보기"] calls = [x for x in grid["줄"] if x.get("단산")] kids = [x for x in grid["줄"] if x.get("일위")] listed = {x["번"]: x["행"] for x in grid["목록"]["단산"]} block = {b["번"]: b["행"] for b in grid["단산"]} callers: dict[int, list[int]] = {} for x in calls: callers.setdefault(x["단산"]["번"], []).append(x["행"]) many = next(no for no, rows in callers.items() if len(rows) > 1) at = [[ids["일위대가표"], x["행"]] for x in calls + kids] at += [[ids["단가산출표"], listed[many]], [ids["단가산출상세"], block[many][1]]] at += [[ids["단가산출상세"], 1], [ids["일위대가표"], 1]] # 머리 · 블록 밖 = 없음 got = _node({"grid": grid, "at": at})["jumps"] for x, jumps in zip(calls, got): no = x["단산"]["번"] first = jumps[0] assert (first["갈래"], first["번"]) == ("상세", no) assert first["곳"] == [{"시트": ids["단가산출상세"], "행": x["단산"]["행"]}] assert jumps[1] == {"갈래": "목록", "번": no, "곳": [{"시트": ids["단가산출표"], "행": [listed[no]] * 2}]} # fmt: skip assert len(jumps) == (3 if len(callers[no]) > 1 else 2) for x, jumps in zip(kids, got[len(calls) :]): assert jumps == [{"갈래": "호표", "번": x["일위"]["번"], "곳": [{"시트": ids["일위대가표"], "행": x["일위"]["행"]}]}] # fmt: skip from_list, from_detail, none1, none2 = got[-4:] assert [j["갈래"] for j in from_list] == ["상세", "부름"] assert [j["갈래"] for j in from_detail] == ["부름", "목록"] assert [s["행"][0] for s in from_detail[0]["곳"]] == callers[many] # 부르는 줄 차례 assert none1 == [] and none2 == [] if parsed["꼴"] == "STmate": # 역할 id = 「단가산출근거」 시트(이름으로 안 찾음) assert grid["원본"]["단가산출상세"] == "단가산출근거"