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

This commit is contained in:
2026-09-13 20:13:10 +09:00
12 changed files with 337 additions and 213 deletions
@@ -1,7 +1,7 @@
{
"schema_version": "1.0",
"dataset_id": "data_work_item_master_manifest",
"generated_at": "2026-09-13T18:54:39+09:00",
"generated_at": "2026-09-13T19:52:49+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": "11c49ecde0a611307a82fd98b3625ee6577c01c2f77db6e1f515b41bf83ce685",
"size_bytes": 861050
"sha256": "6d1b46ce4e619f0f828dbf111e05a50c0bc22cff969fca1e38d5736f6e7dd440",
"size_bytes": 861142
},
{
"file": "form_undetermined_2026-01-01.json",
@@ -22,8 +22,8 @@
},
{
"file": "basis_missing_2026-01-01.json",
"sha256": "cb97edff0f47183173e23549c77f98f0509441edecc291bfcabba23254e60e9d",
"size_bytes": 18770
"sha256": "bc3b92c991e03ddd6a2ca837e4a03a0729e3022fb783f741e6c504f5cb3a2813",
"size_bytes": 15905
}
]
}
@@ -130,24 +130,6 @@
"pum_form": "requirement",
"line": 2062
},
{
"pum_table_id": "F0092",
"section": "5-1-1. 관목굴취",
"pum_form": "requirement",
"line": 2081
},
{
"pum_table_id": "F0093",
"section": "5-1-2. 교목굴취(나무높이)",
"pum_form": "requirement",
"line": 2099
},
{
"pum_table_id": "F0094",
"section": "5-1-3. 교목굴취(근원직경)",
"pum_form": "requirement",
"line": 2125
},
{
"pum_table_id": "F0095",
"section": "5-1-3. 교목굴취(근원직경)",
@@ -160,108 +142,18 @@
"pum_form": "requirement",
"line": 2177
},
{
"pum_table_id": "F0098",
"section": "5-2. 뿌리돌림",
"pum_form": "requirement",
"line": 2194
},
{
"pum_table_id": "F0099",
"section": "5-3-1. 나무식재",
"pum_form": "requirement",
"line": 2219
},
{
"pum_table_id": "F0102",
"section": "5-3-2. 관목식재(단식)",
"pum_form": "requirement",
"line": 2254
},
{
"pum_table_id": "F0103",
"section": "5-3-3. 관목식재(군식)",
"pum_form": "requirement",
"line": 2272
},
{
"pum_table_id": "F0104",
"section": "5-3-4. 교목식재(나무높이)",
"pum_form": "requirement",
"line": 2291
},
{
"pum_table_id": "F0106",
"section": "5-3-5. 교목식재(흉고직경)",
"pum_form": "requirement",
"line": 2322
},
{
"pum_table_id": "F0108",
"section": "5-3-5. 교목식재(흉고직경)",
"pum_form": "requirement",
"line": 2355
},
{
"pum_table_id": "F0109",
"section": "5-4. 파종조림",
"pum_form": "requirement",
"line": 2366
},
{
"pum_table_id": "F0110",
"section": "5-5. 천연하종갱신",
"pum_form": "requirement",
"line": 2383
},
{
"pum_table_id": "F0111",
"section": "5-6. 움싹갱신",
"pum_form": "requirement",
"line": 2396
},
{
"pum_table_id": "F0112",
"section": "5-7. 생태보완조림",
"pum_form": "requirement",
"line": 2409
},
{
"pum_table_id": "F0113",
"section": "5-8. 큰나무 공익조림",
"pum_form": "requirement",
"line": 2426
},
{
"pum_table_id": "F0114",
"section": "5-9. 해안조림",
"pum_form": "requirement",
"line": 2439
},
{
"pum_table_id": "F0116",
"section": "5-11. 사초심기",
"pum_form": "requirement",
"line": 2475
},
{
"pum_table_id": "F0117",
"section": "5-12. 떼붙임(재배잔디)",
"pum_form": "requirement",
"line": 2496
},
{
"pum_table_id": "F0118",
"section": "5-13. 떼심기",
"pum_form": "requirement",
"line": 2511
},
{
"pum_table_id": "F0121",
"section": "5-16-1. 단끊기",
"pum_form": "requirement",
"line": 2565
},
{
"pum_table_id": "F0126",
"section": "5-19-1. 표토절취 및 모으기",
@@ -364,24 +256,6 @@
"pum_form": "requirement",
"line": 3244
},
{
"pum_table_id": "F0164",
"section": "6-6. 가지치기 및 수형교정",
"pum_form": "requirement",
"line": 3276
},
{
"pum_table_id": "F0165",
"section": "6-7-1. 교목 시비",
"pum_form": "requirement",
"line": 3304
},
{
"pum_table_id": "F0166",
"section": "6-7-2. 관목 시비",
"pum_form": "requirement",
"line": 3318
},
{
"pum_table_id": "F0170",
"section": "7-1-1. 수확",
@@ -490,12 +364,6 @@
"pum_form": "requirement",
"line": 3763
},
{
"pum_table_id": "F0201",
"section": "8-1-1. 약제주입기",
"pum_form": "requirement",
"line": 3945
},
{
"pum_table_id": "F0202",
"section": "8-1-2. 약제주입병",
@@ -2,7 +2,7 @@
"schema_version": "1.0",
"dataset_id": "work_item_master_forest",
"effective_date": "2026-01-01",
"generated_at": "2026-09-13T18:54:39+09:00",
"generated_at": "2026-09-13T19:52:49+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": 182,
"basis_missing": 138,
"basis_grouped": 37
"basis_found": 204,
"basis_missing": 116,
"basis_grouped": 48
},
"orphan_tables": [
{
@@ -12716,7 +12716,7 @@
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": 100.0,
"basis_unit": "본",
"basis_source": "절 이름",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -12873,7 +12873,7 @@
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": 1000.0,
"basis_unit": "㎡",
"basis_source": "절 이름",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -13185,9 +13185,9 @@
"source_line": 2081,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 10.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -13247,9 +13247,9 @@
"source_line": 2099,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -13351,9 +13351,9 @@
"source_line": 2125,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -13737,9 +13737,9 @@
"source_line": 2194,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -13893,9 +13893,9 @@
"source_line": 2219,
"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": [],
@@ -14088,9 +14088,9 @@
"source_line": 2254,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 10.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14150,9 +14150,9 @@
"source_line": 2272,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 10.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14212,9 +14212,9 @@
"source_line": 2291,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14389,9 +14389,9 @@
"source_line": 2322,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14647,9 +14647,9 @@
"source_line": 2366,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "ha",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14722,9 +14722,9 @@
"source_line": 2383,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "ha",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14777,9 +14777,9 @@
"source_line": 2396,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "ha",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14822,9 +14822,9 @@
"source_line": 2409,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "ha",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14881,9 +14881,9 @@
"source_line": 2426,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "본",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -14928,9 +14928,9 @@
"source_line": 2439,
"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": [],
@@ -15062,9 +15062,9 @@
"source_line": 2475,
"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": [],
@@ -15158,9 +15158,9 @@
"source_line": 2511,
"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": [],
@@ -15362,9 +15362,9 @@
"source_line": 2565,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 100.0,
"basis_unit": "m",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -18216,9 +18216,9 @@
"source_line": 3276,
"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": [],
@@ -18331,9 +18331,9 @@
"source_line": 3304,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 10.0,
"basis_unit": "주",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
@@ -18396,9 +18396,9 @@
"source_line": 3318,
"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": [],
@@ -21434,9 +21434,9 @@
"source_line": 3945,
"pum_form": "requirement",
"form_basis": "직종 표기((인)·인부·공)",
"basis_quantity": null,
"basis_unit": null,
"basis_source": null,
"basis_quantity": 1.0,
"basis_unit": "ha",
"basis_source": "본문",
"resource_shares": {},
"partial_ratio": false,
"expression_cells": [],
+2 -2
View File
@@ -263,7 +263,7 @@
}
],
"unit_price": {
"note": "하단 일위대가 틀(PLAN 3장 하단 ①) — 갈 곳 unit_price 줄마다 한 줄. 수량은 원단위 줄(from_row), 단가는 B09 단가표. 공종 코드는 인계 대응표(찰쌓기 FP-13-04-05 · 갈래 뒷길이)와 10장 판정(기초잡석 FP-12-25)을 따름. 모르터는 코드 미정이라 막힘으로 둠",
"note": "하단 일위대가 틀(PLAN 3장 하단 ①) — 갈 곳 unit_price 줄마다 한 줄. 수량은 원단위 줄(from_row), 단가는 B09 단가표. 공종 코드는 인계 대응표(찰쌓기 FP-13-04-05 · 갈래 뒷길이)와 10장 판정(기초잡석 FP-12-25)을 따름. 모르터는 산림 품셈에 배합 절이 없어 AX-WK-c0842a0d 모르타르 배합 1:3(건설 품셈 건축 9-1-1)",
"rows": [
{
"seq": 1,
@@ -272,7 +272,7 @@
"work_item_code": "FP-13-04-05",
"variant_from": "L3"
},
{ "seq": 2, "name": "모르터", "from_row": 8 },
{ "seq": 2, "name": "모르터", "from_row": 8, "work_item_code": "AX-WK-c0842a0d" },
{ "seq": 3, "name": "기초잡석", "from_row": 15, "work_item_code": "FP-12-25" }
]
}
@@ -325,4 +325,6 @@ def test_창구가_실제_단가표로_찰쌓기_갈래를_찾는다(client: Tes
assert rows[1]["ref_code"] == "B-FP-13-04-05#55cm이하"
assert rows[1]["labor"] > 0 and rows[1]["quantity"] == pytest.approx(2.5 * 1.09**0.5)
assert rows[3]["ref_code"] == "B-FP-12-25"
assert rows[2]["reason"] == "공종 코드 미정" and table["complete"] is False
# 모르터 — 산림 품셈에 배합 절이 없어 AX-WK 모르타르 배합 1:3(2026-09-13)
assert rows[2]["ref_code"] == "B-AX-WK-c0842a0d" and rows[2]["labor"] > 0
assert table["complete"] is True
@@ -105,3 +105,42 @@ def test_단목베기_5m_미만이_실무값_근처로_선다():
total = build.book.resolve("B-FP-04-02-02#5m미만").total
# 영월 설계내역 1.9.2 「잡관목제거 벌목(5m미만)」 @882 — 노임 연도 차이로 ±5 % 안이면 같은 읽기
assert Decimal("838") <= total <= Decimal("926"), total
def test_단위_글자_없는_분모형_밑수도_본문에서_읽는다():
"""「(인/100m당)」 — 「단위:」 없이 분모만 적은 22 곳(2026-09-13). 「인」은 밑수가 아니다."""
from B08_Quantity.B08_Quantity_Build_WorkItemMaster import basis_from_source
assert basis_from_source(["(인/100m당)", "", "| 공정별 |"], 3) == (100.0, "m")
assert basis_from_source(["(인/본당)", "| 규격 |"], 2) == (1.0, "본")
assert basis_from_source(["(무한궤도/0.7㎥)", "| 규격 |"], 2) == (None, None) # 「당」 없음
_단끊기 = {
"pum_table_id": "F-단끊기",
"pum_form": "requirement",
"basis_quantity": 100.0,
"basis_unit": "m",
"condition_note": ["공정별", "보통인부(인)", "비고"],
"raw_row": [
["보통토사", "경질ㆍ고사점토 및 자갈섞인 점토", "호박돌 섞인 토사", "", ""],
["절 취", "2.4", "3.3", "5.4", ""],
["수평잡기 및 단정리", "0.34", "0.34", "0.34", ""],
["잡석 및 뿌리 정리", "0.36", "0.36", "0.36", ""],
["절·성토면 고르기", "1.17", "1.17", "1.17", ""],
["합계", "2.03", "2.09", "2.23", ""],
],
}
def test_단끊기는_공정_줄을_더하고_작업반장을_20인당_1인_더한다():
catalog = ResourceCatalog(entries=[*MACHINES, CatalogEntry("1001", "작업반장", "labor")])
result = AxisResult()
match_table({"work_item_code": "FP-05-16-01"}, _단끊기, catalog, result)
assert _codes(result, "보통토사") == {"1002": Decimal("0.0427"), "1001": Decimal("0.002135")}
assert _codes(result, "호박돌 섞인 토사")["1002"] == Decimal("0.0727")
assert "FP-05-16-01" not in result.partial_items
# 적어 두지 않은 공종은 같은 모양이어도 더하지 않는다(뭉기기 13-12-1 은 줄마다 단위가 다름)
other = AxisResult()
match_table({"work_item_code": "FP-13-12-01"}, _단끊기, catalog, other)
assert not other.rows
@@ -0,0 +1,25 @@
"""산림 품셈에 절이 없는 공종 `AX-WK-*` (명세 2장 ② 갈래 WK · 2026-09-13).
지키는 것 — 모르타르 배합 1:3 = 보통인부 0.66인/㎥(건설 품셈 건축 9-1-1, 모래체가름 포함) ·
시멘트·모래는 자재 단가 층이 없어 금액에 안 들고 「안 붙은 줄」로 드러남.
"""
from __future__ import annotations
from decimal import Decimal
from B09_Estimation.B09_Estimation_UnitPrice import cached_build
from B09_Estimation.B09_Estimation_WorkItems_AX import MORTAR_MIX
def test_모르타르_배합은_보통인부_066인이고_자재는_드러내기만() -> None:
build = cached_build()
title = build.book.title(f"B-{MORTAR_MIX}")
assert (title.name, title.spec, title.unit) == ("모르타르 배합", "1:3", "㎥")
money = build.book.resolve(f"B-{MORTAR_MIX}")
wage = build.book.resolve("1002").labor # 보통인부
assert money.labor == wage * Decimal("0.66") and money.material == 0
assert [label.split(" —")[0] for label in build.unattached[MORTAR_MIX]] == [
"시멘트 510 kg",
"모래 1.10 ㎥",
]