Files
Aislo/M02_MasterTemplete/M02_San_Link.py
T
eomsangdonandClaude Opus 5.5 38ad78d380 feat(827-2): 불러오기 재료 찾기 — 단서 넷 · 자동 규칙 A~F2 · 판정 네 갈래 · 별도계상 품목 이음
- 새 M02_San_MatFind: 줄 판정 자원「재료찾기」{판정 · 방법 · 키 · 후보 · 단서} — 이어짐 · 규격 없음 — 고르기 · 후보 여럿 · 우리 표에 없음
- 단서: 산근 줄 설명 글 · 산근 머리 · STC 내역서 자재 줄 · STmate 엑셀 일위대가표(규격 빈 줄 · 별도계상 줄만 좁힘 · 관급/사급은 안 가름)
- 자동 규칙 A · B · C · E · F · F2(G 안 씀) — 원천 넷 이미 이은 464 종에 얹어 다른 키 0 · E 는 이름 같은 우리 줄 있으면 안 씀
- 별도계상 줄: 품목은 판정 키에만 · 줄 이음 · 단가 0 그대로
- 의심 고침: 블레이드 d=320~400 t=3.2 → 4" (규격 수 하나를 두 번 맞춤 — spec_fit 한 번씩) · 유압식드릴롯드 → 드릴롯드 AX 1.5(후보 하나 + 규격 수 부딪힘 = 이음 안 함) · 참싸리(박피)는 사용자 과녁대로 둠
- 전수: 금액 같음 3241/3241 · 기계 그대로 · 재료 이음 EST 원파일 99.7 그대로 · EST 엑셀 82.9→84.6 · STmate 원파일 59.8→68.5 · STmate 엑셀 66.7→76.5 · 새 이음 32 줄 틀림 0 · 키 바뀜 0
- 시험: test_817_1 표 일곱 줄 후보→이음(사용자 과녁 1 순위 키 그대로 · 보조강재만 새 키 MT030619) · 낡은 기대 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 11:41:17 +09:00

