Files
Aislo/resources/tester/test_779_4_sheet_map.py
eomsangdonandClaude Opus 5.5 c4c541838e feat(779-4 · 779-6): 엑셀 보기 창 세 보기 · 연결 칠 · 범례 · 뛰기 · 뒤로 · 열린 창 다시 씀
- 탭 셋 일위대가표 · 단가산출표 · 단가산출 상세 = 서버 역할 id(이름으로 안 찾음 · STmate 단가산출근거 뛰기 풀림) · 두 형식 같은 탭 글 · 원본 시트 이름은 막대
- 새 logic/M02_MasterTemplete_Hopyo_SheetMap.ts(순수 — 줄 칠 · 블록 칠 · 머리 행 = 가장 나쁜 칠 · 뛸 곳) · 「이어짐」 = 확인 창 체크 규칙(캐시가 이김)
- 새 M02_MasterTemplete_Hopyo_SheetNav.ts — 범례 createLegend · 맥락 단추(상세 · 목록 · 하위 호표 · 부르는 줄 차례로) · 뒤로 · 두 번 누름 = 첫 단추 · 다시 칠(목록 다시 읽음 · 캐시 고침 · 만들기) · 칠은 A 열 ~ 끝 열 · 띠는 A 열
- Hopyo_Sheet.ts = 잇는 줄만(580 → 609) · 줄 행 누름 = 그 줄 그대로 · 머리 행 = 호표 영역
- 779-6 — 같은 파일 창은 새 모달 대신 그 창을 앞으로 꺼내 그 자리로 · 닫기 = 제자리로 내림 · 마지막 격자 다시 씀(확인 · 고르기 창 [엑셀 보기] 모두 openSheet 한 곳으로 지나감)
- Hopyo_Check.ts saveDraft = 다시 칠 알림 한 줄(663 → 662) · Hopyo_Api.ts at 내보냄(676 그대로)
- 서버 Match.view — 목록 「후보」 중 이름 후보 안 찾은 줄만 다시 셈(뒤 셈 끝 뒤) → 확인 창 「없음」과 같음(빨강) · 북부청 임도 목록 응답 2.81 → 3.49 초(다시 셈 48 줄 · 없음 11)
- 시험: 새 test_779_4_sheet_map(꼴 표 · 블록 칠 · 실물 EST · STmate 칠 행 수 · 뛸 곳) · test_m02_hopyo_screen 779 지킴 더함
- 낡은 시험 고침: test_779_2 기대 「후보 ≠ 없음 허용」 → 「모두 같음」(까닭 = 이번 덧 셈) · 도우미 helper_m02_hopyo_check · helper_729_3_hopyo_left 에 document 대역(까닭 = saveDraft 알림이 document 를 씀)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:54:23 +09:00

173 lines
8.3 KiB
Python

"""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["원본"]["단가산출상세"] == "단가산출근거"