feat(779-5): 블록 안 줄 칠 순수 셈 — 블록 줄 [[행, 상태, 차]] → 출처 행만 · 머리 행 = 가장 나쁜 줄

- SheetMap `unitTone`(이어짐 초록 · 금액 참고 차 = 주황 · 후보 파랑 · 못 이음 빨강 · 밖 흐림) · 줄 재료 없으면 옛 블록 칠
- 서버 줄 상태(D-2 뒤 Marks 덧칸)는 다음 커밋
- 시험 새 test_779_5_block_line_tone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 13:35:28 +09:00
co-authored by Claude Opus 5.5
parent 4231902d57
commit 2260227b5d
2 changed files with 64 additions and 2 deletions
@@ -7,6 +7,8 @@
* · 「이어짐」 = 확인 창과 한 규칙(`Hopyo_Check` `isChecked && checkable` · 캐시 고름이 이김) — 캐시에 대상이 없으면
* 서버 체크 제안(서버가 배 확실할 때만 켬)을 그대로 믿음
* · 호표 머리 행 = 그 호표 줄 칠 중 가장 나쁜 것 · 블록 = 만든 SK 갈래 › 없으면 부르는 첫 줄 판정 「예상」 · 안 불림 = 밖
* · 블록 안 줄(779-5) = 블록 `줄 [[행, 상태, 차]]`(Marks · D-2 줄 잇기 상태) → 그 출처 행만 칠 · 머리 행 = 줄 가장 나쁜 칠 ·
* 「엑셀과 다름」 = 이어졌는데 금액 참고 차만 있을 때 · 줄 재료 없으면 옛 블록 칠(범위 통째)
* · 엑셀 셈 어긋남(778-5 D-6) = 블록 `어긋남`(Marks) → 상세 시트 행 차례 목록 — 칠 · 판정에 안 씀(띠 · 글만)
* · 뛸 곳 = 일위대가 단산 줄 → 상세 블록 · 목록 행 · (여럿이면) 다른 부르는 줄 / 일위 줄 → 하위 호표 / 목록 행 → 상세 · 부르는 줄 / 상세 → 부르는 줄 · 목록
* 화면 · fetch · sessionStorage 없음(`CLAUDE.md` 4장).
@@ -45,6 +47,8 @@ export interface BlockMark {
SK: string | null;
갈래: string | null;
못옮김: unknown[];
/** 779-5 블록 안 단위 줄 `[출처 행, 줄 잇기 상태, 금액 참고 차]` — 없으면 옛 블록 칠 */
줄?: [number, string, boolean][];
/** 778-5 D-6 엑셀 셈 어긋남(불리는 블록 · 없으면 null) */
어긋남?: Miss | null;
}
@@ -124,6 +128,13 @@ export function blockTone(m: BlockMark | undefined): { tone: Tone; 글: string }
return { tone, 글: `예상 — 부르는 첫 줄 ${m.판정}` };
}
/** 블록 안 줄 칠(779-5) — D-2 줄 잇기 상태 · `diff` = 이어졌는데 엑셀 금액과 참고 차 */
const UNIT_TONE: Record<string, Tone> = { 이어짐: "success", 후보: "info", "못 이음": "danger", 밖: "muted" }; // prettier-ignore
export const unitTone = (state: string, diff: boolean): Tone => {
const tone = UNIT_TONE[state] ?? "info";
return tone === "success" && diff ? "warning" : tone;
};
/** 보기 셋 찾기표 */
export interface SheetIndex {
ids: Partial<Record<Role, string>>;
@@ -226,10 +237,24 @@ export function paintOf(
}
const by = new Map(blocks.map((b) => [b.번, b]));
for (const [no, [a, b]] of ix.block) {
const { tone, 글 } = blockTone(by.get(no));
const mark = by.get(no);
const at = ix.listRow.get(no);
if (mark?.줄?.length) {
const tones = mark.줄.map(([row, state, diff]) => {
const tone = unitTone(state, diff);
const more = diff && state === "이어짐" ? " · 엑셀 금액과 차(참고)" : "";
if (a <= row && row <= b) put(detail, row, { tone, 글: `단산${no} 줄 · ${state} → ${WORD[tone]}${more}` }); // prettier-ignore
return tone;
});
const tone = worst(tones);
const m = { tone, 글: `단산${no} · 가장 나쁜 줄 ${WORD[tone]} · ${blockTone(mark).글}` };
put(detail, a, m); // 머리 행 = 줄 가장 나쁜 칠
if (at) put(list, at, m);
continue;
}
const { tone, 글 } = blockTone(mark);
const m = { tone, 글: `단산${no} · ${WORD[tone]} · ${글}` };
for (let r = a; r <= b; r += 1) put(detail, r, m);
const at = ix.listRow.get(no);
if (at) put(list, at, m);
}
return out;