feat(779-2): 불러오기 엑셀 하이라이트 재료 — 목록 호표마다 줄상태 · 만든 SC 키 + 새 hopyo-marks 길(블록 판정 · SK · 갈래 · 못옮김, 읽기만)

- Match.view 목록: 호표마다 줄상태 [[차례, 상태, 체크]] · 만든 (이미 센 줄 상태 실음 · 덧 셈 0)
- 새 M02_Hopyo_Marks.py: 블록 [{번, 판정, SK, 갈래, 못옮김}] = Sangeun.made + Fit.result 읽기만 · 머리에 칠 꼴 대응표
- Router: GET /layers/{layer}/hopyo-marks/{key} (파일 끝에 한 덩이)
- 시험: test_779_2_hopyo_marks (줄상태 수 = 줄 수 · 읽기만 = 파일 변화 0 · 판정 = 캐시)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 08:55:56 +09:00
co-authored by Claude Sonnet 5.5
parent 4e0ea28aec
commit f9aa8832bb
4 changed files with 164 additions and 1 deletions
+63
View File
@@ -0,0 +1,63 @@
"""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`)이 이 재료 + 캐시 고름으로 내림 — 여기는 상태 이름을 그대로 실음.
"""
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_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 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)
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 [],
}
)
return out
+7 -1
View File
@@ -447,7 +447,8 @@ def units_of(doc: dict[str, Any]) -> dict[int, str]:
def view(folder: Path, key: str, no: int | None = None, to: Path | None = None) -> dict[str, Any]:
"""실무자료 보기 — `no` 없으면 호표마다 [체크 제안 수, 줄 수] · 있으면 그 호표 줄마다 맞춤(3-3)
"""실무자료 보기 — `no` 없으면 호표마다 [체크 제안 수, 줄 수] · `줄상태 [[차례, 상태, 체크]]` ·
`만든`(그 호표 SC 키 · 없으면 None) · 있으면 그 호표 줄마다 맞춤(3-3)
·
`to` = 만들 층(장부 · 확인된 사전조합 · 없으면 파일 층) · 맞춤셈이 없으면 뒤 셈을 넣음."""
lines = _line_mod()
@@ -457,6 +458,11 @@ def view(folder: Path, key: str, no: int | None = None, to: Path | None = None)
hopyos = [lines.hopyo_view(h, ctx, False) for h in doc.get("호표") or []]
keep = ("번", "이름", "규격", "단위", "금액", "맞춤", "행", "연결할", "연결된")
brief = [{k: h.get(k) for k in keep if k in h} for h in hopyos]
for one, h in zip(brief, hopyos): # 779-2 · 이미 센 줄 상태를 버리지 않음(덧 셈 0)
one["줄상태"] = [
[i, x["맞춤"]["상태"], x["맞춤"]["체크"]] for i, x in enumerate(h["줄"])
]
one["만든"] = ctx.done.get(lines.hopyo_build.mark(key, int(h["번"])))
ready = [sum(h["맞춤"][0] for h in hopyos), sum(h["맞춤"][1] for h in hopyos)]
head = {**hopyo_store.brief(doc), "검산": doc.get("검산"), "셈": ctx.calc}
links = {k: sum(h[k] for h in hopyos) for k in ("연결할", "연결된")}
+16
View File
@@ -31,6 +31,7 @@ from M02_MasterTemplete import M02_Hopyo_Forget as hopyo_forget
from M02_MasterTemplete import M02_Hopyo_Grid as hopyo_grid
from M02_MasterTemplete import M02_Hopyo_Item as hopyo_item
from M02_MasterTemplete import M02_Hopyo_Ledger as hopyo_ledger
from M02_MasterTemplete import M02_Hopyo_Marks as hopyo_marks
from M02_MasterTemplete import M02_Hopyo_Match as hopyo_match
from M02_MasterTemplete import M02_Hopyo_Paste as hopyo_paste
from M02_MasterTemplete import M02_Hopyo_Relink as hopyo_relink
@@ -622,3 +623,18 @@ def hopyo_to_item(
args = (folder, source, body.키, body.번, lines, whole, body.묶음, body.이름, _who(session))
args += (body.새로, body.씀)
return _call(folder, hopyo_item.make_item, *args)
@router.get("/layers/{layer}/hopyo-marks/{key}")
def hopyo_marks_read(
layer: Layer,
key: str,
owner: str | None = None,
to: Layer | None = None,
to_owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""하이라이트 재료(779-2 · 읽기만) — `블록 [{번, 판정, SK, 갈래, 못옮김}]` · `to` = SK 를 볼 층."""
folder = _root(session, layer, owner)
target = _root(session, to, to_owner) if to else folder
return _call(folder, lambda: {"블록": hopyo_marks.blocks(folder, key, target)})