Files
Aislo/resources/tester/test_812_3_native_layout.py
T
eomsangdonandClaude Opus 5.5 58a3e039b3 feat(813-2): 보조 문서 부품 표지 73 · 작업량당 기계경비 6 지움 · 단가산출 목록에서 뺌 · 기계 → 부품 길
- 표지 = 수식 문서 머리 · 옛 로직 줄 칸 「부품」 — 기계경비식 9 · 작업량 틀 60(기종 = 기계표 상세구분 · 규격 열쇠) · 계수식 4 · 변환기가 옮김 · 틀 문서 9 장
- 지움 = 작업량당 기계경비 6(GC001145 · 1148 · 1151 · 1156 · 1159 · 1166 · 부르는 곳 0 · 요구 734-4 · 735-4) — 수식 문서 · 옛 로직 · 키대장 폐기 칸 · 빌더 · 변수표
- 이름 꼬리 4 = 원문 제목(지게운반 · 인력운반(기계설비) · 벽돌운반 · 수중펌프 설치 및 해체) · 키 폐기 0
- 서버 = `M01_MasterData_Parts` — M01 `/logics` · M02 `/logic-lib/logics` 목록에서 부품 뺌(1,440) · 키로 열기 · 부르기 그대로 · `/m01/parts`(기종 계산식 · 틀 · 계수식) · `/m02/logic-lib/machine-parts` · 연결 찾기 과녁에서 부품 뺌(1,444 → 1,435 · 판정 코드 그대로)
- 금액 전후 = 문서 1,513 · 셈 9,626(기본 + 옵션 표본) 모두 같음 · 791_1 지문 남은 1,513 같음 → 지문 다시 뜸 · 802_2 변환 대조 1,513 · 목록 xlsx 다시 씀(요약 수)
- 낡은 시험 = 1,519 · 1,518 기대 → 1,513 · GC001148 견본 → 같은 꼴 GF000089 토사깍기(기계)(블록 Q 39.293 · 합계 2,190) · 지운 6 을 셈하던 줄 걷음 · 목록 수 1,440 · 묶음 328 · 부르는 수 507
- 새 지킴 시험 test_813_2_parts

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

224 lines
9.0 KiB
Python

