/* * **수 하나가 뜻을 바꾸는 자리** — 상태별로 갈라 센다 (2026-08-23). * * ── 무엇이 있었나 ─────────────────────────────────────────────────────────── * 미러가 완료된 작업까지 상태에 들고 있게 되면서(관측 흡수에서 `completed` 를 떨어뜨리지 않게 커널을 * 고친 뒤) 개념 지도의 작업 카드가 「0」에서 「2,820」이 되었다. 이 카드는 **상태의 인구조사**이므로 * 그 수가 맞다 — 그런데 사람은 그것을 「지금 할 일 2,820건」으로 읽는다. * * 수를 줄여 맞는 척하게 만들지 않는다(그러면 인구조사가 거짓이 된다). 대신 **무엇으로 이루어진 * 수인지** 함께 낸다: 「진행 중 28 · 완료 2,792」. * * ── 지키는 것 ─────────────────────────────────────────────────────────────── * · 낱말은 **커널이 실어 보낸 그대로**다. 여기서 묶거나 고쳐 부르지 않는다(화면이 라벨을 입히고, * 라벨이 없으면 낱말이 그대로 보여 누락이 드러난다). * · 상태를 말하지 않는 축은 **아무것도 내지 않는다.** 빈 표를 내면 화면이 「상태가 없는 축」과 * 「상태를 못 읽은 축」을 구별할 수 없다. * · 한 번만 훑는다. 카드가 이미 같은 배열의 길이를 세므로 셈이 한 벌 늘지 않는다. */ /** 상태 한 갈래와 그 건수. */ export interface StatusCount { status: string count: number } /** 이 축의 개체를 상태별로 센다. 상태가 없으면 빈 객체(필드를 만들지 않는다). */ export function statusTally(bucket: unknown): { statusCounts?: StatusCount[] } { if (!Array.isArray(bucket) || !bucket.length) return {} const tally = new Map() for (const e of bucket) { const s = (e as any)?.status if (typeof s !== 'string' || !s) continue tally.set(s, (tally.get(s) ?? 0) + 1) } if (!tally.size) return {} return { statusCounts: [...tally].map(([status, count]) => ({ status, count })).sort((x, y) => y.count - x.count) } }