diff --git a/B09_Estimation/B09_Estimation_MachineCost.py b/B09_Estimation/B09_Estimation_MachineCost.py
new file mode 100644
index 00000000..81b0dca1
--- /dev/null
+++ b/B09_Estimation/B09_Estimation_MachineCost.py
@@ -0,0 +1,185 @@
+"""B09 원가계산 — 기계경비 (PLAN 9-3 의 `S` → `X` 두 단계).
+
+실무·교본이 같은 두 단계다 (신규 문서 5장 라-1 예제도 같은 모양):
+
+ S 취득가(천원) ──(손료계수)──► X 시간당 중기사용료
+ = 손료(경비) + 연료(재료) + 운전사(노무)
+
+이 모듈이 내는 것은 **시간당 사용료 한 시간분**이고, 3분할(재료·노무·경비)로 낸다.
+`B09_Estimation_PriceBook` 의 `MACHINE_BASE`(S) · `MACHINE_HOURLY`(X) 층에 그대로 앉는다.
+
+데이터 (`resources/data_cost_input_value/mach_base_2026.json`)
+ - `mach_price` **613 기종** — `machine_code` + `specification` 으로 규격이 갈린다.
+ - `mach_loss_coef` **387건** — 시간당 손료계수·내용시간·연간표준시간.
+ - ⚠ `mach_fuel_rate` **0건** · `mach_operator_map` **0건** — **비어 있다.**
+ 연료소모량과 기종별 운전사 직종은 품셈 본문에 있고, 아직 뽑히지 않았다.
+
+⚠ **그래서 이 모듈은 손료(경비)까지만 채우고, 연료·운전사는 「공백」으로 표시한다.**
+0 으로 때우면 **시간당 사용료가 절반 이하로 나오고 그대로 총액에 섞인다** — 실측
+비중이 노무 53 % · 재료 20 % · 경비 27 % 라 손료만으로는 4분의 1 남짓이다.
+"""
+
+from __future__ import annotations
+
+import json
+import os
+from dataclasses import dataclass, field
+from decimal import Decimal
+from typing import Any
+
+from B09_Estimation.B09_Estimation_PriceBook import Money3
+
+_CATALOG_SUBPATH = ("resources", "data_cost_input_value")
+_THOUSAND = Decimal(1000)
+
+
+class MachineCostError(LookupError):
+ """기계경비를 세울 수 없는 경우. 0 으로 때우지 않고 멈춘다."""
+
+
+def _project_root() -> str:
+ return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+def _read_json(file_name: str) -> dict[str, Any]:
+ with open(os.path.join(_project_root(), *_CATALOG_SUBPATH, file_name), encoding="utf-8") as h:
+ return json.load(h)
+
+
+@dataclass(frozen=True)
+class MachineSpec:
+ """기종 한 줄 — 규격까지 붙어야 한 대가 정해진다."""
+
+ machine_code: str
+ name: str
+ specification: str
+ price_thousand_krw: Decimal
+ loss_coefficient_per_hour: Decimal | None = None
+ economic_life_hours: int | None = None
+ annual_standard_hours: int | None = None
+
+ @property
+ def display_name(self) -> str:
+ return f"{self.name} {self.specification}".strip()
+
+
+@dataclass
+class MachineCatalog:
+ """613 기종. **이름만으로는 못 고른다** — 규격이 있어야 한 대가 정해진다."""
+
+ machines: dict[str, MachineSpec] = field(default_factory=dict)
+
+ def by_name(self, name: str) -> list[MachineSpec]:
+ return [m for m in self.machines.values() if m.name == name]
+
+ def resolve(self, name: str, specification: str) -> MachineSpec | None:
+ """이름 + 규격으로 한 대를 고른다. 규격이 없으면 **고르지 않는다**."""
+ found = self.by_name(name)
+ if not found:
+ return None
+ if len(found) == 1 and not specification:
+ return found[0]
+ narrowed = [m for m in found if m.specification == specification]
+ return narrowed[0] if len(narrowed) == 1 else None
+
+ def get(self, machine_code: str) -> MachineSpec:
+ try:
+ return self.machines[machine_code]
+ except KeyError as exc:
+ raise MachineCostError(f"기종 카탈로그에 없는 코드입니다: {machine_code}") from exc
+
+
+def load_machine_catalog(file_name: str = "mach_base_2026.json") -> MachineCatalog:
+ """취득가 613건에 손료계수 387건을 붙여 카탈로그 한 벌을 만든다."""
+ variables = _read_json(file_name)["variables"]
+ coefficients = {
+ r["machine_code"]: r for r in variables.get("mach_loss_coef", {}).get("records", [])
+ }
+
+ catalog = MachineCatalog()
+ for row in variables.get("mach_price", {}).get("records", []):
+ code = row["machine_code"]
+ coefficient = coefficients.get(code, {})
+ catalog.machines[code] = MachineSpec(
+ machine_code=code,
+ name=row["machine_name"],
+ specification=str(row.get("specification", "")),
+ price_thousand_krw=Decimal(str(row["price_thousand_krw"])),
+ loss_coefficient_per_hour=(
+ Decimal(str(coefficient["loss_coefficient_per_hour"]))
+ if "loss_coefficient_per_hour" in coefficient
+ else None
+ ),
+ economic_life_hours=coefficient.get("economic_life_hours"),
+ annual_standard_hours=coefficient.get("annual_standard_hours"),
+ )
+ return catalog
+
+
+def hourly_loss_cost(machine: MachineSpec) -> Decimal:
+ """시간당 손료 = 취득가 × 손료계수.
+
+ 취득가가 **천원 단위**라 원으로 환산한다 — 이 단위를 놓치면 1,000배 틀린다.
+ """
+ if machine.loss_coefficient_per_hour is None:
+ raise MachineCostError(
+ f"{machine.display_name}: 손료계수가 없습니다 (취득가만 있는 기종 226건 중 하나)"
+ )
+ return machine.price_thousand_krw * _THOUSAND * machine.loss_coefficient_per_hour
+
+
+@dataclass
+class HourlyMachineCost:
+ """시간당 중기사용료 — 3분할과 **채우지 못한 성분 목록**을 함께 낸다."""
+
+ machine: MachineSpec
+ money: Money3
+ gaps: list[str] = field(default_factory=list)
+
+ @property
+ def is_complete(self) -> bool:
+ return not self.gaps
+
+
+def hourly_machine_cost(
+ machine: MachineSpec,
+ *,
+ fuel_liters_per_hour: Decimal | None = None,
+ fuel_price_per_liter: Decimal | None = None,
+ operator_daily_wage: Decimal | None = None,
+ operator_hours_per_day: int = 8,
+) -> HourlyMachineCost:
+ """시간당 사용료 한 시간분.
+
+ 손료(경비)는 카탈로그로 바로 나온다. **연료(재료)·운전사(노무)는 값을 주지 않으면
+ 0 으로 때우지 않고 `gaps` 에 적어 돌려준다** — 빠진 채로 총액에 섞이는 것이
+ 이 자리에서 제일 위험하다.
+ """
+ gaps: list[str] = []
+ expense = hourly_loss_cost(machine)
+
+ material = Decimal(0)
+ if fuel_liters_per_hour is None or fuel_price_per_liter is None:
+ # TODO(미결 PLAN 9-6): `mach_fuel_rate` 0건 — 연료소모량이 품셈 본문에만 있다.
+ gaps.append("연료소모량(L/hr) 미확보 — 재료비 성분 비어 있음")
+ else:
+ material = fuel_liters_per_hour * fuel_price_per_liter
+
+ labor = Decimal(0)
+ if operator_daily_wage is None:
+ # TODO(미결 PLAN 9-6): `mach_operator_map` 0건 — 기종별 운전사 직종이 품셈 본문에만 있다.
+ gaps.append("운전사 직종 매핑 미확보 — 노무비 성분 비어 있음")
+ else:
+ labor = operator_daily_wage / Decimal(operator_hours_per_day)
+
+ return HourlyMachineCost(
+ machine=machine,
+ money=Money3(material=material, labor=labor, expense=expense),
+ gaps=gaps,
+ )
+
+
+def catalog_gaps(catalog: MachineCatalog) -> dict[str, int]:
+ """카탈로그 자체의 공백 — 몇 기종이 손료계수를 못 가졌나."""
+ missing = [m for m in catalog.machines.values() if m.loss_coefficient_per_hour is None]
+ return {"machines": len(catalog.machines), "without_loss_coefficient": len(missing)}
diff --git a/B09_Estimation/B09_Estimation_ResourceAxis.py b/B09_Estimation/B09_Estimation_ResourceAxis.py
index d121d20b..ab7c6d58 100644
--- a/B09_Estimation/B09_Estimation_ResourceAxis.py
+++ b/B09_Estimation/B09_Estimation_ResourceAxis.py
@@ -169,6 +169,30 @@ def load_labor_catalog(file_name: str = "labor_const_2026-01-01.json") -> Resour
return ResourceCatalog(entries=entries, aliases=dict(variables.get("aliases", {})))
+def load_machine_catalog_entries(file_name: str = "mach_base_2026.json") -> list[CatalogEntry]:
+ """기종 카탈로그 613건을 매칭용 항목으로 편다.
+
+ ⚠ **규격이 매칭의 일부**다 — 「굴착기(무한궤도)」만 23 규격이라 이름만으로는
+ 한 대가 안 정해진다(지시 4번). `CatalogEntry.spec` 에 규격을 실어 둔다.
+ """
+ from B09_Estimation.B09_Estimation_MachineCost import load_machine_catalog
+
+ catalog = load_machine_catalog(file_name)
+ return [
+ CatalogEntry(code=m.machine_code, name=m.name, kind="machine", spec=m.specification)
+ for m in catalog.machines.values()
+ ]
+
+
+def load_combined_catalog() -> ResourceCatalog:
+ """노임 + 기종을 한 벌로. 자재는 카탈로그가 아직 없다."""
+ labor = load_labor_catalog()
+ return ResourceCatalog(
+ entries=[*labor.entries, *load_machine_catalog_entries()],
+ aliases=labor.aliases,
+ )
+
+
def load_work_item_master(
file_name: str = "work_item_master_2026-01-01.json",
) -> dict[str, Any]:
@@ -191,6 +215,57 @@ def split_name_and_spec(cell: str) -> tuple[str, str]:
return name, spec
+#: 기종 이름의 괄호 안은 **규격과 형식이 섞여** 있다 —
+#: 「굴착기(무한궤도, 0.7㎥)」 는 이름 `굴착기(무한궤도)` + 규격 `0.7` 이다.
+#: 형식(무한궤도·타이어)은 이름의 일부이고, 숫자가 든 조각만 규격이다.
+_RE_MACHINE = re.compile(r"^(?P[^()()]+)[((](?P[^))]*)[))]")
+_RE_SIZE_TOKEN = re.compile(r"\d+(?:\.\d+)?")
+
+
+def parse_machine_cell(cell: str) -> tuple[str, str]:
+ """기종 셀을 카탈로그 이름과 규격으로 가른다.
+
+ 「굴착기(무한궤도, 0.7㎥)」 → (`굴착기(무한궤도)`, `0.7`)
+ 「굴착기 (무한궤도)」 → (`굴착기(무한궤도)`, ``) ← 규격은 옆 칸에 있다
+ 괄호가 없으면 원문 그대로 돌려준다.
+ """
+ text = _normalize(cell)
+ match = _RE_MACHINE.match(text)
+ if not match:
+ return text, ""
+ base = match.group("base")
+ parts = [p for p in re.split(r"[,·/]", match.group("inner")) if p]
+ form_parts = [p for p in parts if not _RE_SIZE_TOKEN.search(p)]
+ size_parts = [p for p in parts if _RE_SIZE_TOKEN.search(p)]
+ name = f"{base}({','.join(form_parts)})" if form_parts else base
+ spec = ""
+ if size_parts:
+ found = _RE_SIZE_TOKEN.search(size_parts[0])
+ spec = found.group(0) if found else ""
+ return name, spec
+
+
+def spec_candidates(cells: list[str]) -> list[str]:
+ """규격이 옆 칸에 있는 표가 많다 — 뒷 칸들에서 규격 후보를 모은다.
+
+ 실측 배치: `['굴착기+부착용집게', '0.2㎥', 'hr', '2.71', …]` ·
+ `['굴착기 (무한궤도)', '굴착기(무한궤도,0.2㎥)', 'hr', '0.80', …]`
+ """
+ found: list[str] = []
+ for cell in cells:
+ text = _normalize(cell)
+ if not text or len(text) > 30:
+ continue
+ _, spec = parse_machine_cell(text)
+ if spec:
+ found.append(spec)
+ continue
+ token = _RE_SIZE_TOKEN.fullmatch(text.rstrip("㎥㎡톤tonm³"))
+ if token:
+ found.append(token.group(0))
+ return found
+
+
def parse_amount(cell: str) -> Decimal | None:
"""숫자 셀만 값으로 본다. 숫자가 아니면 None — 억지로 읽지 않는다."""
text = _normalize(cell)
@@ -276,6 +351,34 @@ class AxisResult:
skipped_forms: dict[str, int] = field(default_factory=dict)
+def _resolve_cell(catalog: ResourceCatalog, name_cell: str, cells: list[str]):
+ """셀 하나를 카탈로그 한 줄로 푼다 — 세 가지 모양을 차례로 시도한다.
+
+ ① 셀 전체가 곧 이름 (「보통인부」)
+ ② 기종 셀 (「굴착기(무한궤도, 0.7㎥)」 → 이름 + 규격)
+ ③ 규격이 **옆 칸**에 있는 표 (「굴착기 (무한궤도)」 | 「0.7㎥」)
+ """
+ machine_name, machine_spec = parse_machine_cell(name_cell)
+ plain_name, plain_spec = split_name_and_spec(name_cell)
+
+ for name, spec in ((machine_name, machine_spec), (plain_name, plain_spec)):
+ if not name:
+ continue
+ entry = catalog.resolve(name, spec)
+ if entry is not None:
+ return entry
+
+ # 이름은 맞는데 규격이 없어 못 고른 경우 — 옆 칸에서 규격을 찾는다.
+ for name in (machine_name, plain_name):
+ if len(catalog.by_name(name)) <= 1:
+ continue
+ for candidate in spec_candidates(cells[1:]):
+ entry = catalog.resolve(name, candidate)
+ if entry is not None:
+ return entry
+ return None
+
+
def match_table(
node: dict[str, Any],
table: dict[str, Any],
@@ -307,9 +410,9 @@ def match_table(
if amount_cell is None:
continue
- name, spec = split_name_and_spec(name_cell)
- entry = catalog.resolve(name, spec)
+ entry = _resolve_cell(catalog, name_cell, cells)
if entry is None:
+ name, spec = split_name_and_spec(name_cell)
found = catalog.by_name(name)
if len(found) > 1:
reason = "규격이 없어 같은 이름 여럿 중 고를 수 없음"
@@ -338,7 +441,7 @@ def match_table(
resource_kind=entry.kind,
resource_code=entry.code,
resource_name=entry.name,
- resource_spec=entry.spec or spec,
+ resource_spec=entry.spec,
amount=amount,
amount_unit=unit,
raw_row_index=index,
diff --git a/resources/data_cost_resource_axis/resource_axis_2026-01-01.json b/resources/data_cost_resource_axis/resource_axis_2026-01-01.json
index f6c2704f..48e47176 100644
--- a/resources/data_cost_resource_axis/resource_axis_2026-01-01.json
+++ b/resources/data_cost_resource_axis/resource_axis_2026-01-01.json
@@ -112,7 +112,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-05-11"
},
{
@@ -304,7 +304,7 @@
"resource_code": "1003",
"resource_kind": "labor",
"resource_name": "특별인부",
- "resource_spec": "체인톱 사용",
+ "resource_spec": "",
"work_item_code": "FP-06-05"
},
{
@@ -364,7 +364,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-03-01"
},
{
@@ -412,7 +412,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-12-02"
},
{
@@ -424,7 +424,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-12-03"
},
{
@@ -436,7 +436,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-07"
},
{
@@ -448,7 +448,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-08"
},
{
@@ -460,7 +460,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-09"
},
{
@@ -472,7 +472,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-13"
},
{
@@ -484,7 +484,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-14"
},
{
@@ -496,7 +496,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-15"
},
{
@@ -508,7 +508,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-16"
},
{
@@ -520,7 +520,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-17"
},
{
@@ -532,9 +532,21 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-09-13-18"
},
+ {
+ "amount": "0.80",
+ "amount_unit": "",
+ "pum_form": "requirement",
+ "pum_table_id": "F0294",
+ "raw_row_index": 0,
+ "resource_code": "0201-0020",
+ "resource_kind": "machine",
+ "resource_name": "굴착기(무한궤도)",
+ "resource_spec": "0.2",
+ "work_item_code": "FP-09-21"
+ },
{
"amount": "0.03",
"amount_unit": "",
@@ -547,6 +559,18 @@
"resource_spec": "",
"work_item_code": "FP-09-21"
},
+ {
+ "amount": "0.46",
+ "amount_unit": "",
+ "pum_form": "requirement",
+ "pum_table_id": "F0294",
+ "raw_row_index": 2,
+ "resource_code": "0201-0070",
+ "resource_kind": "machine",
+ "resource_name": "굴착기(무한궤도)",
+ "resource_spec": "0.7",
+ "work_item_code": "FP-09-21"
+ },
{
"amount": "0.03",
"amount_unit": "",
@@ -568,7 +592,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-10-06-02"
},
{
@@ -580,7 +604,7 @@
"resource_code": "1003",
"resource_kind": "labor",
"resource_name": "특별인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-10-07-01"
},
{
@@ -592,7 +616,7 @@
"resource_code": "1003",
"resource_kind": "labor",
"resource_name": "특별인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-10-07-02"
},
{
@@ -604,7 +628,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-10-07-02"
},
{
@@ -616,7 +640,7 @@
"resource_code": "1003",
"resource_kind": "labor",
"resource_name": "특별인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-10-07-03"
},
{
@@ -628,7 +652,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-10-07-03"
},
{
@@ -640,7 +664,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-10-07-04"
},
{
@@ -844,7 +868,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-12-24-02"
},
{
@@ -856,7 +880,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-12-26"
},
{
@@ -868,7 +892,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-12-24-01"
},
{
@@ -904,7 +928,7 @@
"resource_code": "1002",
"resource_kind": "labor",
"resource_name": "보통인부",
- "resource_spec": "인",
+ "resource_spec": "",
"work_item_code": "FP-13-02-01"
},
{
@@ -943,6 +967,18 @@
"resource_spec": "",
"work_item_code": "FP-13-06-02"
},
+ {
+ "amount": "4.80",
+ "amount_unit": "",
+ "pum_form": "requirement",
+ "pum_table_id": "F0420",
+ "raw_row_index": 4,
+ "resource_code": "0201-0080",
+ "resource_kind": "machine",
+ "resource_name": "굴착기(무한궤도)",
+ "resource_spec": "0.8",
+ "work_item_code": "FP-13-06-02"
+ },
{
"amount": "1.86",
"amount_unit": "",
@@ -955,6 +991,18 @@
"resource_spec": "",
"work_item_code": "FP-13-06-03"
},
+ {
+ "amount": "6.86",
+ "amount_unit": "",
+ "pum_form": "requirement",
+ "pum_table_id": "F0421",
+ "raw_row_index": 4,
+ "resource_code": "0201-0080",
+ "resource_kind": "machine",
+ "resource_name": "굴착기(무한궤도)",
+ "resource_spec": "0.8",
+ "work_item_code": "FP-13-06-03"
+ },
{
"amount": "0.58",
"amount_unit": "",
@@ -967,6 +1015,18 @@
"resource_spec": "",
"work_item_code": "FP-13-07-01"
},
+ {
+ "amount": "2.40",
+ "amount_unit": "",
+ "pum_form": "requirement",
+ "pum_table_id": "F0422",
+ "raw_row_index": 4,
+ "resource_code": "0201-0080",
+ "resource_kind": "machine",
+ "resource_name": "굴착기(무한궤도)",
+ "resource_spec": "0.8",
+ "work_item_code": "FP-13-07-01"
+ },
{
"amount": "1.01",
"amount_unit": "",
@@ -1048,12 +1108,12 @@
"sha256": "111208fd482dcfc3b8c8dd58004b153382fc46555d0d00985c19b45ebbc2e0dd"
},
"stats": {
- "rows": 86,
+ "rows": 91,
"skipped_forms": {
"coefficient": 19,
"reference": 94,
"undetermined": 83
},
- "unmatched": 274
+ "unmatched": 269
}
}
\ No newline at end of file
diff --git a/resources/data_cost_resource_axis/unmatched_2026-01-01.json b/resources/data_cost_resource_axis/unmatched_2026-01-01.json
index 06884b2a..d8aded08 100644
--- a/resources/data_cost_resource_axis/unmatched_2026-01-01.json
+++ b/resources/data_cost_resource_axis/unmatched_2026-01-01.json
@@ -1112,18 +1112,6 @@
"reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
"work_item_code": "FP-09-19-03"
},
- {
- "cell": "굴착기 (무한궤도)",
- "pum_table_id": "F0294",
- "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
- "work_item_code": "FP-09-21"
- },
- {
- "cell": "굴착기(무한궤도,0.7㎥)",
- "pum_table_id": "F0294",
- "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
- "work_item_code": "FP-09-21"
- },
{
"cell": "콘크리트",
"pum_table_id": "F0313",
@@ -1289,7 +1277,7 @@
{
"cell": "절단기",
"pum_table_id": "F0348",
- "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
+ "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음",
"work_item_code": "FP-12-11-02"
},
{
@@ -1484,24 +1472,6 @@
"reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
"work_item_code": "FP-13-06-01"
},
- {
- "cell": "굴착기 (무한궤도)",
- "pum_table_id": "F0420",
- "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
- "work_item_code": "FP-13-06-02"
- },
- {
- "cell": "굴착기 (무한궤도)",
- "pum_table_id": "F0421",
- "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
- "work_item_code": "FP-13-06-03"
- },
- {
- "cell": "굴착기 (무한궤도)",
- "pum_table_id": "F0422",
- "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)",
- "work_item_code": "FP-13-07-01"
- },
{
"cell": "굴 삭 기 (무한궤도)",
"pum_table_id": "F0423",