- 상태 = M02_Hopyo_Units.line(이어짐 · 고를 것 · 못 이음) · 셈 밖 = 밖 · 불리는 블록만 - 차 = 이어진 노무 줄의 엑셀 노임 ≠ M01 지금 노임(주황 「엑셀과 다름」 = 금액 참고 차만) · 기계 · 자재 참고 차는 D-3 셈 뒤 - SheetMap 상태 이름 「고를 것」 맞춤 · 실물 시험(북부청 임도 231 · 영월 135 줄 칠 = 단위 줄 수) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
150 lines
7.8 KiB
Python
150 lines
7.8 KiB
Python
"""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
|