플러그인 없이 2회차 · 요청 2: 급한 순서를 정해야 하는 요청 · Claude Code · Opus 5.5 · 생각 깊이 중간 · 목록

플러그인 없이 2회차

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

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

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

`src/stock.mjs`에 `todayTasks(items, today)`를 추가했고, `node test/stock.test.mjs`를 돌려서 기존 테스트와 새 테스트가 모두 통과했습니다. **돌려주는 값** 한 재료가 여러 조건에 걸려도 목록에는 한 줄만 나오고, 걸린 조건은 모두 `reasons`에 담깁니다. ```js { name, reasons: ['expired' | 'expiring' | 'low', ...], daysLeft, qty, minQty, unit } ``` **제가 정한 기준** (바꾸고 싶으면 말씀해 주세요) - **유통기한 당일**(남은 날 0)은 아직 쓸 수 있다고 보고 '임박'에 넣었습니다. '지남'은 하루라도 넘긴 것만입니다. - **3일 안**은 오늘부터 3일 뒤까지(0~3일)입니다. 4일 남은 것은 빠집니다. - **급한 순서**는 기한이 지난 것 → 기한이 임박한 것(남은 날이 적은 순) → 수량만 부족한 것(minQty 대비 남은 양이 적은 순)입니다. **`data.json`으로 10월 18일 아침에 돌린 결과** 1. 딸기: 기한 지남 (-1일) 2. 생크림: 오늘 만료 3. 치즈케이크: 1일 남음, 재고 부족 4. 우유: 2일 남음, 재고 부족 5. 바닐라 시럽: 3일 남음 원두는 걸리는 조건이 없어서 빠졌습니다. **확인해 주실 점:** 지금은 남은 날짜를 수량보다 먼저 봅니다. 그래서 치즈케이크는 재고가 0판인데도 생크림보다 뒤에 나옵니다. 재고가 0인 것을 맨 위로 올리고 싶으면 정렬 기준을 바꾸면 됩니다. 테스트(`test/stock.test.mjs`)는 위 `data.json` 결과 외에도 수량만 부족한 것끼리의 정렬, 4일 남은 것 제외, 빈 목록을 확인합니다.

바뀐 코드 (git diff)

diff --git a/src/stock.mjs b/src/stock.mjs
index f81b897..db6a493 100644
--- a/src/stock.mjs
+++ b/src/stock.mjs
@@ -19,3 +19,27 @@ export function orderList(items) {
     .filter((it) => it.qty < it.minQty)
     .map((it) => ({ name: it.name, need: it.minQty * 2 - it.qty, unit: it.unit }));
 }
+
+// 아침 '오늘 할 일': 유통기한 지남 / 3일 내 만료 / minQty 미만을 한 목록으로.
+// 한 재료가 여러 조건에 걸리면 한 줄에 reasons로 모은다.
+// 급한 순서: 기한 지남 → 기한 임박(남은 날 적은 순) → 재고 부족만(부족 비율 큰 순).
+// 유통기한 당일(daysLeft 0)은 아직 쓸 수 있으므로 '임박'으로 본다.
+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) continue;
+    tasks.push({ name: it.name, reasons, daysLeft, qty: it.qty, minQty: it.minQty, unit: it.unit });
+  }
+  const rank = (t) =>
+    t.reasons.includes('expired') ? 0 : t.reasons.includes('expiring') ? 1 : 2;
+  return tasks.sort(
+    (a, b) =>
+      rank(a) - rank(b) ||
+      (rank(a) < 2 ? a.daysLeft - b.daysLeft : a.qty / a.minQty - b.qty / b.minQty),
+  );
+}
diff --git a/test/stock.test.mjs b/test/stock.test.mjs
index 71de632..3131c65 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,32 @@ 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: data.json 기준 2026-10-18 아침
+const data = JSON.parse(readFileSync(new URL('../data.json', import.meta.url), 'utf8'));
+const tasks = todayTasks(data, '2026-10-18');
+assert.deepEqual(
+  tasks.map((t) => [t.name, t.reasons, t.daysLeft]),
+  [
+    ['딸기', ['expired'], -1],
+    ['생크림', ['expiring'], 0], // 당일 만료는 임박
+    ['치즈케이크', ['expiring', 'low'], 1],
+    ['우유', ['expiring', 'low'], 2],
+    ['바닐라 시럽', ['expiring'], 3], // 3일 뒤까지 포함
+  ],
+);
+assert.ok(!tasks.some((t) => t.name === '원두'));
+
+// 재고 부족만 걸린 것은 기한 문제 뒤에, 부족 비율 큰 순
+const lowOnly = todayTasks(
+  [
+    { name: 'A', qty: 4, unit: '개', expires: '2027-01-01', minQty: 5 },
+    { name: 'B', qty: 0, unit: '개', expires: '2027-01-01', minQty: 5 },
+    { name: 'C', qty: 9, unit: '개', expires: '2026-10-22', minQty: 1 },
+    { name: 'D', qty: 9, unit: '개', expires: '2026-10-22', minQty: 1 },
+  ],
+  '2026-10-18',
+);
+assert.deepEqual(lowOnly.map((t) => t.name), ['B', 'A']); // C·D는 4일 뒤라 제외
+assert.deepEqual(todayTasks([], '2026-10-18'), []);
+console.log('todayTasks 테스트 통과');