"""797-1a 지킴 — 엑셀 불러오기 빠진 정보 표지(요구 658) · 812-1 뒤 남은 것. ③ 당초 / 적용 칸(`M02_San_Book.applied_how` → 엑셀 읽기기 블록 표지 → 원본 표지) ④ 조정 비율 · 낙찰률 미지원(`M02_San_Emit.stmate`) ⑤ 읽기 불가 사유(`M02_Sangeun_FromHopyo.keep`) — 작은 견본(엑셀 없이). 옛 EST 글 추정 표지(① 금액 줄 못 읽음 · ② 단가 후보 여럿) · 줄 단위 합격 · 표본 밖 수 · 버림1 감시는 옛 엔진과 함께 걷음 — 812-1 `test_812_1_xlsx_golden`(실제 짝 · 합성 · 실무 11 금액)이 지킴. `FILES`(실무 11) = 812-1 시험이 씀. """ from __future__ import annotations from pathlib import Path import helper_m02_hopyo_golden as golden from M02_MasterTemplete import M02_Doc_FromNative as fn from M02_MasterTemplete import M02_San_Book as book from M02_MasterTemplete import M02_San_Emit as em from M02_MasterTemplete import M02_San_Stmate as stm from M02_MasterTemplete import M02_San_Xlsx as sx from M02_MasterTemplete import M02_Sangeun_FromHopyo as fh NOTE = em.NOTE DOCS = golden.DOCS def notes(out: dict) -> list[str]: return [m for m in out["원본"]["표지"] if NOTE in m] def hard(out: dict) -> list[str]: """못 옮김 표지(참고 표지 뺌).""" return [m for m in out["원본"]["표지"] if NOTE not in m] # ── ③ 당초 / 적용 칸 ─────────────────────────────────────────────────── def _row(applied: tuple, first: tuple) -> list: r = [None] * 29 for j, v in zip((5, 6, 7, 8), applied, strict=True): r[j] = v for j, v in zip((25, 26, 27, 28), first, strict=True): r[j] = v return r def test_applied_or_first_column_is_flagged() -> None: same = book.applied_how(_row((100, 40, 30, 30), (100, 40, 30, 30))) assert same == ({"T": 100, "L": 40, "M": 30, "S": 30}, None) diff = book.applied_how(_row((100, 40, 30, 30), (90, 40, 30, 20))) assert diff[0]["T"] == 100 assert diff[1] == "적용 칸 사용 — 당초와 다름 (계 적용 100 · 당초 90 · 경비 적용 30 · 당초 20)" empty = book.applied_how(_row((None, None, None, None), (90, 40, 30, 20))) assert empty[0] == {"T": 90, "L": 40, "M": 30, "S": 20} assert empty[1] == "당초 칸 사용 — 적용 칸 비어 있음" sums = book.applied_how(_row((100, 40, 30, 10), (90, 40, 30, 20))) assert sums[0]["T"] == 90 assert ( sums[1] == "당초 칸 사용 — 적용 칸 합이 안 맞음 (계 적용 100 · 당초 90 · 경비 적용 10 · 당초 20)" ) nothing = book.applied_how(_row((None,) * 4, (0, 0, 0, 0))) assert nothing[1] is None # 쓸 값이 없는 줄 assert book.applied(_row((100, 40, 30, 30), (90, 40, 30, 20)))["T"] == 100 def test_first_column_note_reaches_block_flags() -> None: head = [[None] * 29 for _ in range(3)] total = [*head, [None, "1", "견본", "", "m"] + [None] * 24] total[2][1:5] = ["No", "품명", "규격", "단위"] total[3][25:29] = [90, 40, 30, 20] sheet = [[None, "단가산출"], [], [None, "산출근거", "합계", "노무비", "재료비", "경비"]] sheet += [ [None, "1.견본[m]", 90, "40", "30", "20"], [None, " 계 : 90", 90, "40", "30", "20"], ] got = sx.read_sheets({"단가산출": sheet, "단가산출총괄표": total}) out = fn.emit(fn.pack(got, "견본.xls"), 1) assert f"{NOTE}당초 칸 사용 — 적용 칸 비어 있음" in out["원본"]["표지"] assert out["원본"]["엑셀"] is True # ── ④ 조정 비율 · 낙찰률 미지원 ──────────────────────────────────────── def _stmate_out(meta: dict) -> dict: prices = { "L00007": {"T": 100, "L": 100, "M": 0, "S": 0, "meta": {}, "이름": "보통인부"}, "D00001": {"T": 0, "L": 0, "M": 0, "S": 0, "meta": meta, "단위": "식"}, } rows = [{"행": 0, "G": "'보통인부' &L00007& * 1 :=A01"}] block = {"코드": "D00001", "이름": "견본", "꼬리": [], "몸": rows} block["머리"] = {"A": "", "B": "", "C": 0, "D": 0, "E": 0, "F": 0} return em.stmate(block, stm.parse_block(rows), prices, "비목버림") def test_adjust_ratio_and_bid_rate_are_flagged() -> None: base = {"ADJ": "T", "SBM": " ", "ADJL": "1", "ADJM": "1", "ADJS": "1", "UC": "F", "NAG": "0"} assert _stmate_out(base)["원본"]["표지"] == [] # 비율 1 · 낙찰률 없음 = 표지 없음 odd = _stmate_out({**base, "ADJL": "0.9"})["원본"]["표지"] assert odd == [f"{NOTE}조정 비율 미지원 · 사람 확인 (노무비 0.9)"] sbm = _stmate_out({**base, "SBM": "S", "ADJL": "0.9"}) assert sbm["로직"]["조정"] == {"옮김": "경비"} and sbm["원본"]["표지"] == [] bid = _stmate_out({**base, "UC": "F", "NAG": "88.5"})["원본"]["표지"] assert bid == [f"{NOTE}낙찰률 88.5 있으나 계약단가 아님(UC) — 낙찰률 미적용 · 사람 확인"] used = _stmate_out({**base, "UC": "T", "NAG": "88.5"}) assert used["원본"]["표지"] == [] and used["원본"]["계약"]["낙찰률"] == 88.5 # ── ⑤ 읽기 불가 사유 ─────────────────────────────────────────────────── def test_unreadable_reason_is_returned(tmp_path: Path) -> None: def keep(name: str, data: bytes) -> str | None: return fh.keep(tmp_path, "HP990001", name, data) assert keep("a.xlsx", b"") == "빈 파일" assert keep("a.xlsx", b"\x9b DRMONE\x00\x01") == "DRM 암호 파일 — 암호 풀린 사본을 올림" assert (keep("a.xlsx", b"PK\x03\x04 not a zip") or "").startswith("엑셀 형식 오류") assert (keep("a.xls", b"plain text") or "").startswith("엑셀 형식 오류") assert not fh.has(tmp_path, "HP990001") # 못 읽은 파일은 안 둠 # ── 실무 엑셀 11(812-1 시험이 씀) ────────────────────────────────────── def _oos() -> dict[str, Path]: pack = DOCS / "2026_실무일위대가 모음" common = DOCS / "2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료" found = { "2016사방": next(pack.glob("2016*.xls"), None), "2026수목": next(pack.glob("2026_상반*.xls"), None), "대흥공통": next((p for p in common.glob("*.xlsx") if not p.name.startswith("~$")), None), } return {k: v for k, v in found.items() if v} FILES = {**golden.FILES, **_oos()}