diff --git a/M02_MasterTemplete/M02_San_MyMachine.py b/M02_MasterTemplete/M02_San_MyMachine.py index 8814b864d..ea626a4a8 100644 --- a/M02_MasterTemplete/M02_San_MyMachine.py +++ b/M02_MasterTemplete/M02_San_MyMachine.py @@ -148,9 +148,10 @@ def _est_sheet(rows: list[list]) -> dict[str, dict]: def _stm_sheet(rows: list[list]) -> dict[str, dict]: - """STmate 엑셀 「중기사용료」 — 「제 N 호표」 뒤 머리 [이름, 규격, 시간, 코드] · 손료 [이름, 규격, 계수(천원당), - 「천원」, 가격 천원 …] · 조종원 [직종, 인, 「인」 …] · 연료 [이름, 등급, ℓ, 「L」 …] · 잡품 [잡품, 주연료의%, % …] · - 「합 계」 끝 → {코드 · 이름|규격: 구성}(손료계수 = 계수 × 10⁴ · 가격 = 천원 × 1000 원).""" + """STmate 엑셀 「중기사용료」 — 칸 차례 [명칭, 규격, 수량, 단위, 단가 …](첫 글 칸부터 · 빈 규격도 칸 자리 지킴 · + 843-2 — 규격 빈 머리 · 손료 줄을 못 읽던 것) · 「제 N 호표」 뒤 머리(단위 「시간」 · 끝 칸 코드 X…) · + 손료(단위 「천원」 · 수량 = 계수(천원당) · 단가 = 가격 천원) · 조종원(「인」) · 연료(「L」) · 전력(「kWh」) · + 잡품(명칭 잡품 · 수량 = %) · 「합 계」 끝 → {코드 · 이름|규격: 구성}(손료계수 = 계수 × 10⁴ · 가격 = 천원 × 1000 원).""" out: dict[str, dict] = {} cur = None for row in rows: @@ -164,26 +165,24 @@ def _stm_sheet(rows: list[list]) -> dict[str, dict]: if first == "합계": cur = None continue - if ( - not cur - and len(v) >= 4 - and str(v[2]) in ("시간", "hr") - and re.fullmatch(r"X\d+", str(v[-1])) - ): - cur.update(_blank(str(v[0]), str(v[1]))) - out[str(v[-1])] = out[key_of(v[0], v[1])] = cur + at = next(i for i, c in enumerate(row) if c not in (None, "")) + name, spec, qty, unit, price = (*row[at : at + 5], *[None] * 5)[:5] + spec, qty, price, unit = spec or "", _num(qty), _num(price), str(unit or "").strip() + if not cur and unit in ("시간", "hr") and re.fullmatch(r"X\d+", str(v[-1])): + cur.update(_blank(str(name), str(spec))) + out[str(v[-1])] = out[key_of(name, spec)] = cur elif not cur: continue - elif len(v) > 4 and str(v[3]) == "천원" and _num(v[2]) and _num(v[4]): - cur["손료"].append([_num(v[2]) * 10_000, _num(v[4]) * 1000, False]) - elif len(v) > 2 and str(v[2]) == "인" and (_num(v[1]) or 0) > 0: - _crew(cur, v[0], v[1]) - elif first in MISC_WORDS and len(v) > 2: - _misc(cur, v[2]) - elif len(v) > 3 and str(v[3]).upper() in ("L", "ℓ"): - _fuel(cur, v[0], v[2]) - elif len(v) > 3 and str(v[3]).upper() == "KWH": - cur["전력"] = _num(v[2]) + elif unit == "천원" and qty and price: + cur["손료"].append([qty * 10_000, price * 1000, False]) + elif unit == "인" and (qty or 0) > 0: + _crew(cur, name, qty) + elif first in MISC_WORDS and qty is not None: + _misc(cur, qty) + elif unit.upper() in ("L", "ℓ"): + _fuel(cur, name, qty) + elif unit.upper() == "KWH": + cur["전력"] = qty return {k: m for k, m in out.items() if m.get("손료")} diff --git a/resources/tester/test_717_1_logic_lib.py b/resources/tester/test_717_1_logic_lib.py index 6040c645f..706f6d297 100644 --- a/resources/tester/test_717_1_logic_lib.py +++ b/resources/tester/test_717_1_logic_lib.py @@ -15,6 +15,7 @@ from fastapi import Depends, FastAPI from fastapi.testclient import TestClient from common_util.common_util_auth import verify_session +from M01_MasterData import M01_MasterData_Router as m01_router from M01_MasterData import M01_MasterData_Store as store from M02_MasterTemplete import M02_Logic_Lib_Read as lib @@ -78,7 +79,9 @@ def test_요소_표_밖_파일은_막음(user: TestClient) -> None: def test_저장_안_한_줄_셈_쓰기_없음(user: TestClient) -> None: before = _prints() saved = user.post(f"{BASE}/calc", json={"key": KEY, "inputs": {}}).json() - row = store.logic(KEY)["logic"] + row = m01_router._num( + store.logic(KEY)["logic"] + ) # 화면이 받는 꼴(M01 /logic = _num · 794-2b 「개정」 수 칸) same = user.post(f"{BASE}/calc", json={"key": KEY, "inputs": {}, "row": row}).json() line = row["호표"][0] row2 = {**row, "호표": [{**line, "수량": f"({line['수량']}) * 2"}, *row["호표"][1:]]} diff --git a/resources/tester/test_828_1_stc_composition.py b/resources/tester/test_828_1_stc_composition.py index 4065fedce..8c1279cf9 100644 --- a/resources/tester/test_828_1_stc_composition.py +++ b/resources/tester/test_828_1_stc_composition.py @@ -262,7 +262,7 @@ def test_machines_equal_excel() -> None: heads = [rows[i + 1] for i, r in enumerate(rows) if re.fullmatch(r"제\d+호표", _flat(r[0]))] assert all(mm.key_of(h[0], h[1]) in mine for h in heads), xt # 엑셀 읽기기가 못 읽는 머리도 m += len(heads) - assert (n, m) == (100, 118) # 엑셀 읽기기 100(울진대흥 머리 코드 없음 9 만) · 엑셀 호표 118 + assert (n, m) == (118, 118) # 843-2 규격 빈 머리 · 손료 줄도 칸 자리로 읽음 = 엑셀 호표 118 모두 def test_my_machine_seed_and_peers() -> None: diff --git a/resources/tester/test_842_4_import_machine_lines.py b/resources/tester/test_842_4_import_machine_lines.py index 24817f9e0..06e1ca677 100644 --- a/resources/tester/test_842_4_import_machine_lines.py +++ b/resources/tester/test_842_4_import_machine_lines.py @@ -5,6 +5,7 @@ ② 몸 = `master_machine_crew` 꼴 인력 · 잡품 줄 · 못 이음 = 비고 「확인 필요」 — main 구성 전부 버린 줄 0 ③ 층에 만든 내 기계 시간당 노무비 = 줄마다 우리 노임 × 인 × 5/24 의 합(둘째 줄부터도 셈에 들어감) ④ 한 줄 구성은 전과 같은 몸(옛 칸 그대로 · 저장 때 목록으로 바뀜) +⑤ STmate 엑셀 「중기사용료」 규격 빈 칸(머리 · 손료 줄) = 칸 자리로 읽음(843-2) — 울진대흥 호표 25 모두 · .STC 구성과 같음 """ from __future__ import annotations @@ -83,7 +84,10 @@ def test_no_line_dropped() -> None: b = mm.body(m, 0, _line(m["이름"], m["규격"]), "main.estx") if b is None or b["방식"] != "바로": continue - v, notes = b["값"], [x for x in b["비고"].split(" · ") if mm.CHECK in x and x != "값 확인 필요"] + v, notes = ( + b["값"], + [x for x in b["비고"].split(" · ") if mm.CHECK in x and x != "값 확인 필요"], + ) src = sum(len(m.get(k) or []) for k in ("인력줄", "연료줄", "재료줄")) src += len([r for r in m.get("잡품줄") or [] if r]) # 0 % = 셈할 것 없음 crew = len(v["인력"]) if "인력" in v else int(bool(v.get("조종원"))) @@ -120,5 +124,34 @@ def test_single_line_same_as_before() -> None: assert "인력" not in v and v["조종원"] == 1.0 and v["잡재료율"] == 11.0 +# ⑤ STmate 엑셀 빈 규격 ───────────────────────────────────────────── +def test_stmate_excel_blank_spec() -> None: + import test_797_1a_import_flags as practice + + from M02_MasterTemplete import M02_Doc_FromNative as fn + from M02_MasterTemplete import M02_San_Book as book + + p = practice.FILES.get("울진대흥") + stc = next(iter(practice.golden.DOCS.rglob("2024년 간선임도사업(기번3).STC")), None) + if not p or not p.exists() or stc is None: + pytest.skip("실무 견본 없음") + got = mm.xlsx_makes(book.sheets(p.name, p.read_bytes())) + codes = sorted(k for k in got if k.startswith("X")) + assert len(codes) == 25 # 호표 25 모두(전 = 9 · 규격 빈 머리 16 못 읽음) + assert got["X00003"]["손료"] == [[2085.0, 62550000.0, False]] # 「굴삭기(0.2m3)」 규격 빈 머리 + assert [x[1] for x in got["X00014"]["손료"]] == [ + 110926000.0, + 16315000.0, + ] # 둘째 손료 규격 빈 줄 + native = fn.read(stc.name, stc.read_bytes())["기계구성"] + keep = ("손료", "조종원", "조종원직종", "주연료종류", "주연료", "잡재료율") + for k in codes: + a, b = got[k], native[k] + assert [[round(x, 3) for x in r[:2]] for r in a["손료"]] == [ + [round(x, 3) for x in r[:2]] for r in b["손료"] + ], k + assert all(a.get(f) == b.get(f) for f in keep[1:]), k + + if __name__ == "__main__": pytest.main([__file__, "-q"])