Files
Aislo/resources/tester/test_707_7_whole_import.py
T
eomsangdonandClaude Opus 5.5 c93554d331 fix(B09 불러오기): 코드로만 가리킨 내역 줄도 일위대가에 이음 · 끝수 줄 꼬리표 · 요율 없음 총공사비 숨김 (707-7)
- 영월 꼴(STmate 엑셀 내역 줄 = 호표 코드만) = 일위대가표 같은 코드 호표 번 채움 → 서고 사본 21 · 이음 34
- 이은 줄 차가 비목마다 1 원/단위 안 = 「끝수」(STmate 하위 호표 반올림) · 끝 알림 한 줄 「엑셀 합계와 차 n 원 = 하위 호표 반올림 끝수 m 줄」(브레인 정함 = 끝수 줄도 이음)
- 요율 못 읽은 공사(.estx · .STC) = 총공사비 칸 안 보임
- 시험 test_707_7 11(영월 끝수 · 일위대가 시트 없음 까닭)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:21:15 +09:00

209 lines
9.9 KiB
Python

"""707-7 경쟁사 공사 통째 불러오기 — 한 번 [불러오기] = 일위대가 · 단가산출(이 공사 서고 사본) →
내역 줄 → 요율 → 단가표(`Router_Import` apply · `Bill_Hopyo`).
- 실무 xlsx(거창 · 대흥 · 소광 · 영월): 호표 빠짐 0 · 705-3 수(고정 · 하위 · 이음) 그대로 ·
직접비 우리 = 엑셀 · 어긋난 줄 0 · 층 쓰기 0(임시 storage 빈 채) ·
사본 = `precombo@project@-@{표지}-{번}` · 하위 = 참조사본
- 내역 단가 ≠ 일위대가표 단가(소광) = 잇지 않고 내역 단가 그대로 · 목록
- 다시 부르기 = 같은 사본 · 두 번 셈 0 · .estx · .STC = 일위대가 · 요율 까닭 「같은 공사 엑셀에서」
- 내 자료집에 저장(703-2) = 하위 호표도 지금 층에 같이 씀(한 번만)
"""
import json
import re
import sys
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 config import config_system # noqa: E402
K = ROOT / "resources" / "knowledge" / "original" / "실무문서"
FILES = {
"거창": "01.산불거창(거창북상병곡)/01.내역서/00.25년_산불_거창(거창,북상,병곡,산8외)내역+수량.xlsx", # noqa: E501
"대흥": "2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료/2024년 간선임도사업(기번3).xlsx", # noqa: E501
"소광": "25년 산불진화임도(기번8)(울진소광)/2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).xlsx", # noqa: E501
"영월": "05. 동부청 영월/내역서/2024년 임도시설사업2차(산불진화_기번6)-영월.xlsx",
"장수": "02.산불 장수/01.내역서/00.단가변경_26년_산불_무주(장수,장수,덕산,산54-1).estx",
"기번3": "2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료/2024년 간선임도사업(기번3).STC", # noqa: E501
} # fmt: skip
#: (호표, 줄, 고정, 하위, 이음, 사본, 내역 이음, 엑셀 직접비, 끝수 줄 · 차) —
#: 영월 = 내역 줄이 코드로만 가리킴(번 채움) · STmate 하위 호표 반올림 끝수 28 줄
#: (줄당 1 원/단위 · 브레인 정함 = 그래도 이음)
WANT = {
"거창": (14, 68, 56, 12, 11, 8, 8, 384916227, 0, 0),
"대흥": (34, 171, 143, 28, 0, 26, 45, 710359136, 0, 0),
"소광": (25, 117, 98, 19, 0, 0, 0, 173311386, 0, 0),
"영월": (23, 138, 135, 3, 0, 21, 34, 767148700, 28, 2951),
}
URL = "/api/projects/8b98ca75-c572-4002-8468-2adae4323414/estimation/import/bill"
ALL = {"hopyo": True, "rates": True, "prices": True}
@pytest.fixture(scope="module")
def world(tmp_path_factory):
from fastapi import FastAPI
from fastapi.testclient import TestClient
from B09_Estimation import B09_Estimation_Router as b09
from B09_Estimation import B09_Estimation_Router_Import as ri
from common_util.common_util_auth import verify_session
from resources.tester.test_145_2_b09_labor_calc import _project
base = tmp_path_factory.mktemp("p7077")
(base / "storage").mkdir()
roots: dict[str, Path] = {}
async def root_of(pid):
return roots["now"]
with pytest.MonkeyPatch.context() as mp:
mp.setattr(config_system, "STORAGE_BASE_DIR", str(base / "storage"))
mp.setattr(ri, "_project_root", root_of)
mp.setattr(b09, "_project_root", root_of)
app = FastAPI()
app.include_router(b09.router)
session = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False}
app.dependency_overrides[verify_session] = lambda: session
client = TestClient(app)
done: dict[str, dict] = {}
def run(name, body=ALL, fresh=True):
if fresh and name in done: # 같은 파일 처음 불러온 결과 다시 씀(읽기 느림)
roots["now"] = done[name]["root"]
return done[name]
if fresh:
roots["now"] = _project(base / name, [])
path = K / FILES[name]
seen = client.post(URL, files={"file": (path.name, path.read_bytes())})
assert seen.status_code == 200, seen.text
got = client.post(URL + "/apply", json={"file_key": seen.json()["file_key"], **body})
assert got.status_code == 200, got.text
out = {**got.json(), "file_key": seen.json()["file_key"], "root": roots["now"]}
if fresh:
done[name] = out
return out
yield {"run": run, "storage": base / "storage"}
def _libs(root):
folder = root / "B09_Estimation/v1/library"
return sorted(p.name for p in folder.iterdir()) if folder.is_dir() else []
@pytest.mark.parametrize("name", list(WANT))
def test_실무_엑셀_통째_빠짐_0_합계_같음_층_쓰기_0(world, name):
got = world["run"](name)
hopyo, lines, fixed, kids, links, copies, billed, direct, tail, gap = WANT[name]
step = got["단계"]["일위대가"]
assert (step["호표"], step["줄"], step["고정"], step["호표참조"], step["이음"]) == (
hopyo, lines, fixed, kids, links) # fmt: skip
assert step["빠짐"] == 0 and step["실패"] == [] and step["사본"] == copies
assert got["알림"]["이음"] == billed
assert got["합계"]["엑셀직접"] == direct and got["합계"]["우리직접"] == direct - gap
assert [x["까닭"] for x in got["어긋남"]] == ["끝수"] * tail
libs = _libs(got["root"])
assert len(libs) == copies
assert all(re.fullmatch(rf"precombo@project@-@{got['file_key']}-\d+", x) for x in libs)
assert "까닭" not in got["단계"]["요율"] and "까닭" not in got["단계"]["단가표"]
assert not [p for p in world["storage"].rglob("*") if p.is_file()] # 층 쓰기 0
def test_대흥_총공사비_엑셀과_같음_하위는_참조사본(world):
got = world["run"]("대흥")
assert got["합계"]["우리총"] == got["합계"]["엑셀총"] == 1201879000
nested = 0
for lib in _libs(got["root"]):
path = got["root"] / "B09_Estimation/v1/library" / lib / "일위대가.json"
labor = json.loads(path.read_text("utf-8"))
copies = labor["참조사본"]
for doc in copies.values():
for one in doc["담은로직"]:
if "참조" in one:
ref = one["참조"]
assert ref["층"] == "project" and ref["주인"] == got["file_key"]
assert f"참조|project|{got['file_key']}|{ref['키']}" in copies
nested += 1
assert nested > 0
def test_소광_내역_단가_다른_줄은_잇지_않음(world):
got = world["run"]("소광")
other = got["단계"]["일위대가"]["단가다름"]
assert len(other) == 12 and other[0]["내역"] == 46564 and other[0]["일위대가"] == 53070
def test_다시_부르기_같은_사본_두_번_셈_0(world):
first = world["run"]("거창")
again = world["run"]("거창", fresh=False)
assert _libs(again["root"]) == _libs(first["root"])
assert again["합계"]["우리직접"] == first["합계"]["우리직접"] == 384916227
rows = again["user"]["bill"]["rows"]
assert len({r["key"] for r in rows}) == len(rows)
@pytest.mark.parametrize("name", ["장수", "기번3"])
def test_원본_파일_일위대가_요율_까닭(world, name):
got = world["run"](name)
assert "같은 공사 엑셀에서" in got["단계"]["일위대가"]["까닭"]
assert "같은 공사 엑셀에서" in got["단계"]["요율"]["까닭"]
assert "labor" in got["단계"]["단가표"]
assert got["합계"]["우리직접"] == got["합계"]["엑셀직접"]
assert _libs(got["root"]) == []
def test_내_자료집에_저장_하위_호표_같이_한_번(world, monkeypatch):
from B09_Estimation import B09_Estimation_Daega_Personal as personal
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs
from M02_MasterTemplete import M02_MasterTemplete_Store as m02
from M02_MasterTemplete import M02_Template_Layers as layers
got = world["run"]("거창")
root = got["root"]
blocks = daega_inputs(root)["blocks"]
head = f"C:참조|project|{got['file_key']}"
tops = {b["cover"]: b for b in blocks if b["cover"].startswith(head)}
with_kids = [c for c, b in tops.items() if any(ln["kind"] == "daega" for ln in b["lines"])]
assert len(with_kids) >= 2
monkeypatch.setattr(config_system, "STORAGE_BASE_DIR", str(world["storage"]))
folder = layers.personal_dir(7, 42)
folder.mkdir(parents=True, exist_ok=True)
made = [personal.build(root, c, {}, f"거창 {i}")["문서"] for i, c in enumerate(with_kids[:2])]
assert all(personal.SUBS in doc for doc in made)
with m02.at(folder):
keys = [personal.write(doc)["이름"] for doc in made]
saved = {p.stem: json.loads(p.read_text("utf-8")) for p in (folder / "precombo").glob("*.json")}
for key in keys:
for one in saved[key]["담은로직"]:
if "참조" in one:
assert one["참조"]["층"] == "personal" and one["참조"]["키"] in saved
copied = [d["본뜬"]["복사원본"] for d in saved.values() if "복사원본" in (d.get("본뜬") or {})]
assert len(copied) == len({json.dumps(x, sort_keys=True) for x in copied}) # 같은 하위 한 번
for p in (folder / "precombo").glob("*.json"): # 다음 시험에 층 쓰기 안 남김
p.unlink()
def test_일위대가_시트_없음_까닭_나머지_그대로(tmp_path):
import io
import openpyxl
from B09_Estimation import B09_Estimation_Router_Import as ri
from B09_Estimation.B09_Estimation_Bill_Excel import read_bill
path = K / FILES["거창"]
book = openpyxl.load_workbook(path)
del book["일위대가"]
out = io.BytesIO()
book.save(out)
data = out.getvalue()
parsed = read_bill(path.name, data)
links, step, same = ri._own_links(tmp_path, path.name, data, parsed, "abcd1234", None)
assert links == {} and "일위대가 시트 없음" in step["까닭"] and same is parsed