From 7ea25982635c73d930f1740e600240dfd6b43993 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 22:40:49 +0900 Subject: [PATCH] =?UTF-8?q?fix(b09):=20=ED=9C=98=EB=B0=9C=EC=9C=A0=20?= =?UTF-8?q?=EA=B8=B0=EA=B3=84=EC=97=90=20=EA=B2=BD=EC=9C=A0=EA=B0=92?= =?UTF-8?q?=EC=9D=B4=20=EB=B6=99=EB=8D=98=20=EA=B2=83=20=E2=80=94=20?= =?UTF-8?q?=EC=9A=B4=EC=A0=84=EA=B2=BD=EB=B9=84=ED=91=9C=20=EC=97=B0?= =?UTF-8?q?=EB=A3=8C=20=EC=A2=85=EB=A5=98=EB=8C=80=EB=A1=9C=20=EC=9C=A0?= =?UTF-8?q?=EA=B0=80=20=C2=B7=20=EC=BD=98=ED=81=AC=EB=A6=AC=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=84=EB=8F=99=EA=B8=B0(4611)=20=EC=9B=90=EB=AC=B8=EA=B0=92?= =?UTF-8?q?=20=EB=90=98=EC=82=B4=EB=A0=A4=20=EC=A7=91=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B5=AC=EC=B2=B4=EC=BD=98=ED=81=AC=EB=A6=AC=ED=8A=B8=20?= =?UTF-8?q?=EA=B0=88=EB=9E=98=EB=A5=BC=20=EC=84=B8=EC=9B=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit · 연료 — `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) Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn --- B09_Estimation/B09_Estimation_MachineCost.py | 13 ++++++ .../B09_Estimation_MachineExpenseSheet.py | 28 +++++++++++-- .../B09_Estimation_MachineOperating.py | 19 ++++++--- B09_Estimation/B09_Estimation_RockLoss.py | 19 ++++++--- B09_Estimation/B09_Estimation_UnitPrice.py | 42 +++++++++++-------- .../data_aliases/aliases_2026-01-01.json | 8 ++++ .../data_work_item_master/_manifest.json | 6 +-- .../work_item_master_2026-01-01.json | 4 +- resources/tester/test_b08_basin_composite.py | 9 ++-- .../tester/test_b09_fuel_kind_vibrator.py | 19 ++++++--- 10 files changed, 120 insertions(+), 47 deletions(-) diff --git a/B09_Estimation/B09_Estimation_MachineCost.py b/B09_Estimation/B09_Estimation_MachineCost.py index 3c1b5576..2e62c4cc 100644 --- a/B09_Estimation/B09_Estimation_MachineCost.py +++ b/B09_Estimation/B09_Estimation_MachineCost.py @@ -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( diff --git a/B09_Estimation/B09_Estimation_MachineExpenseSheet.py b/B09_Estimation/B09_Estimation_MachineExpenseSheet.py index 46f756cf..7a80b66f 100644 --- a/B09_Estimation/B09_Estimation_MachineExpenseSheet.py +++ b/B09_Estimation/B09_Estimation_MachineExpenseSheet.py @@ -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), diff --git a/B09_Estimation/B09_Estimation_MachineOperating.py b/B09_Estimation/B09_Estimation_MachineOperating.py index 9cc5f298..06e7bcd2 100644 --- a/B09_Estimation/B09_Estimation_MachineOperating.py +++ b/B09_Estimation/B09_Estimation_MachineOperating.py @@ -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)) diff --git a/B09_Estimation/B09_Estimation_RockLoss.py b/B09_Estimation/B09_Estimation_RockLoss.py index b740a9cc..756df6aa 100644 --- a/B09_Estimation/B09_Estimation_RockLoss.py +++ b/B09_Estimation/B09_Estimation_RockLoss.py @@ -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: diff --git a/B09_Estimation/B09_Estimation_UnitPrice.py b/B09_Estimation/B09_Estimation_UnitPrice.py index bac57652..100028f0 100644 --- a/B09_Estimation/B09_Estimation_UnitPrice.py +++ b/B09_Estimation/B09_Estimation_UnitPrice.py @@ -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 「잡품 단가 칸 = 연료 diff --git a/resources/data_aliases/aliases_2026-01-01.json b/resources/data_aliases/aliases_2026-01-01.json index 304af9cd..b41c5f8a 100644 --- a/resources/data_aliases/aliases_2026-01-01.json +++ b/resources/data_aliases/aliases_2026-01-01.json @@ -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·소림", diff --git a/resources/data_work_item_master/_manifest.json b/resources/data_work_item_master/_manifest.json index b991f144..07545afd 100644 --- a/resources/data_work_item_master/_manifest.json +++ b/resources/data_work_item_master/_manifest.json @@ -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", diff --git a/resources/data_work_item_master/work_item_master_2026-01-01.json b/resources/data_work_item_master/work_item_master_2026-01-01.json index 5388da5c..fef4885e 100644 --- a/resources/data_work_item_master/work_item_master_2026-01-01.json +++ b/resources/data_work_item_master/work_item_master_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": [ + "구체콘크리트", "버림콘크리트" ] }, diff --git a/resources/tester/test_b08_basin_composite.py b/resources/tester/test_b08_basin_composite.py index 02f939f1..8f2bcc42 100644 --- a/resources/tester/test_b08_basin_composite.py +++ b/resources/tester/test_b08_basin_composite.py @@ -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: diff --git a/resources/tester/test_b09_fuel_kind_vibrator.py b/resources/tester/test_b09_fuel_kind_vibrator.py index 5e48d47b..5a2ed1f5 100644 --- a/resources/tester/test_b09_fuel_kind_vibrator.py +++ b/resources/tester/test_b09_fuel_kind_vibrator.py @@ -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"])