Files
Aislo/M02_MasterTemplete/M02_Sangeun_FromHopyo.py
T
eomsangdonandClaude Sonnet 5.5 0792a17e2c feat(797-1a): 엑셀 불러오기 빠진 정보 표지 다섯 + 줄 단위 합격 시험 · 표본 밖 고정
- 금액 줄 못 읽음: 결과 뒤 꼬리 글은 떼고 금액 줄로 읽음(2016 틀린 줄 2 → 0) · 못 읽는 글끝 · 식못읽음 = 표지 + 보인 값 보존
- 단가 후보 여럿 · 사람 확인 / 추정(문맥 · 기계 세 줄) 표지 — 임의 선택 대신 표지
- 당초 / 적용 칸 표지(Book.applied_how) · 조정 비율 · 낙찰률 미지원 표지
- FromHopyo.keep 이 읽기 불가 사유(빈 파일 · DRM 암호 · 형식 오류)를 돌려줌
- 사람 확인 표지는 「참고 · 」 머리 — 못 옮김 표지와 가름 · test_793_2 는 못 옮김 표지만 셈(낡은 기대 수정)
- test_797_1a 신설: 작은 견본 · 표지 없는 틀린 줄 0 · 표본 밖 수목 142 · 대흥공통 46 · 2016 374 고정 · 버림1 일치율 감시
- 금액 결과 그대로(엔진 · 수식 · Copy 손대지 않음)

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

128 lines
5.8 KiB
Python

