Merge remote-tracking branches 'origin/sub_desktop_1' and 'origin/sub_laptop_1' into sub_laptop_2

This commit is contained in:
2026-09-20 23:09:57 +09:00
18 changed files with 334 additions and 79 deletions
@@ -4,6 +4,7 @@
담당: 소요량_*.json · 계수_*.json (읽기+쓰기) · 로직_*.json · 원문 md (읽기만).
다시 돌리면 같은 결과 — 순수 함수 + 정렬.
"""
import glob
import json
import re
@@ -337,6 +337,25 @@ def check_links(whole: mf.Master) -> list[str]:
return out
def check_units(files: dict[str, dict], whole: mf.Master) -> list[str]:
"""단위 경고 — 재료 고르기 조건의 후보 단위가 그 줄 단위와 다름(호표 줄 · 품셈재료 줄).
데이터가 틀린 것이 아니라 조건을 더 좁혀야 하는 자리라 로직 검사와 따로 봄."""
out = []
for key, row in whole.index.get("MP", {}).items():
cond = row.get("고르기")
if isinstance(cond, dict):
out += mf.check_unit(whole, whole.label(key) + " 고르기", cond, row.get("단위"))
for name, data in files.items():
if data.get("그룹") != "로직":
continue
for row in data.get("", []):
for item in row.get("호표", []):
if isinstance(item.get("요소"), dict):
where = f"{name} · {row.get('')} 호표 {item.get('이름', '')} 요소"
out += mf.check_unit(whole, where, item["요소"], item.get("단위"))
return out
def _check_table(where: str, table: dict) -> list[str]:
out, conds, cols = [], table.get("조건", {}), table.get("값칸", {})
allowed = set(conds) | set(cols) | {"단위", ""} | {c + "원문" for c in cols}
@@ -613,6 +632,8 @@ def main(argv: list[str]) -> int:
report["본문"] = check_body(files)
if mode in ("로직", "전부"):
report["로직"] = check_logics(files, whole)
if mode in ("단위", "전부"):
report["단위"] = check_units(files, whole)
for title, found in report.items():
print(f"({title}) {len(found)}")
for line in found:
@@ -481,7 +481,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
# 고르기 조건 묶음은 그대로 넘김 — `element` 가 대표 줄·첫 줄로 풂
ref = item["요소"] if isinstance(item["요소"], dict) else fill(item["요소"], env)
try:
price, source = element(master, ref, env)
price, source = element(master, ref, env, item.get("단위"))
except FormulaError:
if qty != 0:
raise
@@ -703,6 +703,7 @@ from master_material import ( # noqa: E402, F401
TARIFF,
candidates,
check_pick,
check_unit,
cond_text,
element,
pick,
@@ -111,35 +111,58 @@ def cond_text(cond: dict) -> str:
return "|".join(str(cond.get(k) or "") for k in PICK_KEYS)
def candidates(master: mf.Master, cond: dict, env: dict | None = None) -> list[str]:
def candidates(
master: mf.Master, cond: dict, env: dict | None = None, unit: str | None = None
) -> list[str]:
"""고르기 조건 안 자재품목 키 — 구분 · 상세구분 · 규격 낱말이 모두 든 줄 · 파일 차례대로.
`unit` 을 주면 그 단위(호표 줄 단위)와 같은 줄만 — 단위가 다르면 다른 물건.
입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄만(그런 줄이 없으면 조건 안 전부)."""
want = [plain_spec(w) for w in str(cond.get("규격") or "").split()]
detail = cond.get("상세구분")
need = plain_unit(unit) if unit else ""
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)
and (not need or plain_unit(row.get("단위")) == need)
]
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:
"""시험 계산이 쓸 줄 — 관리자가 정한 `대표`, 없으면 조건 안 값 있는 첫 줄."""
def pick(master: mf.Master, cond: dict, env: dict, unit: str | None = None) -> str:
"""시험 계산이 쓸 줄 — 관리자가 정한 `대표`, 없으면 조건 안 값 있는 첫 줄(단위 같은 줄만)."""
if cond.get("대표"):
return str(cond["대표"])
keys = candidates(master, cond, env)
keys = candidates(master, cond, env, unit)
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]:
def check_unit(master: mf.Master, where: str, cond: dict, unit: str | None) -> list[str]:
"""단위 경고 — 호표 줄·품셈재료 줄 단위와 맞는 후보가 없거나 대표 줄 단위가 다름.
틀린 데이터가 아니라 좁혀야 할 조건이라 `check_pick` 과 따로 봄(`check_master 단위`)."""
rows = master.index.get(MARKET, {})
rep = str(cond.get("대표") or "")
if not unit or set(cond) - set(PICK_KEYS) or not cond.get("구분"):
return []
if rep in rows:
if plain_unit(rows[rep].get("단위")) != plain_unit(unit):
return [
f"{where} · 대표 줄 단위 「{rows[rep].get('단위')}」 이 줄 단위 「{unit}」 과 다름"
]
return []
if candidates(master, cond) and not candidates(master, cond, None, unit):
return [f"{where} · 단위 「{unit}」 후보 0 — 조건 안 줄은 모두 다른 단위"]
return []
def check_pick(master: mf.Master, where: str, cond: dict, unit: str | None = None) -> list[str]:
"""고르기 조건 검사 — 칸 이름 · 구분·상세구분이 자재품목에 있는지 · 대표 줄 · 후보 0."""
if set(cond) - set(PICK_KEYS) or not cond.get("구분"):
return [f"{where} · 재료 고르기 조건 모양 「{cond}」 — {' · '.join(PICK_KEYS)}"]
@@ -157,11 +180,13 @@ def check_pick(master: mf.Master, where: str, cond: dict) -> list[str]:
return out
def element(master: mf.Master, ref, env: dict) -> tuple[object, str | None]:
def element(
master: mf.Master, ref, env: dict, unit: str | None = None
) -> tuple[object, str | None]:
"""요소 값과 출처 — 자재품목은 제 줄, 품셈재료는 연결을 따라가 낮은 값 · 그 밖은 줄의 `값`.
`ref` 가 고르기 조건 묶음이면 대표 줄(없으면 조건 안 첫 줄)로 풂."""
`ref` 가 고르기 조건 묶음이면 대표 줄(없으면 단위 같은 조건 안 첫 줄)로 풂."""
if isinstance(ref, dict):
ref = pick(master, ref, env)
ref = pick(master, ref, env, unit)
row = master.get(ref)
if ref[:2] == MARKET: # 자재품목을 바로 가리키는 호표 줄
return _lowest(master, ref)