"""경쟁사 산근 줄 → 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)}