439 lines
14 KiB
Markdown
439 lines
14 KiB
Markdown
## 整体架构概览
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "用户界面层 (TUI)"
|
||
TUI[Textual App]
|
||
Screens[应用屏幕]
|
||
Widgets[谜题组件]
|
||
end
|
||
|
||
subgraph "内核层 Kernel"
|
||
Reactor[调度反应器]
|
||
Algorithms[算法模块]
|
||
Particles[数据模型]
|
||
Puzzles[谜题引擎]
|
||
RepoLib[仓库系统]
|
||
Auxiliary[辅助工具]
|
||
end
|
||
|
||
subgraph "服务层"
|
||
Config[配置管理 ConfigDict]
|
||
Logger[日志系统]
|
||
Timer[时间服务]
|
||
Audio[音频服务]
|
||
TTS[TTS 服务]
|
||
Favorites[收藏管理]
|
||
Attic[持久化]
|
||
Hasher[哈希服务]
|
||
end
|
||
|
||
subgraph "提供者层"
|
||
AudioProv[音频提供者]
|
||
TTSProv[TTS 提供者]
|
||
LLMProv[LLM 提供者]
|
||
end
|
||
|
||
subgraph "数据层"
|
||
RepoDir[TOML/JSON 仓库目录]
|
||
ConfigDir[TOML 配置目录]
|
||
Logs[日志文件]
|
||
end
|
||
|
||
TUI --> Screens
|
||
Screens --> Reactor
|
||
Screens --> RepoLib
|
||
Screens --> Widgets
|
||
Widgets --> Puzzles
|
||
Widgets --> Reactor
|
||
Reactor --> Algorithms
|
||
Reactor --> Particles
|
||
Reactor --> Puzzles
|
||
Particles --> RepoLib
|
||
RepoLib --> Config
|
||
RepoLib --> Auxiliary
|
||
Auxiliary --> Lict
|
||
Auxiliary --> Evalizer
|
||
TUI --> Config
|
||
TUI --> Logger
|
||
TUI --> Audio
|
||
TUI --> TTS
|
||
Config --> ConfigDir
|
||
Audio --> AudioProv
|
||
TTS --> TTSProv
|
||
Attic --> RepoDir
|
||
```
|
||
|
||
## 数据模型
|
||
|
||
项目以物理粒子隐喻为核心, 将记忆单元拆解为三个模型:
|
||
|
||
### Nucleon (核子) — 内容层
|
||
|
||
```
|
||
Nucleon(ident, payload, common)
|
||
```
|
||
|
||
- **只读**内容容器. 通过 `Evalizer` (基于 `eval()` 的模板系统)对 payload 和 common 进行编译展开.
|
||
- 包含 `puzzles` 字段, 定义该记忆单元支持哪些谜题类型.
|
||
- 从 `repo.payload` 和 `repo.typedef["common"]` 配对创建.
|
||
- 一旦创建, 内容不可修改 (`__setitem__` 抛出 `AttributeError`).
|
||
|
||
### Electron (电子) — 状态层
|
||
|
||
```
|
||
Electron(ident, algodata, algo_name)
|
||
```
|
||
|
||
- 算法状态数据的包装器. 每个 Electron 绑定一个算法 (`algorithms[algo_name]`).
|
||
- `algodata` 是到仓库 `algodata.lict` 中对应字典的**引用**, 修改即持久化.
|
||
- 核心方法:`activate()` (标记激活)、`revisor()` (评分迭代)、`is_due()` (到期判断).
|
||
|
||
### Orbital (轨道) — 策略层
|
||
|
||
```
|
||
orbital = {
|
||
"schedule": ["quick_review", "recognition"],
|
||
"routes": {
|
||
"quick_review": [["MCQ", "1.0"], ["Cloze", "0.5"]],
|
||
"recognition": [["Recognition", "1.0"]],
|
||
}
|
||
}
|
||
```
|
||
|
||
- 定义复习阶段流程和各阶段内谜题选择策略的纯字典.
|
||
- 每个阶段对应一组 `(谜题类型, 概率系数)` 元组列表, 概率系数 >1 的部分表示强制重复次数.
|
||
|
||
### Atom (原子) — 运行时组装
|
||
|
||
```
|
||
Atom(nucleon, electron, orbital)
|
||
```
|
||
|
||
- 三者的运行时组合, 附带 `runtime` 运行时标志 (`locked`, `min_rate`, `new_activation`).
|
||
- 是 UI 和调度层操作的基本单位.
|
||
- `revise()` 方法在 `locked` 为真时调用 `electron.revisor(min_rate)`, 执行最终评分迭代.
|
||
|
||
**关系图**:
|
||
|
||
```mermaid
|
||
graph LR
|
||
subgraph "持久化存储"
|
||
Payload[(payload.toml)]
|
||
Common[(typedef.toml)]
|
||
Algodata[(algodata.json)]
|
||
Schedule[(schedule.toml)]
|
||
end
|
||
subgraph "运行时组装"
|
||
Nucleon -->|内容| Atom
|
||
Electron -->|状态| Atom
|
||
Orbital -->|策略| Atom
|
||
end
|
||
|
||
Payload -->|Repo| Nucleon
|
||
Common -->|Repo| Nucleon
|
||
Algodata -->|Repo| Electron
|
||
Schedule -->|Repo| Orbital
|
||
```
|
||
|
||
## 调度反应器 (Reactor)
|
||
|
||
调度反应器是核心业务流程引擎, 采用三层嵌套的有限状态机设计 (基于 `transitions` 库).
|
||
|
||
### 状态枚举定义
|
||
|
||
| 状态机 | 状态 | 说明 |
|
||
|--------|------|------|
|
||
| **RouterState** | `unsure` | 初始状态, 自动推进 |
|
||
| | `quick_review` | 快速复习阶段 |
|
||
| | `recognition` | 新记忆识别阶段 |
|
||
| | `final_review` | 最终总复习阶段 |
|
||
| | `finished` | 完成, 执行评分 |
|
||
| **ProcessionState** | `active` | 进行中 |
|
||
| | `finished` | 已完成 |
|
||
| **ExpanderState** | `exammode` | 考试模式 (正面答题) |
|
||
| | `retronly` | 回溯模式 (仅识别) |
|
||
|
||
### 状态机嵌套结构
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "Router (全局路由器)"
|
||
R[Router<br/>状态: unsure→quick_review<br/>→recognition→final_review<br/>→finished]
|
||
P1[Procession 队列1: 快速复习]
|
||
P2[Procession 队列2: 新记忆]
|
||
P3[Procession 队列3: 总复习]
|
||
R --> P1
|
||
R --> P2
|
||
R --> P3
|
||
end
|
||
|
||
subgraph "Procession (单阶段队列)"
|
||
P1 --> E1[Expander 原子A]
|
||
P1 --> E2[Expander 原子B]
|
||
P1 --> E3[Expander 原子C]
|
||
M{forward 推进} --> |完成| Finish((FINISHED))
|
||
end
|
||
|
||
subgraph "Expander (单原子展开器)"
|
||
E1 --> S[(轨道策略)]
|
||
S -->|概率展开| PZ1[谜题1: MCQ]
|
||
S -->|概率展开| PZ2[谜题2: Cloze]
|
||
PZ1 -->|评分| RPT[report]
|
||
PZ2 -->|评分| RPT
|
||
RPT -->|finish| RETRO[retronly 回溯模式]
|
||
end
|
||
```
|
||
|
||
### 数据流详解
|
||
|
||
```
|
||
Router.__init__(atoms)
|
||
│
|
||
├─ 新旧原子分流
|
||
│ ├─ old_atoms → Procession(quick_review) "初始复习"
|
||
│ └─ new_atoms → Procession(recognition) "新记忆"
|
||
│
|
||
└─ 所有原子 → Procession(final_review) "总体复习"
|
||
│
|
||
└─ Procession.forward()
|
||
│
|
||
├─ cursor >= len(atoms) → finish()
|
||
└─ cursor < len(atoms) → next_atom
|
||
│
|
||
└─ Procession.get_expander()
|
||
│
|
||
└─ Expander(atom, route)
|
||
│
|
||
├─ 读取 orbital.routes[route_value]
|
||
├─ 概率展开为谜题列表 self.puzzles_inf
|
||
├─ exammode → 依次展示谜题
|
||
├─ report(rating) → 记录最低评分
|
||
├─ forward() → 下一个谜题或 finish → retronly
|
||
└─ retronly → 展示 Recognition
|
||
│
|
||
└─ Atom.revise()
|
||
│
|
||
└─ Electron.revisor(min_rate)
|
||
│
|
||
└─ Algorithm.revisor(algodata, feedback)
|
||
```
|
||
|
||
评分累积机制: 原子在多谜题阶段的最终评分取所有谜题的最低评分 (`min_rate`), 确保严格评估.
|
||
|
||
## 算法系统
|
||
|
||
所有算法继承自 `BaseAlgorithm`, 以类方法的风格实现, 通过 `algorithms` 字典注册.
|
||
|
||
| 算法 | 文件 | 状态 | 说明 |
|
||
|------|------|------|------|
|
||
| **SM-2** | `sm2.py` | ✅ 完成 | 经典 SuperMemo 1987 算法 |
|
||
| **NSP-0** | `nsp0.py` | ✅ 完成 | 非间隔过滤调度器 |
|
||
| **SM-15M** | `sm15m.py` | ✅ 完成 | 从 CoffeeScript 移植的 SM-15 |
|
||
| **FSRS** | `fsrs.py` | ✅ 部分完成 | 优化器不可用 |
|
||
| **Base** | `base.py` | ✅ 基类 | 定义 `AlgodataDict` 结构和默认值 |
|
||
|
||
每个算法提供以下类方法:
|
||
|
||
| 方法 | 功能 |
|
||
|------|------|
|
||
| `revisor(algodata, feedback, is_new_activation)` | 根据评分迭代记忆数据 |
|
||
| `is_due(algodata)` | 判断是否到期复习 |
|
||
| `get_rating(algodata)` | 获取评分信息 |
|
||
| `nextdate(algodata)` | 获取下一次复习时间戳 |
|
||
| `check_integrity(algodata)` | 校验 algodata 数据结构完整性 |
|
||
|
||
### 算法数据结构 (AlgodataDict)
|
||
|
||
```python
|
||
{
|
||
"real_rept": int, # 实际复习次数
|
||
"rept": int, # 当前重复计数
|
||
"interval": int, # 间隔天数
|
||
"last_date": int, # 上次复习日期
|
||
"next_date": int, # 下次到期日期
|
||
"is_activated": int, # 是否已激活 (0/1)
|
||
"last_modify": float, # 最后修改时间戳
|
||
}
|
||
```
|
||
|
||
## 仓库系统 (Repo)
|
||
|
||
仓库为 TOML/JSON 文件目录, 无数据库依赖.
|
||
|
||
### 目录结构
|
||
|
||
```
|
||
data/repo/<package_name>/
|
||
├── manifest.toml # 元信息: title, author, package, desc
|
||
├── typedef.toml # 通用元数据、谜题定义、注解
|
||
├── payload.toml # 记忆条目 (key=ident)
|
||
├── algodata.json # 算法状态 (key=ident)
|
||
└── schedule.toml # 轨道/复习策略
|
||
```
|
||
|
||
### Repo 类设计
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class Repo {
|
||
+dict schedule
|
||
+Lict payload
|
||
+dict manifest
|
||
+dict typedef
|
||
+Lict algodata
|
||
+Path source
|
||
+Lict nucleonic_data_lict
|
||
+dict orbitic_data
|
||
+Lict electronic_data_lict
|
||
+from_repodir(source) ~Repo
|
||
+from_dict(dictdata) ~Repo
|
||
+create_new_repo() ~Repo
|
||
+persist_to_repodir(save_list, source)
|
||
+export_to_dict() dict
|
||
}
|
||
```
|
||
|
||
- `payload` 和 `algodata` 使用 `Lict` (列表+字典混合容器), 支持双模式访问.
|
||
- `_generate_particles_data()` 在初始化时自动将 payload 数据转换为 `Nucleon` 所需的格式.
|
||
- 默认保存列表 `default_save_list = ["algodata"]`, 仅持久化算法状态.
|
||
|
||
## Lict 集合
|
||
|
||
`Lict` 继承 `MutableSequence`, 同时维护列表和字典访问:
|
||
|
||
```python
|
||
lict = Lict()
|
||
lict.append(("key1", value1)) # 列表追加
|
||
lict["key1"] # 字典访问
|
||
lict[0] # 索引访问
|
||
lict.keys() # 所有键
|
||
lict.dicted_data # 纯字典导出
|
||
```
|
||
|
||
脏同步机制:修改列表时自动同步字典, 修改字典时自动同步列表. 用于 `payload` 和 `algodata` 的双模式访问需求.
|
||
|
||
## 配置系统 (ConfigDict)
|
||
|
||
`ConfigDict` 继承 `UserDict`, 是**单例模式**的 TOML 懒加载配置管理器.
|
||
|
||
### 配置目录约定
|
||
|
||
```
|
||
data/config/
|
||
├── _.toml # 顶层默认值 (递归合并)
|
||
├── interface/
|
||
│ ├── _.toml # interface 层默认值
|
||
│ ├── global.toml
|
||
│ └── puzzles.toml
|
||
├── services/
|
||
│ ├── _.toml # services 层默认值
|
||
│ ├── audio.toml
|
||
│ └── tts.toml
|
||
└── repo/
|
||
└── _.toml
|
||
```
|
||
|
||
- `_.toml` 文件 = 该目录层级的默认值, 合并到父级
|
||
- 带后缀文件 = 按需懒加载
|
||
- 子目录 = 递归子配置
|
||
|
||
### 上下文管理
|
||
|
||
```python
|
||
from heurams.context import config_var, ConfigContext
|
||
|
||
# 全局访问
|
||
config = config_var.get()
|
||
algo = config["interface"]["global"]["algorithm"]
|
||
|
||
# 作用域覆盖
|
||
with ConfigContext(test_config):
|
||
... # 临时使用测试配置
|
||
```
|
||
|
||
## 提供者系统 (Providers)
|
||
|
||
可插拔的后端实现, 通过 `providers/__init__.py` 中的字典注册.
|
||
|
||
| 类别 | 提供者 | 说明 |
|
||
|------|--------|------|
|
||
| **TTS** | `edge_tts` | Microsoft Edge TTS (在线) |
|
||
| | `basetts` | 桩基类 (未实现) |
|
||
| **Audio** | `playsound` | 跨平台音频播放 |
|
||
| | `termux` | Android Termux 环境 |
|
||
| **LLM** | `openai` | OpenAI 兼容 API (未完整实现) |
|
||
|
||
选择方式:`services/*.toml` 中的 `provider` 字段.
|
||
|
||
## 谜题系统 (Puzzles)
|
||
|
||
谜题引擎用于在复习阶段生成评估视图:
|
||
|
||
| 谜题 | 文件 | 说明 |
|
||
|------|------|------|
|
||
| **MCQ** | `mcq.py` | 选择题 (Multiple Choice) |
|
||
| **Cloze** | `cloze.py` | 完形填空 (Cloze Deletion) |
|
||
| **Recognition** | `recognition.py` | 认读识别 |
|
||
| **Guess** | `guess.py` | 猜测词义 |
|
||
| **Base** | `base.py` | 抽象基类 |
|
||
|
||
谜题通过轨道策略 (Orbital)在 `Expander` 中按概率展开, 每个原子可产生多个谜题, 每个谜题独立评分.
|
||
|
||
## 服务层
|
||
|
||
| 服务 | 文件 | 说明 |
|
||
|------|------|------|
|
||
| **Config** | `config.py` | `ConfigDict(UserDict)` TOML 懒加载单例 |
|
||
| **Logger** | `logger.py` | `get_logger(name)` → 层级日志 (`heurams.*`) |
|
||
| **Timer** | `timer.py` | `get_daystamp()` / `get_timestamp()`, 支持可配置覆盖 |
|
||
| **Audio** | `audio_service.py` | 音频播放, 路由到配置的音频提供者 |
|
||
| **TTS** | `tts_service.py` | 文本转语音, 路由到配置的 TTS 提供者 |
|
||
| **Favorites** | `favorite_service.py` | JSON5 持久化的收藏管理器 (单例) |
|
||
| **Attic** | `attic.py` | 结构化 pickle 持久化, 支持 `<DAYSTAMP>`/`<TIMESTAMP>` 占位符 |
|
||
| **Hasher** | `hasher.py` | MD5 哈希 |
|
||
| **Epath** | `epath.py` | 点符号嵌套字典访问 (`epath(dct, "a.b.c")`) |
|
||
| **TextProc** | `textproc.py` | `truncate()`, `domize()`, `undomize()` |
|
||
|
||
日志系统:每个模块通过 `get_logger(__name__)` 创建自己的日志器, 日志文件 10MB 轮转, 最多 5 个备份, 追加到 `heurams.log`.
|
||
|
||
## 复习全流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant User as 用户
|
||
participant UI as TUI
|
||
participant Router as Router
|
||
participant Procession as Procession
|
||
participant Expander as Expander
|
||
participant Atom as Atom
|
||
participant Electron as Electron
|
||
participant Algo as Algorithm
|
||
|
||
User->>UI: 开始复习
|
||
UI->>Router: Router(atoms)
|
||
Router->>Procession: 创建复习队列
|
||
Router->>Procession: 创建新记忆队列
|
||
Router->>Procession: 创建总复习队列
|
||
Procession->>Expander: 展开当前原子
|
||
Expander->>Expander: 解析轨道策略
|
||
Expander-->>UI: 展示谜题
|
||
User->>UI: 评分 (1-5)
|
||
UI->>Expander: report(rating)
|
||
Expander->>Expander: forward() 下一个谜题
|
||
Expander-->>UI: 下一个谜题或回溯
|
||
Expander->>Expander: finish() → retronly
|
||
Expander-->>UI: Recognition 回溯
|
||
User->>UI: 最终评分
|
||
UI->>Atom: revise()
|
||
Atom->>Electron: revisor(min_rate)
|
||
Electron->>Algo: revisor(algodata, feedback)
|
||
Algo-->>Electron: 更新 algodata
|
||
Algo-->>Atom: 更新 interval, next_date
|
||
Procession->>Procession: forward() 下一原子
|
||
Procession-->>Router: 队列完成
|
||
Router->>Router: 切换阶段
|
||
Router-->>UI: 完成 (finished)
|
||
UI->>User: 显示总结
|
||
```
|