내장 Learning 스타일 1회차 · 요청 2: 급한 순서를 정해야 하는 요청 · Claude Code · Opus 5.5 · 생각 깊이 중간 · 목록

내장 Learning 스타일 1회차

요청 2: 급한 순서를 정해야 하는 요청

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

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

`src/stock.mjs`에 `todayTasks(items, today)`를 추가했고 `test/stock.test.mjs`에 테스트도 넣어 돌렸습니다. 각 재료가 목록에 들어가는지, 어떤 사유로 들어가는지는 테스트를 통과했습니다. 다만 **급한 순서 정렬은 아직 동작하지 않습니다.** 정렬 규칙을 정하는 `compareUrgency`가 지금 항상 0을 돌려줘서, 목록이 입력 순서 그대로 나옵니다. `todayTasks`는 재료 하나당 `{ name, qty, unit, daysLeft, reasons }` 한 줄을 돌려줍니다. 사유가 여러 개면 `reasons`에 함께 담기므로 같은 재료가 두 번 나오지 않습니다. `data.json`에서 경계에 걸리는 경우는 이렇게 처리했습니다. - **생크림**: 유통기한이 오늘(`daysLeft === 0`)이라 "지난 것"이 아니라 "3일 안"으로 분류했습니다. - **바닐라 시럽**: 딱 3일 남아서 포함했습니다. - **딸기**: 수량이 minQty와 같아서 기존 `lowStock`처럼 "부족"으로 보지 않았습니다. `★ Insight ─────────────────────────────────────` - 분류(`classify`)와 정렬(`compareUrgency`)을 나눠 둔 덕분에, "무엇이 목록에 들어가는가"는 정렬 규칙과 상관없이 먼저 테스트할 수 있었습니다. - `Array.prototype.sort`는 순서를 바꾸지 않는 안정 정렬이라, 비교 결과가 0이면 원래 순서가 유지됩니다. 그래서 지금은 `data.json`에 적힌 순서 그대로 나옵니다. - "만료"와 "임박"은 `if / else if`로 둘 중 하나만 붙고, "부족"은 따로 검사해서 날짜 사유와 함께 붙을 수 있습니다. `─────────────────────────────────────────────────` ● **Learn by Doing** **Context:** 분류와 테스트는 끝났고, 남은 건 "무엇이 더 급한가"를 정하는 규칙입니다. 이 규칙이 사장님이 아침에 맨 위에서 보는 항목을 결정하기 때문에, 실제 카페 운영 감각이 필요한 부분입니다. 비교 함수는 `a`, `b` 두 할 일(`{ name, qty, daysLeft, reasons }`)을 받습니다. **Your Task:** `src/stock.mjs`의 `compareUrgency(a, b)`를 구현해 주세요. `TODO(human)`을 찾으면 됩니다. `a`가 더 급하면 음수, `b`가 더 급하면 양수를 돌려주면 됩니다(5~8줄 정도). **Guidance:** - **사유 우선 vs 날짜 우선:** "만료 > 임박 > 부족"처럼 사유 등급으로 먼저 나눌 수 있습니다. 아니면 `daysLeft`를 바로 비교할 수도 있는데, 그러면 만료된 것은 음수라 자연스럽게 맨 위로 옵니다. 다만 유통기한이 먼 "부족만" 항목은 맨 아래로 내려갑니다. - **겹치는 경우:** 우유·치즈케이크처럼 "임박 + 부족"인 재료를 "임박만"인 생크림보다 위에 둘지 정해야 합니다. 예를 들어 `reasons.length`를 비교 기준에 넣을 수 있습니다. - **수량 0:** 치즈케이크는 `qty: 0`이라 오늘 팔 게 아예 없습니다. 품절을 따로 끌어올릴지도 정해 주세요. - **동점일 때:** 마지막 기준으로 `a.name.localeCompare(b.name, 'ko')`를 쓰면 매일 같은 순서로 보여서 아침에 보기 편합니다. 구현해 주시면 그 규칙에 맞는 순서 테스트를 추가해서 다시 돌리겠습니다.

바뀐 코드 (git diff)

diff --git a/src/stock.mjs b/src/stock.mjs
index f81b897..4600db7 100644
--- a/src/stock.mjs
+++ b/src/stock.mjs
@@ -19,3 +19,28 @@ export function orderList(items) {
     .filter((it) => it.qty < it.minQty)
     .map((it) => ({ name: it.name, need: it.minQty * 2 - it.qty, unit: it.unit }));
 }
+
+const SOON_DAYS = 3;
+
+// 한 재료가 여러 사유에 걸리면 한 줄에 합친다: { name, daysLeft, reasons: ['expired'|'expiring'|'low'] }
+function classify(it, today) {
+  const daysLeft = daysBetween(today, it.expires);
+  const reasons = [];
+  if (daysLeft < 0) reasons.push('expired');
+  else if (daysLeft <= SOON_DAYS) reasons.push('expiring');
+  if (it.qty < it.minQty) reasons.push('low');
+  return { name: it.name, qty: it.qty, unit: it.unit, daysLeft, reasons };
+}
+
+// a가 더 급하면 음수, b가 더 급하면 양수, 같으면 0
+export function compareUrgency(a, b) {
+  // TODO(human): 급한 순서 규칙 구현
+  return 0;
+}
+
+export function todayTasks(items, today) {
+  return items
+    .map((it) => classify(it, today))
+    .filter((t) => t.reasons.length > 0)
+    .sort(compareUrgency);
+}
diff --git a/test/stock.test.mjs b/test/stock.test.mjs
index 71de632..0764a33 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,21 @@ 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(byName['원두'], undefined); // 넉넉하고 유통기한도 멀다
+assert.deepEqual(byName['딸기'].reasons, ['expired']); // 어제 만료 (qty == minQty 는 부족 아님)
+assert.deepEqual(byName['생크림'].reasons, ['expiring']); // 오늘 만료 = 아직 안 지남
+assert.equal(byName['생크림'].daysLeft, 0);
+assert.deepEqual(byName['바닐라 시럽'].reasons, ['expiring']); // 딱 3일 남음 → 포함
+assert.deepEqual(byName['우유'].reasons, ['expiring', 'low']); // 두 사유가 한 줄로 합쳐짐
+assert.deepEqual(byName['치즈케이크'].reasons, ['expiring', 'low']);
+assert.equal(tasks.length, 5); // 중복 없이 5개
+assert.deepEqual(todayTasks([], '2026-10-18'), []);
+console.log('todayTasks 분류 테스트 통과');
+
+// TODO: compareUrgency 구현 후 순서 테스트 추가