"""M02 불러오기 단산 블록 → 가져온 식 단가산출(PLAN 793-3b · 요구 646 · 647 · 652) — 서버 단독.
올림 때 경쟁사 산근 시트(`M02_San_Book.sheets` · STmate 숨은 G 열 포함)를
`실무자료/산근/<HP>.json` 에 둠
(원본 엑셀은 안 둠 · HP 지울 때 같이 지움) → [가져온 식으로 열기] = 그 블록을 793-2a 내보내기
(`M02_San_Emit` · 이음 = 1차 줄 판정) → 793-2 만들기(`M02_Sangeun_Imported.create` · 개인 층 ·
엑셀 단가 그대로 · `가져온.근거후보` = 793-3a 근거 제안 `M02_San_Basis_Store.block` 근거후보) ·
같은 내용 SK 가 있으면 그것(다시씀 · 근거후보 그대로).
블록 번 = 판정 블록 번(STmate = 코드 「D00102」 숫자 · EST = 단가산출 블록 번).
STmate 블록 끝전(비목버림 | 합버림_큰나머지)은 파일 설정 — 참조 평가기로 둘 다 셈해
엑셀 계와 맞는 것.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from common_util.common_util_json import atomic_write_json
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
from M02_MasterTemplete import M02_MasterTemplete_Store as store
DIRNAME = "산근"
RELOAD = "가져온 식 자료 없음 — 엑셀을 다시 올림"
RULES = ("비목버림", "합버림_큰나머지")
def _path(folder: str | Path, key: str) -> Path:
return hopyo_store._dir(folder) / DIRNAME / f"{key}.json"
def unreadable(data: bytes) -> str | None:
"""읽기 불가 사유(797-1a) — 빈 파일 · DRM 암호(파일 머리 `DRMONE`) · 아니면 None."""
if not data:
return "빈 파일"
if b"DRMONE" in data[:16]:
return "DRM 암호 파일 — 암호 풀린 사본을 올림"
return None
def keep(folder: str | Path, key: str, name: str, data: bytes) -> str | None:
"""올린 엑셀 → 산근 시트 둠(STmate · EST 꼴만 · 못 읽으면 안 둠 — 올리기는 됨).
돌려줌 = 못 읽은 사유(빈 파일 · DRM 암호 · 형식 오류) · 둠 · 산근 꼴 아님 = None."""
from M02_MasterTemplete import M02_San_Book as book
if why := unreadable(data):
return why
try:
sheets = book.sheets(name, data)
except Exception as e: # noqa: BLE001 — 산근은 덤(가져온 식 열기만 막힘) · 사유는 돌려줌
return f"엑셀 형식 오류 — {type(e).__name__}: {str(e)[:80]}"
if not book.kind_of(sheets):
return None
path = _path(folder, key)
path.parent.mkdir(parents=True, exist_ok=True)
atomic_write_json(path, {"파일": name, "시트": sheets})
return None
def has(folder: str | Path, key: str) -> bool:
return _path(folder, key).is_file()
def _rule(blk: dict, nodes: list, prices: dict) -> str:
"""STmate 블록 끝전 — 엑셀 계(꼬리 · 없으면 머리)와 맞는 쪽 · 둘 다 아니면 비목버림."""
from M02_MasterTemplete import M02_San_Eval as ev
tails = [t for t in blk["꼬리"] if t["A"].startswith("계") and not t["A"].startswith("계약")]
want = (tails or [blk["머리"]])[0].get("C")
for rule in RULES:
try:
got = ev.stmate_block(blk, nodes, prices, rule)["계"]["T"]
except Exception: # noqa: BLE001 — 못 셈 = 다음 갈래
continue
if want is not None and abs(float(got) - float(want)) < 0.5:
return rule
return RULES[0]
def emit(folder: str | Path, key: str, no: int) -> dict[str, Any]:
"""HP 단산 블록 하나 → `{로직, 원본}`(793-2a 꼴 · 이음 = M01 키)."""
from M02_MasterTemplete import M02_San_Book as book
from M02_MasterTemplete import M02_San_Emit as em
from M02_MasterTemplete import M02_San_Est as est
from M02_MasterTemplete import M02_San_Stmate as stm
hopyo_store.read(folder, key) # 없는 HP = 404
try:
saved = json.loads(_path(folder, key).read_bytes())
except (OSError, ValueError) as e:
raise store.StoreError(409, RELOAD) from e
sheets, name = saved["시트"], str(saved.get("파일") or "")
if book.kind_of(sheets) == "STmate":
blk = next((b for b in book.stmate_blocks(sheets) if int(b["코드"][1:]) == int(no)), None)
if blk is None:
raise store.StoreError(404, f"없는 단산{no}")
prices = book.stmate_prices(sheets)
nodes = stm.parse_block(blk["몸"])
return em.stmate(blk, nodes, prices, _rule(blk, nodes, prices), True, name)
prices, total = book.est_prices(sheets)
blk = next((b for b in book.est_blocks(sheets) if int(b["번"]) == int(no)), None)
if blk is None or blk["번"] not in total:
raise store.StoreError(404, f"없는 단산{no}")
return em.est(blk, est.parse_block(blk["글"], prices), total[blk["번"]], True, name)
def make(source: Path, key: str, no: int, name: str = "") -> dict[str, Any]:
"""지금 층(만들 층)에 가져온 식 SK — 같은 내용이 있으면 그 SK(`다시씀`)."""
from M02_MasterTemplete import M02_San_Basis_Store as san_basis
from M02_MasterTemplete import M02_Sangeun_Imported as imp
out = emit(source, key, no)
title = str(name or "").strip() or str(out["로직"].get("이름") or f"단산{no}")
try:
basis = san_basis.block(source, key, no).get("근거후보") or None
except Exception: # noqa: BLE001 — 근거 제안 못 셈 = 빈 칸(가져온 식은 만듦)
basis = None
try:
got = imp.create(out["로직"], out["원본"], title, basis=basis)
return {**got, "다시씀": False}
except store.StoreError as e:
same = (e.detail or {}).get("같은것") if isinstance(e.detail, dict) else None
if e.status != 409 or not same:
raise
got = imp.create(out["로직"], out["원본"], title, use=same[0]["키"])
return {**got, "다시씀": True}