learning-output-style 플러그인 1회차 · 요청 1: 함수 두 개, 규칙을 다 정해 준 요청 · Claude Code · Opus 5.5 · 생각 깊이 중간 · 목록

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

요청 1: 함수 두 개, 규칙을 다 정해 준 요청

요청문: src/stock.mjs에 두 함수를 추가해 줘. expiringSoon(items, today)는 유통기한이 오늘부터 3일 뒤까지(오늘 포함)인 재료 이름 목록을, expired(items, today)는 유통기한이 이미 지난 재료 이름 목록을 돌려줘. today는 'YYYY-MM-DD' 문자열이야. 순서는 들어온 순서 그대로. test/stock.test.mjs에 테스트도 추가하고 돌려 봐.

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

`src/stock.mjs`에 두 함수를 추가하고 테스트를 돌렸습니다. 기존 테스트와 새 테스트 모두 통과했습니다. ``` 기본 테스트 통과 유통기한 테스트 통과 ``` **동작 방식** - 두 함수 모두 파일에 이미 있던 `daysBetween(today, it.expires)`로 남은 일수를 구합니다. - `expiringSoon`: 남은 일수가 0~3인 재료만 이름으로 돌려줍니다. 0이 오늘, 3이 3일 뒤입니다. - `expired`: 남은 일수가 0보다 작은 재료만 이름으로 돌려줍니다. - `filter` 다음에 `map`을 쓰는 방식이라 들어온 순서가 그대로 유지됩니다. `★ Insight ─────────────────────────────────────` - `parseDate`가 `Date.UTC`로 날짜를 만들어서 일광절약시간(DST)이 바뀌는 날에도 하루 차이가 정확히 86,400,000ms입니다. 그래서 `daysBetween` 결과를 정수로 믿고 쓸 수 있습니다. - `'YYYY-MM-DD'` 문자열은 사전순과 날짜순이 같아서 `it.expires < today`처럼 문자열 비교만으로도 기한 경과를 판단할 수 있습니다. 그래도 "3일 뒤"를 계산하려면 날짜 연산이 필요하니, 두 함수 모두 `daysBetween`으로 맞췄습니다. `─────────────────────────────────────────────────` **추가한 테스트** (`test/stock.test.mjs`, 기준일 2026-10-18) - 오늘 만료(0일)와 정확히 3일 뒤는 포함, 4일 뒤와 어제는 제외되는지 확인 - 순서가 섞인 입력에서 원래 순서가 유지되는지 확인 - 10/29에서 11/01처럼 월이 바뀌는 경우 확인 - 빈 배열을 넣으면 빈 배열이 나오는지 확인 `data.json`에 이 기준일을 적용하면 결과는 이렇습니다. - `expiringSoon`: 우유, 생크림, 바닐라 시럽, 치즈케이크 - `expired`: 딸기

바뀐 코드 (git diff)

diff --git a/src/stock.mjs b/src/stock.mjs
index f81b897..0d1b19f 100644
--- a/src/stock.mjs
+++ b/src/stock.mjs
@@ -19,3 +19,16 @@ export function orderList(items) {
     .filter((it) => it.qty < it.minQty)
     .map((it) => ({ name: it.name, need: it.minQty * 2 - it.qty, unit: it.unit }));
 }
+
+export function expiringSoon(items, today) {
+  return items
+    .filter((it) => {
+      const left = daysBetween(today, it.expires);
+      return left >= 0 && left <= 3;
+    })
+    .map((it) => it.name);
+}
+
+export function expired(items, today) {
+  return items.filter((it) => daysBetween(today, it.expires) < 0).map((it) => it.name);
+}
diff --git a/test/stock.test.mjs b/test/stock.test.mjs
index 71de632..89bd877 100644
--- a/test/stock.test.mjs
+++ b/test/stock.test.mjs
@@ -1,5 +1,5 @@
 import assert from 'node:assert/strict';
-import { lowStock, orderList, daysBetween } from '../src/stock.mjs';
+import { lowStock, orderList, daysBetween, expiringSoon, expired } 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 +8,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('기본 테스트 통과');
+
+// 유통기한: 오늘(0일) ~ 3일 뒤 경계, 어제(-1일), 4일 뒤는 제외
+const today = '2026-10-18';
+const shelf = [
+  { name: '생크림', qty: 2, unit: '팩', expires: '2026-10-18', minQty: 2 },
+  { name: '딸기', qty: 1, unit: 'kg', expires: '2026-10-17', minQty: 1 },
+  { name: '바닐라 시럽', qty: 1, unit: '병', expires: '2026-10-21', minQty: 1 },
+  { name: '레몬', qty: 5, unit: '개', expires: '2026-10-22', minQty: 3 },
+  { name: '우유', qty: 3, unit: 'L', expires: '2026-10-20', minQty: 5 },
+  { name: '버터', qty: 1, unit: '개', expires: '2026-09-30', minQty: 1 },
+];
+assert.deepEqual(expiringSoon(shelf, today), ['생크림', '바닐라 시럽', '우유']);
+assert.deepEqual(expired(shelf, today), ['딸기', '버터']);
+// 월이 바뀌는 경계
+assert.deepEqual(expiringSoon([{ name: 'A', expires: '2026-11-01' }], '2026-10-29'), ['A']);
+assert.deepEqual(expiringSoon([], today), []);
+assert.deepEqual(expired([], today), []);
+console.log('유통기한 테스트 통과');