Merge remote-tracking branch 'origin/dev' into sub_laptop_1

This commit is contained in:
2026-09-13 18:21:44 +09:00
8 changed files with 171 additions and 28 deletions
@@ -1,7 +1,7 @@
{
"schema_version": "1.0",
"dataset_id": "data_work_item_master_manifest",
"generated_at": "2026-09-09T11:44:28+09:00",
"generated_at": "2026-09-13T17:55:11+09:00",
"built_by": "B08_Quantity/B08_Quantity_Build_WorkItemMaster.py",
"source": {
"dataset_id": "pum_forest",
@@ -12,8 +12,8 @@
"files": [
{
"file": "work_item_master_2026-01-01.json",
"sha256": "3462899767156784158f724513c793052711806260e60db59d3e2a5fbf1fab21",
"size_bytes": 856810
"sha256": "43b6d48175847e1d4011aad4a0597313008f791ec7eba0429d39d279e3e1fcb3",
"size_bytes": 856831
},
{
"file": "form_undetermined_2026-01-01.json",
@@ -22,8 +22,8 @@
},
{
"file": "basis_missing_2026-01-01.json",
"sha256": "644b179f7d4b57641fc4ea6df27ef3e3904a03a0bf49c6a4a8bcf44f1195bdb1",
"size_bytes": 19018
"sha256": "cb97edff0f47183173e23549c77f98f0509441edecc291bfcabba23254e60e9d",
"size_bytes": 18770
}
]
}
@@ -106,18 +106,6 @@
"pum_form": "requirement",
"line": 1957
},
{
"pum_table_id": "F0086",
"section": "4-2-1. 100본당",
"pum_form": "requirement",
"line": 1967
},
{
"pum_table_id": "F0087",
"section": "4-2-2. 1,000㎡당",
"pum_form": "requirement",
"line": 1997
},
{
"pum_table_id": "F0088",
"section": "4-3. 위험목 베기",
@@ -2,7 +2,7 @@
"schema_version": "1.0",
"dataset_id": "work_item_master_forest",
"effective_date": "2026-01-01",
"generated_at": "2026-09-09T11:44:28+09:00",
"generated_at": "2026-09-13T17:55:11+09:00",
"dataset_version": {
"dataset_id": "pum_forest",
"effective_date": "2026-01-01",
@@ -21,9 +21,9 @@
"tables_attached": 456,
"tables_orphan": 19,
"form_undetermined": 77,
"basis_found": 180,
"basis_missing": 140,
"basis_grouped": 35
"basis_found": 182,
"basis_missing": 138,
"basis_grouped": 37
},
"orphan_tables": [
{
@@ -12700,9 +12700,9 @@
"source_line": 1967,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 100.0,
"basis_unit": "본",
"basis_source": "절 이름",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -12857,9 +12857,9 @@
"source_line": 1997,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1000.0,
"basis_unit": "㎡",
"basis_source": "절 이름",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
+107
View File
@@ -0,0 +1,107 @@
"""표 읽기 (2026-09-13 축 C 1장 · 브레인 차례 ②).
지키는 것
① 밑수가 **절 이름에만** 있는 표는 이름 전체가 「수+단위+당」일 때만 뽑는다(4-2-2 「1,000㎡당」)
② 뭉친 줄: 「굴 삭 기 (무한궤도)」 표기 맞추기 · 비고 줄 거름 · 「-」 칸은 그 갈래에 품 없음
③ 조합 기종 「굴착기+부착용집게」는 공백이 달라도 카탈로그 「부착용 집게」에 붙는다
④ 형식을 안 적은 「굴 삭 기」는 잠정 우선순위로 고르지 않는다(판정 Ⓒ)
⑤ 실무값 대조 — 단목베기 5m 미만이 영월 설계내역 「잡관목제거 벌목(5m미만)」 882원/㎡ 근처로 선다
"""
from __future__ import annotations
from decimal import Decimal
from B08_Quantity.B08_Quantity_Build_WorkItemMaster import basis_from_name
from B09_Estimation.B09_Estimation_ResourceAxis import (
AxisResult,
CatalogEntry,
ResourceCatalog,
match_table,
)
def test_절_이름_밑수는_이름_전체가_밑수일_때만():
assert basis_from_name("1,000㎡당") == (1000.0, "㎡")
assert basis_from_name("100본당") == (100.0, "본")
assert basis_from_name("나무주사(100본당)") == (None, None)
assert basis_from_name("자재의 1회당 표준 운반량") == (None, None)
def _packed(*rows: list[str]) -> dict:
return {
"pum_table_id": "T-시험",
"pum_form": "requirement",
"basis_quantity": 10.0,
"basis_unit": "㎡",
"raw_row": [list(row) for row in rows],
}
MACHINES = [
CatalogEntry("0201-0080", "굴착기(무한궤도)", "machine", "0.8"),
CatalogEntry("0211-0080", "굴착기(타이어)", "machine", "0.8"),
CatalogEntry("0201-0020", "굴착기(무한궤도)", "machine", "0.2"),
CatalogEntry("7206-0020", "부착용 집게", "machine", "0.2"),
CatalogEntry("7206-0100", "부착용 집게", "machine", "1.0"),
CatalogEntry("1002", "보통인부", "labor"),
CatalogEntry("1037", "벌목부", "labor"),
CatalogEntry("1003", "특별인부", "labor"),
]
def _codes(result: AxisResult, variant: str) -> dict[str, Decimal]:
return {r.resource_code: r.amount for r in result.rows if r.variant == variant}
def test_뭉친_줄_굴삭기_표기를_맞추고_비고는_거른다():
table = _packed(
["직경 40㎝이상 ∼60㎝미만", "직경 60㎝이상 ∼80㎝미만"],
["보통인부", "", "인", "1.04(1.17)", "1.08(1.22)"],
["굴 삭 기 (무한궤도)", "0.8㎥", "h", "3.84", "3.52"],
["비고", "- 본 품의 집재거리는 100m까지를 기준하며 매 100m 증가마다 30%씩 가산한다."],
)
result = AxisResult()
match_table({"work_item_code": "FP-13-06-01"}, table, ResourceCatalog(entries=MACHINES), result)
first = _codes(result, "직경 40㎝이상 ∼60㎝미만")
assert first["0201-0080"] == Decimal("0.384") # 무한궤도 — 이름에 형식이 적혀 있음
assert "0211-0080" not in first
assert "FP-13-06-01" not in result.partial_items # 비고 줄로 막히지 않음
def test_조합_기종은_공백이_달라도_붙는다():
table = _packed(
["5m 미만", "5m이상~8m미만"],
["벌목부 보통인부", "", "인 인", "2.14 0.51", "2.80 0.66"],
["굴착기+부착용집게", "0.2㎥", "hr", "2.71", "3.54"],
)
result = AxisResult()
match_table({"work_item_code": "FP-04-02-02"}, table, ResourceCatalog(entries=MACHINES), result)
assert set(_codes(result, "5m 미만")) >= {"0201-0020", "7206-0020"}
assert "FP-04-02-02" not in result.partial_items
def test_형식_없는_굴삭기와_빈칸_표시():
table = _packed(
["식생매트설치", "복 토"],
["특 별 인 부", "", "인", "0.014", "-"],
["굴 삭 기", "0.6 ㎥", "시간", "-", "0.031"],
)
result = AxisResult()
match_table({"work_item_code": "FP-13-13-02"}, table, ResourceCatalog(entries=MACHINES), result)
# 「-」 — 복토 갈래에는 특별인부가 안 든다(자리만 지킴)
assert _codes(result, "식생매트설치") == {"1003": Decimal("0.0014")}
# 형식이 없는 굴삭기는 무한궤도·타이어 중 고르지 않는다 → 막고 사유를 남긴다
assert "굴 삭 기" in result.partial_items.get("FP-13-13-02", "")
def test_단목베기_5m_미만이_실무값_근처로_선다():
from B09_Estimation.B09_Estimation_UnitPrice import build_unit_prices
build = build_unit_prices()
assert "FP-04-02-02" not in build.partial_ratio
# 합판거푸집 — 일부러 안 푼 표는 **그 까닭**이 사유로 뜬다(사용횟수 비율 미구현)
assert "사용횟수" in (build.component_gaps.get("FP-12-04") or "")
total = build.book.resolve("B-FP-04-02-02#5m미만").total
# 영월 설계내역 1.9.2 「잡관목제거 벌목(5m미만)」 @882 — 노임 연도 차이로 ±5 % 안이면 같은 읽기
assert Decimal("838") <= total <= Decimal("926"), total