Files
what-to-eat/skills/add-recipe/SKILL.md
T

183 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` 重新生成。