- 3 연결 찾기 = 엑셀 이름 규격 · 단위 꼬리 뗀 이름으로 맞댐(`M02_Doc_Match_Note.bare` · 보이는 이름 그대로) · STmate 블록 규격도 가져온 칸에
- 4 기계 세 줄 = 기계 한 줄 — STmate X 코드 노 · 재 · 경 세 참조 줄(같은 수량 · 번지) 묶음(묶어서 셈 못 하는 세 줄만 그대로) ·
문서 단계 묶기 넓힘(차례 아무렇게 · 시간당 경비 이음 없으면 엑셀 값 묶음 · 번지 같을 때) · 조합 다음 줄 = 세 줄 뒤 · 같은 기계 한 항목
- 6 공구손료 율 비목 차이(재료비 ↔ 경비) = 참고(`M02_Doc_Match_Value.tool_cost`) — 확정 안 막음
- 7 못 이은 줄 까닭(`M02_San_Link.why` — 자재 없음 · 운임표 없음 · 기계 표에 없음 · 별칭 대기 · 후보 여럿 · 시간당 경비 없음 · 조합) =
문서 설명 · 이음 없는 자원 띠 · 연결 「없음」 띠 = 「우리 기초 없음 — 우리 정보로 셈 n/m 줄」 · 형제만 = 「조합」
- 8 유가 = 전국평균 줄 + 지역 옵션(`MO:경유:{S_지역}`) · 전력 = 일반용(갑)Ⅰ 저압 + 계약종별 옵션 · 엑셀 값 = 검산 칸
- 9 재료 = 지역 꼬리 뗀 이름 · 규격 정확 일치면 이음(지역 여럿 = 줄 지역 › 서울)
- 10 조합 첫 줄 규격 = 그 기계 · 얼린 줄도 소계 식에(북부청임도 33) · 기계 줄 찾기 판정 늘 붙음 · 올린 직후 409(HP 보이기 전 산근 보관) ·
엑셀 블록 「엑셀 읽기 확정」 글 · 전수 도우미 str() 뗌 · 줄 쌓기 이름에 규격이 들면 한 번만
- M02_San_Link 새 파일(San_Emit 700 줄 막음 — 요소 잇기 · 시간당 · 조합 · 까닭)
- 시험: 새 test_814_1 · 812-2 · 810-4 거푸집 41 = 근접 → 확정(공구손료 비목 = 값 · 요구 744 C1) · 바꾸기 거절 견본 = 19 ·
812-9 조합 첫 줄 규격 「0.7+브레이카」 → 「0.7」(칩 글 · 요구 738 흠 12)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
8.2 KiB
Python
168 lines
8.2 KiB
Python
"""경쟁사 산근 줄 → M01 요소 이음(PLAN 814-1 · `M02_San_Emit` 에서 뗌 — 셈 꼴 글 · 표지 그대로).
|
||
|
||
`m01` = 1차 줄 판정 유일 짝(인력 · 재료 · 기계 · 유가 · 환율) · `hourly_text` = 기계 시간당 경비 부름 글 ·
|
||
`combo_link` = 조합 기계(굴착기 + 브레이커 · 집게) 우리 조합 식 부름 글.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
import unicodedata
|
||
|
||
NOTE = "참고 · " # `M02_San_Emit.NOTE` 와 같음(사람 확인 표지 머리)
|
||
OIL_BASE = ("전국평균", "일반용(갑)Ⅰ 저압") # 유가 · 전력 기본(요구 748 · 우리 기초 기본값)
|
||
BASE_REGION = "서울" # 같은 이름 · 규격 자재가 지역마다 있을 때 기본(우리 기초 검색어 꼴)
|
||
_REGION = re.compile(r"\s*,\s*([가-힣]{2,4})\s*$") # 자재 이름 지역 꼬리 「, 서울」 · 「,서울」
|
||
|
||
|
||
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 not in ("인력", "재료", "기계"):
|
||
return None, None
|
||
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 == "기계"
|
||
): # 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 == "재료":
|
||
if 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 전부 → 줄 규격이 상세구분(전국 · 지역)이면 그것만
|
||
got = [
|
||
{"키": r["키"], "규격": str(r.get("상세구분") or "")}
|
||
for r in elem.oil_rows(line["이름"])
|
||
]
|
||
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 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 _flat(text: object) -> str:
|
||
return re.sub(r"\s+", "", unicodedata.normalize("NFKC", str(text or ""))).lower()
|
||
|
||
|
||
def region_of(name: object) -> tuple[str, str]:
|
||
"""자재 이름 → (지역 꼬리 뗀 이름, 지역 | 빈 글)."""
|
||
text = str(name or "")
|
||
m = _REGION.search(text)
|
||
return (text[: m.start()], m.group(1)) if m else (text, "")
|
||
|
||
|
||
def exact(line: dict, got: list[dict]) -> set[str]:
|
||
"""재료 후보 가운데 지역 꼬리 뗀 이름 · 규격이 줄과 정확히 같은 것(빈칸 · 전각 · 대소문자 정규화) —
|
||
지역마다 여럿이면 줄 지역 › 서울 › 같은 단위 › 키 차례 첫째 하나 · 없음 = 빈 모음(사람 확인)."""
|
||
name, mine = region_of(line.get("이름"))
|
||
want = (_flat(name), _flat(line.get("규격")))
|
||
hits = [x for x in got if x.get("키") and (_flat(region_of(x["이름"])[0]), _flat(x.get("규격"))) == want] # fmt: skip
|
||
if not hits:
|
||
return set()
|
||
unit = _flat(line.get("단위"))
|
||
|
||
def rank(x: dict) -> tuple:
|
||
where = region_of(x["이름"])[1]
|
||
return (where != mine if mine else True, where != BASE_REGION, _flat(x.get("단위")) != unit, str(x["키"])) # fmt: skip
|
||
|
||
return {min(hits, key=rank)["키"]}
|
||
|
||
|
||
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
|
||
return f"로직({got['키']}{''.join(f', {k}={v}' for k, v in got['입력'].items())})"
|
||
|
||
|
||
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 elem.oil_rows(name):
|
||
return "유가 후보 여럿 — 고름"
|
||
line = {"이름": name, "규격": res.get("규격") or "", "단위": res.get("단위") or ""}
|
||
if hrow.m01_picks("재료", line):
|
||
return "자재 후보 여럿 — 고름"
|
||
return "운임표 없음" if any(w in name for w in FREIGHT) else "자재 없음(우리 자재표)"
|
||
if kind == "인력":
|
||
return "노임 없음"
|
||
if kind == "단가산출":
|
||
return "안쪽 단가산출 못 이음"
|
||
return "갈래 밖(견적 · 고정 값)"
|
||
|
||
|
||
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)}
|