fix(b09): 휘발유 기계에 경유값이 붙던 것 — 운전경비표 연료 종류대로 유가 · 콘크리트 진동기(4611) 원문값 되살려 집수정 구체콘크리트 갈래를 세움
· 연료 — `fuel_kind` 를 조립이 안 읽어 휘발유 기계 10종(래머·플레이트 콤팩터·믹서·커터·진동기)이 경유값이었음 → `M-FUEL-휘발유` · 시도 판도 종류대로 · 기계 하나 사용료·중기경비 장도 같은 값 · 진동기 — 건설품셈 8-3 (4611) 두 줄이 한 칸에 뭉쳐 규격·손료계수가 비었음 → 원문값 되살림(전기식 ø45 0.75㎾ 4,935 · 엔진식 ø45 2.6㎾ 5,101 · 판정 없음) · 12-15 「봉상후렉시블(45mm)」 → 엔진식 4611-0350 범위 별칭 — 12-15 는 전기·엔진을 안 적음 · 12-34-1 「3.5HP」 = 2.6㎾ · 운전경비표에 엔진식만(브레인 승인) → 구체콘크리트 갈래가 진동기 1/5.4 hr/㎥ 와 함께 섬 · 661 뒤처리 — 중기경비 장의 `#암석` 이 조합 16%·비암석 계수로 보이던 것을 보정 상각·정비·계와 본 잡품률로 · 마스터 재생성 — 12-15 갈래 키만(구체콘크리트 더함) 휘발유 기계 전수 대조: 재료비 +13~113원/hr · 조립에 선 것은 플레이트 콤팩터(+20원/hr → 9-14-02 +5원)와 진동기 · 나머지 제목 그대로 · 프로젝트 내역 그대로 · 잴 시험 일곱 빨강→초록 · 끄기 다섯 빨강 · 전체 시험 1695 + 330 통과 (시험 파일은 앞선 autopush 가 먼저 쓸어 올린 빨강본 — 이 커밋이 초록으로 맞춤) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
@@ -145,6 +145,15 @@ GLUED_MACHINE_FIXES: dict[str, tuple[str, str]] = {
|
||||
"7120-0746": ("버킷식준설기", "7.46kW"),
|
||||
"7995-0050": ("배관파이프", "ø50-2.6m"),
|
||||
}
|
||||
#: ⚠ **한 칸에 두 줄이 뭉친 표** (2026-09-14 661 뒤 ① 브레인) — 원천이 (4611) 콘크리트 진동기 표의
|
||||
#: 두 기종(「4611-0075 0350」 · 규격·계수가 한 칸에 둘씩)을 못 갈라 **규격이 비고 손료계수가 없음**.
|
||||
#: 규격·시간당 계는 **건설공사 표준품셈 제8장 (4611)** 원문(L2504) 그대로 — 판정 없음.
|
||||
#: 상각 3,000 + 정비 1,167 + 관리 768 = 4,935 · 3,000 + 1,333 + 768 = 5,101 (표의 「계」와 같음)
|
||||
#: ⚠ 원천이 바로 실으면 이 표는 지운다(위 표와 같은 약속). 취득가는 원천 값 그대로.
|
||||
MERGED_ROW_FIXES: dict[str, tuple[str, Decimal]] = {
|
||||
"4611-0075": ("전기식 플렉시블형 ø45(0.75㎾)", Decimal("0.0004935")),
|
||||
"4611-0350": ("엔진식 플렉시블형 ø45(2.6㎾)", Decimal("0.0005101")),
|
||||
}
|
||||
#: 이름이 빈 분류 — 분류번호(코드 앞 넷) → 원문 이름. 규격은 원천 값 그대로.
|
||||
EMPTY_NAME_BY_GROUP: dict[str, str] = {
|
||||
"0240": "유압식 진동콤팩터(굴착기 부착용)",
|
||||
@@ -234,6 +243,10 @@ def load_machine_catalog(file_name: str = "mach_base_2026.json") -> MachineCatal
|
||||
elif code in GLUED_MACHINE_FIXES:
|
||||
name, spec = GLUED_MACHINE_FIXES[code]
|
||||
row = {**row, "machine_name": name, "specification": spec}
|
||||
elif code in MERGED_ROW_FIXES and "loss_coefficient_per_hour" not in coefficient:
|
||||
spec, merged_coefficient = MERGED_ROW_FIXES[code]
|
||||
row = {**row, "specification": spec}
|
||||
coefficient = {**coefficient, "loss_coefficient_per_hour": merged_coefficient}
|
||||
elif not str(row.get("machine_name") or "").strip() and code[:4] in EMPTY_NAME_BY_GROUP:
|
||||
row = {**row, "machine_name": EMPTY_NAME_BY_GROUP[code[:4]]}
|
||||
catalog.machines[code] = MachineSpec(
|
||||
|
||||
@@ -88,7 +88,6 @@ def machine_expense_sheets(build: Any) -> list[dict[str, Any]]:
|
||||
catalog = load_machine_catalog()
|
||||
operating = {row.machine_code: row for row in load_operating_records().records}
|
||||
wages = load_operator_wages()
|
||||
fuel_price, fuel_meta = load_fuel_price()
|
||||
loss = _loss_records()
|
||||
|
||||
sheets: list[dict[str, Any]] = []
|
||||
@@ -101,11 +100,32 @@ def machine_expense_sheets(build: Any) -> list[dict[str, Any]]:
|
||||
continue
|
||||
record = operating.get(machine_code)
|
||||
raw = loss.get(machine_code) or {}
|
||||
if variant == "암석":
|
||||
# 암석 손료보정(8-1-7 1) — 장도 보정한 상각·정비로 보여야 「계」와 맞음(661 뒤처리).
|
||||
from B09_Estimation.B09_Estimation_RockLoss import rock_parts
|
||||
|
||||
parts = rock_parts(machine_code)
|
||||
if parts is not None:
|
||||
keys = ("depreciation", "maintenance", "management", "source")
|
||||
# 화면 JSON 은 수 — Decimal 을 그대로 실으면 응답이 안 섬
|
||||
raw = {
|
||||
**raw,
|
||||
**{f"{k}_coefficient_1e_minus_7": float(v) for k, v in zip(keys, parts)},
|
||||
}
|
||||
loss_per_hour = (
|
||||
Decimal(str(int(raw["source_coefficient_1e_minus_7"]))) * Decimal("1e-7")
|
||||
if variant == "암석" and "source_coefficient_1e_minus_7" in raw
|
||||
else machine.loss_coefficient_per_hour
|
||||
)
|
||||
money = build.book.resolve(code)
|
||||
|
||||
gaps: list[str] = []
|
||||
attachment = machine_code.startswith(_ATTACHMENT_PREFIXES)
|
||||
fuel_liters = getattr(record, "fuel_liters_per_hour", None)
|
||||
# 연료 종류대로 그 유가(휘발유 기계가 경유값으로 보이던 자리 · 661 뒤 ②).
|
||||
fuel_price, fuel_meta = (
|
||||
load_fuel_price(kind=record.fuel_kind) if fuel_liters is not None else (None, {})
|
||||
)
|
||||
misc_percent = getattr(record, "misc_material_percent", None)
|
||||
occupation = getattr(record, "operator_occupation_code", "") or ""
|
||||
wage = wages.get(occupation)
|
||||
@@ -136,8 +156,8 @@ def machine_expense_sheets(build: Any) -> list[dict[str, Any]]:
|
||||
"management_coefficient": raw.get("management_coefficient_1e_minus_7"),
|
||||
"loss_coefficient": raw.get("source_coefficient_1e_minus_7"),
|
||||
"loss_krw_per_hour": _money(
|
||||
machine.price_thousand_krw * _THOUSAND * machine.loss_coefficient_per_hour
|
||||
if machine.loss_coefficient_per_hour is not None
|
||||
machine.price_thousand_krw * _THOUSAND * loss_per_hour
|
||||
if loss_per_hour is not None
|
||||
else None
|
||||
),
|
||||
# ② 운전경비
|
||||
@@ -145,7 +165,7 @@ def machine_expense_sheets(build: Any) -> list[dict[str, Any]]:
|
||||
"fuel_price_per_liter": _money(fuel_price),
|
||||
"fuel_scope": fuel_meta.get("region_name") or "전국 공시가",
|
||||
"misc_material_percent": (
|
||||
str(COMBINED_MISC_PERCENT) if variant else _money(misc_percent)
|
||||
str(COMBINED_MISC_PERCENT) if variant == "조합" else _money(misc_percent)
|
||||
),
|
||||
"operator_code": occupation,
|
||||
"operator_daily_wage": _money(wage),
|
||||
|
||||
@@ -316,9 +316,14 @@ def write_operating_records(
|
||||
#: 시도별 유가 판 — 품셈 8-1-7 5호 「유류가격은 **해당지역의 가격**으로 한다」.
|
||||
#: ⚠ **파일이 있을 때만 지역을 고를 수 있다** — 없으면 전국평균 한 벌로 돈다(코드로 막지 않음).
|
||||
REGIONAL_OIL_FILE = "oil_regional_2026-09-09.json"
|
||||
#: 운전경비표 연료 종류 → 유가 판 변수. ⚠ 종류를 안 읽고 경유로 때우면 휘발유 기계가 싸게 섬
|
||||
#: (2026-09-14 661 뒤 ② — 플레이트 콤팩터·진동기·믹서·래머·커터가 경유값이었음).
|
||||
FUEL_VARIABLES = {"경유": "oil_diesel", "휘발유": "oil_gasoline"}
|
||||
|
||||
|
||||
def load_regional_fuel_table(oil_file: str = REGIONAL_OIL_FILE) -> tuple[dict[str, dict], dict]:
|
||||
def load_regional_fuel_table(
|
||||
oil_file: str = REGIONAL_OIL_FILE, kind: str = "경유"
|
||||
) -> tuple[dict[str, dict], dict]:
|
||||
"""(시도코드 → {이름·값}, 판 신원). 판이 없으면 **빈 표**를 돌려준다.
|
||||
|
||||
⚠ 원문에 있는 코드 `00`(전국)은 **지역 선택지에서 뺀다** — 그 자리는 전국평균 판이
|
||||
@@ -328,7 +333,7 @@ def load_regional_fuel_table(oil_file: str = REGIONAL_OIL_FILE) -> tuple[dict[st
|
||||
payload = _read_json(*_CATALOG_SUBPATH, oil_file)
|
||||
except FileNotFoundError:
|
||||
return {}, {}
|
||||
diesel = payload["variables"]["oil_diesel"]
|
||||
diesel = payload["variables"][FUEL_VARIABLES[kind]]
|
||||
table = {
|
||||
str(record["sido_code"]): {
|
||||
"name": str(record["sido_name"]),
|
||||
@@ -346,7 +351,7 @@ def load_regional_fuel_table(oil_file: str = REGIONAL_OIL_FILE) -> tuple[dict[st
|
||||
|
||||
|
||||
def load_fuel_price(
|
||||
oil_file: str = "oil_2026-08-14.json", region: str | None = None
|
||||
oil_file: str = "oil_2026-08-14.json", region: str | None = None, kind: str = "경유"
|
||||
) -> tuple[Decimal, dict[str, str]]:
|
||||
"""경유 단가와 그 판의 신원. `region`(시도코드)을 주면 **그 지역 값**으로 선다.
|
||||
|
||||
@@ -355,7 +360,7 @@ def load_fuel_price(
|
||||
⚠ 준 지역이 판에 없으면 **조용히 전국평균으로 눕지 않고** 그 사실을 신원에 적는다.
|
||||
"""
|
||||
payload = _read_json(*_CATALOG_SUBPATH, oil_file)
|
||||
diesel = payload["variables"]["oil_diesel"]
|
||||
diesel = payload["variables"][FUEL_VARIABLES[kind]]
|
||||
meta = {
|
||||
"dataset_id": payload.get("dataset_id", ""),
|
||||
"effective_date": payload.get("effective_date", ""),
|
||||
@@ -366,7 +371,7 @@ def load_fuel_price(
|
||||
if not region:
|
||||
return Decimal(str(diesel["value"])), meta
|
||||
|
||||
table, region_meta = load_regional_fuel_table()
|
||||
table, region_meta = load_regional_fuel_table(kind=kind)
|
||||
picked = table.get(str(region))
|
||||
if picked is None:
|
||||
meta["region"] = str(region)
|
||||
@@ -429,10 +434,12 @@ def hourly_cost_of(machine_code: str, *, region: str | None = None):
|
||||
if record is None:
|
||||
return hourly_machine_cost(machine)
|
||||
|
||||
fuel_price, _ = load_fuel_price(region=region)
|
||||
wages = load_operator_wages()
|
||||
|
||||
liters = record.fuel_liters_per_hour
|
||||
fuel_price = (
|
||||
None if liters is None else load_fuel_price(region=region, kind=record.fuel_kind)[0]
|
||||
)
|
||||
if liters is not None and record.misc_material_percent is not None:
|
||||
# 잡재료는 **주연료의 %** 라 유가와 같이 움직인다(PLAN 8-18 유가 민감분).
|
||||
liters = liters * (Decimal(1) + record.misc_material_percent / Decimal(100))
|
||||
|
||||
@@ -70,18 +70,25 @@ def rock_rate(code: str) -> tuple[int | None, str]:
|
||||
return None, ""
|
||||
|
||||
|
||||
def rock_coefficient(code: str) -> Decimal | None:
|
||||
"""암석 손료계수 = (상각 + 정비) × (1 + 가산) + 관리 — 1e-7 정수 아래 버림(봉화 3533.75 → 0.3533)."""
|
||||
def rock_parts(code: str) -> tuple[Decimal, Decimal, Decimal, int] | None:
|
||||
"""(보정 상각, 보정 정비, 관리, 계) — 1e-7 단위 · 계는 정수 아래 버림(봉화 3533.75 → 3533)."""
|
||||
rate, _ = rock_rate(code)
|
||||
record = _sources()[0].get(code)
|
||||
if rate is None or record is None:
|
||||
return None
|
||||
parts = [
|
||||
depreciation, maintenance, management = (
|
||||
Decimal(str(record[f"{key}_coefficient_1e_minus_7"]))
|
||||
for key in ("depreciation", "maintenance", "management")
|
||||
]
|
||||
raised = (parts[0] + parts[1]) * (1 + Decimal(rate) / 100) + parts[2]
|
||||
return raised.quantize(Decimal(1), rounding=ROUND_FLOOR) * _E7
|
||||
)
|
||||
factor = 1 + Decimal(rate) / 100
|
||||
raised = (depreciation * factor, maintenance * factor, management)
|
||||
return (*raised, int(sum(raised).quantize(Decimal(1), rounding=ROUND_FLOOR)))
|
||||
|
||||
|
||||
def rock_coefficient(code: str) -> Decimal | None:
|
||||
"""암석 손료계수(원당) = (상각 + 정비) × (1 + 가산) + 관리 — `rock_parts` 의 계 × 1e-7."""
|
||||
parts = rock_parts(code)
|
||||
return None if parts is None else parts[3] * _E7
|
||||
|
||||
|
||||
def _rock_hourly(book: Any, code: str) -> str | None:
|
||||
|
||||
@@ -295,28 +295,33 @@ def _add_machine_layers(
|
||||
"""
|
||||
catalog = load_machine_catalog()
|
||||
operating = {r.machine_code: r for r in load_operating_records().records}
|
||||
fuel_price, fuel_meta = load_fuel_price(region=fuel_region)
|
||||
wages = load_operator_wages()
|
||||
incomplete: list[str] = []
|
||||
|
||||
fuel_code = f"{FUEL_CODE_PREFIX}경유"
|
||||
if fuel_code not in book.titles:
|
||||
book.add_title(
|
||||
PriceTitle(
|
||||
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),
|
||||
def fuel_title(kind: str) -> str:
|
||||
"""연료 종류마다 자재 제목 한 벌(`M-FUEL-경유`·`M-FUEL-휘발유`) — 운전경비표 종류대로."""
|
||||
code = f"{FUEL_CODE_PREFIX}{kind}"
|
||||
if code not in book.titles:
|
||||
price, meta = load_fuel_price(region=fuel_region, kind=kind)
|
||||
book.add_title(
|
||||
PriceTitle(
|
||||
code=code,
|
||||
kind=PriceKind.MATERIAL,
|
||||
name=kind,
|
||||
# ⚠ 어느 판으로 섰는지 **줄에 남긴다** — 지역 값과 전국평균은 리터당
|
||||
# 수십 원이 갈려 단가가 조용히 달라지는 자리다.
|
||||
spec=(
|
||||
f"{meta.get('region_name')} 공시가"
|
||||
if meta.get("region_name")
|
||||
else "전국 공시가"
|
||||
),
|
||||
unit="L",
|
||||
slots=_slots(price),
|
||||
)
|
||||
)
|
||||
)
|
||||
return code
|
||||
|
||||
fuel_title("경유")
|
||||
|
||||
for code in sorted(machine_codes):
|
||||
machine = catalog.machines.get(code)
|
||||
@@ -391,6 +396,7 @@ def _add_machine_layers(
|
||||
|
||||
liters = record.fuel_liters_per_hour
|
||||
if liters is not None:
|
||||
fuel_code = fuel_title(record.fuel_kind)
|
||||
book.add_detail(PriceDetail(hourly_code, fuel_code, liters, note="주연료"))
|
||||
if record.misc_material_percent is not None:
|
||||
# 잡품은 **주연료비 × 율의 가산 행**(명세 7장 · STmate 17번 §3 「잡품 단가 칸 = 연료
|
||||
|
||||
@@ -43,6 +43,14 @@
|
||||
"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": "resource",
|
||||
"from": "다짐:봉상후렉시블(45mm)",
|
||||
"to": "4611-0350",
|
||||
"scope": "FP-12-15",
|
||||
"pum_edition": "2026-01-01",
|
||||
"basis": "12-15 집수정 표(원문 L6460)는 「봉상후렉시블(45mm) Q=5.4㎥/hr」 만 적고 전기·엔진을 안 적음 — 건설품셈 8-3 (4611) 은 전기식 플렉시블형 ø45(0.75㎾)·엔진식 플렉시블형 ø45(2.6㎾) 둘 · 12-34-1 「콘크리트 진동기(3.5HP)」 = 2.6㎾ 엔진식 · 운전경비표(8-4)에 엔진식 4611-0350(휘발유 1.0L)만 있음 → 엔진식. 전기식이 필요하면 그때 엶. 2026-09-14 브레인 승인(661 뒤 ①)"
|
||||
},
|
||||
{
|
||||
"axis": "variant",
|
||||
"from": "0.2·소림",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "data_work_item_master_manifest",
|
||||
"generated_at": "2026-09-14T21:01:09+09:00",
|
||||
"generated_at": "2026-09-14T22:25:14+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": "915a1403611a9c713e5c59bff4422a2fe9f53d4168a44a9fada43d14b6c9d9d6",
|
||||
"size_bytes": 838599
|
||||
"sha256": "2f0d8bfb3836597ee6f2fd755e2f2e172cb33df1e1e9061af81982a43497f9b2",
|
||||
"size_bytes": 838655
|
||||
},
|
||||
{
|
||||
"file": "form_undetermined_2026-01-01.json",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"dataset_id": "work_item_master_forest",
|
||||
"effective_date": "2026-01-01",
|
||||
"pum_edition": "2026-01-01",
|
||||
"generated_at": "2026-09-14T21:01:09+09:00",
|
||||
"generated_at": "2026-09-14T22:25:14+09:00",
|
||||
"dataset_version": {
|
||||
"dataset_id": "pum_forest",
|
||||
"effective_date": "2026-01-01",
|
||||
@@ -33149,6 +33149,7 @@
|
||||
"special_glyphs": [],
|
||||
"capacity_formula_here": false,
|
||||
"variant_key": [
|
||||
"구체콘크리트",
|
||||
"버림콘크리트"
|
||||
],
|
||||
"condition_note": [
|
||||
@@ -33212,6 +33213,7 @@
|
||||
}
|
||||
],
|
||||
"variant_keys": [
|
||||
"구체콘크리트",
|
||||
"버림콘크리트"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -35,15 +35,18 @@ def _basin(structure: dict) -> dict:
|
||||
return next(r for r in handoff["work_items"] if r["origin"] == "structure")
|
||||
|
||||
|
||||
def test_버림_갈래는_서고_구체_갈래는_다짐기_없어_안_섬() -> None:
|
||||
def test_버림_갈래는_서고_구체_갈래는_다짐기가_풀려야_섬() -> None:
|
||||
"""⑴ 다짐기 빠진 구체 갈래는 안 세움 — 2026-09-14 661 뒤 ① 진동기(엔진식 4611-0350)가 풀려 이제 섬."""
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import cached_build, detail_of
|
||||
|
||||
build = cached_build()
|
||||
assert "B-FP-12-15#구체콘크리트" not in build.book.titles
|
||||
assert "B-FP-12-15#구체콘크리트" in build.book.titles
|
||||
rows = detail_of(build, "B-FP-12-15#버림콘크리트")["rows"]
|
||||
assert {r["ref_code"]: float(r["quantity"]) for r in rows} == {"1013": 0.15, "1002": 0.27}
|
||||
body = {r["ref_code"] for r in detail_of(build, "B-FP-12-15#구체콘크리트")["rows"]}
|
||||
assert "X-4611-0350" in body, body # 인력만으로 서지 않음(⑴ 까닭 그대로)
|
||||
left = " ".join(build.unattached.get("FP-12-15", []))
|
||||
assert "구체콘크리트" in left and "봉상후렉시블" in left, left
|
||||
assert "구체콘크리트" not in left and "봉상후렉시블" not in left, left
|
||||
|
||||
|
||||
def test_콘크리트_집수정은_조립_조각으로_가고_막힌_까닭이_드러남() -> None:
|
||||
|
||||
@@ -29,10 +29,13 @@ def test_휘발유_기계는_휘발유값_경유_기계는_경유값() -> None:
|
||||
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 (
|
||||
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-경유"}
|
||||
|
||||
|
||||
@@ -66,7 +69,11 @@ def test_진동기_원문값_되살림() -> None:
|
||||
|
||||
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")}
|
||||
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
|
||||
@@ -89,7 +96,7 @@ def test_중기경비_장은_갈래대로_잡품과_손료계수() -> None:
|
||||
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 and sheets["X-0201-0070"]["loss_coefficient"] == 2085
|
||||
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