ok
This commit is contained in:
162
plans/Plan-95f2e196.plan.md
Normal file
162
plans/Plan-95f2e196.plan.md
Normal file
@@ -0,0 +1,162 @@
|
||||
<!-- 95f2e196-ce33-4253-ace0-0a2ba260a0c2 -->
|
||||
---
|
||||
todos:
|
||||
- id: "finalize-article-schema"
|
||||
content: "course_article 增加 content_json(含 nameList/skipWords);正文不再依赖 course_article_sentence"
|
||||
status: pending
|
||||
- id: "nce-import-command"
|
||||
content: "NCE_*.json → course + course_article;导入时写入 skipWords/nameList(人名/地名免敲)"
|
||||
status: pending
|
||||
- id: "article-api"
|
||||
content: "GET 课文 API:content_json 直出 TypeWords Article DTO(nameList 供默写/听写跳过)"
|
||||
status: pending
|
||||
- id: "article-vocab-api"
|
||||
content: "课文生词 API:course_article_vocab(词头)+ vocab_sense + course_word_sense,与正文分离"
|
||||
status: pending
|
||||
- id: "content-patch-admin"
|
||||
content: "低频勘误:PATCH content_json 的 text/translate/skipWords,或单课重导"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
# 课文内容存储方案建议(修订 v2)
|
||||
|
||||
## 结论
|
||||
|
||||
- **课文正文**:`course_article.content_json`(几乎不改,偶发勘误)
|
||||
- **课文生词**:关系表(`course_article_vocab` + `course_word_sense`)
|
||||
- **人名/地名免敲**:写在 `content_json` 的 **`skipWords`**(API 层映射为 TypeWords 的 `nameList`)
|
||||
|
||||
---
|
||||
|
||||
## 人名 / 地名:练习时不用敲
|
||||
|
||||
### 需求
|
||||
|
||||
默写或听写课文时,**人名、地名**应预先标注,练习流程 **自动跳过**,不要求学生输入(如 `Silbury`、`Pinhurst`、`James`、`Scott`)。
|
||||
|
||||
### TypeWords 已有能力(可直接复用)
|
||||
|
||||
[`TypingArticle.vue`](TypeWords/packages/core/src/components/article/TypingArticle.vue) 已支持 `article.nameList`:
|
||||
|
||||
- 命中 `nameList` 的 token 在 `next()` 时 **自动跳过**
|
||||
- 内置跳过:`Mr` / `Mrs` / `Ms` / `Dr` / `Miss`
|
||||
- 多词条目 `"James Scott"` 会拆成 `james`、`scott` 分别匹配
|
||||
|
||||
**不需要**在正文里加 `{Silbury}` 这类 inline 标记;**维护一份免敲词表**即可。
|
||||
|
||||
### 在 `content_json` 里怎么存
|
||||
|
||||
推荐 **两层字段**(存库清晰 + 对接 TypeWords 零改动):
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "No wrong numbers",
|
||||
"text": "Mr.James Scott has a garage in Silbury ...",
|
||||
"textTranslate": "...",
|
||||
"audioSrc": "/sound/article/nce2-1/No wrong numbers.mp3",
|
||||
"lrcPosition": [[15.45, 24.87]],
|
||||
|
||||
"skipWords": [
|
||||
{ "token": "James", "type": "person" },
|
||||
{ "token": "Scott", "type": "person" },
|
||||
{ "token": "Silbury", "type": "place" },
|
||||
{ "token": "Pinhurst", "type": "place" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `skipWords[]` | collin **权威存储**;`type`: `person` / `place`(可扩展 `org`) |
|
||||
| `nameList` | **API 输出** 时由 `skipWords[].token` 去重生成为 TypeWords 字段(可不落库重复存) |
|
||||
|
||||
Lesson5 示例映射:
|
||||
|
||||
```
|
||||
skipWords → nameList: ["James", "Scott", "Silbury", "Pinhurst"]
|
||||
(Mr 已由 TypeWords 内置跳过)
|
||||
```
|
||||
|
||||
### 练习端行为
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
API["GET 课文 API"]
|
||||
Map["skipWords → nameList"]
|
||||
TW["TypeWords TypingArticle"]
|
||||
Skip["isNameWord 自动 next"]
|
||||
|
||||
API --> Map --> TW --> Skip
|
||||
```
|
||||
|
||||
- **默写 / 听写**:同一套 `nameList` 逻辑
|
||||
- **UI 可选增强**(后续):按 `skipWords.type` 在课文预览里给人名/地名不同样式(不影响是否跳过)
|
||||
|
||||
### 导入时谁维护 skipWords
|
||||
|
||||
1. **人工标注**(推荐 MVP):导入 NCE 时在源 JSON 或 Excel 补一列「免敲词」
|
||||
2. **半自动**:导入脚本从 `text` 匹配已知的本课 `course_article_vocab` 之外的专名词表(准确度有限)
|
||||
3. **勘误**:只改 `content_json.skipWords`,`content_version + 1`
|
||||
|
||||
**不要**单独建 `course_article_skip_token` 表 — 与「几乎不改」的 JSON 方案一致,免敲词随课文打包即可。
|
||||
|
||||
---
|
||||
|
||||
## 课文正文:content_json(不变)
|
||||
|
||||
| 层级 | 存储 |
|
||||
|------|------|
|
||||
| 正文 | `course_article.content_json` |
|
||||
| 元数据 | `course_article` 列:`lesson_no`, `title`, `audio_url`, `status` |
|
||||
| 生词 | 关系表(一词一行 + 义项) |
|
||||
| 词典 | `word` / `dict` |
|
||||
|
||||
`content_json` **不含** `newWords`;**含** `skipWords`(人名/地名)。
|
||||
|
||||
完整示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "A private conversation",
|
||||
"titleTranslate": "私人谈话",
|
||||
"text": "...",
|
||||
"textTranslate": "...",
|
||||
"audioSrc": "...",
|
||||
"lrcPosition": [],
|
||||
"skipWords": [
|
||||
{ "token": "theatre", "type": "place" }
|
||||
],
|
||||
"quote": null,
|
||||
"question": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与 TypeWords 字段对照
|
||||
|
||||
| TypeWords `Article` | collin 来源 |
|
||||
|---------------------|-------------|
|
||||
| `title`, `text`, `textTranslate`, `lrcPosition`, `audioSrc` | `content_json` |
|
||||
| `nameList` | API 由 `skipWords[].token` 生成 |
|
||||
| `sections` | 前端 `genArticleSectionData(text)` |
|
||||
| 生词 | 独立 API,不进 JSON |
|
||||
|
||||
---
|
||||
|
||||
## 对迁移 / API 的调整
|
||||
|
||||
1. `course_article.content_json` 文档化 **`skipWords`** 结构
|
||||
2. `ArticleApiService`:读 JSON → 填充 `nameList` → 返回 TypeWords DTO
|
||||
3. NCE 导入 Command:支持从源文件 `nameList` 迁移,或从 sidecar 配置读 `skipWords`
|
||||
4. 废弃 `course_article_sentence`(正文不再用句子表)
|
||||
|
||||
---
|
||||
|
||||
## 实施优先级
|
||||
|
||||
1. 定稿 `content_json` schema(含 `skipWords`)
|
||||
2. NCE 导入 + 首批课文手工标注免敲词
|
||||
3. GET 课文 API(`skipWords` → `nameList`)
|
||||
4. 生词 API(与正文分离)
|
||||
5. 勘误:PATCH `text` / `translate` / `skipWords`
|
||||
Reference in New Issue
Block a user