feat(750-1): SK 셈 답 하나에 보기 넷 — 텍스트 수식 글 · 시험 계산 중간 · 일위대가 줄 모달 줄모드

- 새 `M02_Sangeun_Views` — SK 고친 줄(`_Over`) · 같은 입력 · 같은 고름 위에서 `master_text.lines` + `annotate` = `글` · 줄마다 cid · 금액 · 소계 · 계 = SK 줄 · 기준(별도계상 뺌 · 단위 환산 ÷배 · 못 셈 까닭) · `중간` = 시험 계산 중간값
- `calc_doc` — 문서 `줄모드` true = 일위대가 그 줄 셈(`line` · 별도계상 기본) · 몸 `보기` false = 덧칸 안 지음(바탕 원값 셈)
- `master_text.lines` · `master_sheet_line.annotate` — `files` 자리에 Master 도 받음(파일 묶음 길 그대로 · M01 `/text` 답 같음)
- `lineDoc` — `줄모드: true`(일위대가 줄 모달 89,299.60 = 그 줄 금액)
- 시험 `test_717_3` — lineDoc = as_doc 대조에서 `줄모드` 칸 따로 확인(서버 as_doc 은 `line` 인자로 줄 셈 · 문서 칸 없음)
- 새 지킴 `test_750_1_sk_views` — 변형 일곱 · 참조 바탕 · 줄 모달 · 보기 false

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 22:32:23 +09:00
co-authored by Claude Opus 5.5
parent 09f241b2b4
commit 9d2b8e4021
8 changed files with 203 additions and 10 deletions
@@ -13,6 +13,7 @@
- `환산근거` = 표 기준배 ↔ 결과단위 배(「10㎡당 → 1㎡당 ÷10」) · 재료 품목 단위 ↔ 줄 단위
(712-2 `master_unit.note`) · 없으면 「」.
줄 차례 맞춤 = `master_text.lines` 와 같은 차례(호표 줄마다 `_shares` 비목 · 뒤 덧줄).
`files` 자리 = 파일 묶음 또는 Master(M02 SK 고친 줄 `_Over` · 750-1 · `_spread` 도 그대로 넘김).
"""
from __future__ import annotations
@@ -151,7 +152,7 @@ def annotate(files: dict, key: str, given: dict, answer: dict) -> dict:
"""`master_text.lines` 답에 줄마다 `KEYS` 덧칸 · 머리 `기준단위` — 못 셈이면 답 그대로."""
if not answer.get("ok") or "groups" not in answer:
return answer
master = mf.Master(files)
master = files if hasattr(files, "logic") else mf.Master(files)
row = master.logic(key)
per = mbs.result_per(row.get("결과단위"))
answer["기준단위"] = _per_text(per) or None
+1 -1
View File
@@ -494,7 +494,7 @@ def lines(files: dict, key: str, given: dict) -> dict:
"""로직 한 줄의 읽는 식 — 비목 묶음마다 줄과 소계 · 돈 아닌 로직은 「결과」 묶음 하나.
호표 줄 계산이 막히면(재료 단가 없음 따위) `row` 구조만으로 이름 식·조각을 짓고
`글`·`금액`은 비움(입력 자체가 없는 경우는 지금처럼 그대로 막힘 — `_env_of` 에서 걸림)."""
master = mf.Master(files)
master = files if hasattr(files, "logic") else mf.Master(files) # Master 째(750-1)
row = master.logic(key)
env = _env_of(master, row, given)
marks = mc.aliases(row, master)
@@ -93,6 +93,7 @@ def test_화면_문서_서버_as_doc_꼴():
"needsRef": ["needsIds", ref],
}
)
assert got["logic"].pop("줄모드") is True and got["ref"].pop("줄모드") is True # 750-1
assert got["logic"] == patch.as_doc({**one, "패치": drop}, whole)
assert got["ref"] == patch.as_doc({**ref, "패치": COVER}, whole)
assert got["bare"]["고름"] == {} and got["bare"]["덮개"] == {}
+94
View File
@@ -0,0 +1,94 @@
"""SK 보기 넷 = 셈 답 하나(PLAN 750-1 · 요구 588-1) — 화면 셈 답 `글`(텍스트 수식) ·
시험 계산(`줄` · `기준` · `중간`) = SK 줄 · 계 · 변형 일곱(기본 · 줄 빼기 · 더하기 · 덧줄 ·
별도계상 · 단위 환산 · 재료 고름) + 참조 바탕 · 일위대가 줄 모달(`줄모드`) = 일위대가 그 줄 셈 ·
`보기` false = 덧칸 없음 · M01 엔진 · storage 안 씀."""
import pytest
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Sangeun_Calc as calc
from M02_MasterTemplete import M02_Sangeun_Doc as sd
from M02_MasterTemplete import M02_Sangeun_Views as views
FORM = "GC000211" # 6-3-1 합판거푸집 — 재료 일곱 · 소모자재 = 재료비 % 덧줄
PLY = "material:합판(내수 12㎜)#0"
DOC = {"키": "SK-000002", "이름": "테스트1", "단위": "㎡", "바탕": {"로직": FORM}}
ADD = {"종류": "인력", "요소": "LB000002", "이름": "보통인부(더함)", "단위": "인", "수량": 0.05}
CASES = {
"기본": {},
"줄 빼기": {"뺀줄": ["material:각재#0"]},
"더하기": {"더한줄": [ADD]},
"덧줄": {"덮개": {"pct:소모자재#0": {"qty": 5}}},
"별도계상": {"자재표시": {PLY: {"별도계상": "별도계상"}}},
"단위 환산": {"단위": "10㎡"},
"재료 고름": {"고름": {PLY: "MT000020"}},
}
def _whole():
return m01.loaded()[1]
def _answer(doc: dict, read=None, line: bool = False) -> dict:
"""`calc_doc` 와 같은 길(층 검사만 뺌)."""
whole = _whole()
clean = sd.clean(doc, None, whole)
got = calc.run(clean, whole, None, read or calc.refs.read_ref, detail=True, line=line)
return labor._json(views.finish(calc.per_unit(got, clean)))
def _same(a: dict) -> None:
text, base = a["글"], a["기준"]
assert text["ok"] is True
money: dict[str, float] = {}
for g in text["groups"]:
assert g["소계"] == pytest.approx(base[g["비목"]])
for one in g["줄"]:
money[one["cid"]] = money.get(one["cid"], 0) + (one["금액"] or 0)
assert set(money) == {x["cid"] for x in a["줄"]} # 글 줄 = SK 줄(cid)
for x in a["줄"]:
assert money[x["cid"]] == pytest.approx(x["금액"] or 0)
assert text["계"] == pytest.approx(base["계"])
assert sum(x["금액"] or 0 for x in a["줄"]) == pytest.approx(base["계"]) # 시험 계산 표 합
assert a["중간"] and all(not isinstance(v, (dict, list)) for v in a["중간"].values())
@pytest.mark.parametrize("name", list(CASES))
def test_four_views_one_answer(name):
a = _answer({**DOC, **CASES[name]})
_same(a)
if name == "별도계상":
assert all(one.get("cid") != PLY for g in a["글"]["groups"] for one in g["줄"])
if name == "단위 환산":
assert a["글"]["나눔"] == pytest.approx(0.1)
plain = _answer(DOC)
assert a["기준"]["계"] == pytest.approx(plain["기준"]["계"] * 10)
if name == "재료 고름":
assert a["기준"]["재료비"] != pytest.approx(_answer(DOC)["기준"]["재료비"])
def test_ref_base_same():
"""참조 바탕(SK 위 SK) — 글도 바탕 SK 고친 줄 위에서."""
parent = {**DOC, **CASES["덧줄"], "뺀줄": ["material:각재#0"]}
ref = {"층": "personal", "주인": None, "키": "SK-000002"}
_same(_answer({**DOC, "키": "SK-000003", "바탕": {"참조": ref}}, read=lambda r: parent))
def test_row_modal_is_hopyo_line():
"""일위대가 줄 모달(`줄모드`) = 일위대가 그 줄 셈(별도계상 기본 · 747 한쪽 흠)."""
whole = _whole()
line = labor._json(labor.drop_apart(labor.run_line(whole, FORM, None), {}))
got = _answer({**DOC, "단위": "식", "줄모드": True}, line=True)
assert got["기준"]["계"] == pytest.approx(line["기준"]["계"])
assert [x["이름"] for x in got["줄"]] == [x["이름"] for x in line["줄"]]
_same(got)
def test_no_views_flag():
"""`보기` false(바탕 원값 셈) = 금액만 · 덧칸 없음 · 금액 같음."""
whole = _whole()
clean = sd.clean(DOC, None, whole)
got = labor._json(calc.run(clean, whole, None, detail=True, seen=False))
assert "글" not in got and "중간" not in got
assert got["기준"]["계"] == pytest.approx(_answer(DOC)["기준"]["계"])