feat(master_data): 품셈재료 고르기 칸 · 로직 재료 줄 일괄 변환 · 후보 값 열 다섯

- `재료_품셈재료.json` 363 줄에 「고르기」 칸 — 자재품목 구분·상세구분[·규격] · 212 줄 채움(규격 조건 19) · 짝 없는 줄은 null + 비고에 까닭
- `scripts/build_품셈재료_고르기.py` — 자재품목을 가른 바로 그 품명 글자 규칙 재사용 · 손으로 바로잡은 25 줄은 OVERRIDE · 연결이 이미 있는 줄은 옛 방식 그대로
- `scripts/build_로직_재료_고르기.py` — 호표 요소 `MP…` 를 고르기 조건으로 바꿈(로직 32 파일 287 줄) · 고르기 빈 줄과 `{이름}` 낀 원문번호 줄은 그대로
- 일괄 시험 계산 전후 — 통과 1119 → 1203 · 새로 멈춤 0 · 앞뒤 다 통과한 1119 줄 금액 변동 0
- `check_master` — 품셈재료 「고르기」 칸 검사(조건 모양 · 없는 구분·상세구분 · 조건 밖 대표 · 후보 0)
- `GET /api/m01/materials` — 후보마다 값 열 다섯(`값들`) · `값` 은 시험 계산이 쓸 낮은 값 · 값 있는 줄을 맨 앞에
- `GET /api/m01/logic` 의 `prices` — 고르기 조건 줄도 실음 · 열쇠는 조건 글 `mf.cond_text` · 후보 수 포함
- `_틀.md` 5장에 고르기 칸 · 일괄 변환 스크립트

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 22:02:28 +09:00
co-authored by Claude Opus 5
parent 826495f199
commit 1bcc957e9a
42 changed files with 959 additions and 657 deletions
+21 -3
View File
@@ -337,11 +337,25 @@ def _brief(ref: str, row: dict) -> dict:
}
def _pick_brief(whole, cond: dict) -> dict | None:
"""고르기 조건 줄의 단가 미리보기 — 후보 수와 시험 계산이 쓸 줄(값 열 다섯 포함)."""
try:
keys = cm.mf.candidates(whole, cond, {})
brief = _market_brief(whole.get(cm.mf.pick(whole, cond, {})))
except (cm.mf.FormulaError, KeyError, TypeError):
return None
return {**brief, "조건": cond, "후보": len(keys)}
def _prices(whole, row: dict) -> dict:
"""호표 요소 → 요소 줄 요약(단가 자동 칸) · `{이름}` 낀 원문번호·하위 로직은 계산 때 정해짐.
품셈재료는 연결을 따라간 낮은 값과 출처 · 자재지역 후보는 계산 때 정해짐(값 None)."""
품셈재료는 연결을 따라간 낮은 값과 출처 · 자재지역 후보는 계산 때 정해짐(값 None).
재료 고르기 조건 줄은 열쇠가 조건 글(`구분|상세구분|규격|대표`) · 값 = 시험 계산이 쓸 줄."""
out = {}
for item in row.get("호표") or []:
if isinstance(item.get("요소"), dict):
out[cm.mf.cond_text(item["요소"])] = _pick_brief(whole, item["요소"])
continue
ref = str(item.get("요소", ""))
if item.get("종류") != "로직" and "{" not in ref:
try:
@@ -413,9 +427,11 @@ def pick(kind: str, q: str, limit: int) -> dict:
def _market_brief(row: dict) -> dict:
"""후보 한 줄 — 값 열 다섯을 다 주고 `값` 에 시험 계산이 쓸 낮은 값(없으면 null)."""
return {
"ref": str(row.get("키")),
**{k: row.get(k) for k in ("구분", "상세구분", "이름", "규격", "단위")},
"값들": {s: row.get(s) for s in cm.mf.SLOTS},
"값": _lowest(row),
"관급": row.get("관급") is not None,
}
@@ -423,14 +439,16 @@ def _market_brief(row: dict) -> dict:
def materials(sub: str, detail: str, spec: str, region: str, limit: int) -> dict:
"""재료 고르기 조건 안 후보 목록 — 화면·테스트 컨테이너가 같이 씀.
`기본` = 시험 계산이 쓸 줄(대표 줄이 없을 때의 첫 줄)."""
`기본` = 시험 계산이 쓸 줄(대표 줄이 없을 때의 첫 줄) · 그 줄을 `items` 맨 앞에 둠."""
whole = cm.master(folder=FOLDER)
cond = {"구분": sub, "상세구분": detail or None, "규격": spec or None}
env = {cm.mf.REGION: region} if region else {}
keys = cm.mf.candidates(whole, cond, env)
first = cm.mf.pick(whole, cond, env) if keys else None
keys = [first] + [k for k in keys if k != first] if first else keys
return {
"total": len(keys),
"기본": cm.mf.pick(whole, cond, env) if keys else None,
"기본": first,
"items": [_market_brief(whole.get(k)) for k in keys[: min(max(limit, 1), 200)]],
}