- M02_San_Link.hourly_text 가 「전력 계약종별=…」 를 따옴표 없이 적어 가져온 식 읽기(_link)가 못 읽음 → 이름을 따옴표로(읽는 쪽은 이미 받음) - 지킴 시험(test_820_1) 왕복 + EST main 504 번 = 저장 금액 · 우리 단가 셈 추가 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
182 lines
9.6 KiB
Python
182 lines
9.6 KiB
Python
"""경쟁사 산근 줄 → 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`)과 같은 글 · 요소 잇기 없음
|
||
|
||
|
||
def m01(kind: str, src: dict) -> tuple[str | None, str | None]:
|
||
"""1차 줄 판정 유일 짝 M01 키 → (키, 표지) — 없음 = (None, None) · 여럿 = (None, 표지 · 유가 ·
|
||
환율만 — 재료 · 인력은 조용히 이음 없음) · 기계 = 기계 찾기(812-9) 못 이으면 늘 표지. 재료 줄 이름이 M01 유가전력(MO)이면
|
||
유가 · 화폐 이름 그대로(달러 · USD …)면 환율(FX)로 판정(797-1 · 「유로폼」 같은 글 속 낱말은 아님)."""
|
||
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
|
||
|
||
line = {k: src.get(k) or "" for k in ("이름", "규격", "단위")}
|
||
if kind == "재료" and apart(
|
||
line
|
||
): # 817-1 — 원가 합 밖(원본 금액 그대로) · 품목은 프로젝트에서 이음
|
||
return None, f"{NOTE}{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("코드"))):
|
||
return ea.one(line, hit) or hit[0], None # 815-1 K1 — EST 원파일 자재 = 우리 자재 출처 코드
|
||
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)
|
||
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
|
||
elif len(keys) > 1 and kind == "재료": # 814-1 — 지역 꼬리 뗀 이름 · 규격 정확 일치
|
||
keys = exact(line, got) or keys
|
||
if not keys and kind == "재료": # 815-1 K3 — 자재표 전체에서 표기 정규화 정확 일치
|
||
keys = {k} if (k := ea.one(line, ea.exact(line["이름"], line["규격"]))) else 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 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
|
||
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("조합식")
|
||
)
|
||
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)}
|