Files
Aislo/resources/tester/test_807_1_import_doc_one.py
T
eomsangdonandClaude Opus 5.5 26bf63b726 feat(810-4): 연결 찾기 엔진 — 가져온 식 ↔ 우리 M01 기초 · 확정일 때만 우리 기초 사본으로 바꾸기 · 되돌리기
- 새 M02_Doc_Match(서버 단독) — 후보(결과단위 · 노임 키 · 재료 · 기계 · 품셈 절 번호) · 옵션 풀이(줄 수량 · 단가가 의존하는 선택 · 입력 변수만 돌려 좁힘 · 글 근거 값 유지) · 줄 대조(같음 · 단가 차이 · 단위 환산 · 기계 규격 · 비목 · 율 값 · 율 줄 · 공구손료 유무 · 줄 없음 · 끝수 · 옵션 글) · 판정 확정(다른 항 0 · 원 단위 같음) / 근접 / 없음(가까운 후보 보이기만)
- 새 M02_Doc_MatchSk — 찾기 = SK 가져온.원본.연결후보 적음 · 바꾸기 = 서버가 다시 찾아 확정만 같은 키에 M01 사본 SK(바탕 · 입력값 = 찾은 옵션) · 원래 몸 = 가져온보관 · 되돌리기 · 근접 · 없음 409
- 길 POST /api/m02/layers/{층}/sangeun/{키}/match {할일 찾기 · 바꾸기 · 되돌리기}(층 쓰기)
- M02_Sangeun_Imported clean — 가져온보관 칸 받음 · M02_Doc_Import 형제 SK 가 우리 기초 사본이면 그 수식 문서로 셈(연결 안 끊김)
- 화면 — 원문 패널 「어디서 왔나」 카드 맨 위 판정 띠(띠 알림 부품) · 확정 = [우리 기초 사본으로 바꾸기] · 근접 · 없음 = 단추 없음 + 다른 항 자세히 · 안 찾음 = [우리 기초와 연결 찾기] · 바꾼 SK = [가져온 식으로 되돌리기] · 저장 안 한 고침 있으면 막음 · 끝나면 SK 다시 엶(새 판)
- 시험 test_810_4_match(판정 셋 · 요구 704 파형강관 Φ800 = GF000179 근접 · 단위 환산 × 6 · 기계 규격 · 율 줄 · 옵션 풀이 · 줄 대조 · 바꾸기 = M01 사본 셈 · 되돌리기 · 근접 409 · 형제 사본 · 전수 S8104_ALL) · helper_810_4_sweep
- 낡은 시험 글 대조 고침 — 717_2b(View 다시 엶 mount) · 750_2(createSangeunSide(opt.match)) · 807_1(원문 패널에 SK 문서 넘김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 21:52:41 +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_SheetSk.ts").read_text("utf-8")
assert "/hopyo-build-all" in sheet and "번: h.번" in sheet
assert '{ 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 # 원문 패널에 SK 문서(가져온 머리 · 810-4 가져온보관)를 넘김