Files
Aislo/M02_MasterTemplete/M02_San_Link.py
T
eomsangdonandClaude Sonnet 5.5 bce7e245e0 fix(820-2): 시간당 이음 글 빈칸 든 입력 이름 따옴표 — 파워트로웰 EST main 504 번 오류 고침
- M02_San_Link.hourly_text 가 「전력 계약종별=…」 를 따옴표 없이 적어 가져온 식 읽기(_link)가 못 읽음 → 이름을 따옴표로(읽는 쪽은 이미 받음)
- 지킴 시험(test_820_1) 왕복 + EST main 504 번 = 저장 금액 · 우리 단가 셈 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 06:07:14 +09:00

182 lines
9.6 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`)과 같은 글 · 요소 잇기 없음
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)}