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

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

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

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

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

`todayTasks` 뼈대와 테스트를 만들었고 돌려 봤어요. 테스트는 모두 통과합니다 (`기본 테스트 통과`, `todayTasks 포함 규칙 테스트 통과`). `★ Insight ─────────────────────────────────────` - 정렬 기준을 `todayTasks` 안에 넣지 않고 `compareUrgency`라는 별도 함수로 뺐어요. 그래서 "무엇을 넣을지"(필터)와 "어떤 순서로 보여 줄지"(정렬)를 따로 바꾸고 따로 테스트할 수 있습니다. - `Array.prototype.sort`는 **안정 정렬**이에요(Node 12 이후 보장). 비교 함수가 0을 돌려주면 원래 순서가 유지돼요. 지금은 TODO 자리에서 0을 반환하고 있어서 목록이 `data.json` 순서 그대로 나오고, 그래서 순서와 상관없는 테스트는 이미 통과합니다. - 테스트에 넣은 경계값 규칙: **오늘 만료(0일)는 "곧 만료"**이고 아직 "지남"이 아니에요. **딱 3일 남은 것도 포함**합니다. 사장님 생각과 다르면 `stock.mjs`의 `daysLeft < 0` / `daysLeft <= 3` 두 줄만 고치면 돼요. `─────────────────────────────────────────────────` ## 직접 짜 볼 부분: `compareUrgency(a, b)` **위치:** `src/stock.mjs` 맨 아래, `// TODO(직접 작성)` 자리 (5~10줄 정도) 이 함수가 "급한 것부터"의 뜻을 정하는 핵심이에요. 오늘 날짜(2026-10-18) 기준으로 이렇게 정렬해야 해요: | 품목 | reasons | daysLeft | 수량/최소 | |---|---|---|---| | 딸기 | expired | -1 | 1/1 | | 생크림 | expiring | 0 | 2/2 | | 치즈케이크 | expiring, low | 1 | **0**/1 | | 우유 | expiring, low | 2 | 3/5 | | 바닐라 시럽 | expiring | 3 | 1/1 | **고민할 점:** - **이유별 등급을 먼저 볼지:** 예를 들어 expired > expiring > low 순서로 등급을 매기는 방법이 있어요. `t.reasons.includes('expired')` 같은 식으로 등급 숫자를 만들어서 `a등급 - b등급`으로 비교하면 됩니다. - **같은 등급 안에서의 순서:** `a.daysLeft - b.daysLeft`처럼 날짜가 빠른 순으로 둘 수 있어요. - **재고 0인 치즈케이크:** 진열대가 비어 있으면 판매를 못 하니까, 오늘 만료인 생크림보다 앞에 둬야 할 수도 있어요. 사장님 입장에서는 "버릴 것 치우기"와 "못 파는 메뉴 채우기" 중 무엇이 더 급할까요? - **조건 두 개에 걸린 품목:** 우유처럼 이유가 두 개면 하나짜리보다 더 급하게 볼지도 정해야 해요. 다 짜면 `test/stock.test.mjs` 마지막의 주석 처리된 `assert`에 기대하는 순서를 채우고 `node test/stock.test.mjs`로 확인해 보세요. 코드를 보여 주시면 같이 검토할게요.

바뀐 코드 (git diff)

diff --git a/src/stock.mjs b/src/stock.mjs
index f81b897..8acb684 100644
--- a/src/stock.mjs
+++ b/src/stock.mjs
@@ -19,3 +19,31 @@ 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, daysLeft, qty, minQty, unit, reasons: ('expired' | 'expiring' | 'low')[] }
+//   expired  : 유통기한 지남 (daysLeft < 0)
+//   expiring : 오늘 포함 3일 안에 끝남 (0 <= daysLeft <= 3)
+//   low      : 수량이 minQty보다 적음
+// 한 품목이 여러 조건에 걸리면 reasons에 모두 담아 한 줄로 보여 준다.
+export function todayTasks(items, today) {
+  const tasks = [];
+  for (const it of items) {
+    const daysLeft = daysBetween(today, it.expires);
+    const reasons = [];
+    if (daysLeft < 0) reasons.push('expired');
+    else if (daysLeft <= 3) reasons.push('expiring');
+    if (it.qty < it.minQty) reasons.push('low');
+    if (reasons.length > 0) {
+      tasks.push({ name: it.name, daysLeft, qty: it.qty, minQty: it.minQty, unit: it.unit, reasons });
+    }
+  }
+  return tasks.sort(compareUrgency);
+}
+
+// 두 task 중 더 급한 쪽이 앞에 오도록 비교한다.
+// a가 더 급하면 음수, b가 더 급하면 양수, 같으면 0을 반환.
+export function compareUrgency(a, b) {
+  // TODO(직접 작성): 급한 순서 규칙
+  return 0;
+}
diff --git a/test/stock.test.mjs b/test/stock.test.mjs
index 71de632..8812379 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,22 @@ 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 byName = Object.fromEntries(tasks.map((t) => [t.name, t]));
+
+assert.equal(tasks.length, 5); // 원두만 빠짐
+assert.equal(byName['원두'], undefined);
+assert.deepEqual(byName['딸기'].reasons, ['expired']); // 어제 만료
+assert.deepEqual(byName['생크림'].reasons, ['expiring']); // 오늘 만료 = 아직 안 지남
+assert.deepEqual(byName['바닐라 시럽'].reasons, ['expiring']); // 딱 3일 남음
+assert.deepEqual(byName['우유'].reasons, ['expiring', 'low']);
+assert.deepEqual(byName['치즈케이크'].reasons, ['expiring', 'low']); // 두 조건 → 한 줄
+assert.equal(byName['딸기'].daysLeft, -1);
+assert.deepEqual(todayTasks([], '2026-10-18'), []);
+console.log('todayTasks 포함 규칙 테스트 통과');
+
+// todayTasks: 급한 순서 (compareUrgency를 작성한 뒤 기대 순서를 채워 주세요)
+// assert.deepEqual(tasks.map((t) => t.name), ['딸기', /* ... */]);