Files
Aislo/M02_MasterTemplete/M02_Hopyo_Marks.py
T
eomsangdonandClaude Opus 5.5 c5067aa2b6 feat(809-1a): 일위대가 불러오기 = 경쟁사 로직 인정 · 요소만 연결 · [수정] 모달 = 일위대가 컨테이너 부품 · 기초 판정 걷음
- [수정] 모달 = Labor_Table(읽기 · 덧칸 표지 · 엑셀 금액) · 줄 = 로직(단가산출 · 일위대가) / 요소(연결됨 · 후보 · 못 이음) · 단가산출 줄 = 가져온 식 SK 수식 탭 모달(Labor_SkEdit) · 서버 hopyo-view(M02_Hopyo_View)
- 엑셀 보기 목록 · 칠 = 줄 표지(View.listing) · 단가산출 탭 = 블록 목록 + [가져온 식으로 열기] · [내 단가산출로]
- 걷음(요구 703): 판정 체계 785(Judge · Solve · Bind · San_Basis 다섯 · Make.Move · Row 1차 판정 · 화면 Check · CheckRows · Rows · Block · JudgeApi · Picks · Judge.css) · [우리 기초로 바꾸기](to-base) · 근거 기초 제안 · Clause 기초 판정
- 시험 지움: 642_2(Bind · Solve) · 642_3(Row 1차 판정) · 642_5(옮기기 Move) · 642_6(판정 화면) · 786_2(옛 [수정] 모달) · 793_3a(근거 → 기초 제안) — 모두 걷은 기초 판정 길 몫
- 시험 고침: 642_1 · 794_2a2 · 794_2b(근거 제안 부분만 뺌) · 793_2 · 793_3b(기초 바꾸기 기대 → 없음) · 627 · 779_2 · 785_4 · 807_1 · m02_hopyo_excel · grid · stmate(judge 길 → view 길)
- 새 시험 test_809_1_import_labor(기초 길 0 · 표지 1,350 줄 · 가져온 식 SK · 모달 = Labor_Table · 금액 = 엑셀)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 19:17:58 +09:00

147 lines
6.9 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`)를 읽기만.
- `판정` = 불리는 블록 「가져온 식」(809-1 · 경쟁사 로직 인정 · 기초 판정 없음) · 안 불리면 None
- `부름` = 부르는 호표 번 [] = 일위대가 밖(780-1) · `어긋남` · `줄` = 모든 블록(밖 포함)
- `SK` = 만든 SK 키 · 없으면 None / `갈래` · `못옮김` = 그 SK 본뜬(출처별) · 없으면 None · []
줄 상태는 목록(`M02_Hopyo_View.listing`) 호표마다 `줄상태 [[차례, 표지, 이어짐]]` · `만든`(SC 키)로 실려 감.
칠 꼴(화면 779-4 `SheetMap`) — 줄 = 이어짐(연결됨 · 로직) 초록 · 후보 파랑 · 못 이음 빨강 / 블록 = 만든 SK 갈래
(못옮김 남음 빨강 · 차이 SK 주황 · 그 밖 초록) · 없으면 「가져온 식」 초록 · 안 불림 흐림.
`어긋남`(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_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
IMPORTED = "가져온 식" # 불리는 블록 = 경쟁사 로직 그대로(809-1)
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)
have = sangeun.made(to, key, here)
all_ = sangeun.calls(hp)[0]
units = hunits.Units() if all_ else None
call = sangeun.callers(hp)
out = []
for no in sorted(all_):
mark = sangeun.mark(key, no)
sk = have.get(mark)
origin = _origin(to, sk, mark, here) if sk else {}
out.append(
{
"번": no,
"이름": all_[no].get("이름"),
"단위": all_[no].get("단위"),
"판정": IMPORTED if call.get(no) else None,
"SK": sk,
"갈래": origin.get("갈래"),
"못옮김": origin.get("못옮김") or [],
"부름": call.get(no, []),
"어긋남": misses(all_[no]),
"줄": lines(all_[no], units) if units else [],
}
)
return out