feat(790-3): 단가산출 시트 숫자 넓힘 화면 — 식 속 상수 · 표값 칸 · 글 줄 · 까닭 · M01 원래로 · M01 바뀜

- 새 M02_MasterTemplete_Sangeun_SheetEdit.ts — 790-2 셈 답 시트.자리 를 칸으로: 식 속 숫자 = 상수 덮개(식 모양 그대로 · 옵션 따라감) · 표 꼬리표 값 = 표값 덮개(「표 연결 끊김」) · 글 줄 더함 · 고침 · 지움(줄마다 [+ 글] · 근거 머리 [+ 근거 글]) · 까닭 칸(고친 줄 · 뺀 줄 · 더한 줄) · [M01 원래로] 줄마다 · 상태줄 전체 · 「M01 바뀜」 배지 · 짝잃음 띠
- Sheet_Model — 식 조각(서버 scan 숫자 · 표 찾기 차례 · 안 맞으면 칸 안 그림) · 산출서에 글 줄 · 되살린 더한줄 열쇠 밂(liftAddKeys)
- Sheet — 옛 [되돌림] 단추를 [M01 원래로]로 · 더한줄 빼면 까닭 · 글줄 열쇠 당김(dropAddKeys) · 문서 고침 = 캐시 → 서버 셈 · 자동저장 없음
- 더한줄 줄 사이 끼움은 SK 모델에 자리 칸이 없어 덩이 끝 그대로(글 줄만 자리 뒤)
- test_790_1 늘림(정적 단언 + 식 조각 · 글줄 산출서 · 열쇠 밂)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 17:34:02 +09:00
co-authored by Claude Opus 5.5
parent d26f7a7e1a
commit 93b13e975f
5 changed files with 633 additions and 29 deletions
@@ -178,3 +178,97 @@ def test_줄_갈래_읽기_쉬운_식_산출서_상태줄():
assert got["status"] == {"원래": 51000, "지금": 51419, "전": 50000} # 원래 = 바탕 셈 계
assert got["origin"]["원래"] == 49000 # 790-2 `원래` 있으면 그 값
assert got["diff"] == ["+383", "−419", ""]
# ── 790-3 E2 화면 이음(식 속 상수 · 표값 · 글 줄 · 까닭 · M01 원래로 · M01 바뀜) ──────────
EDIT = M02 / "M02_MasterTemplete_Sangeun_SheetEdit.ts"
def test_790_3_숫자_넓힘_칸_정적():
s = _text(EDIT)
assert "<button" not in s and "<input" not in s and "document.createElement" not in s
assert "fetch(" not in s and "sessionStorage." not in s and len(s.splitlines()) < 700
for call in (
"setConst(c.doc, spot, p.n, x)", # 식 속 숫자 = 상수 덮개
"setTable(c.doc, spot, p.n, x)", # 표 꼬리표 값 = 표값 덮개
'createBadge(t("Cut"), "warning")', # 표 연결 끊김
"addText(c.doc, spot,", # 글 줄 더함 · 고침 · 지움
"editText(c.doc, i, v)",
"dropText(c.doc, i)",
"setReason(c.doc, key, x)", # 까닭
"restoreM01(c.doc, spot)", # 줄마다 M01 원래로
"restoreM01(c.doc)", # 전체
'createBadge(t("Changed"), "danger")', # M01 바뀜
"c.spots?.짝잃음",
):
assert call in s, call
sheet = _text(SHEET)
for call in (
"formulaEditor(ctx, it.자리)",
"reasonCell(ctx, it.자리, it.changed)",
"spotTail(ctx, it.자리,",
"restoreAll(ctx)",
"lostBanner(ctx)",
"removedReasons(ctx, d.removed)",
"dropAddKeys(d.doc as ConstDoc, r.added)", # 뺀 더한줄 = 까닭 · 글줄 열쇠 당김
"liftAddKeys(d.doc as ConstDoc, at)", # 되살림 = 거꾸로 밂
"paperLines(d.text, d.rows, d.ans, (d.doc as ConstDoc).글줄)", # 글 줄 산출서에
"spots: spotsOf(d.ans)",
):
assert call in sheet, call
assert "Object.assign(doc, next);" in sheet # 상세가 쥔 같은 문서 객체 · 자동저장 없음
assert len(sheet.splitlines()) < 700
SPOT = {
"식": "찾기(QC000431, 유형=유형).시공량 * (1 - (높이감률 + 만약(지붕슬래브 == '아님', 0, 17)) / 100)",
"상수": [{"n": 0, "값": 1, "원값": 1}, {"n": 1, "값": 0, "원값": 0},
{"n": 2, "값": 17, "원값": 17}, {"n": 3, "값": 100, "원값": 100}],
"표값": [{"n": 0, "글": "찾기(QC000431, 유형=유형).시공량", "값": 45, "원값": 45}],
} # fmt: skip
def _run_e2():
src = IMPORTS.sub("", _text(MODEL))
bad = {**SPOT, "상수": SPOT["상수"][:3]}
body = (
f"\nconst S = {json.dumps(SPOT, ensure_ascii=False)};"
f"\nconst B = {json.dumps(bad, ensure_ascii=False)};"
f"\nconst F = {json.dumps(FIX, ensure_ascii=False)};"
"\nconst show = (p) => p && p.map((x) => x.t === '글' ? x.글 : x.t === '수' ? `[#${x.n}]` : `[표${x.n} ${x.표}]`).join('');"
"\nconst notes = [{ 자리: '', 글: '맨 위 글' }, { 자리: 'labor:LB000007#0', 글: '형틀 뒤 글' },"
" { 자리: 'material:폼타이#0', 글: '꺼진 줄 뒤' }, { 자리: 'labor:LB000007#0', 글: '' }];"
"\nconsole.log(JSON.stringify({"
"\n parts: show(formulaParts(S)),"
"\n bad: formulaParts(B),"
"\n none: formulaParts({ 식: '형틀목공', 상수: [], 표값: [] }),"
"\n spots: numSpots(\"만약(a == '3회', LB000007 * 2.5, 줄.'합판 12㎜' / 10)\"),"
"\n paper: paperLines(F.text, F.rows, F.ans, notes).map((p) => p.kind + ':' + p.글),"
"\n lift: liftAddKeys({ 까닭: { 'add:#0': 'a', 'add:#1/x#0': 'b', 'mid:q': 'c' },"
" 글줄: [{ 자리: 'add:#2', 글: 'g' }] }, 1),"
"\n spotsNone: spotsOf(null),"
"\n}));"
) # fmt: skip
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-"],
input=src + body, capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def test_790_3_식_조각_글줄_산출서_열쇠_밂():
got = _run_e2()
assert got["parts"] == (
"[표0 QC000431] × ([#0] - (높이감률 + 만약(지붕슬래브 == '아님', [#1], [#2])) / [#3])"
) # 서버 scan 차례 그대로 · 표 찾기 = 한 칸
assert got["bad"] is None and got["none"] is None # 차례가 안 맞으면 숫자 칸 안 그림
assert len(got["spots"]) == 2 # 2.5 · 10 만 — 따옴표 글(3회 · 12㎜) · 이름(LB000007) 안 숫자 뺌
paper = got["paper"]
assert paper[0] == "글:맨 위 글" and paper[1] == "글:꺼진 줄 뒤" # 맨 위 · 안 그린 줄 = 위로
i = paper.index("줄:형틀목공 : 275,790 × 0.1111 = 30,643.3")
assert paper[i + 1] == "글:형틀 뒤 글" and "글:" not in paper[i + 2] # 빈 글 안 찍음
assert got["lift"]["까닭"] == {"add:#0": "a", "add:#2/x#0": "b", "mid:q": "c"}
assert got["lift"]["글줄"] == [{"자리": "add:#3", "글": "g"}]
assert got["spotsNone"] is None