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
+16 -29
View File
@@ -103,7 +103,7 @@ def files_of(group: str) -> list[dict]:
_REF_KEY = re.compile(r"^[A-Z]{2}\d{6}$")
_REF_FILE = {
"LB": "인력.json",
"MT": "재료_시중물가.json",
"MT": "재료_자재품목.json",
"MN": "재료_나라장터자재.json",
"MP": "재료_품셈재료.json",
"EQ": "기계.json",
@@ -111,16 +111,9 @@ _REF_FILE = {
def _ref_keys(row: dict) -> list[str]:
"""줄이 가리키는 키 — 준용 · 연결 · 조달 출처(「나라장터:<키>」)."""
put = (
row.get("준용"),
row.get("연결"),
(row.get("값") or {}).get("조달", {}).get("출처")
if isinstance(row.get("값"), dict)
else None,
)
out = [v.removeprefix("나라장터:") for v in put if isinstance(v, str)]
return [k for k in out if _REF_KEY.match(k)]
"""줄이 가리키는 키 — 준용 · 연결."""
put = (row.get("준용"), row.get("연결"))
return [v for v in put if isinstance(v, str) and _REF_KEY.match(v)]
def refs_of(rows_: list[dict]) -> dict[str, str]:
@@ -301,29 +294,24 @@ def elements(group: str, q: str, limit: int) -> dict:
return {"total": len(hits), "items": hits[: min(max(limit, 1), 200)]}
_PICK = {
"procure": "재료_나라장터자재.json",
"price": "재료_시중물가.json",
"job": "인력.json",
}
_PICK = {"price": "재료_자재품목.json", "job": "인력.json"}
def _squash(text) -> str:
return "".join(str(text or "").lower().split())
def _lowest(value) -> object:
"""시중물가 줄 — 시중 다섯 칸 · 조달 값 가운데 낮은 값 · 그 밖은 `값` 그대로."""
if not isinstance(value, dict):
return value
nums = [v for v in value["시중"].values() if isinstance(v, (int, float))]
supply = value["조달"].get("값")
return min(nums + ([supply] if supply is not None else []), default=None)
def _lowest(row: dict) -> object:
"""자재품목 줄 — 값 열 다섯 가운데 낮은 값(시험 계산용) · 값 칸이 있는 줄은 그 값 그대로."""
if "값" in row:
return row["값"]
nums = [row[s] for s in cm.mf.SLOTS if row.get(s) is not None]
return min(nums, default=None)
def pick(kind: str, q: str, limit: int) -> dict:
"""고르기 창 — `procure` 나라장터자재(ref = 나라장터:<키>) · `price` 시중물가(관급 조달 줄 포함 · ref = 키)
· `job` 값 있는 공표 노임 직종(미확보 빼고 모든 구분)(ref = 키) · 이름·규격 낱말이 모두 들어간 줄."""
"""고르기 창 — `price` 자재품목(관급 줄 포함 · ref = 키) · `job` 값 있는 공표 노임 직종
(미확보 빼고 모든 구분)(ref = 키) · 이름·규격 낱말이 모두 들어간 줄."""
if kind not in _PICK:
raise StoreError(404, f"없는 고르기 「{kind}」")
words = [_squash(w) for w in q.split()]
@@ -333,13 +321,12 @@ def pick(kind: str, q: str, limit: int) -> dict:
continue
text = _squash(f"{row.get('이름')} {row.get('규격')} {' '.join(row.get('옛이름') or [])}")
if all(w in text for w in words):
key = str(row.get("키"))
hits.append(
{
"ref": f"나라장터:{key}" if kind == "procure" else key,
"ref": str(row.get("키")),
**{k: row.get(k) for k in ("원문번호", "구분", "이름", "규격", "단위")},
"값": _lowest(row.get("값")),
"관급": kind == "price" and row["값"]["조달"].get("값") is not None,
"값": _lowest(row),
"관급": row.get("관급") is not None,
}
)
return {"total": len(hits), "items": hits[: min(max(limit, 1), 200)]}