내장 Learning 스타일 2회차 · 요청 3: 요청 2 + "핵심 부분은 직접 짜 보고 싶어" · Claude Code · Opus 5.5 · 생각 깊이 중간 · 목록

내장 Learning 스타일 2회차

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

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

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

`src/stock.mjs`에 `todayTasks`를 만들고 `test/stock.test.mjs`에 테스트를 추가했어요. 테스트를 돌려 보니 둘 다 통과했습니다. 할 일을 모으는 부분은 끝났지만, 아직 "급한 것부터" 정렬하지는 않아요. 그 부분은 직접 짜 보실 수 있게 비워 뒀어요. `★ Insight ─────────────────────────────────────` - **모으기와 정렬하기를 나눴어요.** `todayTasks`는 할 일을 모으기만 하고, 순서는 `compareTasks`가 정해요. 그래서 정렬 규칙을 바꿀 때 모으는 코드는 손대지 않아도 돼요. `compareTasks`도 따로 내보내 두어서 혼자 테스트할 수 있어요. - **한 재료가 여러 조건에 걸리면 조건마다 한 건씩 넣어요.** 예를 들어 `우유`는 "곧 끝남"과 "수량 부족" 두 건으로 들어가요. 사장님 입장에서는 "빨리 쓰기"와 "주문하기"가 서로 다른 할 일이라서 이렇게 했어요. - **"3일 안에"는 0~3일로 정했어요.** 오늘이 기한인 것(0일)과 딱 3일 남은 것(`바닐라 시럽`)까지 포함하고, 4일 남은 것은 빠져요. 테스트에서 이 경계를 직접 확인해요. `─────────────────────────────────────────────────` 지금 테스트는 "무엇이 모였는가"만 확인하고 순서는 보지 않아요. 지금 `compareTasks`가 항상 `0`을 돌려줘서 순서가 정해지지 않기 때문이에요. ● **Learn by Doing** **Context:** `todayTasks(items, today)`는 재고를 돌면서 할 일을 모아요. 할 일 한 건은 `{ name, kind, days, qty, minQty, unit }` 모양이고, `kind`는 `'expired'`(지남), `'expiring'`(0~3일 남음), `'low'`(minQty보다 적음) 중 하나예요. 모은 목록은 마지막에 `tasks.sort(compareTasks)`로 정렬하는데, 이 비교 함수가 아직 비어 있어요. 사장님이 아침에 목록 맨 위에서 무엇을 보게 될지가 여기서 정해져요. **Your Task:** `src/stock.mjs`의 `compareTasks(a, b)` 안에 `TODO(human)`이 있어요. a가 더 급하면 음수, b가 더 급하면 양수, 똑같이 급하면 `0`을 돌려주도록 채워 주세요. 5~10줄이면 충분해요. **Guidance:** - **종류끼리의 순서:** 보통은 지남 → 곧 끝남 → 부족 순서가 자연스러워요. 그런데 오늘 기한인 생크림과 재고가 0인 치즈케이크 중 무엇이 더 급할까요? 치즈케이크가 0개면 손님에게 바로 못 파는 거라 더 급할 수도 있어요. - **같은 종류 안에서:** 날짜 관련 할 일은 `days`가 작을수록 급해요(`a.days - b.days`). 부족한 할 일은 모자란 양(`minQty - qty`)이나 비율(`qty / minQty`)로 비교할 수 있어요. 단위가 L, kg, 팩으로 서로 달라서 비율이 더 공정할 수도 있어요. - **쉬운 방법:** `const rank = { expired: 0, expiring: 1, low: 2 }`처럼 종류에 숫자를 매기고 `rank[a.kind] - rank[b.kind]`로 먼저 비교해 보세요. 그 값이 `0`일 때만 다음 기준으로 넘어가면 돼요. - **마지막 기준:** 그래도 같으면 `a.name.localeCompare(b.name)`처럼 이름순으로 정해 두세요. 그러면 매일 아침 목록 순서가 들쭉날쭉하지 않아요. 다 짜시면 말씀해 주세요. 정하신 규칙에 맞춰 순서를 확인하는 테스트를 추가하고 같이 돌려 볼게요.

바뀐 코드 (git diff)

diff --git a/src/stock.mjs b/src/stock.mjs
index f81b897..4a900f3 100644
--- a/src/stock.mjs
+++ b/src/stock.mjs
@@ -19,3 +19,26 @@ export function orderList(items) {
     .filter((it) => it.qty < it.minQty)
     .map((it) => ({ name: it.name, need: it.minQty * 2 - it.qty, unit: it.unit }));
 }
+
+// 오늘 할 일 한 건: { name, kind, days, qty, minQty, unit }
+// kind: 'expired'(유통기한 지남) | 'expiring'(0~3일 남음) | 'low'(minQty 미만)
+// days: 오늘부터 유통기한까지 남은 날 수 (지났으면 음수)
+// 한 재료가 여러 조건에 걸리면 조건마다 한 건씩 들어간다.
+export function todayTasks(items, today) {
+  const tasks = [];
+  for (const it of items) {
+    const days = daysBetween(today, it.expires);
+    const base = { name: it.name, days, qty: it.qty, minQty: it.minQty, unit: it.unit };
+    if (days < 0) tasks.push({ ...base, kind: 'expired' });
+    else if (days <= 3) tasks.push({ ...base, kind: 'expiring' });
+    if (it.qty < it.minQty) tasks.push({ ...base, kind: 'low' });
+  }
+  return tasks.sort(compareTasks);
+}
+
+// 급한 할 일이 앞에 오도록 두 건을 비교한다.
+// a가 먼저면 음수, b가 먼저면 양수, 같으면 0.
+export function compareTasks(a, b) {
+  // TODO(human)
+  return 0;
+}
diff --git a/test/stock.test.mjs b/test/stock.test.mjs
index 71de632..9d51077 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,25 @@ 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)));
+const tasks = todayTasks(data, '2026-10-18');
+const keys = tasks.map((t) => `${t.name}:${t.kind}`).sort();
+assert.deepEqual(keys, [
+  '딸기:expired', // 어제(10-17) 지남
+  '바닐라 시럽:expiring', // 3일 남음 → 경계값 포함
+  '생크림:expiring', // 오늘(10-18)까지
+  '우유:expiring',
+  '우유:low', // 한 재료가 두 조건에 걸리면 두 건
+  '치즈케이크:expiring',
+  '치즈케이크:low',
+].sort());
+// 원두(여유 있음), 딸기 qty=minQty(미만 아님)는 low에 없어야 함
+assert.ok(!tasks.some((t) => t.name === '원두'));
+assert.equal(tasks.find((t) => t.name === '딸기' && t.kind === 'expired').days, -1);
+
+// 경계: 4일 남은 건 제외, 빈 목록은 빈 결과
+assert.deepEqual(todayTasks([{ name: 'x', qty: 9, unit: '개', expires: '2026-10-22', minQty: 1 }], '2026-10-18'), []);
+assert.deepEqual(todayTasks([], '2026-10-18'), []);
+console.log('todayTasks 수집 테스트 통과');