Files
Aislo/resources/master_data/scripts/master_material.py
T
eomsangdonandClaude Opus 5 4277906258 feat(master_data): 재료 마스터 모양 확정 반영 — 시중물가 시중·조달 두 묶음 · 재료_품셈재료 연결 · 낮은 값과 출처
- 재료_시중물가: 채택 · 견적가 칸 뺌 · 값 = 시중{다섯 칸 + 면수} · 조달{값·출처·기준}
- 관급 줄 가격정보 4,552 → 조달(출처 가격정보 · 기준 = 원천 면수) · 나라장터 온전 짝 54 → 조달 출처 「나라장터:<열쇠>」(값 안 베낌)
- 재료_나라장터자재 머리 「자료」 = 조달 자료
- 재료_자체 → 재료_품셈재료: 값·출처 칸 없음 · 요구절 · 연결(열쇠 5 · 후보 조건 3 · 못 이음 352)
- 엔진(master_material): 연결된 줄의 시중·조달 가운데 낮은 값 · 호표 줄 출처 · 후보 조건은 로직 입력 「자재지역」 — 줄 없으면 「지역 값 없음」 · 규격 여럿이면 「후보 여럿」
- 로직: 재료:자체 → 재료:품셈재료 516줄 · 자재지역 입력 24 로직 · check_master 품셈재료 틀·연결 검사 · M01 prices 출처
- 일괄 시험 계산(울진): 계산됨 1,109 · 값 없음 232 · 후보 여럿 8 · 미공표 2

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
2026-09-19 23:38:09 +09:00

99 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""품셈재료 단가 — 연결을 따라 시중물가 줄의 시중·조달 값 가운데 낮은 값 (`_틀.md` 4장 재료 값).
`master_formula` 가 요소 값을 풀 때 부름. 서로 부르는 모듈이라 `master_formula` 는 끝에서 읽음.
"""
from __future__ import annotations
import re
import unicodedata
from decimal import Decimal
LINKED = "재료:품셈재료:"
MARKET = ("가격정보", "물가자료", "유통물가", "물가정보", "거래가격")
REGION = "자재지역" # 로직 입력 — 「지역」 은 유가 고르기가 씀
_UNITS = {"ea": "개", "ton": "톤", "t": "톤", "l": "", "리터": "", "set": "조", "세트": "조"}
def plain(s) -> str:
"""이름 맞대기 — NFKC · 소문자 · 빈칸 없앰 · 지름 기호 하나로."""
s = unicodedata.normalize("NFKC", str(s or "")).lower()
return re.sub(r"\s+", "", re.sub(r"[φΦ∅⌀]", "ø", s))
def plain_spec(s) -> str:
return plain(s).replace("×", "x").replace("*", "x")
def plain_unit(s) -> str:
s = plain(s).replace("", "l")
return _UNITS.get(s, s)
def _offers(master: mf.Master, key: str) -> list[tuple[Decimal, str]]:
"""시중물가 줄 하나의 (값, 출처) — 시중 다섯 칸 · 조달(나라장터 연결은 그 줄 값)."""
value = master.get(f"재료:시중물가:{key}")["값"]
out = [
(value["시중"][slot], f"재료:시중물가:{key}.시중.{slot}")
for slot in MARKET
if value["시중"].get(slot) is not None
]
supply = value["조달"]
if supply.get("값") is not None:
out.append((supply["값"], f"재료:시중물가:{key}.조달"))
elif str(supply.get("출처") or "").startswith("나라장터:"):
ref = "재료:나라장터자재:" + supply["출처"].split(":", 1)[1]
if master.get(ref).get("값") is not None:
out.append((master.get(ref)["값"], ref))
return out
def _market_index(master: mf.Master) -> list[tuple[str, str, str, str, str]]:
"""(품명 뿌리, 이름, 규격, 단위, 열쇠) — 「레미콘(관급)-경상북도(울진군)」 뿌리 = 레미콘."""
if not hasattr(master, "_market"):
master._market = []
for key, row in master.index.get(("재료", "시중물가"), {}).items():
name = plain(row.get("이름"))
root = re.sub(r"\(.*?\)", "", name.split(",")[0]).split("-")[0]
master._market.append(
(root, name, plain_spec(row.get("규격")), plain_unit(row.get("단위")), key)
)
return master._market
def _candidates(master: mf.Master, ref: str, row: dict, env: dict) -> list[str]:
"""후보 조건{이름 · 규격 · 지역: 입력} — 뿌리가 이름으로 끝나고 이름에 지역 글자 · 단위 같음."""
cond = row["연결"]
if cond.get("지역") == "입력" and REGION not in env:
raise mf.FormulaError(f"「{ref}」 입력 「{REGION}」 없음")
region = plain(env.get(REGION, "")) if cond.get("지역") == "입력" else ""
want, spec, unit = plain(cond["이름"]), plain_spec(cond.get("규격")), plain_unit(row["단위"])
hits = [
(s, key)
for root, name, s, u, key in _market_index(master)
if root.endswith(want) and region in name and u == unit and (not spec or s == spec)
]
hits = [(s, key) for s, key in hits if _offers(master, key)]
if not hits:
raise mf.FormulaError(f"「{ref}」 지역 값 없음 — {env.get(REGION)}")
if len({s for s, _ in hits}) > 1:
raise mf.FormulaError(f"「{ref}」 후보 여럿 — 규격 선택 필요 ({len(hits)}줄)")
return [key for _, key in hits]
def element(master: mf.Master, ref: str, env: dict) -> tuple[object, str | None]:
"""요소 값과 출처 — 품셈재료는 연결을 따라가 낮은 값 · 그 밖은 줄의 `값` 그대로(출처 None)."""
row = master.get(ref)
if not ref.startswith(LINKED):
return row.get("값"), None
link = row.get("연결")
if not link:
return None, None
keys = [link] if isinstance(link, str) else _candidates(master, ref, row, env)
offers = [x for key in keys for x in _offers(master, key)]
return min(offers, key=lambda x: x[0]) if offers else (None, None)
import master_formula as mf # noqa: E402 — 서로 부름 · 이 모듈 이름이 다 선 뒤에 읽음