From c58c38142cac990531b608268f0e26c7478f97fa Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 16 Sep 2026 11:57:21 +0900 Subject: [PATCH] =?UTF-8?q?fix(z01):=20=EC=9E=90=EC=9E=AC=200=EC=9B=90=207?= =?UTF-8?q?0=20=EC=A4=84=20=E2=80=94=20=EA=B0=92=EC=9D=B4=20=EC=95=88=20?= =?UTF-8?q?=EC=8B=A4=EB=A6=B0=20=EA=B3=B5=EC=8B=9C=EB=A5=BC=20=EA=B1=B4?= =?UTF-8?q?=EB=84=88=EB=9B=B0=EA=B3=A0=20=EC=95=9E=20=EC=A0=9C=EA=B0=92=20?= =?UTF-8?q?=EC=B1=84=ED=83=9D=20=C2=B7=20=EC=82=AC=EC=9C=A0=20=EB=82=A8?= =?UTF-8?q?=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 까닭 — 고르기 규칙이 「물품코드마다 가장 나중 공시」인데 값이 안 실린 공시(`prce=0`)가 옛 제값을 덮음. 각형강관 25*25*1.4t `1,460 → 1,500 → 0`. 「가격이 0」이 아니라 **그 공시에 값이 안 실린 것**임 - 새 규칙 `_build_mat_price.py` — **원천 스냅숏은 읽기만** · 마스터 0원 줄만 손댐 · 6,999 중 **70 줄만 바뀌고 나머지 6,929 줄은 한 글자도 안 바뀜**(줄 수 그대로) · 0원 0 남음 - ⚠ 같은 날짜에 0 과 제값이 함께 실림(2025-04-24) — 날짜만 보면 0 을 집음. **0 을 건너뛴 뒤** 가장 나중 공시를 고름 - 조용히 안 바꿈 — 줄마다 `price_adopted_reason` 「○ 공시에 값이 없어 ○ 값을 씀」 · 공시 **날짜·번호도 채택한 공시의 것**으로(어느 판 값인지 화면에서 보임) · `selection_policy` 에 0 건너뛰기 명시 · 못 고칠 줄 자리 `zero_price_unrecovered`(오늘은 빔) - 이름표에 새 열 「값을 고른 사유」 · 마스터가 바뀌어 폴더 지문(`_manifest`) sha·크기 갱신 - 새 시험 7건 — 0원이 남는지 · 사유가 붙는지 · 날짜·번호가 채택 공시와 같은지 · 같은 날 0 과 제값이 섞여도 제값을 집는지 · 0원 아닌 줄을 안 건드렸는지 · 앞 제값이 없으면 빨강이 아니라 목록에 남는지 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FFCnEYNH4tsS2MbHvzBhZk --- .../data_cost_input_value/_build_mat_price.py | 100 +++ .../data_cost_input_value/_manifest.json | 4 +- .../mat_price_public_2026-08-14.json | 638 ++++++++++-------- .../data_master_labels/labels_2026-01-01.json | 41 +- resources/tester/test_mat_price_zero.py | 123 ++++ 5 files changed, 616 insertions(+), 290 deletions(-) create mode 100644 resources/data_cost_input_value/_build_mat_price.py create mode 100644 resources/tester/test_mat_price_zero.py diff --git a/resources/data_cost_input_value/_build_mat_price.py b/resources/data_cost_input_value/_build_mat_price.py new file mode 100644 index 00000000..858486e2 --- /dev/null +++ b/resources/data_cost_input_value/_build_mat_price.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +"""관급자재 단가 — **0 원 공시를 건너뛰고 앞 제값 공시를 채택**하는 규칙(2026-09-16 브레인). + +왜 — 우리 고르기 규칙이 「물품코드마다 가장 나중 공시」라, 값이 안 실린 공시(`prce = 0`)가 +옛 제값을 덮었다. 각형강관 25*25*1 이 `1,460 → 1,500 → 0` 으로 끝난 것이 그것이다. +「가격이 0」이 아니라 **그 공시에 값이 안 실린 것**이라, 0 을 값으로 받으면 안 된다. + +무엇을 고치나 + - 마스터의 0 원 줄만 고친다. **원천 스냅숏은 안 건드린다**(읽기만). + - 0 이 아닌 줄은 한 글자도 안 건드린다 — 규칙을 통째로 다시 돌리지 않는다. + - 고친 줄마다 **사유**(`price_adopted_reason`)를 남긴다. 조용히 바꾸지 않는다. + - 공시 날짜·번호도 **채택한 공시의 것**으로 바꾼다 — 어느 판 값인지 화면에서 보여야 한다. + - 앞 공시에도 값이 없어 0 이 남는 줄은 `zero_price_unrecovered` 로 낸다(규칙으로 못 고침). + +⚠ 같은 날짜에 0 과 제값이 함께 실린다(2025-04-24) — 날짜만 보지 말고 **0 을 건너뛴 뒤** + 가장 나중 공시를 고른다. + +돌리기: `./venv/Scripts/python.exe resources/data_cost_input_value/_build_mat_price.py` +""" + +from __future__ import annotations + +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +RAW = ROOT / "resources/knowledge/original/원가계산/자재단가/나라장터_시설공통자재_2026-08-14.json" +MASTER = ROOT / "resources/data_cost_input_value/mat_price_public_2026-08-14.json" + +#: 값이 안 실린 공시를 가리는 잣대 — 빈 칸·0 은 「값 없음」이다. +EMPTY_PRICES = {"", "0", "0.0", "0.00"} + +SELECTION_POLICY = ( + "latest notice per product identification number within the local snapshot, " + "skipping notices whose price is empty or zero " + "(a zero price means the notice carried no price, not a price of zero)" +) +UNRECOVERED_NOTE = ( + "앞 공시에도 값이 없어 0 이 남은 줄. **규칙으로 못 고친다** — 0 을 값으로 쓰지 말고 " + "그 자재가 실제로 필요해지면 원천을 다시 받거나 견적을 받아야 한다." +) + + +def price_of(row: dict) -> str: + return str(row.get("prce", "")).strip() + + +def has_price(row: dict) -> bool: + return price_of(row) not in EMPTY_PRICES + + +def notice_day(row: dict) -> str: + return str(row.get("nticeDt", ""))[:10] + + +def build() -> dict: + raw = json.loads(RAW.read_text(encoding="utf-8")) + master = json.loads(MASTER.read_text(encoding="utf-8")) + records = master["variables"]["mat_price"]["records"] + + history: dict[str, list[dict]] = {} + for row in raw: + history.setdefault(str(row.get("prdctIdntNo")), []).append(row) + for rows in history.values(): + rows.sort(key=lambda r: str(r.get("nticeDt")), reverse=True) + + repaired, unrecovered = 0, [] + for record in records: + if str(record.get("price_krw")).strip() not in EMPTY_PRICES: + continue + was = notice_day(record | {"nticeDt": record.get("notice_datetime")}) + adopted = next((r for r in history.get(str(record["item_code"]), ()) if has_price(r)), None) + if adopted is None: + unrecovered.append( + { + "item_code": record["item_code"], + "classification_name": record.get("classification_name", ""), + "specification": record.get("specification", ""), + "notice_datetime": record.get("notice_datetime", ""), + "reason": "이 물품코드의 모든 공시에 값이 없음", + } + ) + continue + record["price_krw"] = int(price_of(adopted)) + record["notice_datetime"] = str(adopted.get("nticeDt", "")) + record["notice_number"] = str(adopted.get("prceNticeNo", "")) + record["price_adopted_reason"] = f"{was} 공시에 값이 없어 {notice_day(adopted)} 값을 씀" + repaired += 1 + + master["selection_policy"] = SELECTION_POLICY + master["zero_price_unrecovered"] = {"note": UNRECOVERED_NOTE, "items": unrecovered} + return {"master": master, "repaired": repaired, "unrecovered": len(unrecovered)} + + +if __name__ == "__main__": + result = build() + MASTER.write_text( + json.dumps(result["master"], ensure_ascii=False, indent=2) + "\n", encoding="utf-8" + ) + print(f"고친 줄 {result['repaired']} · 못 고친 줄 {result['unrecovered']}") diff --git a/resources/data_cost_input_value/_manifest.json b/resources/data_cost_input_value/_manifest.json index 71b93fe7..56945ecf 100644 --- a/resources/data_cost_input_value/_manifest.json +++ b/resources/data_cost_input_value/_manifest.json @@ -48,8 +48,8 @@ "file": "mat_price_public_2026-08-14.json", "dataset_id": "mat_price_public", "effective_date": "2026-08-14", - "sha256": "4f5f76cf5b39ee4e3152fec11b9f5efc70ecea075a66a5cc1bda6ff7691de5b7", - "size_bytes": 4741651 + "sha256": "75ddfe85b0140d782cd00187ec459e995cd0db9982e7bd48f62f83cd65248acc", + "size_bytes": 4748976 }, { "file": "oil_2026-08-14.json", diff --git a/resources/data_cost_input_value/mat_price_public_2026-08-14.json b/resources/data_cost_input_value/mat_price_public_2026-08-14.json index 42ef3562..cb63bf93 100644 --- a/resources/data_cost_input_value/mat_price_public_2026-08-14.json +++ b/resources/data_cost_input_value/mat_price_public_2026-08-14.json @@ -10,7 +10,7 @@ "role": "primary" } ], - "selection_policy": "latest notice per product identification number within the local snapshot", + "selection_policy": "latest notice per product identification number within the local snapshot, skipping notices whose price is empty or zero (a zero price means the notice carried no price, not a price of zero)", "source_row_count": 27485, "variables": { "mat_price": { @@ -25702,15 +25702,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 25*25*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306448", + "price_krw": 1500, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907910", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110392", @@ -25734,15 +25735,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 30*30*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306451", + "price_krw": 1770, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907913", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110397", @@ -25766,15 +25768,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 40*40*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306455", + "price_krw": 2360, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907917", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110405", @@ -25782,15 +25785,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 50*50*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306460", + "price_krw": 2940, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907922", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110406", @@ -26230,15 +26234,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 30*20*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306495", + "price_krw": 1500, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907957", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110471", @@ -26278,15 +26283,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 40*20*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306498", + "price_krw": 1770, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907960", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110476", @@ -26326,15 +26332,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 50*30*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306502", + "price_krw": 2360, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907964", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110486", @@ -26470,15 +26477,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 100*50*1.4t", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306518", + "price_krw": 4560, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907980", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20110518", @@ -47670,15 +47678,16 @@ "classification_name": "컨테이너하우스", "specification": "컨테이너하우스, 사무실용, 2.4*9.0*2.6m", "unit": "개", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306873", + "price_krw": 5375598, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908335", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139632", @@ -47686,15 +47695,16 @@ "classification_name": "컨테이너하우스", "specification": "컨테이너하우스, 사무실용, 2.4*12*2.6m", "unit": "개", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306874", + "price_krw": 7245371, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908336", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139633", @@ -47862,15 +47872,16 @@ "classification_name": "컨테이너하우스", "specification": "컨테이너하우스, 창고용, 2.4*9.0*2.6m", "unit": "개", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306888", + "price_krw": 4125994, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908350", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139655", @@ -47990,15 +48001,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, BASE CHANNEL, 2.0mm이상", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306774", + "price_krw": 3724, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908236", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139692", @@ -48006,15 +48018,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, TOP CHANEL, 2.0mm이상", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306775", + "price_krw": 3244, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908237", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139693", @@ -48022,15 +48035,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 외부판넬, 벽, 1.2*2.4m", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306776", + "price_krw": 41213, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908238", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139694", @@ -48038,15 +48052,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 외부판넬, 창문, 1.2*2.4m", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306777", + "price_krw": 74699, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908239", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139695", @@ -48054,15 +48069,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 외부판넬, 철재문, 1.2*2.4m", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306778", + "price_krw": 161892, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908240", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139696", @@ -48070,15 +48086,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 내부판넬, 벽, 1.2*2.4m", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306779", + "price_krw": 36816, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908241", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139698", @@ -48086,15 +48103,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 판넬죠인트, 2.4m", "unit": "조", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306780", + "price_krw": 6636, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908242", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139699", @@ -48102,15 +48120,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, CANOPY, 0.6*1.2m", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306781", + "price_krw": 23795, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908243", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139700", @@ -48118,15 +48137,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 박공판넬, 0.6*1.2m", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306782", + "price_krw": 9361, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908244", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139701", @@ -48134,15 +48154,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 루우프시이트, 칼라시트, 0.5mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306783", + "price_krw": 7568, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908245", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139702", @@ -48150,15 +48171,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 트러스, 7.2m", "unit": "개", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306784", + "price_krw": 60504, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908246", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139703", @@ -48166,15 +48188,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 중도리, c-60*30*10*2.3mm", "unit": "개", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306785", + "price_krw": 1608, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908247", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139704", @@ -48182,15 +48205,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, 천정판+그라스울 50mm", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306786", + "price_krw": 11714, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908248", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139705", @@ -48198,15 +48222,16 @@ "classification_name": "조립식건물부재", "specification": "조립식건물부재, T-BAR, 24*38*0.4mm", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306787", + "price_krw": 1343, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908249", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20139910", @@ -48326,15 +48351,16 @@ "classification_name": "건설용거푸집", "specification": "건설용거푸집, 강, 600*1500*63.5mm", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304954", + "price_krw": 49912, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906392", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20140323", @@ -48358,15 +48384,16 @@ "classification_name": "건설용거푸집", "specification": "건설용거푸집, 강, 450*1500*63.5mm", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304956", + "price_krw": 29704, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906394", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20140335", @@ -48390,15 +48417,16 @@ "classification_name": "건설용거푸집", "specification": "건설용거푸집, 강, 400*1500*63.5mm", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304958", + "price_krw": 26893, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906396", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20140339", @@ -48422,15 +48450,16 @@ "classification_name": "건설용거푸집", "specification": "건설용거푸집, 강, 300*1500*63.5mm", "unit": "매", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304960", + "price_krw": 23599, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906398", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20140347", @@ -50582,15 +50611,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 스판크리트, 200mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305909", + "price_krw": 114118, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907371", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20153195", @@ -51110,15 +51140,16 @@ "classification_name": "PVC바닥재", "specification": "PVC바닥재, 450*450*t3mm, VIP타일, 마블", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304864", + "price_krw": 34382, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906302", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20153979", @@ -51894,15 +51925,16 @@ "classification_name": "불연천장재", "specification": "불연천장재, 아스텍스, 6*300*600mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305660", + "price_krw": 4908, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907122", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20155076", @@ -60198,15 +60230,16 @@ "classification_name": "광택제", "specification": "광택제, 수산", "unit": "kg", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305219", + "price_krw": 3211, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906681", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20277439", @@ -60342,15 +60375,16 @@ "classification_name": "합성풀", "specification": "합성풀, 건설용", "unit": "kg", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306963", + "price_krw": 9376, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908425", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20277692", @@ -62902,15 +62936,16 @@ "classification_name": "평강", "specification": "평강, t3*13∼16mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306930", + "price_krw": 930000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908392", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20289313", @@ -62918,15 +62953,16 @@ "classification_name": "평강", "specification": "평강, t4*13∼16mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306931", + "price_krw": 930000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908393", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20289314", @@ -62934,15 +62970,16 @@ "classification_name": "평강", "specification": "평강, t4.5*13∼16mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306932", + "price_krw": 930000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908394", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20289315", @@ -62950,15 +62987,16 @@ "classification_name": "평강", "specification": "평강, t5*13∼16mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306933", + "price_krw": 930000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908395", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20289316", @@ -64646,15 +64684,16 @@ "classification_name": "PVC바닥재", "specification": "PVC바닥재, 910*100*t3mm, 골드우드타일", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304870", + "price_krw": 15842, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906308", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20434126", @@ -64662,15 +64701,16 @@ "classification_name": "PVC바닥재", "specification": "PVC바닥재, 450*450*t3mm, 골드타일", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304869", + "price_krw": 14444, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906307", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20434127", @@ -64694,15 +64734,16 @@ "classification_name": "PVC바닥재", "specification": "PVC바닥재, 450*450*t3mm, MVP타일", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304867", + "price_krw": 9505, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906305", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20434133", @@ -64838,15 +64879,16 @@ "classification_name": "불연천장재", "specification": "불연천장재, 아스텍스, 6*300*600mm, 유공", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305661", + "price_krw": 7981, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907123", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20434404", @@ -64854,15 +64896,16 @@ "classification_name": "불연천장재", "specification": "불연천장재, SF-1200, M-Bar용, 12*300*600mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305679", + "price_krw": 8240, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907141", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20434423", @@ -66374,15 +66417,16 @@ "classification_name": "불연천장재", "specification": "불연천장재, EZ-1200, 이지톤, M-bar용, 12*300*600", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305690", + "price_krw": 8120, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907152", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20612102", @@ -66390,15 +66434,16 @@ "classification_name": "화학섬유카펫", "specification": "화학섬유카펫, 코오롱, CA, 9mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306965", + "price_krw": 35246, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908427", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20683679", @@ -74422,15 +74467,16 @@ "classification_name": "PVC바닥재", "specification": "PVC바닥재, 450*450*t3mm, VIP타일, 포스트", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304865", + "price_krw": 34382, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906303", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20730857", @@ -74742,15 +74788,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 60*40*1.4mm", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306507", + "price_krw": 2940, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907969", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20814168", @@ -74790,15 +74837,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 75*45*1.4mm", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306512", + "price_krw": 3540, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907974", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20814171", @@ -76566,15 +76614,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 스판크리트, 120mm(45)", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305906", + "price_krw": 91900, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907368", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20969828", @@ -76582,15 +76631,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 스판크리트, 150mm(45)", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305907", + "price_krw": 101999, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907369", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20969829", @@ -76598,15 +76648,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 스판크리트, 175mm(45)", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305908", + "price_krw": 106039, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907370", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "20969844", @@ -87430,15 +87481,16 @@ "classification_name": "일반구조용압연강판", "specification": "일반구조용압연강판, STS평철304, 2.0mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306541", + "price_krw": 5100000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908003", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "21868520", @@ -87510,15 +87562,16 @@ "classification_name": "일반구조용압연강판", "specification": "일반구조용압연강판, STS평철304, 7.0mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306546", + "price_krw": 6064000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908008", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "21868525", @@ -87574,15 +87627,16 @@ "classification_name": "일반구조용압연강판", "specification": "일반구조용압연강판, STS평철304, 16mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306550", + "price_krw": 6064000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908012", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "21868529", @@ -87590,15 +87644,16 @@ "classification_name": "일반구조용압연강판", "specification": "일반구조용압연강판, STS평철304, 19mm", "unit": "톤", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306551", + "price_krw": 6400000, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908013", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "21868530", @@ -89398,15 +89453,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 60*60*t1.4mm", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306466", + "price_krw": 3540, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907928", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "21871653", @@ -89446,15 +89502,16 @@ "classification_name": "일반구조용각형강관", "specification": "일반구조용각형강관, 각형강관, 75*75*t1.4mm", "unit": "m", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306472", + "price_krw": 4560, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907934", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "21871816", @@ -89878,15 +89935,16 @@ "classification_name": "화학섬유카펫", "specification": "화학섬유카펫, 스완, RQ, 9mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306964", + "price_krw": 35246, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908426", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "22073563", @@ -89894,15 +89952,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 스판크리트, 265mm(45)", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305910", + "price_krw": 120178, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907372", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "22073564", @@ -89910,15 +89969,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 스판크리트, 315mm(45)", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305911", + "price_krw": 130277, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907373", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "22073570", @@ -91574,15 +91634,16 @@ "classification_name": "장판지", "specification": "장판지, 명가, 2.2T*1830", "unit": "M2", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24306744", + "price_krw": 13698, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22908206", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "22256401", @@ -91766,15 +91827,16 @@ "classification_name": "복층유리", "specification": "복층유리, 로이, 일면반강화, 24mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305654", + "price_krw": 68072, + "notice_datetime": "2024-10-30 16:58:14", + "notice_number": "R24CW21466476", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2024-10-30 값을 씀" }, { "item_code": "22365230", @@ -91782,15 +91844,16 @@ "classification_name": "복층유리", "specification": "복층유리, 로이, 양면반강화, 24mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305657", + "price_krw": 83142, + "notice_datetime": "2024-10-30 16:58:14", + "notice_number": "R24CW21466482", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2024-10-30 값을 씀" }, { "item_code": "22365241", @@ -93494,15 +93557,16 @@ "classification_name": "PVC바닥재", "specification": "PVC바닥재, 비닐바닥재, 소리지움, 4.5t*1830", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24304876", + "price_krw": 26838, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22906314", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "22446972", @@ -99702,15 +99766,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 벽판용속빈PC판, 스판크리트, 120mm(45)", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305912", + "price_krw": 90891, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907374", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "22616476", @@ -99718,15 +99783,16 @@ "classification_name": "슬래브용속빈PC판", "specification": "슬래브용속빈PC판, 벽판용속빈PC판, 스판크리트, 150mm(45)", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305913", + "price_krw": 100990, + "notice_datetime": "2025-04-24 00:00:00", + "notice_number": "R25CW22907375", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2025-04-24 값을 씀" }, { "item_code": "22617062", @@ -101686,15 +101752,16 @@ "classification_name": "복층유리", "specification": "복층유리, 로이, 투명일면반강화, 28mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305656", + "price_krw": 83415, + "notice_datetime": "2024-10-30 16:58:14", + "notice_number": "R24CW21466480", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2024-10-30 값을 씀" }, { "item_code": "23290408", @@ -101702,15 +101769,16 @@ "classification_name": "복층유리", "specification": "복층유리, 로이, 투명양면반강화, 28mm", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305659", + "price_krw": 95974, + "notice_datetime": "2024-10-30 16:58:14", + "notice_number": "R24CW21466486", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2024-10-30 값을 씀" }, { "item_code": "23375752", @@ -101926,15 +101994,16 @@ "classification_name": "복층유리", "specification": "복층유리, 로이복층/투명 일면반강화, 26mm 5+16+5", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305655", + "price_krw": 73980, + "notice_datetime": "2024-10-30 16:58:14", + "notice_number": "R24CW21466478", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2024-10-30 값을 씀" }, { "item_code": "23574727", @@ -101942,15 +102011,16 @@ "classification_name": "복층유리", "specification": "복층유리, 로이복층/투명 양면반강화, 26mm 5+16+5", "unit": "㎡", - "price_krw": 0, - "notice_datetime": "2025-10-23 00:00:00", - "notice_number": "R25CW24305658", + "price_krw": 91251, + "notice_datetime": "2024-10-30 16:58:14", + "notice_number": "R24CW21466484", "business_division_code": "포110002", "business_division_name": "시설공통자재(건축)", "vat_basis": "부가가치세별도", "price_type": "기타가격", "delivery_condition": "현장도착도", - "field": "건축" + "field": "건축", + "price_adopted_reason": "2025-10-23 공시에 값이 없어 2024-10-30 값을 씀" }, { "item_code": "23576116", @@ -112016,5 +112086,9 @@ "group": "asphalt_concrete", "reason": "shopping mall snapshot not locally stored" } - ] + ], + "zero_price_unrecovered": { + "note": "앞 공시에도 값이 없어 0 이 남은 줄. **규칙으로 못 고친다** — 0 을 값으로 쓰지 말고 그 자재가 실제로 필요해지면 원천을 다시 받거나 견적을 받아야 한다.", + "items": [] + } } diff --git a/resources/data_master_labels/labels_2026-01-01.json b/resources/data_master_labels/labels_2026-01-01.json index 6577a6ae..4607da7e 100644 --- a/resources/data_master_labels/labels_2026-01-01.json +++ b/resources/data_master_labels/labels_2026-01-01.json @@ -2,7 +2,7 @@ "schema_version": "1.0", "dataset_id": "data_master_labels", "effective_date": "2026-01-01", - "generated_at": "2026-09-16T10:49:25+09:00", + "generated_at": "2026-09-16T11:39:55+09:00", "note": "마스터 자료의 **사람이 읽을 이름표**. 값이 아니라 이름만 담는다 — 여기를 고쳐도 계산은 안 바뀐다. Z01 마스터 관리 화면이 표·열 이름을 여기서 읽는다.", "policy": { "labels_only": "값·수식·단가를 담지 않는다. 마스터 파일은 손대지 않는다.", @@ -210,7 +210,15 @@ "key": "notice_datetime", "name_ko": "공고 일시", "unit": "", - "visible": true + "visible": true, + "note": "채택한 공시의 날짜 — 0 원 공시를 건너뛴 줄은 앞 공시 날짜가 선다." + }, + { + "key": "price_adopted_reason", + "name_ko": "값을 고른 사유", + "unit": "", + "visible": true, + "note": "⭐ 0 원 공시를 건너뛰고 앞 제값을 쓴 줄에만 선다(70 줄). 「○ 공시에 값이 없어 ○ 값을 씀」." }, { "key": "notice_number", @@ -1079,14 +1087,14 @@ }, "counts": { "merged_tables": 5, - "merged_columns": 88, + "merged_columns": 89, "files": 34, "tables": 91, - "columns": 455, - "value_groups": 172, + "columns": 456, + "value_groups": 173, "unknown": 0, "value_groups_by_kind": { - "doc": 106, + "doc": 107, "value": 66 } }, @@ -4382,6 +4390,12 @@ "name_ko": "분야", "unit": "", "visible": true + }, + { + "key": "price_adopted_reason", + "name_ko": "값을 고른 사유", + "unit": "", + "visible": true } ] } @@ -4404,6 +4418,12 @@ "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", "kind": "doc" + }, + { + "key": "zero_price_unrecovered", + "name_ko": "값을 못 찾은 0 원 줄", + "summary": "앞 공시에도 값이 없어 0 이 남은 줄. 규칙으로 못 고친다 — 그 자재가 필요해지면 원천을 다시 받아야 한다.", + "kind": "doc" } ] }, @@ -6765,6 +6785,11 @@ "unit": "", "visible": true }, + "price_adopted_reason": { + "name_ko": "값을 고른 사유", + "unit": "", + "visible": true + }, "price_krw": { "name_ko": "단가", "unit": "원", @@ -7566,6 +7591,10 @@ "name_ko": "원천 줄 수", "summary": "걸러 내기 전 원본 줄 수." }, + "mat_price_public::zero_price_unrecovered": { + "name_ko": "값을 못 찾은 0 원 줄", + "summary": "앞 공시에도 값이 없어 0 이 남은 줄. 규칙으로 못 고친다 — 그 자재가 필요해지면 원천을 다시 받아야 한다." + }, "material_surcharge::observed_practice": { "name_ko": "실무 관측", "summary": "실무 집계에서 본 할증률. 참고이지 기본값이 아니다." diff --git a/resources/tester/test_mat_price_zero.py b/resources/tester/test_mat_price_zero.py new file mode 100644 index 00000000..5c2bfa42 --- /dev/null +++ b/resources/tester/test_mat_price_zero.py @@ -0,0 +1,123 @@ +"""관급자재 단가 — **0 원 공시를 건너뛰고 앞 제값을 쓴다**(2026-09-16 브레인). + +왜 — 우리 고르기 규칙이 「물품코드마다 가장 나중 공시」라, 값이 안 실린 공시(`prce = 0`)가 +옛 제값을 덮었다(각형강관 25*25*1.4t `1,460 → 1,500 → 0`). 「가격이 0」이 아니라 +**그 공시에 값이 안 실린 것**이라, 0 을 값으로 받으면 안 된다. + +⚠ 조용히 바꾸지 않는다 — 고친 줄에는 사유가 남고, 공시 날짜·번호도 **채택한 공시의 것**이 된다. +⚠ 앞 공시에도 값이 없어 0 이 남는 줄은 **빨강이 아니라 목록**(`zero_price_unrecovered`)으로 남는다. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +MASTER = ROOT / "resources" / "data_cost_input_value" / "mat_price_public_2026-08-14.json" +RAW = ROOT / "resources/knowledge/original/원가계산/자재단가/나라장터_시설공통자재_2026-08-14.json" +EMPTY_PRICES = {"", "0", "0.0", "0.00"} + + +@pytest.fixture(scope="module") +def master() -> dict: + return json.loads(MASTER.read_text(encoding="utf-8")) + + +@pytest.fixture(scope="module") +def records(master) -> list[dict]: + return master["variables"]["mat_price"]["records"] + + +def _empty(value) -> bool: + return str(value).strip() in EMPTY_PRICES + + +def test_값이_없는_줄은_목록에만_남는다(master, records): + """⭐ 규칙으로 고칠 수 있는 0 원 줄은 하나도 안 남는다. + + 앞 공시에도 값이 없는 줄만 `zero_price_unrecovered` 에 남고, 그 줄은 표에서도 0 이다. + """ + listed = {str(item["item_code"]) for item in master["zero_price_unrecovered"]["items"]} + zeros = {str(r["item_code"]) for r in records if _empty(r.get("price_krw"))} + assert zeros == listed, f"목록에 없는 0 원 줄: {sorted(zeros - listed)}" + + +def test_못_고친_줄에는_사유가_붙는다(master): + for item in master["zero_price_unrecovered"]["items"]: + assert item["reason"].strip(), item["item_code"] + assert master["zero_price_unrecovered"]["note"].strip() + + +def test_고친_줄마다_사유가_남는다(records): + """조용히 바꾸지 않는다 — 「○ 공시에 값이 없어 ○ 값을 씀」이 줄에 남아야 한다.""" + repaired = [r for r in records if r.get("price_adopted_reason")] + assert repaired, "고친 줄이 하나도 없다" + for row in repaired: + reason = row["price_adopted_reason"] + assert "값이 없어" in reason and "씀" in reason, reason + assert not _empty(row["price_krw"]), row["item_code"] + + +def test_고친_줄의_공시_날짜와_번호가_채택한_공시의_것이다(records): + """어느 판 값인지 화면에서 보여야 한다 — 날짜가 0 원 공시에 머물면 안 된다.""" + raw = json.loads(RAW.read_text(encoding="utf-8")) + history: dict[str, list[dict]] = {} + for row in raw: + history.setdefault(str(row.get("prdctIdntNo")), []).append(row) + for rows in history.values(): + rows.sort(key=lambda r: str(r.get("nticeDt")), reverse=True) + + wrong = [] + for row in (r for r in records if r.get("price_adopted_reason")): + adopted = next( + (h for h in history.get(str(row["item_code"]), ()) if not _empty(h.get("prce"))), + None, + ) + assert adopted is not None, row["item_code"] + if str(row["price_krw"]) != str(int(str(adopted["prce"]).strip())): + wrong.append(f"{row['item_code']} 단가 {row['price_krw']} ≠ {adopted['prce']}") + elif row["notice_datetime"] != str(adopted.get("nticeDt", "")): + wrong.append(f"{row['item_code']} 날짜 {row['notice_datetime']}") + elif row["notice_number"] != str(adopted.get("prceNticeNo", "")): + wrong.append(f"{row['item_code']} 번호 {row['notice_number']}") + elif ( + row["price_adopted_reason"].endswith(f"{str(adopted['nticeDt'])[:10]} 값을 씀") is False + ): + wrong.append(f"{row['item_code']} 사유가 채택 공시와 다름") + assert not wrong, wrong + + +def test_0원_아닌_줄은_안_건드린다(records): + """규칙을 통째로 다시 돌리지 않는다 — 사유가 붙은 줄만 손댄 줄이다.""" + touched = [r for r in records if r.get("price_adopted_reason")] + assert len(touched) < len(records) // 10, ( + "손댄 줄이 너무 많다 — 규칙이 전체를 다시 쓴 것 아닌가" + ) + assert len(records) == 6999, "줄 수가 바뀌었다" + + +def test_고르는_방침에_0원_건너뛰기가_적혀_있다(master): + policy = master["selection_policy"] + assert "zero" in policy and "skip" in policy.lower(), policy + + +def test_같은_날짜에_0과_제값이_함께_있어도_제값을_고른다(): + """2025-04-24 는 같은 날 0 과 제값이 함께 실린다 — 날짜만 보면 0 을 집을 수 있다.""" + raw = json.loads(RAW.read_text(encoding="utf-8")) + same_day: dict[str, set[bool]] = {} + for row in raw: + if str(row.get("nticeDt", ""))[:10] != "2025-04-24": + continue + same_day.setdefault(str(row.get("prdctIdntNo")), set()).add(_empty(row.get("prce"))) + both = [code for code, kinds in same_day.items() if kinds == {True, False}] + assert both, "같은 날 0 과 제값이 함께 있는 물품이 없다 — 시험 전제가 바뀌었다" + + master = json.loads(MASTER.read_text(encoding="utf-8")) + rows = {str(r["item_code"]): r for r in master["variables"]["mat_price"]["records"]} + for code in both: + row = rows.get(code) + if row is not None: + assert not _empty(row["price_krw"]), f"{code} 가 0 으로 섰다"