From 01691d4c14d3f6a2c5c5bfa99547ac5afdb48a76 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 00:14:02 +0900 Subject: [PATCH] =?UTF-8?q?feat(B09):=20=EC=9E=90=EC=9B=90=20=EC=B6=95=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=EC=84=B1=20+=20=EB=A7=88=EC=8A=A4=ED=84=B0?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=A7=80=EB=AC=B8=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - B08 공종 마스터 재생성(ab39c174) 반영 — 자원 축 116 → 119 줄, 미판정 83 → 76. 새로 붙은 것: 강관동바리(형틀목공·보통인부) · 신구 BOX접합(미장공) - 기존 119 줄 중 **수치가 바뀐 줄 0건** — 밑수 판정 교정이 기존 소요량을 건드리지 않았음. 일위대가 총액 분포도 그대로 (최소 233.4 · 중앙 86,034.0 · 최대 1,916,466.0, 의심 저가 0건) - 산출물에 `source_master_file`(마스터 파일 sha256) 추가 — 기존 `source_dataset_version` 은 품셈 원판 지문이라 마스터를 다시 생성해도 안 움직여, 낡은 파생물을 드러내지 못했음 검증: pytest 136 통과, 새 공종 2건 일위대가 적층 확인 (강관동바리 27,908.7 · 신구 BOX접합 33,273.1) Co-Authored-By: Claude Opus 5 (1M context) --- B09_Estimation/B09_Estimation_ResourceAxis.py | 21 ++++++++ .../resource_axis_2026-01-01.json | 48 +++++++++++++++++-- .../unmatched_2026-01-01.json | 42 ++++++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/B09_Estimation/B09_Estimation_ResourceAxis.py b/B09_Estimation/B09_Estimation_ResourceAxis.py index 1c9e4525..84b72ba5 100644 --- a/B09_Estimation/B09_Estimation_ResourceAxis.py +++ b/B09_Estimation/B09_Estimation_ResourceAxis.py @@ -20,6 +20,7 @@ from __future__ import annotations +import hashlib import json import os import re @@ -526,6 +527,23 @@ def build_resource_axis(master: dict[str, Any], catalog: ResourceCatalog) -> Axi OUTPUT_SUBPATH = ("resources", "data_cost_resource_axis") +def _master_file_fingerprint(master: dict[str, Any]) -> dict[str, str]: + """공종 마스터 **파일 자체**의 지문. 낡은 파생물을 드러내는 유일한 근거다. + + `dataset_version`(품셈 원판 지문)은 마스터가 다시 생성돼도 그대로라, 그것만 + 적어 두면 「내 자원 축이 옛 마스터에서 나왔다」는 사실이 안 보인다. + """ + effective_date = master.get("effective_date", "") + file_name = f"work_item_master_{effective_date}.json" + path = os.path.join(_project_root(), *_MASTER_SUBPATH, file_name) + try: + with open(path, "rb") as handle: + digest = hashlib.sha256(handle.read()).hexdigest() + except OSError: + return {"file": file_name, "sha256": ""} + return {"file": file_name, "sha256": digest} + + def write_resource_axis( result: AxisResult, master: dict[str, Any], @@ -546,6 +564,9 @@ def write_resource_axis( "effective_date": effective_date, # 어느 공종 축 판에 붙인 것인지 — 세 쪽을 그대로 옮겨 적는다(PLAN 9-2). "source_dataset_version": master.get("dataset_version", {}), + # ⚠ 위 지문은 **품셈 원판**의 것이라 B08 이 마스터를 다시 생성해도 안 움직인다. + # 낡음을 실제로 드러내려면 **마스터 파일 자체의 지문**이 있어야 한다. + "source_master_file": _master_file_fingerprint(master), "policy": { "axis": "resource_only", "work_item_axis_owner": "B08", 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 5774d43a..7b3a2e23 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 @@ -1015,6 +1015,30 @@ "resource_spec": "", "work_item_code": "FP-12-18" }, + { + "amount": "0.07", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0367", + "raw_row_index": 3, + "resource_code": "1007", + "resource_kind": "labor", + "resource_name": "형틀목공", + "resource_spec": "", + "work_item_code": "FP-12-20" + }, + { + "amount": "0.05", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0367", + "raw_row_index": 4, + "resource_code": "1002", + "resource_kind": "labor", + "resource_name": "보통인부", + "resource_spec": "", + "work_item_code": "FP-12-20" + }, { "amount": "0.034", "amount_unit": "", @@ -1087,6 +1111,18 @@ "resource_spec": "", "work_item_code": "FP-12-24-01" }, + { + "amount": "0.12", + "amount_unit": "", + "pum_form": "requirement", + "pum_table_id": "F0378", + "raw_row_index": 2, + "resource_code": "1027", + "resource_kind": "labor", + "resource_name": "미장공", + "resource_spec": "", + "work_item_code": "FP-12-28" + }, { "amount": "0.004", "amount_unit": "", @@ -1407,13 +1443,17 @@ "file": "pum_forest_2026.json", "sha256": "111208fd482dcfc3b8c8dd58004b153382fc46555d0d00985c19b45ebbc2e0dd" }, + "source_master_file": { + "file": "work_item_master_2026-01-01.json", + "sha256": "593653135d5a2871180e7a3921f9238629b275438ef47230412aa21e9bdd80c0" + }, "stats": { - "rows": 116, + "rows": 119, "skipped_forms": { "coefficient": 19, - "reference": 94, - "undetermined": 83 + "reference": 98, + "undetermined": 76 }, - "unmatched": 348 + "unmatched": 355 } } \ 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 915d08fc..b2414650 100644 --- a/resources/data_cost_resource_axis/unmatched_2026-01-01.json +++ b/resources/data_cost_resource_axis/unmatched_2026-01-01.json @@ -1772,6 +1772,24 @@ "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-12-18" }, + { + "cell": "강관 동바리", + "pum_table_id": "F0367", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-12-20" + }, + { + "cell": "외관(60.6mm×2.3mm)", + "pum_table_id": "F0367", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-12-20" + }, + { + "cell": "잡재료비(재료비의)", + "pum_table_id": "F0367", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-12-20" + }, { "cell": "아스팔트(㏊-500)", "pum_table_id": "F0368", @@ -1844,6 +1862,30 @@ "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", "work_item_code": "FP-12-27-02" }, + { + "cell": "실런트", + "pum_table_id": "F0377", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-12-27-03" + }, + { + "cell": "인력(설치비)", + "pum_table_id": "F0377", + "reason": "카탈로그에 없는 이름 (기계·자재 카탈로그 미확보 포함)", + "work_item_code": "FP-12-27-03" + }, + { + "cell": "에폭시 접착제", + "pum_table_id": "F0378", + "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음", + "work_item_code": "FP-12-28" + }, + { + "cell": "시너", + "pum_table_id": "F0378", + "reason": "규격이 없어 같은 이름 여럿 중 고를 수 없음", + "work_item_code": "FP-12-28" + }, { "cell": "결속선(R-0.9mm)", "pum_table_id": "F0387",