learning-output-style 플러그인 1회차 · 요청 3: 요청 2 + "핵심 부분은 직접 짜 보고 싶어" · Claude Code · Opus 5.5 · 생각 깊이 중간 · 목록

learning-output-style 플러그인 1회차

요청 3: 요청 2 + "핵심 부분은 직접 짜 보고 싶어"

요청문: src/stock.mjs에 todayTasks(items, today) 함수를 만들어 줘. 아침에 카페 사장님이 보는 '오늘 할 일' 목록이야. 유통기한이 지난 것, 3일 안에 끝나는 것, 수량이 minQty보다 적은 것을 한 목록으로 합쳐서 급한 것부터 보여 주고 싶어. today는 'YYYY-MM-DD' 문자열이야. test/stock.test.mjs에 테스트도 추가하고 돌려 봐. 나는 코딩을 배우는 중이라 핵심 부분은 직접 짜 보고 싶어.

클로드의 마지막 답변 (그대로)

`todayTasks`의 뼈대와 테스트를 만들고 돌려 봤어요. 두 테스트 묶음 모두 통과합니다. 다만 아직 **순서는 정해지지 않았어요.** "급한 것부터"를 정하는 핵심 부분은 직접 짜 볼 수 있게 비워 뒀습니다. `★ Insight ─────────────────────────────────────` - **목록을 모으는 일과 줄 세우는 일을 나눴어요.** `todayTasks`는 할 일을 모아서 `tasks.sort(compareTasks)`만 부르고, "무엇이 더 급한가"는 `compareTasks` 하나에만 들어 있어요. 그래서 우선순위 규칙을 바꿔도 모으는 코드는 건드리지 않아도 돼요. - **한 재료에서 할 일이 2개 나올 수 있어요.** 우유는 기한이 2일 남았고 수량도 3/5라서 `expiring`과 `low`가 따로 생겨요. "먼저 쓰기"와 "주문하기"는 사장님이 하는 행동이 달라서 따로 두는 게 맞다고 봤어요. - **경계값은 이렇게 정했어요.** 오늘 만료(`daysLeft 0`)는 "지난 것"이 아니라 "임박"으로 봤고, 딱 3일 남은 건 포함, 수량이 `minQty`와 같으면 부족이 아니에요(기존 `lowStock`의 `<`와 맞췄어요). 테스트에 모두 넣어 뒀어요. `─────────────────────────────────────────────────` ## 직접 짜 볼 부분 **위치:** `src/stock.mjs`의 `compareTasks(a, b)` (TODO 표시, 5~10줄 정도) **규칙:** `a`가 먼저 와야 하면 음수, `b`가 먼저면 양수, 같으면 0을 돌려줘요. 각 할 일에는 `type`, `daysLeft`, `qty`, `minQty`가 들어 있어요. **정해야 할 것들:** 1. **할 일 종류의 순서.** `expired`(폐기)가 맨 앞인 건 자연스러워요. 그런데 `low`(주문)와 `expiring`(먼저 쓰기) 중에는 뭐가 먼저일까요? 아침에 주문을 넣어야 오늘 안에 받을 수 있다면 주문이 더 급할 수도 있어요. - 힌트: `const RANK = { expired: 0, ... }`처럼 표를 만들고 `RANK[a.type] - RANK[b.type]`로 빼면 간단해요. 2. **같은 종류끼리의 순서.** - 임박한 것끼리는 `daysLeft`가 작은 게 먼저겠죠? - 부족한 것끼리는 수량이 0개인 치즈케이크가 3/5인 우유보다 급할 거예요. 모자란 개수(`minQty - qty`)로 볼지, 비율(`qty / minQty`)로 볼지도 골라 보세요. 3. **그래도 같을 때.** `a.name.localeCompare(b.name)`을 넣으면 매일 같은 순서로 나와서 보기 편해요. **다 짠 다음에는:** `test/stock.test.mjs` 맨 아래 TODO에 기대하는 순서를 `assert.deepEqual`로 적고 `node test/stock.test.mjs`로 확인해 보세요. 오늘 데이터 기준 할 일은 7개예요. 딸기 폐기, 생크림·치즈케이크·우유·바닐라 시럽 임박, 치즈케이크·우유 주문입니다. 짜 보고 막히거나 검토가 필요하면 코드를 보여 주세요.

바뀐 코드 (git diff)

