Files
Aislo/resources/tester/test_807_1_import_doc_one.py
T
eomsangdonandClaude Opus 5.5 9c803024e0 feat(807-1): 엑셀 불러오기 = 수식 문서 · 단가산출 · 일위대가 하나씩 개인 층으로 · 원문 패널 기초 찾기 + 「어디서 왔나」 카드
- 임도품셈 판 대응표 CSV 새 자리(STmate/old/30_원자료) — 불러오기 시험 26 실패 → 0
- 가져온 식 SK = 수식 문서(멈춘 wip/802-4b-1 M02_Doc_Import 를 메인 판에 다시 얹음 · 빈 산근 = 엑셀 0 원 줄) —
  줄 쌓기 「수식」 탭 길 열림 · 셈 = 단가기준(엑셀 값 기억 | 연결 값) · 화면 캐시 기록 · 수식 길 저장 `단가기준`
- 1,021 블록 전수(실무 여덟 + 표본 밖 셋) 엑셀 검산 · 엑셀 전후 · 우리 전후 모두 같음 · 셈됨 줄지 않음
- 옮기기 도구 「가져온식」 상태 걷음(가져온 줄 문서로 대조)
- [내 일위대가로] = 호표 하나 + 하위 → 개인 층 SC · 부르는 단산 = 가져온 식 SK(같은 내용 = 있는 것 · 못 이은 줄 = 고정)
- [내 단가산출로] = 단산 블록 판 → 개인 층 가져온 식 SK(개인 층이면 [가져온 식으로 열기] 숨김)
- 원문 패널(M01 로직 · M02 단가산출) 맨 위 「단가산출 기초 찾기」 · 가져온 식 SK 첫 화면 = 「어디서 왔나」 카드
  (프로그램 · 파일 · 블록 · 행 · 가져온 때 · 표지 · 엑셀 글의 품셈 절 글만)
- 수식 탭 [엑셀 단가 그대로 | 우리 단가로](단가산출 화면 · 저장 뒤 페이지 문서 단가기준 맞춤)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:11:09 +09:00

161 lines
7.8 KiB
Python

# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""807-1 엑셀 불러오기 = 바뀐 구조(요구 699) — 임시 storage · M01 그대로.
① 임도품셈 판 대응표 CSV 새 자리(폴더 재편)
② 단가산출 하나 = 개인 층 가져온 식 SK → 수식 길(줄 쌓기 「수식」 탭) 읽힘 · 금액 = 엑셀 ·
[우리 단가로] · [엑셀 단가 그대로] = 수식 길 저장 `단가기준`
③ 일위대가 하나 = 개인 층 SC + 부르는 단산 = 가져온 식 SK(중복이면 있는 것) · 금액 = 엑셀
④ 원문 패널 「어디서 왔나」 카드 · 품셈 절 글(TS `originOf`) · 기초 찾기 칸 이음
실무 엑셀 없으면 그 시험만 skip.
"""
from __future__ import annotations
import json
import subprocess
from pathlib import Path
import helper_m02_hopyo_golden as golden
import pytest
from M02_MasterTemplete import M02_Doc_Import as dim
from M02_MasterTemplete import M02_Doc_Sk_Path as path
from M02_MasterTemplete import M02_Hopyo_Build as hb
from M02_MasterTemplete import M02_Hopyo_Clause as clause
from M02_MasterTemplete import M02_Hopyo_Excel as hx
from M02_MasterTemplete import M02_Hopyo_Store as hs
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Sangeun_FromHopyo as fh
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
ROOT = Path(__file__).resolve().parents[2]
def test_road_map_csv_new_place() -> None:
assert clause._ROAD_MAP.is_file() and "STmate/old/30_원자료" in clause._ROAD_MAP.as_posix()
assert clause.road_map()
def _hp(world, tag: str = "북부청임도") -> tuple[Path, str]:
src = golden.FILES.get(tag)
if src is None or not src.exists():
pytest.skip(f"실무 엑셀 없음 {tag}")
folder, data = world["personal"], src.read_bytes()
with store.at(folder):
parsed = hx.read_book(src.name, data)
row = hs.save(folder, src.name, parsed, {"이름": "sub3임시"}, None, hs.fingerprint(data))
fh.keep(folder, row["키"], src.name, data)
return folder, row["키"]
def _sk(key: str) -> dict:
return store.read("sangeun", key)["문서"]
# ── ② 단가산출 하나 ─────────────────────────────────────────────────────
def test_one_sangeun_formula_route_and_basis(world) -> None:
folder, hp = _hp(world)
with store.at(folder):
made = fh.make(folder, hp, 5) # 단산5 콘테이너운반 — 기계 시간당 이음
key = made["이름"]
doc = _sk(key)
assert dim.is_imported(doc) and doc["가져온"]["원본"]["가져온때"]
read = path.read(key) # 줄 쌓기 「수식」 탭 길(옛 400 「802-4b 몫」 없어짐)
assert read["사본"]["단가기준"] == "엑셀"
assert float(read["셈"]["계"]) == pytest.approx(float(made["검산"]["엑셀"]["계"]), abs=1)
assert read["연결"] is not None
ours = path.save(key, {"단가기준": "우리", "판": read["판"]})
assert ours["사본"]["단가기준"] == "우리" and ours["셈"]["셈됨"] is True
back = path.save(key, {"단가기준": "엑셀", "판": ours["판"]})
assert back["셈"]["계"] == read["셈"]["계"]
with pytest.raises(store.StoreError):
path.save(key, {"단가기준": "남의", "판": back["판"]})
# ── ③ 일위대가 하나 ─────────────────────────────────────────────────────
def test_one_hopyo_to_personal_sc_and_sks(world) -> None:
folder, hp = _hp(world)
with store.at(folder):
got = hb.build_all(folder, folder, hp, None, True, 3) # 제3호표 임도표지석 — 단산 셋
assert [x["번"] for x in got["만듦"]] == [3] and got["같음"] == 1 and not got["실패"]
sks = got["단가산출"]
assert len(sks) == 3 and all(dim.is_imported(_sk(k)) for k in sks)
sc = store.read("precombo", got["만듦"][0]["키"])["문서"]
refs = {(x.get("참조") or {}).get("키") for x in sc["담은로직"]}
assert set(sks) <= refs # SC 줄 = 그 SK 참조
again = hb.build_all(folder, folder, hp, None, True, 3) # 다시 = 있는 것(그대로 · 같은 SK)
assert again["그대로"] == 1 and sorted(again["단가산출"]) == sorted(sks)
assert len(list((folder / "sangeun").glob("SK-*.json"))) == 3
with pytest.raises(store.StoreError):
hb.build_all(folder, folder, hp, None, True, 9999)
# ── ④ 원문 패널 ─────────────────────────────────────────────────────────
_NODE = """
const fs = require("fs"), path = require("path");
const ts = require(path.join(process.cwd(), "config", "node_modules", "typescript"));
const at = path.join(process.cwd(), "M02_MasterTemplete", "logic", "M02_Sangeun_Origin.ts");
const js = ts.transpileModule(fs.readFileSync(at, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
new Function("exports", "module", "require", js)(box.exports, box, () => ({}));
const got = JSON.parse(fs.readFileSync(0, "utf8"));
process.stdout.write(JSON.stringify(got.map((m) => box.exports.originOf(m))));
"""
def _origin(metas: list) -> list:
out = subprocess.run(
["node", "-e", _NODE],
input=json.dumps(metas, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
cwd=ROOT,
check=True,
)
return json.loads(out.stdout)
def test_origin_card_and_clauses(world) -> None:
folder, hp = _hp(world)
with store.at(folder):
meta = _sk(fh.make(folder, hp, 5)["이름"])["가져온"]
stm = {
"로직": {},
"원본": {
"갈래": "STmate",
"코드": "D02200",
"글줄": [
{"행": 3, "글": "4. 기초지정[2025년 표준품셈3-4-8]"},
{"행": 4, "글": "임도품셈 5-1-2 준용"},
{"행": 5, "글": "임도품셈 5-1-2 다시"},
],
},
}
est, st, plain = _origin([json.loads(json.dumps(meta, default=str)), stm, {"이름": "우리"}])
assert est["프로그램"] == "EST" and est["블록"] == "단산5" and est["파일"].endswith(".xls")
assert est["행"].startswith("단가산출 ") and est["때"] and est["표지"]
books = [(c["책"], c["절"]) for c in est["품셈"]]
assert ("산림표준품셈", "10-4") in books and ("건설표준품셈", "8-1-3") in books
assert st["블록"] == "D02200"
assert [(c["책"], c["절"], c["행"]) for c in st["품셈"]] == [
("표준품셈", "3-4-8", 3),
("임도품셈", "5-1-2", 4),
]
assert plain is None # 우리 SK = 카드 없음(원문 먼저)
def test_source_find_on_m01_and_m02_panels() -> None:
m02 = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Sangeun_Side.ts").read_text("utf-8")
m01 = (ROOT / "M01_MasterData/M01_MasterData_UI_LogicLab_Side.ts").read_text("utf-8")
assert "createSourceFind" in m02 and "createSourceFind" in m01
assert "originOf" in m02
sheet = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Hopyo_Mine.ts").read_text("utf-8")
block = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Hopyo_Block.ts").read_text("utf-8")
assert "/hopyo-build-all" in sheet and "번: h.번" in sheet
assert 'target: { layer: "personal", owner: null }' in block and "openImported" in block
detail = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts").read_text("utf-8")
assert "doc.가져온);" in detail # 원문 패널에 가져온 머리를 넘김