Files
eomsangdonandClaude Opus 5.5 48848bb287 refactor(785-7): 옛 맞춤 층 Python 지움 · 서버 새 길로 정리 · 낡은 시험 정리
- 지움(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>
2026-11-05 01:15:20 +09:00

154 lines
8.0 KiB
Python
Raw Permalink 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`) + 블록 판정을 읽기만.
- `판정` = 블록 판정(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