- 길: 이름 끝 .estx = `B09_Estimation_Estx` read_bill · read_prices(같은 그릇) · 요율 = 400 「같은 공사 엑셀에서」 - 미리 보기 `호표` = 대가 · 산근 목록 수 + 내역 줄이 코드로 가리킨 수(구성 줄 귀속은 안 함) - 창: 파일 고르기 *.estx · [단가표도 같이](받은 patch 를 캐시에 함께) · 지난 파일 요율 실패 = 까닭 알림 뒤 다시 고름 - 시험 test_707_5 8(길 갈림 · 호표 수 · 깨진 파일 400) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
142 lines
6.2 KiB
Python
142 lines
6.2 KiB
Python
"""707-5 EST 원본(.estx) 바로 읽기 — `B09_Estimation_Estx` (읽기만 · 저장 안 함).
|
|
|
|
- 실무 .estx 셋(거창 · 장수 · 진안 — 암호 없음) = 내역 줄 수 · 직접비 합 = 파일 안 순공사비 줄 ·
|
|
조달수수료 = 율 · 노임 · 자재 · 기계 · 호표 목록 줄 수
|
|
- 거창 = 같은 공사 엑셀(707-1)과 줄마다 같음(이름 · 수량 · 금액 · 제외 · 묶음 나무) ·
|
|
직접비 384,916,227 · 707-1 짜기 그릇에 그대로 부음
|
|
- 장수 · 진안 = 엑셀이 DRM — 대조 대신 줄 수 · 금액 합만 기록
|
|
- 깨진 파일 · 표 없음 = 막힘
|
|
"""
|
|
|
|
import io
|
|
import sys
|
|
import time
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B09_Estimation import B09_Estimation_Bill_Import as imp # noqa: E402
|
|
from B09_Estimation import B09_Estimation_Estx as ex # noqa: E402
|
|
from B09_Estimation import B09_Estimation_Store as store # noqa: E402
|
|
from B09_Estimation.B09_Estimation_Bill_Excel import read_bill as read_excel # noqa: E402
|
|
from B09_Estimation.B09_Estimation_Calc_Bill import compute_estimation # noqa: E402
|
|
|
|
K = ROOT / "resources" / "knowledge" / "original" / "실무문서"
|
|
FILES = {
|
|
"거창": K / "01.산불거창(거창북상병곡)/01.내역서/00.25년_산불_거창(거창,북상,병곡,산8외) _단가변경.estx", # noqa: E501
|
|
"장수": K / "02.산불 장수/01.내역서/00.단가변경_26년_산불_무주(장수,장수,덕산,산54-1).estx",
|
|
"진안": K / "03.산불진안/01.내역서/00.단가변경_26년_산불_무주(진안,백운,백암,산1-2).estx",
|
|
} # fmt: skip
|
|
EXCEL = (
|
|
K / "01.산불거창(거창북상병곡)/01.내역서/00.25년_산불_거창(거창,북상,병곡,산8외)내역+수량.xlsx"
|
|
)
|
|
#: (항목 수, 직접비, 제외, 노임, 자재, 기계, 대가, 산근) — 장수 · 진안 = 기록(엑셀 DRM)
|
|
WANT = {
|
|
"거창": (60, 384916227, 74634214, 17, 52, 22, 14, 61),
|
|
"장수": (58, 496306311, 61483490, 16, 45, 22, 3, 65),
|
|
"진안": (72, 465955223, 56991694, 17, 55, 22, 14, 74),
|
|
}
|
|
EMPTY = {"materials": [], "bill": {"items": []}, "hopyo": {}}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def data():
|
|
return {k: p.read_bytes() for k, p in FILES.items()}
|
|
|
|
|
|
@pytest.mark.parametrize("name", list(FILES))
|
|
def test_실무_estx_줄_수_합계_순공사비(data, name):
|
|
t = time.perf_counter()
|
|
bill = ex.read_bill(FILES[name].name, data[name])
|
|
prices = ex.read_prices(FILES[name].name, data[name])
|
|
hopyo = ex.read_hopyo(FILES[name].name, data[name])
|
|
print(f"{name} 읽기 {time.perf_counter() - t:.3f} s")
|
|
got = (
|
|
sum(1 for x in bill["줄"] if not x["묶음"]),
|
|
bill["합계"]["직접"],
|
|
bill["합계"]["제외"],
|
|
*(len(prices[k]) for k in ("labor", "material", "machine")),
|
|
len(hopyo["대가"]),
|
|
len(hopyo["산근"]),
|
|
)
|
|
assert got == WANT[name]
|
|
assert bill["조달수수료율"] == 0.54 and bill["합계줄"] == 1
|
|
# 직접비 = 파일 안 순공사비 줄(LinkIndex 54) 금액
|
|
net = ex.table(ex._zip(data[name]), "EstMainDat", ("LinkIndex", "TC"))
|
|
assert [r["TC"] for r in net if r["LinkIndex"] == 54] == [bill["합계"]["직접"]]
|
|
# 항목 금액 = 성분 셋 · 호표 연결 = 코드
|
|
for line in bill["줄"]:
|
|
if not line["묶음"]:
|
|
assert line["호표"] and line["호표"]["코드"], line["이름"]
|
|
assert all(r["price"] > 0 for r in prices["labor"])
|
|
assert all(h["합계"] == sum(h["단가"].values()) for h in hopyo["대가"] + hopyo["산근"])
|
|
|
|
|
|
def test_거창_엑셀과_줄마다_같음(data):
|
|
mine = ex.read_bill("a.estx", data["거창"])
|
|
excel = read_excel(EXCEL.name, EXCEL.read_bytes())
|
|
|
|
def key(line):
|
|
return (line["이름"].replace(" ", ""), line["수량"], line["금액"], line["제외"])
|
|
|
|
items = [[key(x) for x in got["줄"] if not x["묶음"]] for got in (mine, excel)]
|
|
assert items[0] == items[1]
|
|
|
|
def tree(got):
|
|
names = {x["행"]: x["이름"] for x in got["줄"]}
|
|
return [(x["이름"], names.get(x["위"])) for x in got["줄"] if x["묶음"]]
|
|
|
|
assert tree(mine) == tree(excel)
|
|
assert mine["합계"] == excel["합계"]
|
|
|
|
|
|
def test_거창_707_1_그릇에_부음(data):
|
|
got = ex.read_bill("a.estx", data["거창"])
|
|
rows = compute_estimation(EMPTY, {}, None, {})["bill"]["rows"]
|
|
made, report = imp.apply(got, {}, rows, "abcd1234")
|
|
made = store.clean_user(made)
|
|
bill = compute_estimation(EMPTY, made, None, {})["bill"]
|
|
assert report["실음"] == len(got["줄"])
|
|
assert bill["totals"]["direct"]["sum"] == 384916227
|
|
assert bill["totals"]["direct"]["blank"] == 0
|
|
assert imp.differences(got, report, bill["rows"]) == []
|
|
assert made["estimate"]["procurement_fee_pct"] == 0.54
|
|
|
|
|
|
def test_자재_출처_칸(data):
|
|
stuff = ex.read_prices("a.estx", data["거창"])["material"]
|
|
wire = next(x for x in stuff if x["name"].startswith("아연도철선"))
|
|
assert wire["price"] == 2480
|
|
assert {"출처": "물가자료", "단가": 2480, "쪽": "27(26.1)"} in wire["sources"]
|
|
|
|
|
|
def test_깨진_파일_표_없음_막힘():
|
|
with pytest.raises(ex.EstxError):
|
|
ex.read_bill("a.estx", b"not a zip")
|
|
with pytest.raises(ex.EstxError):
|
|
ex.read_bill("a.estx", b"PK\x03\x04 broken")
|
|
buf = io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w") as z:
|
|
z.writestr("Other.dat", b"")
|
|
with pytest.raises(ex.EstxError, match="EstMainDat"):
|
|
ex.read_bill("a.estx", buf.getvalue())
|
|
|
|
|
|
def test_불러오기_길_estx_갈림(data):
|
|
"""707-5 고리 — 이름 끝 .estx = 원본 읽기 · 호표 = 목록 수 + 코드로 가리킨 줄(이음 후보)."""
|
|
from B09_Estimation import B09_Estimation_Router_Import as route
|
|
|
|
name = "거창.ESTX"
|
|
parsed = route._parse(name, data["거창"])
|
|
assert parsed["꼴"] == "estx" and parsed["합계"]["직접"] == 384916227
|
|
assert route._hopyo(name, data["거창"], parsed) == {"대가": 14, "산근": 61, "가리킴": 48}
|
|
assert route._hopyo("a.xlsx", data["거창"], parsed) is None
|
|
with pytest.raises(route.HTTPException) as got:
|
|
route._parse("a.estx", b"not a zip")
|
|
assert got.value.status_code == 400
|