Files
Aislo/M02_MasterTemplete/M02_Doc_Match_Value.py
T
eomsangdonandClaude Opus 5.5 28acddb4b9 feat(812-11): 연결 찾기 값 기준 넓힘 — 같은 기종 규격 · 수량 = 참고 · 바꾸기 때 기계 줄 갈아 끼움 · 기계 작업 갈래
- 새 M02_Doc_Match_Value — 같은 기종(이름 머리 + 괄호 낱말 · 조합 구성) 규격만 다름 = 참고 「규격」 + 바꿈 · 같은 자원 수량 다름 = 참고 「수량 끝자리」 · 「품 숫자」 · 「수량」 · 같은 배 두 줄 넘음 = 단위 환산(로직) · 기계 한 묶음 + 작업량 식 = 「기계 작업」(수량 · 규격까지 같은 기초만 확정) · 작업량 틀 꼴 참고 · 옵션 값으로 못 가름 = 「옵션 확인」
- M02_Doc_Match — 사실에 단가 기호 · 규격 · 수량 글 · 조합 표지 · 값 참고 적은 조합 먼저 · 선택 글 근거 쉼표 무시(D1,800 = D1800) · 같은 자원 아닌 수량 짝 = 그 로직 까닭
- M02_Doc_MatchSk — 바꾸기 뒤 사본 문서 기계 입력(원문번호) · 줄 이름 · 규격 · 묶음 제목 = 가져온 규격(수식 길 저장) · 되돌리기 = 보관 몸 수식 사본 그대로
- 띠 — 기계 작업 「기계 한 줄 단가산출 — 그대로 씀 · 값은 [단가 갱신]으로 우리 값」 · 참고 요약에 규격 · 수량 · 옵션 확인
- helper_810_4_sweep — 과녁 = 부품 뺀 문서(화면과 같음)
- 시험 고침: 810_4 단위 환산 = 같은 배 두 줄(한 줄만 배 = 수량 값) · 812_2 기계 규격 = 무한궤도 ↔ 타이어(같은 기종 규격 = 값)
- 전수 2,220: 확정 184 → 337 · 근접 576 → 848 · 없음 1,460 → 837 · 기계 작업 198 · 확정 다시 셈 다름 0 · 목록 _목록_812-11_연결판정.xlsx

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:11:55 +09:00

221 lines
10 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*$")
# ── 기종 ──────────────────────────────────────────────────────────────
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