feat(B09): 유가 지역 단가를 물림 — 시도를 고르면 그 값으로 기계 연료비가 다시 섬

품셈 8-1-7 5호 「유류가격은 해당지역의 가격으로 한다」. 칸은 있었으나 시도별 값이
없어 잠겨 있었음. 오피넷 avgSidoPrice.do 스냅샷을 받아 데이터셋으로 세우고 물림.

- 안 고르면 전국평균 그대로 — 현장 소재지를 임의로 찍지 않음.
- 판에 없는 지역은 조용히 전국평균으로 눕지 않고 사유를 남김.
- 「지역 공시가」를 고를 수 있는지는 코드가 아니라 판이 정함.
- ⚠ 원천의 시도 가름이 행정구역과 다름(20=전남광주 한 줄, 07·16 없음) — 원문 그대로 둠.
- 수집 스크립트에 시도별 호출을 더함(전국평균과 두 벌로 보존).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-09 21:22:15 +09:00
co-authored by Claude Opus 5
parent 92ba0780cd
commit f39c195b52
12 changed files with 786 additions and 46 deletions
+20 -3
View File
@@ -229,15 +229,20 @@ def _apply_combined_misc_rate(book: PriceBook, work_item_titles: list[str]) -> i
return swapped
def _add_machine_layers(book: PriceBook, machine_codes: set[str]) -> list[str]:
def _add_machine_layers(
book: PriceBook, machine_codes: set[str], fuel_region: str | None = None
) -> list[str]:
"""`S`(취득가) · `L`(운전사) · `M`(연료) 을 세우고 그 위에 `X` 를 올린다.
시간당 사용료를 **미리 계산해 넣지 않는다** — 층을 실제로 쌓아야 화면이
「무엇으로 이루어졌나」를 보일 수 있다(PLAN 8-13 계산 과정을 감추지 않음).
⚠ `fuel_region`(시도코드) — 품셈 8-1-7 5호 「유류가격은 **해당지역의 가격**」.
안 주면 전국평균이다(현장 소재지를 임의로 찍지 않음).
"""
catalog = load_machine_catalog()
operating = {r.machine_code: r for r in load_operating_records().records}
fuel_price, _ = load_fuel_price()
fuel_price, fuel_meta = load_fuel_price(region=fuel_region)
wages = load_operator_wages()
incomplete: list[str] = []
@@ -248,6 +253,13 @@ def _add_machine_layers(book: PriceBook, machine_codes: set[str]) -> list[str]:
code=fuel_code,
kind=PriceKind.MATERIAL,
name="경유",
# ⚠ 어느 판으로 섰는지 **줄에 남긴다** — 지역 값과 전국평균은 리터당
# 수십 원이 갈려 단가가 조용히 달라지는 자리다.
spec=(
f"{fuel_meta.get('region_name')} 공시가"
if fuel_meta.get("region_name")
else "전국 공시가"
),
unit="L",
slots=_slots(fuel_price),
)
@@ -523,6 +535,7 @@ def build_unit_prices(
factor_choices: dict[tuple[str, str], Decimal] | None = None,
machine_picks: dict[str, str] | None = None,
misc_material_percent: Decimal | None = None,
fuel_region: str | None = None,
) -> UnitPriceBuild:
"""자원 축을 일위대가(`B`)로 조립한다.
@@ -534,6 +547,8 @@ def build_unit_prices(
⚠ `misc_material_percent` — 공구손료·잡재료(산림품셈 1-2-6). **안 주면 줄이 안 선다** —
사용자 확정 5차 작은 것 1 「지금은 안 넣되 숫자 넣으면 되게 열어 둘 것」 그대로다.
⚠ `fuel_region` — 유가 시도코드(품셈 8-1-7 5호). **안 주면 전국평균**이다.
"""
from B09_Estimation.B09_Estimation_FactorChoices import (
chosen_values,
@@ -637,7 +652,7 @@ def build_unit_prices(
# 공식표에만 나오는 기종도 사용료 층을 세운다 — 안 세우면 공식이 붙을 데가 없다.
machine_codes |= formula_machine_codes(master)
machine_codes |= {row["machine_code"] for rows in capacity_rows.values() for row in rows}
build.incomplete_machines = _add_machine_layers(build.book, machine_codes)
build.incomplete_machines = _add_machine_layers(build.book, machine_codes, fuel_region)
# 규격 갈래(`variant`)가 있으면 **갈래마다 따로 세운다** — 무근·철근·소형구조물은
# 품이 달라 한 일위대가로 뭉치면 어느 것도 안 맞는다.
@@ -946,6 +961,7 @@ def cached_build(
range_choices: tuple[tuple[str, str], ...] = (),
machine_picks: tuple[tuple[str, str], ...] = (),
misc_material_percent: str = "",
fuel_region: str = "",
) -> UnitPriceBuild:
"""조립 결과를 한 번만 만든다 — 품셈 3 MB 를 요청마다 다시 읽지 않는다.
@@ -968,6 +984,7 @@ def cached_build(
factor_choices=chosen_values(scan_range_factors(master), settings),
machine_picks=machine_choices(settings),
misc_material_percent=parse_misc_material_percent(misc_material_percent),
fuel_region=fuel_region or None,
)