--- name: add-recipe description: 给 Cook(食用手册)新增菜式。当用户说「增加菜式」「加几道菜」「添几个菜谱」「补点新菜」「增加 XX 类菜」「加个 XX 做的菜」时使用。按「先摸清现有数据 → 上网找数据里没有的新菜 → 拆解成食材/做法/厨具/菜系类别 → 写入并校验」的流程执行。 metadata: author: YunYouJun repository: https://github.com/YunYouJun/cook --- # 新增菜式 你在给 Cook(食用手册)补充菜谱数据。每次运行把**若干道新菜**写进 `packages/data/recipe.csv`,跑完校验后向用户汇报。 ## 数量规则 - 用户明确说了数量(「加 10 道」「补 5 个」),就按用户说的来。 - **用户没说数量,默认加 5 道菜。** - 用户指定了主题或范围(「加几道川菜」「补点用砂锅的菜」),数量仍按上一条判断。 ## 数据落点 只有一个文件要改:`packages/data/recipe.csv`。 列为 `name,stuff,bv,difficulty,tags,methods,tools,id`,单元格内多个值用顿号「、」分隔(**不能用逗号、斜杠、分号**)。 ``` name,stuff,bv,difficulty,tags,methods,tools,id 青椒炒肉,青椒、猪肉,,普通,家常菜,炒,一口大锅,637 电饭煲版广式腊肠煲饭,腊肠、米,BV1NE411Q7Jj,简单,广式,煲,电饭煲,0 ``` | 列 | 规则 | | ---------- | ------------------------------------------------------------------------------------------------------------- | | name | 必填。菜名要能让人一眼看出做法,习惯写成「厨具版+做法+主料」或「主料+做法」 | | stuff | 必填。只能填 `packages/data/food.json` 里的食材标准名,具体品种(虾仁、鲈鱼)要写成标准名(虾、鱼) | | bv | B 站 BV 号或 av 号。**可以留空**——应用里点菜走的是 B 站搜索链接,不是这个视频号;填了就必须是真实可播放的视频 | | difficulty | 简单 / 普通 / 困难,或留空 | | tags | 菜系类别 + 其它标签(快手菜、下饭、汤、杂烩、辣…),见下表 | | methods | 做法(炒、煎、蒸、煮、煲、烤、炸…),见下表 | | tools | 只能填 `packages/data/food.json` 里的厨具名 | | id | 必填,非负整数且唯一。**新菜用当前最大 id + 1**(现在是 637、638…) | **id 一旦发布就不能修改、不能复用。** 收藏、反馈、筛选信号都按 id 记录。新菜一律追加到文件末尾,id 递增。 ## 取值白名单 ### 食材(35 个,来自 `packages/data/food.json`) - 蔬菜:土豆、胡萝卜、花菜、白萝卜、西葫芦、番茄、芹菜、黄瓜、洋葱、莴笋、菌菇、茄子、豆腐、包菜、白菜、青椒、韭菜、南瓜 - 肉蛋水产:午餐肉、香肠、腊肠、鸡肉、猪肉、鸡蛋、虾、牛肉、骨头、鱼、蟹 - 主食:面食、面包、米、方便面、玉米、红薯 需要列表里没有的食材时,**先在 `packages/data/food.json` 里加**,再加别名到 `packages/data/aliases.json`(如「柿子椒」→「青椒」),然后才写进菜谱。别名映射用户输入的写法到标准名。 ### 厨具(6 个) 烤箱、空气炸锅、微波炉、电饭煲、一口大锅、砂锅 ### 菜系类别(tags 里的菜系标签) 必须从下表里选**标签值**写进 tags,选了哪个菜系筛选就只认哪个标签: | 菜系 | 可用标签 | | ------ | ------------ | | 川菜 | 川菜 | | 湘菜 | 湘菜 | | 粤菜 | 粤菜、广式 | | 苏菜 | 苏菜 | | 东北菜 | 东北菜 | | 西北菜 | 西北特色 | | 日式 | 日式、日式菜 | | 意式 | 意式 | | 法式 | 法式、法餐 | | 西式 | 西式 | | 国宴菜 | 国宴菜 | **每道新菜都尽量打上菜系标签。** 应用里一旦选了菜系,只有带该标签的菜会出现——标签不全会导致选了菜系就几乎没结果。 ### 做法分组(methods + tags 的组合) 用户在应用里选的「做法」是分组,按下面的规则命中: | 做法分组 | 怎么填 | 备注 | | -------- | ---------------------------------------- | ------------------------------------ | | 炒菜 | methods 填「炒」 | | | 汤羹 | **tags 填「汤」**,methods 填煮/煲/炖 | 靠标签识别,不是靠 methods | | 炖煮 | methods 填炖、煮、煲、卤、焖、烧、灼 | | | 蒸 | methods 填「蒸」 | | | 炸煎 | methods 填炸、煎、烙 | | | 烤 | methods 填烤、烘、焗 | | | 凉拌 | methods 填拌、凉拌、腌、腌制、泡菜、油泼 | | | 微波 | methods 填「微波加热」 | | | 火锅 | **tags 填「杂烩」**,且食材 **≥10 种** | 靠标签识别;食材少于 10 种不算大杂烩 | 「辣」标签同样靠 tags 识别,选了「不吃辣」时会排除带此标签的菜。 ## 步骤 ### 1. 摸清现有数据 先搞清楚「已经有什么、缺什么」,不要凭印象加菜。 ```bash # 全部菜谱(638 行,含表头) cat packages/data/recipe.csv # 菜谱总数 wc -l packages/data/recipe.csv # 现有菜系分布:每种菜系各有多少道 node -e "const fs=require('fs');const l=fs.readFileSync('packages/data/recipe.csv','utf8').trim().split(/\r?\n/).slice(1);const c={};l.forEach(r=>{const t=r.split(',')[4]||'';t.split('、').filter(Boolean).forEach(x=>{if(/菜|式|广式|法餐/.test(x))c[x]=(c[x]||0)+1})});console.log(c)" # 某个食材出现在多少道菜里(找出只有 1-2 道菜的冷门食材) node -e "const fs=require('fs');const l=fs.readFileSync('packages/data/recipe.csv','utf8').trim().split(/\r?\n/).slice(1);const c={};l.forEach(r=>r.split(',')[1].split('、').filter(Boolean).forEach(x=>c[x]=(c[x]||0)+1));console.log(Object.entries(c).sort((a,b)=>a[1]-b[1]))" # 某个厨具下有多少道菜 node -e "const fs=require('fs');const l=fs.readFileSync('packages/data/recipe.csv','utf8').trim().split(/\r?\n/).slice(1);const c={};l.forEach(r=>r.split(',')[6].split('、').filter(Boolean).forEach(x=>c[x]=(c[x]||0)+1));console.log(c)" ``` 同时读 `packages/data/food.json`(食材/厨具词典)和 `packages/data/aliases.json`(别名)。 重点找这几类空洞,补菜时优先填: - **菜系空洞**:某个菜系只有 1-2 道菜,用户一选就几乎没结果。 - **冷门食材**:某个食材只出现在 1-2 道菜里,选它必然匹配不到。 - **厨具 × 食材交叉**:某个食材配某个厨具一道菜都没有(比如砂锅只有十来道菜,覆盖的食材很窄)。 - **做法 × 菜系交叉**:某菜系里缺某类做法(比如粤菜没有凉拌、日式没有汤)。 ### 2. 上网找新菜 - 围绕要补的空洞找菜:「XX 家常做法」「XX 砂锅」「XX 快手菜」这类关键词。 - 优先选**常见、做法明确、食材在词典里**的家常菜。冷门菜、分子料理、需要特殊设备的不要。 - **判重**:菜名和已有菜重复的、或同名同做法的,不加。先用步骤 1 的命令或直接搜 CSV 确认。 - **不要编造**菜谱或视频号。找不到真实信息就换一道菜,别硬凑。 - 找视频(可选):搜索时带上 `bilibili.com/video` 关键词,从结果里拿 BV 号。 ### 3. 拆解并写入 对每一道新菜,逐列拆开: 1. **菜名**:`青椒炒肉` 2. **食材**:拆成词典里的标准名 —— 青椒、猪肉 3. **厨具**:这道菜实际用什么锅做 —— 一口大锅 4. **做法**:methods 填具体动作 —— 炒 5. **菜系类别**:tags 填菜系标签 —— 川菜、家常菜 6. **难度**:简单 / 普通 / 困难,拿不准留空 7. **id**:从当前最大 id + 1 开始,逐个递增 然后**追加到 `packages/data/recipe.csv` 末尾**(不要改已有行)。 ### 4. 校验 按顺序跑完,全绿才算完成: ```bash pnpm convert # CSV → JSON,必须先跑 pnpm validate # 有错误时退出码为 1 pnpm fixtures # 重新生成 recommend.json(改了数据必须重跑) pnpm test -- --run pnpm lint ``` 常见问题: - **`pnpm validate` 报「食材/厨具不在词典中」** —— 名称不对,改成标准名;词典里确实没有就先加进 `food.json`。 - **`pnpm test` 失败且是 `test/recommendations.test.ts` 里的 `counts` 断言** —— 新菜改变了推荐结果的分组数量,属于预期变化,更新这些数字即可。 - **`pnpm test` 报 fixtures 不一致** —— `pnpm fixtures` 没跑或跑漏了。 - **`pnpm lint` 报 md 文件格式错** —— 文档类改动注意行尾和代码块内不要留多余空行。 ### 5. 汇报 告诉用户: - 加了几道菜,每道的:菜名、食材、厨具、做法、菜系、难度、id - 补的是哪些空洞(比如「补了砂锅 + 香菇 3 道」「粤菜凉拌 2 道」) - 校验结果(`pnpm validate` 错误数、`pnpm test` 通过情况) - 哪些地方拿不准、需要用户定夺(比如新加的食材名、没找到合适视频的菜) ## 注意事项 - 不要删除或修改已有行。发现已有数据有问题(重复、失效视频)时,在汇报里指出,让用户决定。 - 不要改 `packages/cook/src/utils/recommend.ts` 里的筛选逻辑,也不要改 `test/` 下的断言来「凑」结果——断言变化只允许是数据量自然变化导致。 - `packages/data/recipe.json` 和 `packages/data/fixtures/recommend.json` 是生成物,不要手写,改 CSV 和用例输入后用 `pnpm convert` / `pnpm fixtures` 重新生成。