Files
Aislo/M02_MasterTemplete/M02_San_Link.py
T
eomsangdonandClaude Opus 5.5 520bb33909 feat(827-3): 불러오기 재료 걸음 3 — 고를 재료 판 · 그 파일 안 고름(별칭 아님) · SK 상세 같은 파일 물음
- 새 M02_San_MatPick: 종 열쇠(이름 · 규격 · 단위 · 단서) · 고름 자리 = 개인 층 실무자료/재료고름/<파일 지문>.json([끝내기] 뒤 남음 · 같은 파일 다시 올리면 그대로) · 판(블록마다 재료찾기 판정 → 종 · 첫 판 한 번 셈 → 산근 저장본 칸) · 만들 때 얹기 · 이미 만든 SK 얹기(가져온 그대로인 줄만 · 바꿈 · 따로 고른 줄 그대로 · 풀면 가져온 그대로)
- 만들기: from-hopyo 가 그 파일 고름째 만듦(저장 안 한 판 고름 = 만들 때 저장) · 원본 지문 · 고른 줄 = 이음 + 재료찾기.고름 · 문서 셈 우리 값(엑셀 값 기억 없음) · 별도계상 = 품목만(줄 이음 · 단가 0 그대로)
- 문서 머리 가져온.재료(판정 · 후보 · 단서 · 처음 연결) · 가져온.지문
- 길: GET/POST hopyo-materials/{HP}(판 · [저장] · 만든 것 적용은 물음 예일 때만) · GET/POST material-picks/{지문}(SK 상세 물음)
- 화면: 단가산출 목록 위 「고를 재료」 판(후보 고름 · 찾기 · 고름 풀기 · n 블록에 적용 · 이어진 재료 접힘 · [저장]) · 저장 안 한 고름 = 떠날 때 물음 · 줄 쌓기 재료 줄 판정 배지 · 풍선말(판정 · 단서 · 후보) · 이름 단추 · [이음 고르기] 뒤 「같은 파일 n 줄」 물음
- 별도계상 설명 = 「별도계상 · 품목: 이름 규격」(엔진 이은 품목은 자재표 이름) — test_827_2 기대 글 고침(품목 이음 → 품목 이름)
- 시험 test_827_3 셋(파일 고름 블록 둘 · 다른 파일 0 · 별칭 · 마스터 그대로 · 풀기 · 다시 올림 유지 · 물음 예일 때만 · 따로 바꾼 줄 보존 · 별도계상 품목 + 단가 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 12:51:04 +09:00

211 lines
11 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`)과 같은 글 · 요소 잇기 없음
APART_ITEM = "별도계상 · 품목 이음 · 셈은 프로젝트에서" # 827-2 — 품목은 판정(`재료찾기`)에만 · 줄 이음 · 단가 0 그대로
def m01(kind: str, src: dict, label: str = "") -> tuple[str | None, str | None]:
"""1차 줄 판정 유일 짝 M01 키 → (키, 표지) — 없음 = (None, None) · 여럿 = (None, 표지 · 유가 ·
환율만 — 재료 · 인력은 조용히 이음 없음) · 기계 = 기계 찾기(812-9) 못 이으면 늘 표지. 재료 줄 이름이 M01 유가전력(MO)이면
유가 · 화폐 이름 그대로(달러 · USD …)면 환율(FX)로 판정(797-1 · 「유로폼」 같은 글 속 낱말은 아님).
재료 = 판정 `src["재료찾기"]`(827-2 `M02_San_MatFind` · label = 산근 줄 설명 글) · 별도계상 줄도 품목을 찾되
이음은 비움(셈 제외 · 단가 0 그대로)."""
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
from M02_MasterTemplete import M02_San_MatFind as mf
line = {k: src.get(k) or "" for k in ("이름", "규격", "단위")}
if kind == "재료" and apart(line): # 817-1 원가 합 밖(원본 금액 그대로) · 827-2 품목은 판정에
clean = hmat_clean(line)
got = mf.find(clean, src, _keys(clean, hrow.m01_picks("재료", clean)), label, apart=True)
return None, f"{NOTE}{APART_ITEM if got['키'] else 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("코드"))):
key = ea.one(line, hit) or hit[0] # 815-1 K1 — EST 원파일 자재 = 우리 자재 출처 코드
return mf.done(key, "코드", src)["키"], None
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)
if kind == "재료": # 827-2 — 지금 찾기 키 → 판정(규칙 · 단서 · 네 갈래)
return mf.find(line, src, _keys(line, got), label)["키"], None
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
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 _keys(line: dict, got: list[dict]) -> set[str]:
"""재료 1차 찾기 키 — 여럿이면 지역 꼬리 뗀 정확 일치(814-1) · 없으면 자재표 전체 정확 일치(815-1 K3)."""
from M02_MasterTemplete import M02_Elem_Alias as ea
keys = {x.get("키") for x in got if x.get("키")}
if len(keys) > 1:
keys = exact(line, got) or keys
if not keys:
keys = {k} if (k := ea.one(line, ea.exact(line["이름"], line["규격"]))) else keys
return keys
def hmat_clean(line: dict) -> dict:
from M02_MasterTemplete import M02_Hopyo_Material as hmat
return {**hmat.clean(line)[0], "단위": line.get("단위") or ""}
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 ""}): # 827-3 품목 이름까지
from M02_MasterTemplete import M02_San_MatPick as mp
return mp.apart_note(res.get("재료찾기") or {})
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("조합식")
or ((x.get("자원") or {}).get("재료찾기") or {}).get("키") # 827-2 별도계상 품목
or ((x.get("자원") or {}).get("재료찾기") or {}).get("고름") # 827-3 사람이 고른 품목
)
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)}