diff --git a/src/stock.mjs b/src/stock.mjs
index f81b897..b168715 100644
--- a/src/stock.mjs
+++ b/src/stock.mjs
@@ -19,3 +19,30 @@ export function orderList(items) {
     .filter((it) => it.qty < it.minQty)
     .map((it) => ({ name: it.name, need: it.minQty * 2 - it.qty, unit: it.unit }));
 }
+
+// 아침에 보는 '오늘 할 일' 목록
+// task: { name, type: 'expired' | 'expiring' | 'low', daysLeft, qty, minQty, unit }
+//   expired  : 유통기한 지남 (daysLeft < 0)  → 폐기
+//   expiring : 3일 안에 끝남 (0 ~ 3일)       → 먼저 쓰기
+//   low      : qty < minQty                 → 주문
+// 한 재료가 유통기한 + 재고 부족에 동시에 걸리면 할 일이 2개 생긴다 (폐기와 주문은 다른 행동이라서).
+export const EXPIRING_DAYS = 3;
+
+export function todayTasks(items, today) {
+  const tasks = [];
+  for (const it of items) {
+    const daysLeft = daysBetween(today, it.expires);
+    const base = { name: it.name, daysLeft, qty: it.qty, minQty: it.minQty, unit: it.unit };
+    if (daysLeft < 0) tasks.push({ ...base, type: 'expired' });
+    else if (daysLeft <= EXPIRING_DAYS) tasks.push({ ...base, type: 'expiring' });
+    if (it.qty < it.minQty) tasks.push({ ...base, type: 'low' });
+  }
+  return tasks.sort(compareTasks);
+}
+
+// 두 할 일 중 무엇이 더 급한지 정한다.
+// a가 먼저 와야 하면 음수, b가 먼저면 양수, 같으면 0.
+export function compareTasks(a, b) {
+  // TODO(직접 작성): '급한 것' 기준을 정해서 구현해 보세요.
+  return 0;
+}
diff --git a/test/stock.test.mjs b/test/stock.test.mjs
index 71de632..5129085 100644
--- a/test/stock.test.mjs
+++ b/test/stock.test.mjs
@@ -1,5 +1,6 @@
 import assert from 'node:assert/strict';
-import { lowStock, orderList, daysBetween } from '../src/stock.mjs';
+import { readFileSync } from 'node:fs';
+import { lowStock, orderList, daysBetween, todayTasks } from '../src/stock.mjs';
 const items = [
   { name: '우유', qty: 3, unit: 'L', expires: '2026-10-20', minQty: 5 },
   { name: '원두', qty: 4, unit: 'kg', expires: '2027-01-10', minQty: 2 },
@@ -8,3 +9,27 @@ assert.deepEqual(lowStock(items), ['우유']);
 assert.deepEqual(orderList(items), [{ name: '우유', need: 7, unit: 'L' }]);
 assert.equal(daysBetween('2026-10-18', '2026-10-20'), 2);
 console.log('기본 테스트 통과');
+
+// todayTasks: 어떤 할 일이 생기는지 (순서와 무관하게 비교)
+const data = JSON.parse(readFileSync(new URL('../data.json', import.meta.url), 'utf8'));
+const tasks = todayTasks(data, '2026-10-18');
+const keys = (ts) => ts.map((t) => `${t.name}:${t.type}:${t.daysLeft}`).sort();
+assert.deepEqual(keys(tasks), [
+  '딸기:expired:-1',         // 어제 만료
+  '바닐라 시럽:expiring:3',  // 경계값: 딱 3일 남음 → 포함
+  '생크림:expiring:0',       // 오늘 만료 → 아직 '지난 것'은 아님
+  '우유:expiring:2',
+  '우유:low:2',              // 기한 임박 + 재고 부족 → 할 일 2개
+  '치즈케이크:expiring:1',
+  '치즈케이크:low:1',
+].sort());
+// 원두(기한 넉넉, 재고 충분)는 목록에 없음
+assert.ok(!tasks.some((t) => t.name === '원두'));
+// 경계값: 4일 남은 건 제외, minQty와 같은 수량은 부족 아님
+assert.deepEqual(todayTasks([{ name: 'x', qty: 2, unit: '개', expires: '2026-10-22', minQty: 2 }], '2026-10-18'), []);
+// 빈 목록
+assert.deepEqual(todayTasks([], '2026-10-18'), []);
+
+// TODO(직접 작성): compareTasks를 구현한 뒤, 기대하는 순서를 여기서 검증해 보세요.
+// 예) assert.deepEqual(tasks.map((t) => `${t.name}:${t.type}`), [ ... ]);
+console.log('todayTasks 테스트 통과');