- 지움(git rm) 10 파일 3,374 줄: M02_Hopyo_Finger · Ours · Option · Fit · Combine · Verdict · Line · SkLink · SkPatch · Basis
- Match: 흐름 몫(candidates · name_cands · fix_typo · clause · b_order · _logic_specs · _line_mod · units_of · _state · view · guess · picks · chain) 지움 · 이름 도구 몫 · line_key 남김
- Words: conds · cond_score · close · cosine · jacc 지움 / Shares: sections 지움(trade · eq_key 남김)
- 옮김: Finger.kind_of → Fixed.kind_of · SkLink(sk_line · kept · left · LEFT · WHY) → Sangeun · SkPatch 기계 몫(MACH_IN · MACHINE · SOLO · _mach_logics) → Units.mach_logics
- Sangeun: 옛 /sangeun 몸(build · _sure · Maker.basis · judge · plan · make) 지움 · Kids: first · proposal · order 지움 · Build: build() · unlinked() 지움(모두 고정 Builder · build_all · _order · _origins 남김)
- Router: 옛 길 지움(GET hopyo/{키}/{번} · guess · hopyo-picks · hopyo-build · hopyo-unlinked · hopyo/{키}/sangeun) · GET hopyo/{키} = Judge.view(1차 줄 판정 줄상태 · 만든 · 블록 풀이 셈)
- Marks: 칠 = Judge.judged 만 · Forget: 맞춤 캐시 지움 줄 걷음 · Ledger: Ours.version 판 칸 걷음 · Item: Kids.first 걷음
- Row(덧): 단산 줄 = 장부 짝이 있어도 늘 「2차 필요」(장부 = 1차 후보 힌트만)
시험 정리(까닭 = 지운 길 · 모듈만 시험함):
- 지움 11 파일: test_m02_hopyo_finger · fit · golden · option · ours · basis · seam · flow · draft_calc · test_635 · test_628(지문 · 순위 · 옵션 거꾸로 · 맡은 절 · 옛 확인 흐름)
- 함수 지움: /sangeun · /hopyo-build · /hopyo-unlinked · hopyo-picks · 옛 줄 맞춤 보기를 부르던 것(572 · 718_1 · 729 · 739_1 · 777_1 · 778_5 · 780_1 · labor_national · 705_3 · m02_hopyo_match · 746_1)
- test_621 S2(지문 해시) 지움 · S1 남김 · test_m02_hopyo_build = 장부 길 시험만(PUT 으로 심음)
- 새 길로: 779_2(줄상태 = 1차 판정 · 칠 셈 전 None) · grid · stmate(hopyo-judge 줄 판) · 778_1 · 778_2(Units.mach_logics) · 642_6(Marks 원문) · 642_3 장부 짝 단산 줄 2차 지킴
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
8.0 KiB
Python
154 lines
8.0 KiB
Python
"""M02 불러오기 엑셀 보기 하이라이트 재료(PLAN 779-2 · 요구서 621 3장 5) — 읽기만(파일 · storage 변화 0).
|
||
|
||
블록 `[{번, 판정, 고를수, SK, 갈래, 못옮김, 부름}]` = 단가산출 만들기 결과(`Sangeun.made`) + 블록 판정을 읽기만.
|
||
- `판정` = 블록 판정(785-6 · `M02_Hopyo_Judge.judged` 파생 저장본 = 기초로 풂 · 기초+덧 · 줄 조합 · 고를 것 ·
|
||
`고를수`) · 안 불리거나 셈 전이면 None
|
||
- `부름` = 부르는 호표 번 [] = 일위대가 밖(780-1) · `어긋남` · `줄` = 모든 블록(밖 포함)
|
||
- `SK` = 만든 SK 키 · 없으면 None / `갈래` · `못옮김` = 그 SK 본뜬(출처별) · 없으면 None · []
|
||
줄 상태는 목록(`M02_Hopyo_Judge.view`) 호표마다 `줄상태 [[차례, 1차 판정, 제안 있음]]` · `만든`(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_Judge as hjudge
|
||
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)
|
||
have = sangeun.made(to, key, here)
|
||
all_ = sangeun.calls(hp)[0]
|
||
units = hunits.Units() if all_ else None
|
||
call = sangeun.callers(hp)
|
||
judge = hjudge.judged(folder, key) or {} # 셈 중 = None
|
||
out = []
|
||
for no in sorted(all_):
|
||
mark = sangeun.mark(key, no)
|
||
sk = have.get(mark)
|
||
origin = _origin(to, sk, mark, here) if sk else {}
|
||
new = judge.get(no) or {}
|
||
out.append(
|
||
{
|
||
"번": no,
|
||
"판정": new.get("판정"),
|
||
"고를수": new.get("고를수"),
|
||
"SK": sk,
|
||
"갈래": origin.get("갈래"),
|
||
"못옮김": origin.get("못옮김") or [],
|
||
"부름": call.get(no, []),
|
||
"어긋남": misses(all_[no]),
|
||
"줄": lines(all_[no], units) if units else [],
|
||
}
|
||
)
|
||
return out
|