feat(master_data): 로직 재료 줄 고르기 조건 — 구분·상세구분·규격 후보 · 대표 줄 · 후보 목록 API
- `_틀.md` 7장에 재료 고르기 문법 — 호표 `요소` 를 조건 묶음으로 · 규격 낱말 조건 · 대표 줄 지정 - 엔진 `candidates`·`pick`·`check_pick` — 조건 안 후보 목록(화면·테스트 컨테이너 공용) · 시험 계산은 대표 줄, 없으면 값 있는 첫 줄(자재지역 우선) - 로직 검사에 고르기 조건 검사 — 칸 이름 · 자재품목에 없는 구분·상세구분 · 조건 밖 대표 · 후보 0 - M01 서버 `GET /api/m01/materials` — 구분·상세구분·규격·자재지역으로 좁힌 후보와 기본 줄 - 옛 방식 재료 줄(`MP…`·`MT…` 키)은 그대로 돎 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
(1) 틀 — 파일 이름 · 머리 칸 · 줄 칸 · 출처 모양 · 키 모양·겹침 · 대장
|
||||
(2) 본문 — 표형 요소의 출처 절을 본문 md 에서 찾아 수를 맞댐 · 결손(본문 표에 있고 요소에 없음)·허구(요소에 있고 본문 절에 없음)
|
||||
(3) 로직 — 변수가 가리키는 요소·표·칸 · 찾기 조건 이름 · 로직 입력 · 돌고 도는 참조 · 끊긴 키
|
||||
(3) 로직 — 변수가 가리키는 요소·표·칸 · 찾기 조건 이름 · 로직 입력 · 재료 고르기 조건 · 돌고 도는 참조 · 끊긴 키
|
||||
(4) 계산 — 로직 키와 입력값으로 호표 줄별 수량·단가·금액 · 비목 합 (`master_formula.py`)
|
||||
수 뽑기는 `resources/knowledge/original/_pipeline/pum_md_tool.py` 의 것을 씀.
|
||||
"""
|
||||
|
||||
@@ -260,8 +260,10 @@ class Master:
|
||||
raise FormulaError(f"없는 요소 「{ref}」")
|
||||
return row
|
||||
|
||||
def label(self, ref: str) -> str:
|
||||
"""알림 글 — 키 옆에 이름."""
|
||||
def label(self, ref) -> str:
|
||||
"""알림 글 — 키 옆에 이름 · 고르기 조건은 조건 글."""
|
||||
if isinstance(ref, dict):
|
||||
return "재료 고르기 " + " · ".join(str(v) for v in ref.values() if v)
|
||||
try:
|
||||
row = self.get(ref)
|
||||
except FormulaError:
|
||||
@@ -476,7 +478,8 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
else {item["비목"]: qty * price}
|
||||
)
|
||||
else:
|
||||
ref = fill(item["요소"], env)
|
||||
# 고르기 조건 묶음은 그대로 넘김 — `element` 가 대표 줄·첫 줄로 풂
|
||||
ref = item["요소"] if isinstance(item["요소"], dict) else fill(item["요소"], env)
|
||||
try:
|
||||
price, source = element(master, ref, env)
|
||||
except FormulaError:
|
||||
@@ -654,6 +657,11 @@ def check_logic(master: Master, row: dict) -> tuple[list[str], set]:
|
||||
item.get("종류") != "로직" and "비목" not in item
|
||||
):
|
||||
out.append(f"{where} · 비목 「{item.get('비목')}」")
|
||||
if isinstance(item["요소"], dict): # 재료 고르기 조건
|
||||
if item.get("종류") != "재료":
|
||||
out.append(f"{where} · 고르기 조건은 종류 「재료」 만")
|
||||
out += check_pick(master, where + " 요소", item["요소"])
|
||||
continue
|
||||
one(item["요소"], where + " 요소", known)
|
||||
group = mk.GROUP_OF.get(mk.id_of(str(item["요소"])))
|
||||
if item.get("종류") != "로직" and group != item.get("종류"):
|
||||
@@ -687,4 +695,14 @@ def find_loops(graph: dict[str, set]) -> list[list[str]]:
|
||||
|
||||
|
||||
# 품셈재료 단가 — 서로 부르는 모듈이라 끝에서 읽음
|
||||
from master_material import LINKED, REGION, SLOTS, TARIFF, element # noqa: E402, F401
|
||||
from master_material import ( # noqa: E402, F401
|
||||
LINKED,
|
||||
PICK_KEYS,
|
||||
REGION,
|
||||
SLOTS,
|
||||
TARIFF,
|
||||
candidates,
|
||||
check_pick,
|
||||
element,
|
||||
pick,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ LINKED = "MP" # 품셈재료 테이블ID
|
||||
MARKET = "MT" # 자재품목 테이블ID
|
||||
PRICED = "MO" # 유가전력 테이블ID — 값 한 칸
|
||||
SLOTS = ("물가자료", "유통물가", "물가정보", "거래가격", "관급") # 자재품목 값 열
|
||||
PICK_KEYS = ("구분", "상세구분", "규격", "대표") # 재료 고르기 조건 칸 (`_틀.md` 7장)
|
||||
REGION = "자재지역" # 로직 입력 — 「지역」 은 유가 고르기가 씀
|
||||
TARIFF = "전력 계약종별" # 로직 입력 — 품셈·계약예규에 계약종별 규정 없음
|
||||
_UNITS = {"ea": "개", "ton": "톤", "t": "톤", "l": "ℓ", "리터": "ℓ", "set": "조", "세트": "조"}
|
||||
@@ -104,8 +105,58 @@ def _candidates(master: mf.Master, ref: str, row: dict, env: dict) -> list[str]:
|
||||
return [key for _, key in hits]
|
||||
|
||||
|
||||
def element(master: mf.Master, ref: str, env: dict) -> tuple[object, str | None]:
|
||||
"""요소 값과 출처 — 자재품목은 제 줄, 품셈재료는 연결을 따라가 낮은 값 · 그 밖은 줄의 `값`."""
|
||||
# ── 재료 고르기 ────────────────────────────────────────────────────────
|
||||
def candidates(master: mf.Master, cond: dict, env: dict | None = None) -> list[str]:
|
||||
"""고르기 조건 안 자재품목 키 — 구분 · 상세구분 · 규격 낱말이 모두 든 줄 · 파일 차례대로.
|
||||
입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄만(그런 줄이 없으면 조건 안 전부)."""
|
||||
want = [plain_spec(w) for w in str(cond.get("규격") or "").split()]
|
||||
detail = cond.get("상세구분")
|
||||
hits = [
|
||||
key
|
||||
for key, row in master.index.get(MARKET, {}).items()
|
||||
if row.get("구분") == cond.get("구분")
|
||||
and (not detail or row.get("상세구분") == detail)
|
||||
and all(w in plain_spec(row.get("규격")) for w in want)
|
||||
]
|
||||
region = plain((env or {}).get(REGION, ""))
|
||||
near = [k for k in hits if region in plain(master.get(k).get("이름"))] if region else []
|
||||
return near or hits
|
||||
|
||||
|
||||
def pick(master: mf.Master, cond: dict, env: dict) -> str:
|
||||
"""시험 계산이 쓸 줄 — 관리자가 정한 `대표`, 없으면 조건 안 값 있는 첫 줄."""
|
||||
if cond.get("대표"):
|
||||
return str(cond["대표"])
|
||||
keys = candidates(master, cond, env)
|
||||
keys = [k for k in keys if _offers(master, k)] or keys
|
||||
if not keys:
|
||||
raise mf.FormulaError(f"재료 고르기 후보 없음 — {cond}")
|
||||
return keys[0]
|
||||
|
||||
|
||||
def check_pick(master: mf.Master, where: str, cond: dict) -> list[str]:
|
||||
"""고르기 조건 검사 — 칸 이름 · 구분·상세구분이 자재품목에 있는지 · 대표 줄 · 후보 0."""
|
||||
if set(cond) - set(PICK_KEYS) or not cond.get("구분"):
|
||||
return [f"{where} · 재료 고르기 조건 모양 「{cond}」 — {' · '.join(PICK_KEYS)}"]
|
||||
out, rows = [], master.index.get(MARKET, {})
|
||||
for slot in ("구분", "상세구분"):
|
||||
if cond.get(slot) and not any(r.get(slot) == cond[slot] for r in rows.values()):
|
||||
out.append(f"{where} · 자재품목에 없는 {slot} 「{cond[slot]}」")
|
||||
rep = cond.get("대표")
|
||||
if rep and str(rep) not in rows:
|
||||
out.append(f"{where} · 대표 줄 「{rep}」 이 자재품목에 없음")
|
||||
elif rep and str(rep) not in candidates(master, cond):
|
||||
out.append(f"{where} · 대표 줄 「{rep}」 이 고르기 조건 밖")
|
||||
if not out and not candidates(master, cond):
|
||||
out.append(f"{where} · 재료 고르기 후보 0")
|
||||
return out
|
||||
|
||||
|
||||
def element(master: mf.Master, ref, env: dict) -> tuple[object, str | None]:
|
||||
"""요소 값과 출처 — 자재품목은 제 줄, 품셈재료는 연결을 따라가 낮은 값 · 그 밖은 줄의 `값`.
|
||||
`ref` 가 고르기 조건 묶음이면 대표 줄(없으면 조건 안 첫 줄)로 풂."""
|
||||
if isinstance(ref, dict):
|
||||
ref = pick(master, ref, env)
|
||||
row = master.get(ref)
|
||||
if ref[:2] == MARKET: # 자재품목을 바로 가리키는 호표 줄
|
||||
return _lowest(master, ref)
|
||||
|
||||
Reference in New Issue
Block a user