183 lines
10 KiB
Markdown
183 lines
10 KiB
Markdown
---
|
||
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` 重新生成。
|