- 새 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>
161 lines
7.8 KiB
Python
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 가져온보관)를 넘김
|