Files
Aislo/M02_MasterTemplete/M02_Hopyo_Marks.py
T
eomsangdonandClaude Opus 5.5 68401ed02f feat(779-5): 블록 안 줄 칠 — Marks 블록 덧칸 줄 [[출처 행, 상태, 차]](D-2 줄 잇기 상태)
- 상태 = M02_Hopyo_Units.line(이어짐 · 고를 것 · 못 이음) · 셈 밖 = 밖 · 불리는 블록만
- 차 = 이어진 노무 줄의 엑셀 노임 ≠ M01 지금 노임(주황 「엑셀과 다름」 = 금액 참고 차만) · 기계 · 자재 참고 차는 D-3 셈 뒤
- SheetMap 상태 이름 「고를 것」 맞춤 · 실물 시험(북부청 임도 231 · 영월 135 줄 칠 = 단위 줄 수)

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

150 lines
7.8 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.
"""M02 불러오기 엑셀 보기 하이라이트 재료(PLAN 779-2 · 요구서 621 3장 5) — 읽기만(파일 · storage 변화 0).
블록 `[{번, 판정, SK, 갈래, 못옮김}]` = 단가산출 만들기 결과(`Sangeun.made`) + 맞춤셈 캐시(`Fit.result`)를 읽기만.
- `판정` = 그 블록을 부르는 첫 줄의 맞춤셈 판정 상태(맞음 · 다른 점 · 후보 …) · 안 불리거나 셈 전이면 None
- `SK` = 만든 SK 키 · 없으면 None / `갈래` · `못옮김` = 그 SK 본뜬(출처별) · 없으면 None · []
줄 상태는 `Match.view` 호표마다 `줄상태 [[차례, 상태, 체크]]` · `만든`(SC 키)로 실려 감.
칠 꼴(화면 779-4 가 정함 · 상태 이름 맞춤표 — 「이어짐」 = 확인 창과 한 규칙 `체크`):
| 칠 | 줄 상태 (`줄상태`) | 블록 (`판정` · `SK` · `못옮김`) |
| 초록 이어짐 | 맞음 · 별칭 · 호표(`체크`) · 체크한 다른 점 | SK 있음 + 못옮김 [] (갈래 「기초」 포함) |
| 주황 엑셀과 다름 | 다른 점 + `체크` | SK 있음 + 갈래 「SK」(차이) |
| 파랑 고를 것 | 후보 · `체크` 아닌 다른 점 · 호표(`체크` 아님) | SK 없음 + 판정 후보 · 고름 대기 |
| 빨강 못 이음 | 없음 · 노임 · (안 이은 줄 = 마법사 몫) | SK 있음 + 못옮김 남음 |
| 흐림 밖 | % · 뺌 | 판정 None (안 불리는 블록) |
| 띠 내가 고름 | 화면 캐시(서버 아님) | — |
최종 판정은 화면(`SheetMap`)이 이 재료 + 캐시 고름으로 내림 — 여기는 상태 이름을 그대로 실음.
`어긋남`(778-5 D-6 · 요구 623 · 624) = 불리는 블록의 엑셀 셈 어긋남(엑셀 = 과거 값 · 사용자가 보고 판단 ·
칠 · 판정에 안 씀) — `{줄합, 금액, 차, 덮음, 줄 [{행, 이름, 묶음, 셈, 금액, 차, 까닭}], 까닭}` · 없으면 None.
줄 = 재현 못 한 단위 줄(`UnitLine.redo` — 엑셀 단가 × 매개값 ≠ 엑셀 줄 금액 · 셈 밖 뺌) · 덮음 실패 = 줄 합 ≠ 블록 금액.
`줄`(779-5) = 불리는 블록 단위 줄 `[[출처 행, 상태, 차]]` — 상태 = 줄 잇기(`M02_Hopyo_Units.line` · D-2
이어짐 · 고를 것 · 못 이음) · 셈 밖 = 「밖」 · 차 = 이어졌는데 엑셀 금액과 참고 차(노무 = 엑셀 노임 ≠ M01 지금 노임).
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from M02_MasterTemplete import M02_Hopyo_Fit as fit
from M02_MasterTemplete import M02_Hopyo_Sangeun as sangeun
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
from M02_MasterTemplete import M02_Hopyo_UnitLine as unit_line
from M02_MasterTemplete import M02_Hopyo_Units as hunits
from M02_MasterTemplete import M02_Labor_Ref as refs
def _origin(to: Path, sk: str, mark: str, here: refs.Here) -> dict[str, Any]:
try:
doc = json.loads((to / sangeun.KIND / f"{sk}.json").read_bytes())
except (OSError, ValueError):
return {}
return sangeun.origin_of(doc, mark, here)
def _won(v: float) -> str:
return f"{v:,.1f}".removesuffix(".0")
def _calc(one: dict[str, Any]) -> float | None:
"""엑셀 단가 × 매개값(× 계수) — `UnitLine.redo` 와 같은 셈 · 매개값 · 단가 없으면 None."""
q, price = one.get("품"), one.get("단가") or {}
if q is None:
return None
if one["묶음"] == "R":
return float(one.get("기준값") or 0) * q
if one["묶음"] == "X":
ratio = one.get("계수") or {}
return sum(float(price[c]) * q * ratio.get(c, 1) for c in unit_line.COSTS if price.get(c))
unit = unit_line.money(price)
return None if unit is None else unit * q
def misses(block: dict[str, Any]) -> dict[str, Any] | None:
"""블록 엑셀 셈 어긋남 — 재현 못 한 줄 · 덮음 실패 · 없으면 None."""
lines = []
for one in block.get("항목") or []:
if one.get("재현") or one.get("밖"):
continue
calc, got = _calc(one), one.get("금액")
if calc is None or got is None:
why = "매개값 · 단가를 못 읽음(글 꼴 밖)" if got is not None else "줄 금액 없음"
else:
why = f"엑셀 단가 × 매개값 = {_won(calc)} ≠ 엑셀 줄 금액 {_won(float(got))}"
diff = round(float(got) - calc, 1) if calc is not None and got is not None else None
name = one.get("이름") or ""
row = {"행": one.get("행"), "이름": name, "묶음": one["묶음"], "셈": calc, "금액": got}
lines.append({**row, "차": diff, "까닭": why})
total, have = unit_line.money(block.get("금액")), block.get("줄합")
cover = block.get("덮음")
if not lines and cover is not False:
return None
diff = round(float(have) - total, 1) if total is not None and have is not None else None
why = (
f"줄 합 {_won(float(have))} ≠ 엑셀 블록 금액 {_won(total)} — 엑셀 합이 줄을 뺐거나 더함"
if cover is False and diff is not None
else f"재현 못 한 줄 {len(lines)}"
)
return {"줄합": have, "금액": total, "차": diff, "덮음": cover, "줄": lines, "까닭": why}
def _wage_diff(units: hunits.Units, one: dict[str, Any], got: dict[str, Any]) -> bool:
"""노무 줄 엑셀 노임 ≠ M01 지금 노임(참고 차)."""
# ponytail: 노무만 · 기계 · 자재 참고 차는 D-3 우리 금액 셈이 생기면 그 값으로
key = (got.get("단위") or {}).get("노임") if one["묶음"] == "L" else None
row = (
next((r for rs in units.lb.values() for r in rs if str(r.get("키")) == key), None)
if key
else None
)
ours, xl = (row or {}).get("값"), (one.get("단가") or {}).get("노무비")
return bool(ours and xl and not unit_line.near(float(ours), float(xl)))
def lines(block: dict[str, Any], units: hunits.Units) -> list[list[Any]]:
"""블록 단위 줄 `[[출처 행, 상태, 차]]`(779-5) — 행 없는 줄 뺌."""
out = []
for one in block.get("항목") or []:
if one.get("행") is None:
continue
if one.get("밖"):
out.append([one["행"], "밖", False])
continue
got = units.line(one)
diff = got["상태"] == hunits.LINKED and _wage_diff(units, one, got)
out.append([one["행"], got["상태"], diff])
return out
def blocks(folder: Path, key: str, to: Path | None = None) -> list[dict[str, Any]]:
"""블록마다 `{번, 판정, SK, 갈래, 못옮김, 어긋남, 줄}` — `to` = 만든 SK 를 볼 층(없으면 파일 층)."""
folder, to = Path(folder), Path(to or folder)
hp = hopyo_store.read(folder, key)
here = refs.where(folder)
cache = (fit.result(folder, key) or {}).get("블록") or {}
have = sangeun.made(to, key, here)
all_, first = sangeun.calls(hp)
units = hunits.Units() if first else None
out = []
for no in sorted(all_):
line = first.get(no)
entry = cache.get(fit.unit_key(line)) if line else None
mark = sangeun.mark(key, no)
sk = have.get(mark)
origin = _origin(to, sk, mark, here) if sk else {}
out.append(
{
"번": no,
"판정": ((entry or {}).get("판정") or {}).get("상태"),
"SK": sk,
"갈래": origin.get("갈래"),
"못옮김": origin.get("못옮김") or [],
"어긋남": misses(all_[no]) if line else None,
"줄": lines(all_[no], units) if line and units else [],
}
)
return out