Files
Aislo/resources/tester/test_b09_basis_sheet.py
T
eomsangdonandClaude Opus 5 89effeca07 feat(B09): 산출기초 — 줄에 달린 근거를 한 장으로 접음
별표2 (5)(가) 열셋째. 근거는 줄마다 이미 있었고 묶는 자리만 없었음.
원가계산에 「산출기초」 탭으로 세움. 넷으로 접음 —
① 어느 판으로 계산했나(데이터 기준일·지문) ② 무엇을 골랐나(고른 값만)
③ 공종마다 무엇을 근거로 했나(줄 문구 그대로) ④ 못 채운 자리(0 으로 안 때운 자리).

- 판 목록은 장부(_manifest.json)를 그대로 읽음 — 목록을 따로 적으면 한쪽만 고쳐짐.
- ⚠ 여기서 값을 다시 계산하지 않음 — 금액 칸이 아예 없음(두 벌 방지, 시험으로 못 박음).

⇒ 「설계서 구성」의 산출기초가 반쪽 → 있음. 법이 정한 13 중 9 가 서고,
  우리가 더 낼 것은 공사설명서 하나(서식·설계하중 표기는 사용자에게 받아야 함).

곁들여: 자재총괄의 「미분류」를 「미정(발주기관 결정)」으로 고침 — 발주기관이 정할
자리인데 우리가 못 만든 것처럼 읽히던 문구(V-14). 값·판정은 그대로.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 00:06:22 +09:00

88 lines
3.5 KiB
Python

"""산출기초 — 줄에 달린 근거를 한 장으로 (2026-09-09).
별표2 (5)(가) 열셋째. **새로 세는 장이 아니다** — 이미 줄마다 달려 있는 근거를 모은다.
⚠ 겨누는 것 다섯
① 어느 판으로 계산했나 — **장부(`_manifest.json`)를 그대로** 읽는다(목록을 따로 안 적음)
② 고른 값만 싣는다 — 안 고른 칸은 기본값이라 줄이 안 선다
③ 근거는 **줄에 달린 문구 그대로** — 여기서 새로 짓지 않는다
④ 못 채운 자리는 **사유째** 모은다(0 으로 때운 자리가 아님)
⑤ 여기서 **값을 다시 계산하지 않는다** — 일위대가가 낸 값과 두 벌이 되면 안 된다
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B09_Estimation.B09_Estimation_BasisSheet import ( # noqa: E402
basis_sheet,
chosen_conditions,
dataset_versions,
work_item_basis,
)
from B09_Estimation.B09_Estimation_UnitPrice import cached_build # noqa: E402
def test_판_목록은_장부를_그대로_읽는다() -> None:
manifest = json.loads(
(ROOT / "resources" / "data_cost_input_value" / "_manifest.json").read_text(
encoding="utf-8"
)
)
ledger = {str(row["file"]) for row in manifest["files"]}
rows = dataset_versions()
assert ledger <= {row["file"] for row in rows}
# 공종 마스터도 함께 실린다 — 품셈 원문을 정규화한 판이다.
assert any(row["dataset_id"] == "work_item_master" for row in rows)
for row in rows:
assert row["effective_date"], row
def test_고른_값만_싣는다() -> None:
assert chosen_conditions({}) == []
assert chosen_conditions({"misc_material_percent": "", "fuel_region": ""}) == []
picked = chosen_conditions({"fuel_region": "01", "transport_distance_km": "12"})
assert [row["value"] for row in picked] == ["01", "12"]
def test_품_할증은_고른_것을_풀어_적는다() -> None:
picked = chosen_conditions({"labor_surcharge": {"1-4-1": "1-4-1:0"}})
assert len(picked) == 1
assert "10%" in picked[0]["item"]
assert "작업시기" in picked[0]["value"]
def test_근거는_줄에_달린_문구_그대로다() -> None:
build = cached_build()
rows = {row["code"]: row for row in work_item_basis(build)}
assert rows, "근거가 달린 공종이 하나도 없습니다"
for code, row in rows.items():
notes = {
detail.note.strip()
for detail in build.book.details.get(code, [])
if detail.note and detail.note.strip()
}
# 줄에 없는 문구를 지어내지 않는다(공종 단위 출처 한 줄만 더 붙을 수 있다).
assert notes <= set(row["notes"])
def test_못_채운_자리는_사유째_모은다() -> None:
sheet = basis_sheet(cached_build(), {})
assert sheet["gaps"], "못 채운 자리가 하나도 없다고 나옵니다 — 셈이 이상합니다"
for row in sheet["gaps"]:
assert row["kind"] and row["code"] and row["reason"]
def test_값을_다시_계산하지_않는다() -> None:
"""⑤ 이 장에는 금액 칸이 없다 — 금액은 일위대가·내역서 몫이다."""
sheet = basis_sheet(cached_build(), {})
text = json.dumps(sheet, ensure_ascii=False)
assert "총액" not in text and "합계 금액" not in text
for row in sheet["work_items"]:
assert set(row) == {"code", "name", "unit", "notes"}