feat(M01): PLAN 7-5 — 안 쓰는 마스터 old/ 로 마저 옮기고 죽은 코드 셋 끊음
- 아무도 안 읽는 최상위·ref 파일 7개를 old/ 로(지우지 않음) — _원문목록·_열목록_화면표· 수량_돌쌓기찰라이브러리_소광리·공종_수량연결_실무·수량_구조물원단위_울진· 품셈_산림_재료할증_울진·품셈_산림_돌종류계수_울진(뒤 넷은 old_code 만 읽어 이제 0) - 공종_자원별칭_실무도 old/ 로 — common_util_aliases.py 를 읽던 시험 둘이 이미 깨져 있어( B08·B09 모듈 없음) 실제로 부르는 살아 있는 코드가 없었음 - 살아 있는 common_util/ 에서 dead 모듈 셋(work_item_key·work_item_link·aliases)을 old_code/common_util/ 로 옮김 — old_code 만 부르던 것이라 옮겨도 부르는 자리 그대로( old_code 는 old_code 를 스스로 루트로 읽는 관례) · 상대경로 계산만 한 단 보정 - 깨져 있던 시험 둘(test_b09_fuel_kind_vibrator·test_b08_root_removal_excavator)도 resources/tester/ 에서 old_code/resources/tester/ 로 — 7-3 이 놓친 것 마저 정리 - UnitPrice.py 의 load_basis_missing() — old/ 스냅샷 대신 새 최상위 마스터 `기준수량없음_산림품셈_2026-01-01.json` 으로(브레인 판정: 마스터 쪽) · old_code 로 옮겨진 뒤 root 계산이 한 단 부족해 조용히 빈 딕셔너리를 돌려주던 것도 같이 바로잡음 (79건 견본 대조 통과) · check_master.py 틀 검사에 특수 파일 면제 한 줄 추가 - 검증 — typecheck · check_master 틀 0건 · M01·자재품목 시험 215 + masterdata·m01_api 98 통과 · 전체 모으기 1475 그대로 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
@@ -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)
|
||||
@@ -0,0 +1,103 @@
|
||||
"""연료 종류(휘발유) · 콘크리트 진동기 — 2026-09-14 브레인 661 뒤 ①②.
|
||||
|
||||
② 운전경비표 `fuel_kind` 를 조립이 안 읽어 **휘발유 기계에 경유값**이 붙고 있었음(플레이트 콤팩터 ·
|
||||
진동기 · 믹서 · 래머 · 커터) — 종류대로 그 유가(전국·시도 둘 다)로 섬.
|
||||
① 콘크리트 진동기 — 원문 8-3 (4611) 두 줄이 한 칸에 뭉쳐 규격·손료계수가 비었음 → 원문값 되살림
|
||||
(전기식 플렉시블형 ø45(0.75㎾) 4,935 · 엔진식 플렉시블형 ø45(2.6㎾) 5,101). 산림 12-15 「봉상후렉시블(45mm)
|
||||
Q=5.4㎥/hr」 은 전기·엔진을 안 적음 → 12-34-1 「콘크리트 진동기(3.5HP)」(= 2.6㎾) · 운전경비표에 엔진식만
|
||||
있는 것을 근거로 엔진식 4611-0350. 집수정 구체콘크리트 갈래가 이제 섬(㉯ ⑴ 조건이 풀림).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import cached_build, detail_of
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DATA = ROOT / "resources" / "data_cost_input_value"
|
||||
|
||||
|
||||
def _oil(file: str, key: str) -> dict:
|
||||
return json.loads((DATA / file).read_text(encoding="utf-8"))["variables"][key]
|
||||
|
||||
|
||||
def test_휘발유_기계는_휘발유값_경유_기계는_경유값() -> None:
|
||||
book = cached_build().book
|
||||
fuels = {r.ref_code for r in book.details["X-1730-0015"] if r.ref_code.startswith("M-FUEL-")}
|
||||
assert fuels == {"M-FUEL-휘발유"}, fuels
|
||||
gasoline = Decimal(str(_oil("oil_2026-08-14.json", "oil_gasoline")["value"]))
|
||||
assert (
|
||||
book.titles["M-FUEL-휘발유"].slots[-1] == gasoline
|
||||
or gasoline in book.titles["M-FUEL-휘발유"].slots
|
||||
)
|
||||
diesel_rows = {
|
||||
r.ref_code for r in book.details["X-0201-0070"] if r.ref_code.startswith("M-FUEL-")
|
||||
}
|
||||
assert diesel_rows == {"M-FUEL-경유"}
|
||||
|
||||
|
||||
def test_시도를_고르면_휘발유도_그_시도값() -> None:
|
||||
records = _oil("oil_regional_2026-09-09.json", "oil_gasoline")["records"]
|
||||
seoul = next(Decimal(str(r["value"])) for r in records if r["sido_code"] == "01")
|
||||
book = cached_build(fuel_region="01").book
|
||||
assert seoul in book.titles["M-FUEL-휘발유"].slots
|
||||
assert "서울" in book.titles["M-FUEL-휘발유"].spec
|
||||
|
||||
|
||||
def test_기계_하나_시간당_사용료도_연료_종류대로() -> None:
|
||||
from B09_Estimation.B09_Estimation_MachineOperating import hourly_cost_of, load_fuel_price
|
||||
|
||||
gasoline, _ = load_fuel_price(kind="휘발유")
|
||||
diesel, _ = load_fuel_price()
|
||||
assert gasoline != diesel
|
||||
cost = hourly_cost_of("1730-0015") # 휘발유 1.0 L/hr · 잡품 20%
|
||||
assert cost.money.material == Decimal("1.0") * Decimal("1.2") * gasoline, cost
|
||||
|
||||
|
||||
def test_진동기_원문값_되살림() -> None:
|
||||
from B09_Estimation.B09_Estimation_MachineCost import load_machine_catalog
|
||||
|
||||
machines = load_machine_catalog().machines
|
||||
assert machines["4611-0350"].specification == "엔진식 플렉시블형 ø45(2.6㎾)"
|
||||
assert machines["4611-0350"].loss_coefficient_per_hour == Decimal("0.0005101")
|
||||
assert machines["4611-0075"].specification == "전기식 플렉시블형 ø45(0.75㎾)"
|
||||
assert machines["4611-0075"].loss_coefficient_per_hour == Decimal("0.0004935")
|
||||
|
||||
|
||||
def test_집수정_구체콘크리트_갈래가_엔진식_진동기로_섬() -> None:
|
||||
build = cached_build()
|
||||
rows = {
|
||||
r["ref_code"]: Decimal(r["quantity"])
|
||||
for r in detail_of(build, "B-FP-12-15#구체콘크리트")["rows"]
|
||||
if r.get("ref_code")
|
||||
}
|
||||
assert rows["X-4611-0350"] == Decimal(1) / Decimal("5.4"), rows
|
||||
left = " ".join(build.unattached.get("FP-12-15", []))
|
||||
assert "봉상후렉시블" not in left and "구체콘크리트" not in left, left
|
||||
|
||||
|
||||
def test_진동기_별칭_사유에_12_15_는_전기_엔진을_안_적음() -> None:
|
||||
from common_util.common_util_aliases import load_aliases
|
||||
|
||||
row = next(r for r in load_aliases("resource") if r["to"] == "4611-0350")
|
||||
assert row["scope"] == "FP-12-15"
|
||||
assert "전기·엔진을 안 적음" in row["basis"] and "12-34-1" in row["basis"], row
|
||||
|
||||
|
||||
def test_중기경비_장은_갈래대로_잡품과_손료계수() -> None:
|
||||
"""661 뒤처리 — `#암석` 장이 조합 16%·비암석 계수로 보이던 자리."""
|
||||
from B09_Estimation.B09_Estimation_MachineExpenseSheet import machine_expense_sheets
|
||||
|
||||
build = cached_build(dump_haul_m=("164.23",))
|
||||
sheets = {s["code"]: s for s in machine_expense_sheets(build)}
|
||||
rock, combined = sheets["X-0201-0070#암석"], sheets["X-0201-0070#조합"]
|
||||
assert rock["misc_material_percent"] == sheets["X-0201-0070"]["misc_material_percent"]
|
||||
assert combined["misc_material_percent"] == "16"
|
||||
assert rock["loss_coefficient"] == 2405.0 and sheets["X-0201-0070"]["loss_coefficient"] == 2085
|
||||
gasoline = next(s for s in sheets.values() if s["machine_code"] == "1730-0015")
|
||||
assert Decimal(gasoline["fuel_price_per_liter"]) == Decimal(
|
||||
str(_oil("oil_2026-08-14.json", "oil_gasoline")["value"])
|
||||
)
|
||||
Reference in New Issue
Block a user