Files
Aislo/M02_MasterTemplete/M02_Doc_Match_Value.py
T
eomsangdonandClaude Opus 5.5 bed35e3bc3 feat(814-1 3·4·6·7·8·9·10): 불러오기 결정 반영 — 세 줄 묶기 · 꼬리 뗀 맞대기 · 유가 · 재료 · 까닭 · 가벼움
- 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>
2026-11-10 14:47:08 +09:00

234 lines
11 KiB
Python

"""연결 찾기 값 기준 넓힘(PLAN 812-11 · 요구 714 · 733 · 734 · 735) — `M02_Doc_Match` 가 부름.
값(참고 · 확정을 안 막음 · 바꾸면 우리 값):
같은 기종 규격만 다름(굴착기 0.7㎥ ↔ 1.0㎥ — 기종 = 기계표 이름 머리 + 괄호 낱말 · 무한궤도 대 타이어 = 다른 기종 ·
조합 기계는 구성 줄마다) = 「규격」(바꾸면 가져온 규격 — [바꾸기] 때 그 줄 기계를 갈아 끼움 `바꿈`) ·
수량 차이(같은 자원 줄) = 「수량 끝자리」(1 % 안) · 「품 숫자」(가져온 수량 = 수 하나) · 「수량」(조건 값 · 비율 · 표 값).
로직(다른 항): 자원 줄 구성(갈래 · 기종 · 노임 직종 · 재료 종류 · 비목) · 줄 없음 · 율 구조 · 단위 환산 =
같은 배(× 6 등)가 두 줄 넘게 같이 걸림(결과 단위 기준이 다름) — 한 줄만 배가 맞는 것은 수량 값.
기계 작업(`work`) = 가져온 문서가 기계 한 묶음(한 기계 줄 · 조합 기계면 그 구성 줄들)뿐 · 수량이 작업량 식(Q) =
우리 기초에 안 붙임(요구 728 · 735-4 — 수량 · 규격까지 같은 우리 기초가 있을 때만 확정) · 공식 꼴이 그 기종
「작업량 틀」(813-2 부품)과 같으면 참고에 틀 이름.
"""
from __future__ import annotations
import re
from decimal import Decimal
from functools import lru_cache
from typing import Any
from M02_MasterTemplete import M02_Machine_Find as mfind
WORK = "기계 작업"
SPEC, QTY, TAIL, POOM = "규격", "수량", "수량 끝자리", "품 숫자"
VALUE = (SPEC, QTY, TAIL, POOM) # 확정 순위 — 값 참고가 적은 조합 · 기초 먼저
CHECK = (*VALUE, "옵션 확인") # 기계 작업 꼴이 확정으로 붙지 않는 참고
_NUMBER = re.compile(r"^\s*\d+(?:\.\d+)?\s*$")
# ── 공구손료 율 비목(814-1 · 요구 744 C1) ─────────────────────────────────
TOOL = "공구손료 비목"
def tool_cost(a: dict, b: dict) -> dict | None:
"""공구손료 율 줄 짝 — 대상은 같고 비목만 다름(실무 재료비 ↔ 우리 경비) = 값(참고 · 확정 안 막음 ·
[바꾸기] = 우리 기초 사본 = 우리 비목) · 공구손료 아님 = None."""
if "공구" not in str(a.get("이름")) + str(b.get("이름")):
return None
say = f"{a['이름']} 비목 {'·'.join(a['비목'])} ↔ {'·'.join(b['비목'])} — 바꾸면 우리 비목"
return {"항": TOOL, "글": say}
# ── 기종 ──────────────────────────────────────────────────────────────
def row_of(code: Any) -> Any:
"""원문번호 → 기계표 줄(`M02_Machine_Find.Row`) · 없음 None."""
key = mfind.by_code(code)
return mfind.table()[2].get(key) if key else None
def same_kind(a: Any, b: Any) -> bool:
"""두 원문번호 = 같은 기종 — 이름 머리 + 괄호 낱말 같음(규격만 다름)."""
x, y = row_of(a), row_of(b)
return bool(x and y) and x.base == y.base and x.words == y.words
@lru_cache(maxsize=4096)
def _combo(name: str) -> tuple | None:
"""조합 기계 이름 → 구성 기종 묶음(정렬) · 낱 기계 = None."""
got = mfind.parts(name)
return tuple(sorted(mfind.split_name(n)[0] for n, _ in got)) if len(got) > 1 else None
def mark_combo(doc: dict, facts: list[dict]) -> None:
"""기계 사실에 `조합`(구성 묶음 · 낱 = None) · `갈라짐`(가져온 조합을 둘로 가른 줄 = 낱 기계로도 맞음)."""
head = _machines(doc)
for f in facts:
if f["갈래"] in ("기계", "산출"):
f["조합"] = _combo(f["이름"])
f["갈라짐"] = any(head.get(str(f.get("기호")), {}).get(k) for k in ("조합", "조합첫"))
def same_combo(a: dict, b: dict) -> bool:
"""조합 여부 같음 — 구성 묶음 같음 · 가른 줄 ↔ 낱 기계도 같음(「굴착기+부착용 집게」 ↔ 굴착기 하나 = 다름)."""
x, y = a.get("조합"), b.get("조합")
return x == y or (a.get("갈라짐") and y is None) or (b.get("갈라짐") and x is None)
def same_machine(a: dict, b: dict) -> bool:
"""같은 기종 기계 줄 — 원문번호 기종 같음 + 조합 구성 같음."""
return same_kind(a["코드"], b["코드"]) and same_combo(a, b)
def _eq(code: Any) -> str:
r = row_of(code)
return f"{r.raw.get('이름')} {r.raw.get('규격') or ''}".strip() if r else str(code)
def spec_note(a: dict, b: dict) -> dict:
"""같은 기종 규격 다름 → 참고 「규격」 + 바꿈(우리 줄 단가 기호 → 가져온 원문번호)."""
ra = row_of(a["코드"])
say = f"규격 다름 {_eq(a['코드'])} ↔ {(row_of(b['코드']).raw.get('규격') or '')}"
swap = {"기호": b.get("기호"), "기계": a["코드"], "이름": ra.raw.get("이름"),
"규격": ra.raw.get("규격") or ""} # fmt: skip
return {"항": SPEC, "글": f"{say} — 바꾸면 가져온 규격", "바꿈": swap}
def loose_note(a: dict, b: dict) -> dict | None:
"""못 이은 가져온 기계(원파일 묶음 값) ↔ 우리 기계 — 규격 글이 다르면 「안 바꿈」 참고."""
loose, ours = (a, b) if a["갈래"] == "산출" else (b, a)
row = row_of(ours["코드"])
if not row or not loose.get("규격"):
return None
if mfind.spec_fit(mfind.tokens(loose["규격"]), row.spec) == "같음":
return None
say = f"규격 다름 {loose['이름']} {loose['규격']} ↔ {_eq(ours['코드'])}"
return {
"항": SPEC,
"글": f"{say} — 가져온 기계를 우리 표에서 못 찾음 · 바꾸면 우리 기계 그대로",
}
# ── 수량 ──────────────────────────────────────────────────────────────
def _show(v: Any) -> str:
return f"{v:.4g}" if isinstance(v, Decimal) else str(v)
def qty_note(a: dict, b: dict) -> dict:
"""같은 자원 줄 수량 다름 → 참고(끝자리 · 품 숫자 · 수량)."""
x, y = a["수량"], b["수량"]
if isinstance(x, Decimal) and isinstance(y, Decimal) and y and abs(x - y) <= abs(y) / 100:
what = TAIL
elif _NUMBER.match(str(a.get("수량글") or "")):
what = POOM
else:
what = QTY
return {"항": what, "글": f"{a['이름']} 수량 다름 {_show(x)} ↔ {_show(y)} — 바꾸면 우리 값"}
def pairs(
take: Any, core: Any, cls: Any, pair: Any, ratio: Any
) -> tuple[list[dict], list[dict], int]:
"""수량 다른 같은 자원 짝(로직 같음 · `pair` 다른 항 0) → (다른 항 = 단위 환산 무리, 참고, 같은 줄 수) ·
`ratio` = 단위 환산 배 글(「× 6」) · 없음 None."""
got = take(
lambda a, b: core(a, b) and cls(a, b) and a["수량"] != b["수량"] and not pair(a, b)[0]
)
hows = [ratio(a["수량"], b["수량"]) for a, b in got]
many = {
h for h in hows if h and hows.count(h) >= 2
} # 같은 배가 두 줄 넘게 = 결과 단위 기준 다름
diffs: list[dict] = []
notes: list[dict] = []
for (a, b), how in zip(got, hows):
if how in many:
diffs.append(
{"항": "단위 환산", "글": f"{a['이름']} 수량 {a['수량']} ↔ {b['수량']} {how}"}
)
else:
notes.append(qty_note(a, b))
notes += pair(a, b)[1]
return diffs, notes, len(got) - sum(1 for h in hows if h in many)
# ── 기계 작업 ─────────────────────────────────────────────────────────
def _machines(doc: dict) -> dict[str, dict]:
head = (doc.get("가져온") or {}).get("기계")
return {str(x.get("기호")): x for x in head or [] if isinstance(x, dict)}
def _one_group(rows: list[dict]) -> bool:
if len(rows) == 1:
return True
return sum(1 for x in rows if x.get("조합첫")) == 1 and all(
x.get("조합") or x.get("조합첫") for x in rows
)
def _form(text: Any) -> str:
"""작업량 식 꼴 — 끝수 함수 벗김 · 빈칸 뺌 · 작은 글자(Cm = cm)."""
t = re.sub(r"\s", "", str(text or ""))
m = re.match(r"^(?:반올림|버림|올림)\((.*),\d+\)$", t)
return (m.group(1) if m else t).lower()
def _q_form(doc: dict, names: set[str]) -> str:
for line in doc.get("식") or []:
if str(line.get("기호")) in names and str(line.get("기호")).lower().startswith("q"):
return _form(line.get("식"))
return ""
def work(doc: dict, mine: list[dict], names: Any) -> dict | None:
"""가져온 문서가 기계 한 묶음 + 작업량 식 수량이면 {판정 기계 작업, 참고} · 아니면 None
(`names` = 식 글 → 쓰는 이름 · 인력 · 재료 · 율 줄이 섞이면 보통 길)."""
heads = _machines(doc)
if not mine or not heads or any(f["갈래"] == "율" for f in mine):
return None
rows = [f for f in mine if not f["합밖"]]
if not rows or any(
str(f.get("기호")) not in heads or f["갈래"] not in ("기계", "산출") for f in rows
):
return None
if not _one_group([heads[str(f["기호"])] for f in rows]):
return None
used = [names(f.get("수량글")) for f in rows]
if not all(used): # 수량이 수뿐 = 작업량 식 아님
return None
notes = []
form = _q_form(doc, set().union(*used))
from M01_MasterData import M01_MasterData_Parts as parts # M02 가 M01 을 부름 — 늦게
for f in rows:
if not form or not row_of(f["코드"]):
continue
got = parts.of_machine(str(f["코드"]))
for t in got["틀"]:
if any(
_form(x.get("식")) == form
for x in t["문서"].get("식") or []
if x.get("기호") == "Q"
):
say = f"{got['기계']['이름']} — 작업량 공식 꼴 = {t['키']} {t['이름']}"
notes.append({"항": "작업량 틀", "글": say})
break
return {"판정": WORK, "참고": notes, "후보수": 0}
# ── 옵션 확인 ─────────────────────────────────────────────────────────
def option_check(doc: dict, pick: dict, same: list[dict], qty_vars: set[str]) -> list[dict]:
"""값이 정확히 맞는 조합이 없고 수량을 정하는 선택이 값으로 안 갈림(같은 순위 조합에 값 여럿) =
고른 값(기본 먼저) + 참고 「옵션 확인」."""
from M02_MasterTemplete.M02_Doc_Copy import md
out = []
for v in doc.get("변수") or []:
vid, link = md.var_id(v), v.get("연결") or {}
if link.get("갈래") != "선택" or vid not in qty_vars:
continue
if len({str(p.get(vid)) for p in same}) > 1:
how = "기본값" if pick.get(vid) == link.get("값") else "고른 값"
say = f"옵션 확인 — {v.get('이름') or vid} = {pick.get(vid)}({how} · 값으로 못 가름)"
out.append({"항": "옵션 확인", "글": say})
return out