knowledge(마스터): 재료 열 한 벌 정리 — 시중물가 → 자재품목 · 값 다섯 열 · 유가·환율도 같은 열

- 재료_시중물가.json → 재료_자재품목.json(테이블ID MT · 키 그대로) · 모든 줄이 같은 열 한 벌(키 · 원문번호 · 구분 · 상세구분 · 이름 · 규격 · 단위 · 물가자료 · 유통물가 · 물가정보 · 거래가격 · 관급 · 출처 · 비고 · 면수)
- 값 묶음(시중·조달)을 풀어 다섯 값을 열로 · 관급 = 조달 값(4,552 줄 · 기간은 비고 「조달 26.7」) · 원문 가격정보만 남은 72 줄은 값 열에 안 넣고 비고 「가격정보 ○○」 · 구분·상세구분은 원문에 분류가 없어 비움
- 재료_오피넷유가 · 환율_한국은행환율도 열 한 벌(기준일 → 규격 · 유가 구분 = 전국평균/시도별 · 상세구분 = 지역) · 상품코드·지역코드 삭제
- 엔진 단가(연결된 줄의 낮은 값 · 출처 = 「<키>.<열>」) · check_master 열 검사 · M01 고르기(procure 걷어냄) · 빌더 · _틀.md · 화면 계약 · 시험 같이 고침

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
2026-09-20 15:55:40 +09:00
co-authored by Claude Opus 5
parent 02b9c8be92
commit 9de61eb251
19 changed files with 48348 additions and 48388 deletions
+41 -10
View File
@@ -43,7 +43,7 @@ BOOKS = (
"준용대상",
"나라장터자재",
"오피넷유가",
"시중물가",
"자재품목",
"한국은행환율",
"조달청제비율",
"품셈재료",
@@ -89,6 +89,39 @@ LABOR_COLS = (
"준용",
)
LABOR_STATUS = ("공표", "산정", "미공표", "미확보")
# 원문별 열 한 벌 — 모든 줄이 이 칸을 이 차례로 가짐(없으면 null)
COLS = {
"자재품목": (
"",
"원문번호",
"구분",
"상세구분",
"이름",
"규격",
"단위",
"물가자료",
"유통물가",
"물가정보",
"거래가격",
"관급",
"출처",
"비고",
"면수",
),
"오피넷유가": (
"",
"원문번호",
"구분",
"상세구분",
"이름",
"규격",
"단위",
"",
"출처",
"비고",
),
}
COLS["한국은행환율"] = COLS["오피넷유가"]
def load(names: list[str] | None = None, folder: Path = MASTER) -> dict[str, dict]:
@@ -181,13 +214,16 @@ def check_form(name: str, data: dict) -> list[str]:
out.append(f"{where} · {merged}{row.get(merged)}」 이 머리에 없음")
if division and not number.startswith(division + " "):
out.append(f"{where} · 원문번호 앞에 부문 「{division}」 없음")
for need in ROW_KEYS[kind]:
cols = COLS.get(data.get("원문"))
for need in cols or ROW_KEYS[kind]:
if need not in row:
out.append(f"{where} · 칸 「{need}」 없음")
if "출처" in row and not _source_ok(row["출처"]):
out.append(f"{where} · 출처 모양 「{row['출처']}")
if data.get("그룹") == "인력":
out += _check_labor(where, row)
if cols and tuple(row) != cols:
out.append(f"{where} · 열이 한 벌이 아님 — 「{' · '.join(cols)}")
if kind == "품셈재료":
out += _check_linked(where, row)
if kind == "요소" and isinstance(row.get(""), dict):
@@ -214,7 +250,7 @@ def _check_labor(where: str, row: dict) -> list[str]:
def _check_linked(where: str, row: dict) -> list[str]:
"""품셈재료 — 값 칸 없음 · 요구절은 출처 모양 · 연결 = 시중물가 키 · 후보 조건 · null."""
"""품셈재료 — 값 칸 없음 · 요구절은 출처 모양 · 연결 = 자재품목 키 · 후보 조건 · null."""
out = [f"{where} · 값 칸을 두지 않음"] if "" in row else []
need = row.get("요구절")
if not (isinstance(need, list) and need and all(_source_ok(x) for x in need)):
@@ -232,22 +268,17 @@ def _check_linked(where: str, row: dict) -> list[str]:
def check_links(whole: mf.Master) -> list[str]:
"""끊긴 키 — 품셈재료 연결 · 시중물가 조달 연결 · 인력 준용이 가리키는 줄."""
"""끊긴 키 — 품셈재료 연결 · 인력 준용이 가리키는 줄."""
out = []
for key, row in whole.index.get("MP", {}).items():
link = row.get("연결")
if isinstance(link, str) and link not in whole.index.get("MT", {}):
out.append(f"{whole.label(key)} · 연결 「{link}」 이 시중물가에 없음")
out.append(f"{whole.label(key)} · 연결 「{link}」 이 자재품목에 없음")
for key, row in whole.index.get("LB", {}).items():
for col in ("준용",):
ref = row.get(col)
if ref not in (None, "") and str(ref) not in whole.index.get("LB", {}):
out.append(f"{whole.label(key)} · {col}{ref}」 없는 인력 키")
nara = whole.index.get("MN", {})
for key, row in whole.index.get("MT", {}).items():
source = str(row[""]["조달"].get("출처") or "")
if source.startswith("나라장터:") and source.split(":", 1)[1] not in nara:
out.append(f"{whole.label(key)} · 조달 「{source}」 이 나라장터자재에 없음")
return out