"""812-3 지킴 — 가져온 단가산출(원파일) 줄 쌓기 차림 = 경쟁사 산출근거 꼴(요구 713).
차림 = `M02_Doc_NativeLayout`.
(a) 견본(거창 덤프 꼴) — 1 측구터파기(토사) 「기 계 … 굴삭기」 항목 + 공식 묶음
q · K · f · E · Cm · Q + 보조인부 항목 · 41 합판거푸집(4회) 근거 글 ·
재료비(재료 · 소계 · 율 · 글) · 인건비(인력 · 소계 · 율 · 글) ·
15 전석 메쌓기 별산 · 단산 참조 Q 직접 · 기계 · 인부 ·
2 가 · 나 소항목 · 봉화 STC = 계 단마다 항목
(b) 전수 — 원파일 EST · STC 산근 블록 모두: 차림 전후 셈(비목 · 계 · 줄 금액) 같음 ·
TS 왕복(문서 → 줄 → 문서) 같음 · TS `stackOf` = 파이썬 거울 `stack_of` ·
수 표 = tmp/s8123/sweep.json(일회 기록)
(c) M01 문서 1,518 — 차림 안 바뀜(쌓기 없음 · 왕복 같음 · 거울 같음)
원파일 없으면 그 시험만 skip(다른 PC).
"""
from __future__ import annotations
import json
import subprocess
import sys
from functools import lru_cache
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
for p in (ROOT, HERE):
if str(p) not in sys.path:
sys.path.insert(0, str(p))
import test_810_2_native_doc as t2 # noqa: E402
from test_810_1_estx import FILES as EST # noqa: E402
from test_810_1_stc import FILES as STC # noqa: E402
from M02_MasterTemplete import M02_Doc_Copy as dc # noqa: E402
from M02_MasterTemplete import M02_Doc_Import as dim # noqa: E402
from M02_MasterTemplete import M02_Doc_NativeLayout as lay # noqa: E402
COSTS = ("노무비", "재료비", "경비", "계")
def _ts(cases: list[dict]) -> list[dict]:
proc = subprocess.run(
["node", str(HERE / "helper_812_3_layout.cjs")],
input=json.dumps(dc.ml._norm(cases), ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
)
assert proc.returncode == 0, proc.stderr[-3000:]
return json.loads(proc.stdout)
def _doc(kind: str, tag: str, no: int, laid: bool = True) -> tuple[dict, dict]:
sk = t2.sk_of(t2.fn.emit(t2.native(kind, tag), no))
keep = lay.apply
if not laid:
lay.apply = lambda doc, meta: [x.pop(lay.KEEP, None) for x in doc.get("금액") or []]
try:
return sk, dim.derive(sk, None, t2.whole())
finally:
lay.apply = keep
def _items(doc: dict) -> list[dict]:
return lay.stack_of(doc)
def _show(doc: dict) -> list[tuple]:
"""(제목, 가나다, [줄 글]) — 금액 = 이름 · 공식 = 「=기호…」 · 글 = 「#글」."""
money = doc["금액"]
out = []
for it in _items(doc):
lines = []
for x in it["줄"]:
if x["꼴"] == "금액":
lines.append(money[x["i"]]["이름"])
elif x["꼴"] == "공식":
lines.append("=" + " ".join(x["기호"]))
else:
lines.append("#" + x["글"])
out.append((it["제목"], bool(it.get("가나다")), lines))
return out
# ── (a) 견본 ─────────────────────────────────────────────────────────────
def test_sample_excavation_machine_and_formula() -> None:
_, doc = _doc("EST", "거창", 1)
got = _show(doc)
assert got == [
("기 계(100%) : 굴삭기(무한궤도) (0.7㎥)", False, ["=q K f E Cm Q", "굴삭기(무한궤도)"]),
("보조인부(1일당 1인)", False, ["보통인부"]),
], got
assert [x["묶음"] for x in doc["금액"]] == [got[0][0], got[1][0]]
def test_sample_formwork_text_rate_subtotal() -> None:
_, doc = _doc("EST", "거창", 41)
got = _show(doc)
assert got[0] == (
"",
False,
["#< 토목품셈 6-3-1 합판거푸집(2022개정) >", "#※ 사용횟수 4회 : 보통"],
)
assert got[1] == (
"재료비",
False,
[
"합판(내수),대전",
"외송각재,대전",
"주자재소계",
"#소모자재(박리재등) (주자재비의 9%)",
"율",
],
)
assert got[2][0] == "인건비 (24년 품셈 개정)" and got[2][2][:3] == [
"형틀목공",
"보통인부",
"인부소계",
]
assert got[2][2][3].startswith("#공구손료") and len(got) == 3
sub = next(x for x in doc["금액"] if x["이름"] == "주자재소계")
assert sub["합밖"] and "비목" not in sub # 소계 = 합 밖 · 비목 칸 빈칸 그대로(810-2)
def test_sample_stone_wall_reference_and_direct_q() -> None:
_, doc = _doc("EST", "거창", 15)
got = _show(doc)
titles = [g[0] for g in got]
assert titles[0] == "전석 구입운반 : 별산" and got[0][2] == []
assert (
titles[1].startswith("뒷채움돌 운반 및 채집")
and got[1][2][-1] == "뒷채움돌기초잡석채집운반"
)
assert got[1][2][0].split()[0] == "=Q" # 단산 참조 Q 직접 = 그 항목 공식 묶음
assert titles[3].startswith("전석쌓기 기계") and got[3][2] == ["=Q_3", "굴삭기(무한궤도)"]
assert got[4] == ("전석쌓기 인부", False, ["석공", "보통인부"])
assert "합" not in "".join(titles) # 합계 절 = 화면 자동 합계(두 번 안 보임)
def test_sample_stc_steps_are_items() -> None:
_, doc = _doc("STC", "봉화", 14)
got = [
(t, g, [x for x in ls if not x.startswith("=P_")]) for t, g, ls in _show(doc)
] # 손 단가 = 기본 자리
assert got == [
("", False, ["#▣ 토공의 비탈규준틀[개소]2-6-1"]),
("재 료 비", False, ["각재(거푸집용,외송)", "판재", "일반못(철못)"]),
("인 건 비", False, ["건축목공", "보통인부"]),
], got
def test_sample_ganada_sub_items() -> None:
_, doc = _doc("EST", "거창", 2)
got = _show(doc)
assert got[0] == ("기 계(100%)", False, [])
assert got[1][:2] == ("굴삭기(무한궤도) (0.7+브레이카)", True)
assert got[2] == ("치즐(0.7㎥용)", True, ["치즐(0.7㎥)"]) # 「다. 소 계」 절 = 뺌
# ── (b) 전수 ─────────────────────────────────────────────────────────────
def _money(sk: dict, doc: dict) -> tuple:
got = dim.run(doc, sk, t2.whole())
lines = sorted(str(x.get("금액")) for x in got.get("줄") or [])
return tuple(str(got.get(c)) for c in COSTS), lines
@lru_cache(maxsize=1)
def _sweep() -> dict:
stat = {"블록": 0, "금액같음": 0, "다름": [], "cases": []}
for kind, files in (("EST", EST), ("STC", STC)):
for tag, path in files.items():
if not path.exists():
continue
for no in t2.fn.numbers(t2.native(kind, tag)):
sk, doc = _doc(kind, tag, no)
_, plain = _doc(kind, tag, no, laid=False)
stat["블록"] += 1
if _money(sk, doc) == _money(sk, plain):
stat["금액같음"] += 1
else:
stat["다름"].append((kind, tag, no))
stat["cases"].append(
{"키": f"{kind}|{tag}|{no}", "문서": doc, "파이썬": _items(doc)}
)
return stat
def test_sweep_money_same_and_ts_round_trip() -> None:
stat = _sweep()
if not stat["블록"]:
pytest.skip("원파일 없음")
assert not stat["다름"] and stat["금액같음"] == stat["블록"], stat["다름"][:5]
got = _ts(stat["cases"])
bad = {f: [g["키"] for g in got if not g[f]] for f in ("같음", "거울")}
assert bad == {"같음": [], "거울": []}, {k: v[:5] for k, v in bad.items()}
by = {}
for g in got:
kind = g["키"].split("|")[0]
row = by.setdefault(
kind, dict.fromkeys(("블록", "항목", "제목", "공식", "글", "쌓기", "무제목만"), 0)
)
row["블록"] += 1
for f in ("항목", "제목", "공식", "글"):
row[f] += g[f]
row["쌓기"] += g["쌓기"]
row["무제목만"] += g["항목"] > 0 and g["제목"] == 0
out = ROOT / "tmp" / "s8123"
out.mkdir(parents=True, exist_ok=True)
(out / "sweep.json").write_text(json.dumps(by, ensure_ascii=False, indent=1), "utf-8")
assert by.get("EST", {}).get("제목", 0) > by.get("EST", {}).get("블록", 0)
# ── (c) M01 ──────────────────────────────────────────────────────────────
def test_m01_docs_layout_unchanged() -> None:
docs = dc.folder()
assert len(docs) >= 1513 # 813-2 지움 6
cases = [{"키": k, "문서": d, "파이썬": _items(d)} for k, d in docs.items()]
got = _ts(cases)
bad = {f: [g["키"] for g in got if not g[f]] for f in ("같음", "거울")}
assert bad == {"같음": [], "거울": []}, {k: v[:5] for k, v in bad.items()}
assert not any(g["쌓기"] for g in got)