Files
Aislo/resources/tester/test_b09_table_reading_2.py
T

169 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""표 읽기 ②(2026-09-14 · PLAN 1장) — 「못 붙은 줄」 로 잘못 서던 자리를 **상태가 움직이는지**로 잼.
9-21 제근 고르기형 표 안내 글이 `unmatched` 에 들어가 못 붙은 줄로 섬 — 밑수는 사용자 확정
(건설품셈 1,000㎡당 빌림)이라 안내는 비고(`known_gap_note`)로 옮김(사유는 안 사라짐)
12-2 표면 마무리 원본이 12-17-1 [주] 문장을 절 제목으로 읽어 표 7장·가짜 갈래 #설치·#철거 가 붙음
"""
from __future__ import annotations
from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note
from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master
from B09_Estimation.B09_Estimation_UnitPrice import cached_build
def test_제근은_못_붙은_줄이_아니고_교차참조_사유는_비고에_남음() -> None:
build = cached_build()
assert not build.unattached.get("FP-09-21"), build.unattached.get("FP-09-21")
assert sum(1 for code in build.book.titles if code.startswith("B-FP-09-21#")) == 6
note = known_gap_note("FP-09-21")
assert "교차 참조" in note and "1,000㎡당" in note
def test_표면_마무리엔_미장공_표만_있고_가짜_갈래가_없음() -> None:
nodes = {n["work_item_code"]: n for n in load_work_item_master()["work_items"]}
assert [t["pum_table_id"] for t in nodes["FP-12-02"]["tables"]] == ["F0334"]
assert "F0356" in [t["pum_table_id"] for t in nodes["FP-12-17-01"]["tables"]]
build = cached_build()
assert [c for c in build.book.titles if c.startswith("B-FP-12-02")] == ["B-FP-12-02"]
assert not build.unattached.get("FP-12-02")
def test_육상_발파암_1_3m_들어내기는_0_1m_로_읽고_고른_값_버린_값을_사유에() -> None:
"""③ 원문 「육상 발파암(12m)와 동일」(고시 5189·5201줄)은 9-13-14 자기 참조 — 같은 절 암절취·
용수 발파암이 모두 「(0~1m)와 동일」 이라 0~1m(9-13-13)로 읽음(2026-09-14 브레인 승인)."""
build = cached_build()
for code in ("FP-09-13-14", "FP-09-13-15"):
assert code not in build.partial_ratio and code not in build.component_gaps, code
# 들어내기 참조 줄은 풀림 — 남는 것은 치즐뿐(기본 조립엔 치즐 단가가 없어 종전대로)
left = [label for label in build.unattached.get(code, []) if "치즐" not in label]
assert not left, left
source = build.factor_sources.get(code, "")
assert "육상 발파암(01m)" in source and "자기 참조" in source, source
def test_비탈면_고르기_발파암_소형브레이커는_1_0_규격으로_코드에_이어짐() -> None:
"""④ 9-19-3 값 「(2.45+3.05)/2/10㎡」 ← 9-19-1 표 ← [주]① 「소형브레이커는 1㎥/분」 —
값의 출처가 규격을 정함(2026-09-14 브레인 승인). 범위 별칭이 **코드로** 곧장 이음(이름만
돌려주면 규격 후보 넷이 다시 흐려짐). 「어어호스」 는 원문 오기 + 카탈로그에 호스 없음."""
from B09_Estimation.B09_Estimation_ResourceAxis import (
build_resource_axis,
load_combined_catalog,
)
axis = build_resource_axis(load_work_item_master(), load_combined_catalog())
rows = [
(r.resource_code, str(r.amount)) for r in axis.rows if r.work_item_code == "FP-09-19-03"
]
assert ("5210-0010", "0.275") in rows, rows
left = [u.cell for u in axis.unmatched if u.work_item_code == "FP-09-19-03"]
assert left == ["어어호스(3/4인치)"], left
def test_토사면_고르기는_두_표가_섞여_값이_불완전하다고_먼저_말함() -> None:
"""⑤ 사유 먼저(2026-09-14 브레인) — 원문은 절토면·성토면 두 표인데 섞여 보통인부 한 줄만
섰었음(조용히 틀린 값). ⑤ 본작업(같은 날)으로 두 표를 갈래로 읽어 「불완전」 사유와 섞인
본표(B-FP-09-19-01)가 함께 걷힘 — 갈래 없는 섞인 값이 다시 서면 빨강."""
assert "불완전" not in known_gap_note("FP-09-19-01")
assert "B-FP-09-19-01" not in cached_build().book.titles
#: ⑤ 9-19-1 원문 L5414~5436 — (단위: 10㎡당)이라 ㎡당 = 표값 ÷ 10.
CUT = "B-FP-09-19-01#절토면·" # 갈래 키는 공백을 지움
FILL = "B-FP-09-19-01#성토면·"
def _per_m2(code: str) -> dict[str, float]:
"""자원 줄(비율 줄 「%」 뺌) → ㎡당 수량."""
from B09_Estimation.B09_Estimation_UnitPrice import detail_of
rows = detail_of(cached_build(), code)["rows"]
return {str(r["ref_code"]): float(r["quantity"]) for r in rows if r.get("unit") != "%"}
def test_토사면_고르기_절토면은_토질_여섯_갈래로_규격까지_이어_섬() -> None:
"""[주]① 공기압축기 3.5㎥/분 · 소형브레이커 1㎥/분 · 굴착기 0.7㎥ — 범위 별칭이 코드로 이음
(카탈로그 0.7 은 무한궤도뿐). 「·」 는 그 갈래에 없음(칸 자리만 지킴)."""
titles = cached_build().book.titles
cut = sorted(code for code in titles if code.startswith(CUT))
assert len(cut) == 6, cut
sand = _per_m2(CUT + "모래ㆍ사질토ㆍ점토ㆍ점질토")
assert sand == {"1002": 0.005, "X-0201-0070": 0.015}, sand
soft_rock = _per_m2(CUT + "연암")
# 공기압축기 3.5 — 운전경비 표 괄호 딸림값 파서 고침(같은 날)으로 기계 층이 서서 붙음
assert soft_rock == {"1002": 0.046, "X-5205-0035": 0.125, "X-5210-0010": 0.245}, soft_rock
from B09_Estimation.B09_Estimation_ResourceAxis import (
build_resource_axis,
load_combined_catalog,
)
axis = build_resource_axis(load_work_item_master(), load_combined_catalog())
rock = {
(r.resource_code, float(r.amount))
for r in axis.rows
if r.work_item_code == "FP-09-19-01" and r.variant == "절토면 · 연암"
}
assert rock == {("1002", 0.046), ("5205-0035", 0.125), ("5210-0010", 0.245)}, rock
def test_토사면_고르기_성토면_인력_두_갈래_기계는_형식_두_갈래() -> None:
"""Ⓒ 둘째 줄이 병합 칸 탓에 한 칸 밀려 옴 — 사람 판정표로만 읽음(일반 보정 금지 · 목재틀흙막이
503만원 전례). 굴착기 0.6㎥ 는 원문이 형식을 안 적음 — 처음(Ⓑ)엔 근거가 없어 안 세웠고,
2026-09-14 브레인 ② 로 무한궤도·타이어 두 갈래를 세움(고르기는 B08 칸 · 제안 무한궤도)."""
build = cached_build()
assert _per_m2(FILL + "인력·점토또는점질토") == {"1002": 0.019}
assert _per_m2(FILL + "인력·모래또는사질토") == {"1002": 0.017}
assert _per_m2(FILL + "기계·무한궤도") == {"X-0201-0060": 0.009}
assert _per_m2(FILL + "기계·타이어") == {"X-0211-0060": 0.009}
left = build.unattached.get("FP-09-19-01", [])
assert not any("굴착기" in label for label in left), left
def test_범위_별칭은_칸이_규격을_적었으면_덮지_않음() -> None:
"""Ⓐ 별칭은 빈 곳을 채우는 것 — 같은 공종 성토면 「굴착기 0.6㎥」 가 0.7 로 덮이면 안 됨."""
from B09_Estimation.B09_Estimation_ResourceAxis import load_combined_catalog
from B09_Estimation.B09_Estimation_ResourceAxis_Join import (
apply_scoped_alias,
scoped_alias_entry,
)
catalog = load_combined_catalog()
assert scoped_alias_entry(catalog, "굴착기", "FP-09-19-01").code == "0201-0070"
side = ["0.6㎥", "시간", "0.09"]
assert scoped_alias_entry(catalog, "굴착기", "FP-09-19-01", side) is None
# 이름만 갈아도 옆 칸 0.6 으로 무한궤도가 조용히 골라짐 — 이름도 그대로
assert apply_scoped_alias(catalog, "굴착기", "FP-09-19-01", side) == "굴착기"
# 값·식 칸은 규격이 아님 — 9-19-3 소형브레이커 줄(값 0.275 · 식)은 그대로 이음
value = ["hr", "0.275", "(2.45+3.05)/2/10㎡"]
assert scoped_alias_entry(catalog, "소형브레이커", "FP-09-19-03", value).code == "5210-0010"
def test_토사면_고르기_원문_주는_값을_안_짓고_비고로() -> None:
"""Ⓓ [주]③ 착암공은 수량이 없어 줄을 안 지음 · Ⓔ [주]② 풍화암 연암 준용은 선택이라 갈래 아님."""
note = known_gap_note("FP-09-19-01")
assert "착암공" in note and "준용" in note, note
def test_제근은_빌린_밑수가_확정이라_갈래가_서면_내역_금액이_섬() -> None:
"""9-21 원문엔 밑수가 없어 마스터 `basis_missing` 에 F0294 가 남음(원문 사실 그대로) — 그러나
사용자 확정(건설품셈 3-9-2 「1,000㎡당」 · ÷1,000 은 ChooseOne 이 검)이 있어 내역에서
「밑수 미확보」로 막히면 안 됨(2026-09-14 브레인). 교차 참조 사유는 비고에 그대로 남음."""
from B09_Estimation.B09_Estimation_BasisSheet import open_gaps
from B09_Estimation.B09_Estimation_BillOfQuantities import build_bill
from B09_Estimation.B09_Estimation_UnitPrice import detail_of, load_basis_missing
assert "F0294" in load_basis_missing()
build = cached_build()
assert "FP-09-21" not in build.basis_missing
assert not [g for g in open_gaps(build) if g["code"] == "FP-09-21"]
title = "B-FP-09-21#굴착기(무한궤도)0.7·소"
# ㎡당 환산이 걸린 값 — 1,000㎡당 그대로면 5만원대로 섬
assert build.book.title(title) and 50 <= int(detail_of(build, title)["total"]) <= 60
item = {"work_item_code": "FP-09-21", "name": "지장목제거", "spec": "뿌리뽑기", "unit": "㎡",
"quantity": "1000", "in_bill": True, "variant_axis": "stand_volume_class",
"variant_value": "굴착기(무한궤도)0.7·소"} # fmt: skip
result = build_bill({"work_items": [item], "materials": []}, build=build)
assert not result.missing, result.missing
row = next(r for r in result.rows if r.code == "FP-09-21")
assert row.amount_krw and int(row.amount_krw) > 0, row.note