- EST 공사 파일 자재 239 중 235 = 공사 직접 번호 — 파일마다 다른 품목(거창 치즐(0.7㎥) M0000000001 ↔ 우리 표 Air Hammer) · 이름 길로 - 견적 줄이 값 칸 다섯이 빈 우리 자재(치즐 0.7 MT000011)로 이어지면 0 원 — 견적 값 그대로 + 표지 - 시험 test_815_1 — 공사 직접 번호 · 값 빈 견적 · 거창 전 블록 금액 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
5.3 KiB
Python
117 lines
5.3 KiB
Python
"""M02 불러오기 요소 잇기 색인(PLAN 815-1 · 요구 755 ~ 764) — M01 요소 줄 「별칭」 칸 · EST 참조 코드 ·
|
|
재료 정확 일치. 서버 단독 · 읽기만(M01 판이 바뀌면 다시 읽음 · `id(whole)`).
|
|
|
|
- `keys(표, 이름, 규격)` = 별칭으로 이어지는 키들(풀이 = `master_alias` · 표 LB 인력 · EQ 기계 · MT 재료 ·
|
|
MO 유가) — 부르는 곳: 단가산출 불러오기(`M02_San_Link.m01`) · 1차 줄 판정(`M02_Hopyo_Row.m01_picks`) ·
|
|
기계 찾기(`M02_Machine_Find.alias`) · 일위대가 호표 줄(`M02_Hopyo_Units`)
|
|
- `est_code(코드)` = EST 원파일 자재 자원 코드(M…) ↔ 우리 자재 줄 `출처` 칸 「자재품목 M…」 같은 줄 ·
|
|
공사 직접 번호(M0000000…)는 빼고 이름 길
|
|
- `exact(이름, 규격)` = 지역 꼬리 뗀 이름 · 규격이 `master_alias.norm` 으로 같은 자재 줄(가까운 규격 아님)
|
|
- `one(줄, 키들)` = 같은 품목(지역 꼬리 뗀 이름 · 규격 같음)의 지역 줄 여럿 → 줄 지역 › 서울 › 같은 단위 ›
|
|
키 차례 하나 · 다른 품목이 섞이면 None(후보 여럿)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from functools import lru_cache
|
|
from typing import Any
|
|
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
|
|
import master_alias as am # noqa: E402 — M01 store 가 scripts 경로를 넣음
|
|
|
|
MARKET = "MT"
|
|
PRICES = ("물가자료", "유통물가", "물가정보", "거래가격", "관급") # 자재 값 칸 다섯
|
|
BASE_REGION = "서울"
|
|
_CODE = re.compile(r"\bM\d{10}\b")
|
|
LOCAL = "M0000000" # 공사 직접 번호(파일마다 다른 품목 · 거창 치즐 = 우리 표 Air Hammer) = 코드로 안 이음
|
|
_REGION = re.compile(r"\s*,\s*([가-힣]{2,4})\s*$") # 자재 이름 지역 꼬리 「, 서울」
|
|
|
|
|
|
def region_of(name: Any) -> tuple[str, str]:
|
|
"""자재 이름 → (지역 꼬리 뗀 이름, 지역 | 빈 글)."""
|
|
text = str(name or "")
|
|
m = _REGION.search(text)
|
|
return (text[: m.start()], m.group(1)) if m else (text, "")
|
|
|
|
|
|
def _same(name: Any, spec: Any) -> tuple[str, str]:
|
|
return am.norm(region_of(name)[0]), am.norm(spec)
|
|
|
|
|
|
@lru_cache(maxsize=2)
|
|
def _book(_whole: int) -> dict[str, Any]:
|
|
whole = m01.loaded()[1]
|
|
tables = {t: whole.numbers.get(t, {}) for t in ("LB", "EQ", "MO")}
|
|
tables[MARKET] = whole.index.get(MARKET, {})
|
|
rows = {t: {str(r.get("키") or k): r for k, r in got.items()} for t, got in tables.items()}
|
|
alias = {t: am.index({"키": k, "별칭": r.get("별칭")} for k, r in got.items()) for t, got in rows.items()} # fmt: skip
|
|
code: dict[str, list[str]] = {}
|
|
exact: dict[tuple[str, str], list[str]] = {}
|
|
for k, r in rows[MARKET].items():
|
|
for c in _CODE.findall(str(r.get("출처") or "")):
|
|
code.setdefault(c, []).append(k)
|
|
exact.setdefault(_same(r.get("이름"), r.get("규격")), []).append(k)
|
|
return {"줄": rows, "별칭": alias, "코드": code, "같음": exact}
|
|
|
|
|
|
def book() -> dict[str, Any]:
|
|
return _book(id(m01.loaded()[1]))
|
|
|
|
|
|
def row(table: str, key: Any) -> dict[str, Any]:
|
|
return book()["줄"].get(table, {}).get(str(key)) or {}
|
|
|
|
|
|
def keys(table: str, name: Any, spec: Any = "") -> list[str]:
|
|
"""별칭 칸으로 이어지는 키들(없음 = [])."""
|
|
b = book()
|
|
got = b["줄"].get(table, {})
|
|
return am.match(b["별칭"].get(table, {}), lambda k: (got.get(k) or {}).get("규격"), name, spec)
|
|
|
|
|
|
def priced(key: Any) -> bool:
|
|
"""우리 자재 줄에 값 칸 다섯 중 하나라도 있음."""
|
|
return any(row(MARKET, key).get(c) is not None for c in PRICES)
|
|
|
|
|
|
def est_code(code: Any) -> list[str]:
|
|
"""EST 자재 코드(M + 수 열) → 우리 자재 키들(출처 칸 같은 코드)."""
|
|
text = str(code or "").strip()
|
|
if not _CODE.fullmatch(text) or text.startswith(LOCAL):
|
|
return []
|
|
return list(book()["코드"].get(text, []))
|
|
|
|
|
|
def exact(name: Any, spec: Any) -> list[str]:
|
|
"""지역 꼬리 뗀 이름 · 규격 정규화 같음 자재 키들."""
|
|
return list(book()["같음"].get(_same(name, spec), []))
|
|
|
|
|
|
def one(line: dict[str, Any], found: list[str]) -> str | None:
|
|
"""같은 품목 지역 줄 여럿 → 하나(줄 지역 › 서울 › 같은 단위 › 키) · 다른 품목 섞임 = None."""
|
|
rows = {k: row(MARKET, k) for k in found}
|
|
if not rows or len({_same(r.get("이름"), r.get("규격")) for r in rows.values()}) != 1:
|
|
return None
|
|
mine, unit = region_of(line.get("이름"))[1], am.norm(line.get("단위"))
|
|
|
|
def rank(k: str) -> tuple:
|
|
where = region_of(rows[k].get("이름"))[1]
|
|
return (where != mine if mine else True, where != BASE_REGION, am.norm(rows[k].get("단위")) != unit, k) # fmt: skip
|
|
|
|
return min(rows, key=rank)
|
|
|
|
|
|
def material(line: dict[str, Any], code: Any = None) -> tuple[str | None, str]:
|
|
"""재료 줄 → (우리 자재 키, 길 「코드」 · 「별칭」 · 「정확 일치」) · 별칭 여럿 = (None, 「별칭 후보 여럿」) ·
|
|
없음 = (None, "")."""
|
|
if hit := est_code(code):
|
|
return one(line, hit) or hit[0], "코드"
|
|
hit = keys(MARKET, line.get("이름"), line.get("규격"))
|
|
if hit:
|
|
got = hit[0] if len(hit) == 1 else one(line, hit)
|
|
return (got, "별칭") if got else (None, "별칭 후보 여럿")
|
|
got = one(line, exact(line.get("이름"), line.get("규격")))
|
|
return (got, "정확 일치") if got else (None, "")
|