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

This commit is contained in:
2026-09-14 17:57:43 +09:00
8 changed files with 161 additions and 5 deletions
@@ -43,6 +43,54 @@
"pum_edition": "2026-01-01",
"basis": "9-19-1 토사면 고르기 절토면 표(원문 L5414)가 자원 머리에 규격을 안 적음 — [주]① 「공기압축기는 3.5㎥/분, 소형브레이커는 1㎥/분, 굴착기는 0.7㎥를 기준한 것이다」 → 굴착기(무한궤도) 0.7(카탈로그 0.7 은 무한궤도뿐). 칸이 규격을 적은 줄(성토면 굴착기 0.6㎥)은 안 덮음. 2026-09-14 브레인 승인(⑤ Ⓐ)"
},
{
"axis": "variant",
"from": "0.2·소림",
"to": "굴착기(무한궤도) 0.2 · 소",
"scope": "FP-09-21",
"pum_edition": "2026-01-01",
"basis": "품셈 9-21 제근 표 — 굴착기(무한궤도) 0.2·0.7 블록 × 임목축적 등급 열 「소·중·밀」([주]① 소림·중림·밀림). B08 이 크기·등급 두 칸을 「크기·등급」 으로 엮어 보냄(2026-09-14 브레인 판정 · 갈래 표기는 이 표 한 곳)"
},
{
"axis": "variant",
"from": "0.2·중림",
"to": "굴착기(무한궤도) 0.2 · 중",
"scope": "FP-09-21",
"pum_edition": "2026-01-01",
"basis": "품셈 9-21 제근 표 — 굴착기(무한궤도) 0.2·0.7 블록 × 임목축적 등급 열 「소·중·밀」([주]① 소림·중림·밀림). B08 이 크기·등급 두 칸을 「크기·등급」 으로 엮어 보냄(2026-09-14 브레인 판정 · 갈래 표기는 이 표 한 곳)"
},
{
"axis": "variant",
"from": "0.2·밀림",
"to": "굴착기(무한궤도) 0.2 · 밀",
"scope": "FP-09-21",
"pum_edition": "2026-01-01",
"basis": "품셈 9-21 제근 표 — 굴착기(무한궤도) 0.2·0.7 블록 × 임목축적 등급 열 「소·중·밀」([주]① 소림·중림·밀림). B08 이 크기·등급 두 칸을 「크기·등급」 으로 엮어 보냄(2026-09-14 브레인 판정 · 갈래 표기는 이 표 한 곳)"
},
{
"axis": "variant",
"from": "0.7·소림",
"to": "굴착기(무한궤도) 0.7 · 소",
"scope": "FP-09-21",
"pum_edition": "2026-01-01",
"basis": "품셈 9-21 제근 표 — 굴착기(무한궤도) 0.2·0.7 블록 × 임목축적 등급 열 「소·중·밀」([주]① 소림·중림·밀림). B08 이 크기·등급 두 칸을 「크기·등급」 으로 엮어 보냄(2026-09-14 브레인 판정 · 갈래 표기는 이 표 한 곳)"
},
{
"axis": "variant",
"from": "0.7·중림",
"to": "굴착기(무한궤도) 0.7 · 중",
"scope": "FP-09-21",
"pum_edition": "2026-01-01",
"basis": "품셈 9-21 제근 표 — 굴착기(무한궤도) 0.2·0.7 블록 × 임목축적 등급 열 「소·중·밀」([주]① 소림·중림·밀림). B08 이 크기·등급 두 칸을 「크기·등급」 으로 엮어 보냄(2026-09-14 브레인 판정 · 갈래 표기는 이 표 한 곳)"
},
{
"axis": "variant",
"from": "0.7·밀림",
"to": "굴착기(무한궤도) 0.7 · 밀",
"scope": "FP-09-21",
"pum_edition": "2026-01-01",
"basis": "품셈 9-21 제근 표 — 굴착기(무한궤도) 0.2·0.7 블록 × 임목축적 등급 열 「소·중·밀」([주]① 소림·중림·밀림). B08 이 크기·등급 두 칸을 「크기·등급」 으로 엮어 보냄(2026-09-14 브레인 판정 · 갈래 표기는 이 표 한 곳)"
},
{
"axis": "variant",
"from": "리핑암",
@@ -173,6 +173,9 @@
"basis_source": "⚠ 교차 참조 — 산림품셈 9-21 표에 밑수 표기가 없어 건설공사 표준품셈 3-9-2 뿌리뽑기 「1,000㎡당」 면적 축을 빌려 씀(사용자 확정 5차 6번). 마스터 밑수는 비어 있음(basis_missing F0294).",
"variant_axis": "stand_volume_class",
"variant_from": "stand_volume_class",
"variant_template": "{root_removal_excavator_m3}·{stand_volume_class}",
"variant_missing_reason": "제근 굴착기 크기·임목축적 등급이 아직 다 입력되지 않았습니다 — 산출 조건에서 고르면 단가가 섭니다(품셈 9-21: 굴착기 0.2·0.7 × 소림·중림·밀림 · 크기 제안 0.7㎥ 산림품셈 10-12-1 [주]①)",
"variant_note": "2026-09-14 브레인 판정 — 크기·등급 두 칸을 템플릿으로 한 값(「0.7·소림」)으로 엮고 B09 갈래 표기는 범위 별칭(FP-09-21)이 앎. 둘 중 하나라도 비면 등급만 싣고 입력 사유.",
"note": "2026-09-13 브레인 판정 Ⓑ — 제근은 토공 줄(토공_수량.md:30 「뿌리다듬기·적재·제근 | 9-20~21 | 벌개제근 연동」)이라 여기서 셈. 준비공 「제근·뿌리다듬기」는 같은 면적이라 참조로만 보임(이중계상 막이). 품은 임목축적 등급(소림·중림·밀림, 9-21 [주]①)으로 갈려 등급을 갈래로 넘김."
}
],
@@ -0,0 +1,70 @@
"""9-21 제근 굴착기 크기 칸 — 2026-09-14 브레인 판정(산출 조건 칸 · 제안 0.7㎥ · 비면 사유).
9-21 품은 굴착기 0.2·0.7 × 임목축적 등급(소·중·밀) 여섯 갈래인데 B08 은 등급만 보내 B09 가 못 고름.
⇒ 매핑 `variant_template` 이 크기·등급 두 입력을 한 값(「0.7·소림」)으로 엮고, 범위 별칭(FP-09-21)이
그 값을 B09 갈래(「굴착기(무한궤도) 0.7 · 소」)로 잇는다 — 갈래 키 표기는 별칭표 한 곳만 앎.
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import ( # noqa: E402
ROOT_REMOVAL_EXCAVATOR_SIZES,
ROOT_REMOVAL_EXCAVATOR_SUGGESTED,
)
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff # noqa: E402
from B08_Quantity.B08_Quantity_Engine_Preparation import STAND_VOLUME_CLASSES # noqa: E402
def _row(**inputs) -> dict:
summary = {"rows": [{"group": "지장목제거", "item": "뿌리뽑기", "spec": "", "unit": "㎡",
"amount": 1000.0, "application_ratio_pct": 100.0}]} # fmt: skip
rows = build_handoff(summary_table=summary, **inputs)["work_items"]
return next(r for r in rows if r["work_item_code"] == "FP-09-21")
def test_크기와_등급을_한_갈래_값으로_엮어_내역_금액이_섬() -> None:
from B09_Estimation.B09_Estimation_BillOfQuantities import build_bill
inputs = {"stand_volume_class": "소림", "root_removal_excavator_m3": "0.7"}
row = _row(**inputs)
assert row["variant_value"] == "0.7·소림" and not row["blocked_kind"], row
summary = {"rows": [{"group": "지장목제거", "item": "뿌리뽑기", "spec": "", "unit": "㎡",
"amount": 1000.0}]} # fmt: skip
bill = build_bill(build_handoff(summary_table=summary, **inputs))
line = next(r for r in bill.rows if r.code == "FP-09-21")
assert line.price_code == "B-FP-09-21#굴착기(무한궤도)0.7·소", line.note
assert line.amount_krw and int(line.amount_krw) > 0, line.note
def test_크기가_비면_금액_없이_입력_사유_등급은_그대로_실음() -> None:
row = _row(stand_volume_class="중림")
assert row["variant_value"] == "중림" # 판정 Ⓑ — 등급 갈래를 잃지 않음
assert row["blocked_kind"] == "input_missing" and "굴착기" in row["blocked_reason"], row
assert _row()["blocked_kind"] == "input_missing"
def test_선택지는_마스터_갈래_키의_크기_제안은_0_7_근거_10_12_1() -> None:
from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master
node = next(
n for n in load_work_item_master()["work_items"] if n["work_item_code"] == "FP-09-21"
)
sizes = {key.split(")")[1].split("·")[0].strip() for key in node["variant_keys"]}
assert sizes == set(ROOT_REMOVAL_EXCAVATOR_SIZES)
value, basis = ROOT_REMOVAL_EXCAVATOR_SUGGESTED
assert value == "0.7" and "10-12-1" in basis
# 크기 × 등급 여섯이 모두 별칭으로 B09 갈래에 닿음
from common_util.common_util_aliases import alias_target, load_aliases
rows = load_aliases("variant")
for size in ROOT_REMOVAL_EXCAVATOR_SIZES:
for grade in STAND_VOLUME_CLASSES:
target = alias_target(rows, f"{size}·{grade}", "FP-09-21", "2026-01-01")
assert target in node["variant_keys"], (size, grade, target)