feat(B09): 자원 축 재생성 + 마스터 파일 지문 기록

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 00:14:02 +09:00
co-authored by Claude Opus 5
parent 2b21c2ff5c
commit 01691d4c14
3 changed files with 107 additions and 4 deletions
@@ -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",
@@ -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
}
}
@@ -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",