208 lines
11 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.
"""경쟁사 산근 줄 → M01 요소 이음(PLAN 814-1 · `M02_San_Emit` 에서 뗌 — 셈 꼴 글 · 표지 그대로).
`m01` = 1차 줄 판정 유일 짝(인력 · 재료 · 기계 · 유가 · 환율) · `hourly_text` = 기계 시간당 경비 부름 글 ·
`combo_link` = 조합 기계(굴착기 + 브레이커 · 집게) 우리 조합 식 부름 글.
"""
from __future__ import annotations
NOTE = "참고 · " # `M02_San_Emit.NOTE` 와 같음(사람 확인 표지 머리)
OIL_BASE = ("전국평균", "일반용(갑)Ⅰ 저압") # 유가 · 전력 기본(요구 748 · 우리 기초 기본값)
QUOTE = "견적 — 그대로(이름 · 값) · [단가 갱신] 대상 아님" # 815-1 K5 · 요구 762-3
APART = "별도계상 · 품목은 프로젝트에서" # 817-1 — 일위대가 길(`M02_CheckApart`)과 같은 글 · 요소 잇기 없음
APART_ITEM = "별도계상 · 품목 이음 · 셈은 프로젝트에서" # 827-2 — 품목은 판정(`재료찾기`)에만 · 줄 이음 · 단가 0 그대로
def m01(kind: str, src: dict, label: str = "") -> tuple[str | None, str | None]:
"""1차 줄 판정 유일 짝 M01 키 → (키, 표지) — 없음 = (None, None) · 여럿 = (None, 표지 · 유가 ·
환율만 — 재료 · 인력은 조용히 이음 없음) · 기계 = 기계 찾기(812-9) 못 이으면 늘 표지. 재료 줄 이름이 M01 유가전력(MO)이면
유가 · 화폐 이름 그대로(달러 · USD …)면 환율(FX)로 판정(797-1 · 「유로폼」 같은 글 속 낱말은 아님).
재료 = 판정 `src["재료찾기"]`(827-2 `M02_San_MatFind` · label = 산근 줄 설명 글) · 별도계상 줄도 품목을 찾되
이음은 비움(셈 제외 · 단가 0 그대로)."""
if kind == "고정":
return quote(src)
if kind not in ("인력", "재료", "기계"):
return None, None
from M02_MasterTemplete import M02_Elem_Alias as ea
from M02_MasterTemplete import M02_Hopyo_Elem as elem
from M02_MasterTemplete import M02_Hopyo_Row as hrow
from M02_MasterTemplete import M02_San_MatFind as mf
line = {k: src.get(k) or "" for k in ("이름", "규격", "단위")}
if kind == "재료" and apart(line): # 817-1 원가 합 밖(원본 금액 그대로) · 827-2 품목은 판정에
clean = hmat_clean(line)
got = mf.find(clean, src, _keys(clean, hrow.m01_picks("재료", clean)), label, apart=True)
return None, f"{NOTE}{APART_ITEM if got['키'] else APART}"
if (
kind == "기계"
): # 812-9 — 하나 = 이음 · 아니면 표지(후보 여럿 · 못 찾음 · 조용히 넘기지 않음)
from M02_MasterTemplete import M02_Machine_Find as mfind
got = mfind.find(line["이름"], line["규격"])
# 찾기 판정 없는 기계 줄 막음(814-1 · 줄 쌓기 표지 · 별칭)
src.setdefault("찾기", {**got, "이름": got.get("이름") or line["이름"], "규격": got.get("규격") or line["규격"]}) # fmt: skip
return got["키"], mfind.mark(got, line["이름"], line["규격"])
if kind == "재료" and src.get("종류") == "자재" and (hit := ea.est_code(src.get("코드"))):
key = ea.one(line, hit) or hit[0] # 815-1 K1 — EST 원파일 자재 = 우리 자재 출처 코드
return mf.done(key, "코드", src)["키"], None
oil = ea.keys("MO", line["이름"], line["규격"]) if kind == "재료" else []
if kind == "재료":
if oil or elem.oil_rows(line["이름"]):
kind = "유가"
elif line["이름"].strip().upper() in {x.upper() for pair in hrow.CURRENCY for x in pair}:
kind = "환율"
if kind == "유가": # MO 전부 → 줄 규격이 상세구분(전국 · 지역)이면 그것만
rows = [ea.row("MO", k) for k in oil] or elem.oil_rows(line["이름"]) # 815-1 별칭 먼저
got = [{"키": r["키"], "규격": str(r.get("상세구분") or "")} for r in rows]
spec = line["규격"].replace(" ", "")
got = (
[x for x in got if x["규격"] == spec]
or [x for x in got if spec and spec in x["규격"]]
or got
)
else:
got = hrow.m01_picks(kind, line)
if kind == "재료": # 827-2 — 지금 찾기 키 → 판정(규칙 · 단서 · 네 갈래)
return mf.find(line, src, _keys(line, got), label)["키"], None
keys = {x.get("키") for x in got if x.get("키")}
if (
len(keys) > 1 and kind == "유가"
): # 814-1 — 전국평균 · 전력 = 일반용(갑)Ⅰ 저압(지역 · 계약종별은 옵션)
keys = {x["키"] for x in got if x["규격"] in OIL_BASE} or keys
if len(keys) == 1:
return keys.pop(), None
if len(keys) > 1 and kind in ("유가", "환율"):
return None, f"{NOTE}{kind} 후보 여럿({len(keys)}) · 사람 확인"
return None, None
def _keys(line: dict, got: list[dict]) -> set[str]:
"""재료 1차 찾기 키 — 여럿이면 지역 꼬리 뗀 정확 일치(814-1) · 없으면 자재표 전체 정확 일치(815-1 K3)."""
from M02_MasterTemplete import M02_Elem_Alias as ea
keys = {x.get("키") for x in got if x.get("키")}
if len(keys) > 1:
keys = exact(line, got) or keys
if not keys:
keys = {k} if (k := ea.one(line, ea.exact(line["이름"], line["규격"]))) else keys
return keys
def hmat_clean(line: dict) -> dict:
from M02_MasterTemplete import M02_Hopyo_Material as hmat
return {**hmat.clean(line)[0], "단위": line.get("단위") or ""}
def apart(line: dict) -> bool:
"""줄 이름 · 규격에 「별도계상」(`M02_Hopyo_Material.clean`)."""
from M02_MasterTemplete import M02_Hopyo_Material as hmat
return hmat.clean(line)[1]
def quote(src: dict) -> tuple[str | None, str | None]:
"""견적 · 운임 · 수수료 꼴 줄(갈래 밖 — EST 견적 · STmate 고정 코드 · 815-1 K5) → 우리 자재에 이어지면
(코드 · 별칭 · 정확 일치 · 단가 줄 · 우리 값 있을 때) 그 키 + 갈래 재료(우리 값 · 엑셀 값은 검산) ·
아니면 그대로 + 표지."""
from M02_MasterTemplete import M02_Elem_Alias as ea
src["견적"] = True
line = {k: src.get(k) or "" for k in ("이름", "규격", "단위")}
key, _how = ea.material(line, src.get("코드"))
if key and src.get("비목") and ea.priced(key): # 우리 값 없는 자재 = 견적 값 그대로
src["갈래"] = "재료"
return key, None
return None, f"{NOTE}{QUOTE}"
def exact(line: dict, got: list[dict]) -> set[str]:
"""재료 후보 가운데 지역 꼬리 뗀 이름 · 규격이 줄과 정확히 같은 것(표기 정규화 `master_alias.norm`) —
지역마다 여럿이면 줄 지역 › 서울 › 같은 단위 › 키 차례 첫째 하나 · 없음 = 빈 모음(사람 확인)."""
from M02_MasterTemplete import M02_Elem_Alias as ea
want = ea._same(line.get("이름"), line.get("규격"))
hits = [x["키"] for x in got if x.get("키") and ea._same(x.get("이름"), x.get("규격")) == want]
return {k} if (k := ea.one(line, hits)) else set()
def hourly_text(key: str | None) -> str | None:
"""기계 EQ → 시간당 기계경비 로직 부름 글(797-1 짝 고름) · 키 · 자료 없음 = None."""
if not key:
return None
from M02_MasterTemplete import M02_Doc_Import as dim
got = dim.hourly_link(key, "노무비")
if not got:
return None
args = "".join(f", {dim._q(k)}={v}" for k, v in got["입력"].items()) # 빈칸 든 이름은 따옴표
return f"로직({got['키']}{args})"
def combo_link(one: dict, parts: list[dict]) -> str | None:
"""조합 기계 둘이 우리 조합 식(GC000270 · 271) 고르기에 있으면 그 부름 글 · 줄 자원에 `조합식` 키."""
from M02_MasterTemplete import M02_Machine_Hourly as mh
got = mh.combo(parts[0]["키"], parts[1]["키"]) if len(parts) == 2 and all(p["키"] for p in parts) else None # fmt: skip
if got:
one["자원"]["조합식"] = got[0]
return got[1] if got else None
# ── 못 이은 줄 까닭(814-1 · 요구 745 C2) ─────────────────────────────────
FREIGHT = ("운임", "화물", "운반비", "하차")
def why(item: dict) -> str:
"""이음 없는 가져온 줄 → 까닭 한 마디(문서 설명 · 띠 · 원문 카드) — 「없음」은 흠이 아니라 우리 정보로
못 이은 까닭(자재 없음 · 운임표 없음 · 기계 표에 없음 · 별칭 대기 · 후보 여럿 · 시간당 경비 자료 없음 · 갈래 밖)."""
res = item.get("자원") or {}
kind = res.get("갈래") or item.get("종류")
name = str(res.get("이름") or item.get("이름") or "")
if res.get("조합식"):
return f"조합 — 우리 조합 식 {res['조합식']} 에 듦"
if kind == "기계":
found = res.get("찾기") or {}
verdict = found.get("판정")
if verdict == "못 찾음":
return (
"별칭 대기"
if str(found.get("까닭") or "").startswith("기종 모름")
else "기계 표에 없음"
)
if verdict == "후보 여럿":
return "기계 후보 여럿 — 고름"
return "시간당 경비 자료 없음" if found.get("키") else "기계 찾기 없음"
if kind == "재료":
from M02_MasterTemplete import M02_Hopyo_Elem as elem
from M02_MasterTemplete import M02_Hopyo_Row as hrow
if apart({"이름": name, "규격": res.get("규격") or ""}):
return APART_ITEM if (res.get("재료찾기") or {}).get("키") else APART
if elem.oil_rows(name):
return "유가 후보 여럿 — 고름"
line = {"이름": name, "규격": res.get("규격") or "", "단위": res.get("단위") or ""}
if picks := hrow.m01_picks("재료", line): # 817-1 ㉯ 1 순위를 글에(고르기 창은 이름 찾기)
return f"자재 후보 여럿 — 고름 · 1 순위 {picks[0]['이름']} {picks[0]['규격']}".strip()
return "운임표 없음" if any(w in name for w in FREIGHT) else "자재 없음(우리 자재표)"
if kind == "인력":
return "노임 없음"
if kind == "단가산출":
return "안쪽 단가산출 못 이음"
return QUOTE
def tally(logic: dict) -> dict:
"""우리 정보로 셈 — 자원 줄 {됨, 줄, 조합(형제 단가산출 부름뿐)} (연결 판정 「없음」 띠 · 814-1)."""
items = logic.get("호표") or []
done = sum(
1
for x in items
if x.get("이음")
or x.get("참조")
or (x.get("자원") or {}).get("조합식")
or ((x.get("자원") or {}).get("재료찾기") or {}).get("키") # 827-2 별도계상 품목
)
inner = [x for x in items if (x.get("자원") or {}).get("갈래") == "단가산출" or x.get("참조")]
return {"됨": done, "줄": len(items), "조합": bool(items) and len(inner) == len(items)}