From 991f7e31b1fba7d3b37927443cdceff1d9facd49 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 7 Sep 2026 23:55:09 +0900 Subject: [PATCH] =?UTF-8?q?fix(B09):=20=EB=B6=84=EB=A5=98=20=EB=94=B1?= =?UTF-8?q?=EC=A7=80=20=ED=96=89=EC=97=90=EC=84=9C=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EB=91=98=EC=A7=B8=20=EC=B9=B8=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=BD=EC=9D=8C=20=E2=80=94=20=EC=9E=90=EC=9E=AC=C2=B7?= =?UTF-8?q?=EC=9E=A5=EB=B9=84=EA=B0=80=20=ED=86=B5=EC=A7=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B9=A0=EC=A7=80=EB=8D=98=20=EC=9E=90=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배분 창이 「초류종자살포 배합이 네 일위대가에 있나」를 물어 확인하다 **구멍을 찾음.** - 품셈 표 중 **첫 칸이 분류 딱지**(「자재」·「장비」)이고 **이름이 둘째 칸**인 것이 있음: `['자재', '종 자', '', 'kg', '0.025']` · `['장비', '종자살포기', '2,500-3,000ℓ', …]`. 첫 칸만 보고 읽어 **그 표의 자재·장비가 통째로 빠지고 있었음** — 씨앗뿜어붙이기에서 종자·비료·피복제·침식안정제·색소·장비 3종이 다 빠지고 **보통인부 한 줄만** 남았음. - 첫 칸이 분류 딱지면 이름을 둘째 칸에서 읽고, 값도 그 뒤 칸에서 찾게 고침. - **매칭 106 → 116**(노무 100 → 110). 산출 파일 다시 냄. **⚠ 그래도 배합은 아직 못 실림 — 원인은 사급 자재 단가 미결임** - 종자·복합비료·화이버·합성접착제·색소가 **관급 카탈로그에 없음**(임도 자재가 관급에 없다는 앞선 발견과 같은 자리). 장비 3종(종자살포기·트럭·물탱크)도 기종 카탈로그에 없음. - **다만 이제 못 맞춘 목록에는 남음** — 구멍을 목록으로 드러내는 규칙 그대로. 시험으로 못 박음(`test_seeding_materials_are_listed_not_dropped`). - ⇒ 사면 4계열의 배합이 **B09 일위대가 몫**인 것은 맞으나, **사급 단가가 들어오기 전에는 못 세움.** 「자재총괄에 이을 것이 없다」는 메인 판정은 유지되고, 그 자재는 **㉡ 6번 슬롯 수동 입력 목록**으로 감(야면석·막자갈·고임돌·물구멍에 이어). 자체검증 — 신규 2건 포함 `pytest tmp/tests/ -q` **136 passed** · ruff 통과. Co-Authored-By: Claude Opus 5 (1M context) --- B09_Estimation/B09_Estimation_ResourceAxis.py | 16 ++- .../resource_axis_2026-01-01.json | 124 +++++++++++++++++- .../unmatched_2026-01-01.json | 74 +++++++---- 3 files changed, 185 insertions(+), 29 deletions(-) diff --git a/B09_Estimation/B09_Estimation_ResourceAxis.py b/B09_Estimation/B09_Estimation_ResourceAxis.py index 09b5f57b..1c9e4525 100644 --- a/B09_Estimation/B09_Estimation_ResourceAxis.py +++ b/B09_Estimation/B09_Estimation_ResourceAxis.py @@ -41,6 +41,13 @@ _CATALOG_SUBPATH = ("resources", "data_cost_input_value") _RE_SPEC = re.compile(r"[((]([^))]+)[))]|(\d+(?:\.\d+)?\s*(?:톤|ton|㎥|m3|㎡|㎜|mm|HP|kW))") _RE_NUMBER = re.compile(r"^-?\d+(?:,\d{3})*(?:\.\d+)?$") +#: 첫 칸이 **분류 딱지**이고 이름이 둘째 칸에 오는 표가 있다. +#: 예 — `['자재', '종 자', '', 'kg', '0.025']` · `['장비', '종자살포기', …]`. +#: 이 표를 첫 칸만 보고 읽으면 **자재·장비가 통째로 빠진다**(2026-09-07 실측 — +#: 씨앗뿜어붙이기에서 종자·비료·피복제·침식안정제·색소·장비 3종이 다 빠지고 +#: 보통인부 한 줄만 남았다). +_GROUP_LABELS = ("자재", "장비", "인력", "노무", "재료", "기계") + #: 표 머리글·소계 행의 첫 칸에 오는 말. 자원이 아니므로 `unmatched` 로도 안 올린다. #: 이것을 안 거르면 못 맞춘 목록이 머리글로 가득 차 **쓸 수 없는 목록**이 된다. #: ⚠ **부분일치로 보면 안 된다.** 「계」를 부분일치로 잡으면 `건설기계운전사`·`비계공`· @@ -447,11 +454,16 @@ def match_table( cells = [str(c) for c in row] if not cells: continue + # 첫 칸이 분류 딱지(「자재」·「장비」)면 **이름은 둘째 칸**이다. name_cell = cells[0] + value_cells = cells[1:] + if _normalize(name_cell) in _GROUP_LABELS and len(cells) > 1: + name_cell = cells[1] + value_cells = cells[2:] # 숫자 셀이 없는 행은 자원 줄이 아니다(제목·설명 행) — 목록에 안 올린다. amount_cell = next( - (parse_amount(c) for c in cells[1:] if parse_amount(c) is not None), None + (parse_amount(c) for c in value_cells if parse_amount(c) is not None), None ) if amount_cell is None: continue @@ -459,7 +471,7 @@ def match_table( # ⚠ **카탈로그 조회를 먼저 한다.** 이름 필터를 앞에 두면 필터가 넓을 때 # 정상 자원이 조용히 사라진다(2026-09-07 실측 — 부분일치 필터가 매칭 14건을 # 지우고 있었음). 카탈로그에 있는 이름은 **정의상 자원**이다. - entry = _resolve_cell(catalog, name_cell, cells) + entry = _resolve_cell(catalog, name_cell, [name_cell, *value_cells]) if entry is None: if is_non_resource_label(name_cell): continue # 머리글·소계 — 못 맞춘 목록에도 안 올린다 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 a1abf61c..5774d43a 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 @@ -211,6 +211,18 @@ "resource_spec": "", "work_item_code": "FP-05-23-03" }, + { + "amount": "0.0007", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0135", + "raw_row_index": 8, + "resource_code": "1038", + "resource_kind": "labor", + "resource_name": "조경공", + "resource_spec": "", + "work_item_code": "FP-05-24-01" + }, { "amount": "0.0004", "amount_unit": "", @@ -223,6 +235,18 @@ "resource_spec": "", "work_item_code": "FP-05-24-01" }, + { + "amount": "0.0007", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0136", + "raw_row_index": 8, + "resource_code": "1038", + "resource_kind": "labor", + "resource_name": "조경공", + "resource_spec": "", + "work_item_code": "FP-05-24-02" + }, { "amount": "0.0004", "amount_unit": "", @@ -235,6 +259,18 @@ "resource_spec": "", "work_item_code": "FP-05-24-02" }, + { + "amount": "0.002", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0139", + "raw_row_index": 1, + "resource_code": "1038", + "resource_kind": "labor", + "resource_name": "조경공", + "resource_spec": "", + "work_item_code": "FP-05-25" + }, { "amount": "0.0007", "amount_unit": "", @@ -415,6 +451,18 @@ "resource_spec": "", "work_item_code": "FP-09-05-01" }, + { + "amount": "0.02", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0251", + "raw_row_index": 0, + "resource_code": "1012", + "resource_kind": "labor", + "resource_name": "용접공", + "resource_spec": "", + "work_item_code": "FP-09-08-01" + }, { "amount": "0.08", "amount_unit": "", @@ -559,6 +607,30 @@ "resource_spec": "", "work_item_code": "FP-09-13-18" }, + { + "amount": "0.019", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0290", + "raw_row_index": 0, + "resource_code": "1002", + "resource_kind": "labor", + "resource_name": "보통인부", + "resource_spec": "", + "work_item_code": "FP-09-19-02" + }, + { + "amount": "0.0328", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0291", + "raw_row_index": 0, + "resource_code": "1002", + "resource_kind": "labor", + "resource_name": "보통인부", + "resource_spec": "", + "work_item_code": "FP-09-19-03" + }, { "amount": "0.80", "amount_unit": "", @@ -943,6 +1015,18 @@ "resource_spec": "", "work_item_code": "FP-12-18" }, + { + "amount": "0.034", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0368", + "raw_row_index": 1, + "resource_code": "1026", + "resource_kind": "labor", + "resource_name": "방수공", + "resource_spec": "", + "work_item_code": "FP-12-21" + }, { "amount": "0.04", "amount_unit": "", @@ -955,6 +1039,18 @@ "resource_spec": "", "work_item_code": "FP-12-21" }, + { + "amount": "0.003", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0370", + "raw_row_index": 2, + "resource_code": "1002", + "resource_kind": "labor", + "resource_name": "보통인부", + "resource_spec": "", + "work_item_code": "FP-12-23" + }, { "amount": "4.0", "amount_unit": "", @@ -991,6 +1087,30 @@ "resource_spec": "", "work_item_code": "FP-12-24-01" }, + { + "amount": "0.004", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0384", + "raw_row_index": 1, + "resource_code": "1002", + "resource_kind": "labor", + "resource_name": "보통인부", + "resource_spec": "", + "work_item_code": "FP-12-33" + }, + { + "amount": "3.8", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0387", + "raw_row_index": 1, + "resource_code": "1008", + "resource_kind": "labor", + "resource_name": "철근공", + "resource_spec": "", + "work_item_code": "FP-12-34-03" + }, { "amount": "2.2", "amount_unit": "", @@ -1288,12 +1408,12 @@ "sha256": "111208fd482dcfc3b8c8dd58004b153382fc46555d0d00985c19b45ebbc2e0dd" }, "stats": { - "rows": 106, + "rows": 116, "skipped_forms": { "coefficient": 19, "reference": 94, "undetermined": 83 }, - "unmatched": 344 + "unmatched": 348 } } \ 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 5143c743..915d08fc 100644 --- a/resources/data_cost_resource_axis/unmatched_2026-01-01.json +++ b/resources/data_cost_resource_axis/unmatched_2026-01-01.json @@ -417,7 +417,7 @@ "work_item_code": "FP-05-22-04" }, { - "cell": "자재", + "cell": "종 자", "pum_table_id": "F0135", "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-05-24-01" @@ -446,6 +446,12 @@ "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-05-24-01" }, + { + "cell": "종자살포기", + "pum_table_id": "F0135", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-05-24-01" + }, { "cell": "트 럭", "pum_table_id": "F0135", @@ -459,7 +465,7 @@ "work_item_code": "FP-05-24-01" }, { - "cell": "자재", + "cell": "종 자", "pum_table_id": "F0136", "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-05-24-02" @@ -488,6 +494,12 @@ "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-05-24-02" }, + { + "cell": "종자살포기", + "pum_table_id": "F0136", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-05-24-02" + }, { "cell": "트 럭", "pum_table_id": "F0136", @@ -501,7 +513,7 @@ "work_item_code": "FP-05-24-02" }, { - "cell": "자재", + "cell": "거 적", "pum_table_id": "F0139", "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-05-25" @@ -999,7 +1011,7 @@ "work_item_code": "FP-09-04-01" }, { - "cell": "자재", + "cell": "폭 약", "pum_table_id": "F0243", "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-09-05-01" @@ -1016,6 +1028,18 @@ "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-09-05-01" }, + { + "cell": "화 약 공", + "pum_table_id": "F0243", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-09-05-01" + }, + { + "cell": "착 암 기", + "pum_table_id": "F0243", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-09-05-01" + }, { "cell": "공기압축기", "pum_table_id": "F0243", @@ -1047,7 +1071,7 @@ "work_item_code": "FP-09-06-01" }, { - "cell": "자재", + "cell": "아세틸렌", "pum_table_id": "F0251", "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-09-08-01" @@ -1079,7 +1103,7 @@ { "cell": "아스팔트", "pum_table_id": "F0255", - "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음", "work_item_code": "FP-09-10-02" }, { @@ -1478,6 +1502,18 @@ "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-09-19-01" }, + { + "cell": "유압식백호우 (무한궤도,0.7㎥)", + "pum_table_id": "F0290", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-09-19-02" + }, + { + "cell": "공기압축기(3.5㎥/min)", + "pum_table_id": "F0291", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-09-19-03" + }, { "cell": "소형브레이커", "pum_table_id": "F0291", @@ -1601,19 +1637,19 @@ { "cell": "각 재", "pum_table_id": "F0336", - "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음", "work_item_code": "FP-12-04" }, { "cell": "철 선", "pum_table_id": "F0336", - "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음", "work_item_code": "FP-12-04" }, { "cell": "박 리 제", "pum_table_id": "F0336", - "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음", "work_item_code": "FP-12-04" }, { @@ -1737,13 +1773,13 @@ "work_item_code": "FP-12-18" }, { - "cell": "자재", + "cell": "아스팔트(㏊-500)", "pum_table_id": "F0368", - "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음", "work_item_code": "FP-12-21" }, { - "cell": "자재", + "cell": "부 직 포", "pum_table_id": "F0370", "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-12-23" @@ -1796,12 +1832,6 @@ "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-12-24-01" }, - { - "cell": "자재", - "pum_table_id": "F0376", - "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", - "work_item_code": "FP-12-27-02" - }, { "cell": "접착제", "pum_table_id": "F0376", @@ -1815,13 +1845,7 @@ "work_item_code": "FP-12-27-02" }, { - "cell": "재료", - "pum_table_id": "F0384", - "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", - "work_item_code": "FP-12-33" - }, - { - "cell": "자재", + "cell": "결속선(R-0.9mm)", "pum_table_id": "F0387", "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-12-34-03"