chore(master_data): 1_기초 8 파일 새 틀로 옮김 · 옛 7 old 로

- 새 요소 파일 6 — 인력_건설노임 · 인력_제조노임 · 재료_나라장터자재 · 재료_오피넷유가 · 환율_한국은행환율 · 요율_조달청제비율
- 출처목록 → _원문목록.json 이름만 바꿈
- scripts/_migrate_base.py 옮김·대조 · _build_mat_price.py 경로·칸 이름 새 파일에 맞춤

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8
This commit is contained in:
2026-09-19 15:33:47 +09:00
co-authored by Claude Sonnet 5
parent 291e8a8cfe
commit 5e2509cbc8
16 changed files with 9099 additions and 21 deletions
@@ -15,7 +15,7 @@
⚠ 같은 날짜에 0 과 제값이 함께 실린다(2025-04-24) — 날짜만 보지 말고 **0 을 건너뛴 뒤**
가장 나중 공시를 고른다.
돌리기: `./venv/Scripts/python.exe resources/data_cost_input_value/_build_mat_price.py`
돌리기: `./venv/Scripts/python.exe resources/master_data/scripts/_build_mat_price.py`
"""
from __future__ import annotations
@@ -23,9 +23,11 @@ from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
from _migrate_base import dump_master
ROOT = Path(__file__).resolve().parents[3]
RAW = ROOT / "resources/knowledge/original/원가계산/자재단가/나라장터_시설공통자재_2026-08-14.json"
MASTER = ROOT / "resources/data_cost_input_value/mat_price_public_2026-08-14.json"
MASTER = ROOT / "resources/master_data/재료_나라장터자재.json"
#: 값이 안 실린 공시를 가리는 잣대 — 빈 칸·0 은 「값 없음」이다.
EMPTY_PRICES = {"", "0", "0.0", "0.00"}
@@ -56,7 +58,7 @@ def notice_day(row: dict) -> str:
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"]
records = master["줄"]
history: dict[str, list[dict]] = {}
for row in raw:
@@ -66,35 +68,34 @@ def build() -> dict:
repaired, unrecovered = 0, []
for record in records:
if str(record.get("price_krw")).strip() not in EMPTY_PRICES:
if str(record.get("값")).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)
was = notice_day(record | {"nticeDt": record.get("공시일시")})
adopted = next((r for r in history.get(str(record["열쇠"]), ()) 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": "이 물품코드의 모든 공시에 값이 없음",
"열쇠": record["열쇠"],
"이름": record.get("이름", ""),
"규격": record.get("규격", ""),
"공시일시": record.get("공시일시", ""),
"사유": "이 물품코드의 모든 공시에 값이 없음",
}
)
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)} 값을 씀"
record["값"] = int(price_of(adopted))
record["공시일시"] = str(adopted.get("nticeDt", ""))
record["공시번호"] = str(adopted.get("prceNticeNo", ""))
record["비고"] = f"{was} 공시에 값이 없어 {notice_day(adopted)} 값을 씀"
repaired += 1
master["selection_policy"] = SELECTION_POLICY
master["zero_price_unrecovered"] = {"note": UNRECOVERED_NOTE, "items": unrecovered}
master["선정규칙"] = SELECTION_POLICY
master["0원미회수"] = {"비고": UNRECOVERED_NOTE, "줄": 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"
)
master = result["master"]
dump_master(MASTER, {k: v for k, v in master.items() if k != "줄"}, master["줄"])
print(f"고친 줄 {result['repaired']} · 못 고친 줄 {result['unrecovered']}")