feat: 更新状态机

This commit is contained in:
2026-01-05 05:25:14 +08:00
parent 65486794b7
commit e1c935f348
19 changed files with 314 additions and 140 deletions

110
README.md
View File

@@ -9,30 +9,72 @@
本仓库在 AGPLv3 下开放源代码(详见 LICENSE 文件)
## 版本日志
- 0.0.x: 简易调度器实现与最小原型.
- 0.1.x: 命令行操作的调度器.
- 0.2.x: 使用 Textual 构建富文本终端用户界面; 项目可行性验证; 采用 SM-2 原始算法, 评估方式为用户自评估原型.
- 0.3.x: 简单的多文件项目; 创建了记忆内容/算法数据结构; 基于 SM-2 改进算法的自动复习测评评估; 重点设计古诗文记忆理解功能; TUI 界面改进; 简单的 TTS 集成.
- 0.4.x: 开发目标转为多用途; 使用模块管理解耦设计; 增加文档与类型标注; 采用上下文设计模式的隐式依赖注入与遵从 IoC, 注册器设计的算法与功能实现; 支持其他调度算法模块 (SM-2, SM-18M 逆向工程实现, FSRS) 与谜题模块; 采用日志调试; 更新文件格式; 引入动态数据模式(宏驱动的动态内容生成), 与基于文件的策略调控; 更佳的用户数据处理; 加入模块化扩展集成; 将算法数据格式换为 json 提高性能; 采用 provider-service 抽象架构, 支持切换服务提供者; 整体兼容性改进.
- 0.5.x: 以仓库(repo)对象作为文件系统与运行时对象间的桥梁, 提高解耦性与性能; 使用具有列表 - 字典 API 同步特性的 "Lict" 对象作为 Repo 数据的内部存储; 将粒子对象作为纯运行时对象, 数据通过引用自动同步至 Repo, 减少负担; 实现声音形式回顾 "电台" 功能; 改进数据存储结构, 实现选择性持久化; 增强可配置性; 使用 Transitions 状态机库重新实现 reactor 状态机, 增强可维护性; 实现整体回顾记忆功能, 与队列式记忆功能并列; 加入状态机快照功能(基于 pickle), 使中断的记忆流程得以恢复; 增加"整体文章引用"功能, 实现从一篇长文本中摘取内容片段记忆并在原文中高亮查看的组织操作.
> 下一步?
> 使用 Flutter 构建酷酷的现代化前端, 增加云同步/文档源服务...
### 0.0.x
- 简易调度器实现与最小原型
### 0.1.x
- 命令行操作的调度器
### 0.2.x
- 使用 Textual 构建富文本终端用户界面
- 项目可行性验证
- 采用 SM-2 原始算法, 评估方式为用户自评估原型
### 0.3.x Frontal 前端
- 简单的多文件项目
- 创建了记忆内容/算法数据结构
- 基于 SM-2 改进算法的自动复习测评评估
- 重点设计古诗文记忆理解功能
- TUI 界面改进
- 简单的 TTS 集成
### 0.4.x Fledge 雏鸟
- 开发目标转为多用途
- 使用模块管理解耦设计
- 增加文档与类型标注
- 采用上下文设计模式的隐式依赖注入与遵从 IoC, 注册器设计的算法与功能实现
- 支持其他调度算法模块 (SM-2, SM-18M 参考理论变体, FSRS) 与谜题模块
- 采用规范的日志调试取代 Textual Devtools 调试
- 更新数据持久化协议规范
- 引入动态数据模式 (宏驱动的动态内容生成) , 与基于文件的策略调控
- 更佳的用户数据处理
- 加入模块化扩展集成
- 更换算法数据格式, 提高性能
- 采用 provider-service 抽象架构, 支持切换服务提供者
- 整体兼容性改进
### 0.5.x Fulcrum 支点
- 以仓库 (repository) 对象作为文件系统与运行时对象间的桥梁, 提高解耦性与性能
- 使用具有列表-字典 API 同步特性的 "Lict" 对象作为 Repo 数据的内部存储
- 将粒子对象作为纯运行时对象, 数据通过引用自动同步至 Repo, 减少负担
- 实现声音形式回顾 "电台" 功能
- 改进数据存储结构, 实现选择性持久化
- 增强可配置性
- 使用 Transitions 状态机库重新实现 Reactor 模块系列状态机, 增强可维护性
- 实现整体回顾记忆功能, 与队列式记忆功能并列
- 加入状态机快照功能 (基于 pickle) , 使中断的记忆流程得以恢复
- 增加 "整体文章引用" 功能, 实现从一篇长文本中摘取内容片段记忆并在原文中高亮查看的组织操作
### 下一步?
- 增加云同步 / 文档源服务
- 使用 Flutter 构建酷酷的现代化前端
- ...
## 特性
### 间隔迭代算法
> 许多出版物都广泛讨论了不同重复间隔对学习效果的影响. 特别是, 间隔效应被认为是一种普遍现象. 间隔效应是指, 如果重复的间隔是分散/稀疏的, 而不是集中重复, 那么学习任务的表现会更好. 因此, 有观点提出, 学习中使用的最佳重复间隔是**最长的但不会导致遗忘的间隔**.
> 许多出版物都广泛讨论了不同重复间隔对学习效果的影响. 特别是, 间隔效应被认为是一种普遍现象. 间隔效应是指, 如果重复的间隔是分散/稀疏的, 而不是集中重复, 那么学习任务的表现会更好. 因此, 有观点提出, 学习中使用的最佳重复间隔是**最长的, 但不会导致遗忘的间隔**.
- 采用经实证的 SM-2 间隔迭代算法, 此算法亦用作 Anki 闪卡记忆软件的默认闪卡调度器
- 动态规划每个记忆单元的记忆间隔时间表
- 动态跟踪记忆反馈数据, 优化长期记忆保留率与稳定性
### 学习进程优化
- 逐字解析: 支持逐字详细释义解析
- 语法分析: 接入生成式人工智能, 支持古文结构交互式解析
- 自然语音: 集成微软神经网络文本转语音 (TTS) 技术
- 多种谜题类型: 选择题 (MCQ)、填空题 (Cloze)、识别题 (Recognition)
- 元数据配置: 支持配置详细附加数据
- 自然语音: 集成文本转语音 (TTS) 功能, 支持"电台"回顾功能
- 多种谜题类型: 选择题 (MCQ), 填空题 (Cloze), 识别题 (Recognition)
- 动态内容生成: 支持宏驱动的模板系统, 根据上下文动态生成题目
- 云同步支持: 通过 WebDAV 协议同步数据到远程服务器
- 云同步支持: 通过多种协议同步数据到远程服务器
### 实用用户界面
- 响应式 Textual 框架构建的跨平台 TUI 界面
@@ -40,10 +82,10 @@
- 简洁直观的复习流程设计
### 架构特性
- 模块化设计: 算法、谜题、服务提供者可插拔替换
- 模块化设计: 算法, 谜题, 服务提供者可插拔替换
- 上下文管理: 使用 ContextVar 实现隐式依赖注入
- 数据持久化: TOML 配置与内容, JSON 算法状态
- 服务抽象: 音频播放TTSLLM 通过 provider 架构支持多种后端
- 服务抽象: 音频播放, TTS, LLM 通过 provider 架构支持多种后端
- 完整日志系统: 带轮转的日志记录, 便于调试
## 安装
@@ -65,43 +107,13 @@
pip install -e .
```
## 使
### 启动应用
## 启动应
```bash
# 在任一目录(建议是空目录或者包根目录, 将被用作存放数据)下运行
python -m heurams.interface
```
### 数据目录结构
应用会在工作目录下创建以下数据目录:
- `data/nucleon/`: 记忆内容 (TOML 格式)
- `data/electron/`: 算法状态 (JSON 格式)
- `data/orbital/`: 策略配置 (TOML 格式)
- `data/cache/`: 音频缓存文件
- `data/template/`: 内容模板
首次运行时会自动创建这些目录.
## 配置
配置文件位于 `config/config.toml`(相对于工作目录). 如果不存在, 会使用内置的默认配置.
### 同步配置
同步功能支持 WebDAV 协议,可在配置文件的 `[sync.webdav]` 段进行配置:
```toml
[sync.webdav]
enabled = false
url = "" # WebDAV 服务器地址
username = "" # 用户名
password = "" # 密码
remote_path = "/heurams/" # 远程路径
sync_mode = "bidirectional" # 同步模式: bidirectional/upload_only/download_only
conflict_strategy = "newer" # 冲突策略: newer/ask/keep_both
verify_ssl = true # SSL 证书验证
```
启用同步后,可通过应用内的同步工具进行数据备份和恢复。
配置文件位于 `./data/config/config.toml`(相对于工作目录). 如果不存在, 会使用内置的默认配置.
## 项目结构
@@ -170,7 +182,7 @@ graph TB
```
src/heurams/
├── __init__.py # 包入口点
├── context.py # 全局上下文、路径、配置上下文管理器
├── context.py # 全局上下文, 路径, 配置上下文管理器
├── services/ # 核心服务
│ ├── config.py # 配置管理
│ ├── logger.py # 日志系统

12
glossary.md Normal file
View File

@@ -0,0 +1,12 @@
# 运行时对象
Atom: 原子, 由核子, 电子, 轨道对象一并构成, 用于处理记忆所需一系列对象
Nucleon: 核子, 负责解析文件动态内容, 并储存记忆材料内容与谜题定义, 是静态只读但可临时覆盖内容的
Electron: 电子, 负责处理记忆算法数据
Orbital: 轨道, 储存记忆阶段信息与谜题阶段内出现配置
# 状态机对象
Transitions: 一种状态机框架库
Reactor: 状态机库
Phaser...
rating: 用户评估生成的值
quality: 用于单元反馈的值

View File

@@ -15,12 +15,7 @@ classifiers = [
"Intended Audience :: Education",
]
keywords = ["spaced-repetition", "memory", "learning", "tui", "textual", "flashcards", "education"]
dependencies = [
"bidict==0.23.1",
"playsound==1.2.2",
"textual==5.3.0",
"toml==0.10.2",
]
readme = "README.md"
[tool.setuptools.packages.find]

View File

@@ -1,6 +1,8 @@
bidict==0.23.1
edge-tts==7.0.2
fsspec==2025.12.0
jieba==0.42.1
playsound==1.2.2
tabulate==0.9.0
textual==5.3.0
toml==0.10.2
requests>=2.31.0
webdavclient3>=3.0.0
transitions==0.9.3

View File

@@ -8,7 +8,7 @@ from textual.reactive import reactive
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Label, Static
import heurams.kernel.evaluators as pz
import heurams.kernel.puzzles as pz
import heurams.kernel.particles as pt
from heurams.context import config_var
from heurams.kernel.reactor import *
@@ -126,12 +126,15 @@ class MemScreen(Screen):
play_by_path(path)
def watch_rating(self, old_rating, new_rating) -> None:
if new_rating == -1: # 安全值
return
self.fission.report(new_rating)
def forward(self, rating):
self.update_state() # 刷新状态
if self.procession == None: # 已经完成记忆
return
if new_rating == -1: # 安全值
return
forwards = 1 if new_rating >= 4 else 0 # 准许前进
forwards = 1 if rating >= 4 else 0 # 准许前进
self.rating = -1
logger.debug(f"试图前进: {"允许" if forwards else "禁止"}")
if forwards:

View File

@@ -1,7 +1,7 @@
"""Kernel 操作辅助函数库"""
import heurams.interface.widgets as pzw
import heurams.kernel.evaluators as pz
import heurams.kernel.puzzles as pz
puzzle2widget = {
pz.RecognitionPuzzle: pzw.Recognition,

View File

@@ -7,7 +7,7 @@ from textual.message import Message
from textual.widget import Widget
from textual.widgets import Button, Label
import heurams.kernel.evaluators as pz
import heurams.kernel.puzzles as pz
import heurams.kernel.particles as pt
from heurams.services.logger import get_logger

View File

@@ -5,7 +5,7 @@ from textual.containers import Container, ScrollableContainer
from textual.widget import Widget
from textual.widgets import Button, Label
import heurams.kernel.evaluators as pz
import heurams.kernel.puzzles as pz
import heurams.kernel.particles as pt
from heurams.services.hasher import hash
from heurams.services.logger import get_logger

View File

@@ -6,13 +6,13 @@ Evaluator 模块 - 生成评估模块
from heurams.services.logger import get_logger
from .base import BaseEvaluator
from .base import BasePuzzle
from .cloze import ClozePuzzle
from .mcq import MCQPuzzle
from .recognition import RecognitionPuzzle
__all__ = [
"BaseEvaluator",
"BasePuzzle",
"ClozePuzzle",
"MCQPuzzle",
"RecognitionPuzzle",
@@ -22,5 +22,5 @@ puzzles = {
"mcq": MCQPuzzle,
"cloze": ClozePuzzle,
"recognition": RecognitionPuzzle,
"base": BaseEvaluator,
"base": BasePuzzle,
}

View File

@@ -4,7 +4,7 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__)
class BaseEvaluator:
class BasePuzzle:
"""谜题基类"""
def refresh(self):

View File

@@ -2,12 +2,12 @@ import random
from heurams.services.logger import get_logger
from .base import BaseEvaluator
from .base import BasePuzzle
logger = get_logger(__name__)
class ClozePuzzle(BaseEvaluator):
class ClozePuzzle(BasePuzzle):
"""填空题谜题生成器
Args:

View File

@@ -2,11 +2,11 @@ import random
from heurams.services.logger import get_logger
from .base import BaseEvaluator
from .base import BasePuzzle
logger = get_logger(__name__)
class GuessEvaluator(BaseEvaluator):
class GuessPuzzle(BasePuzzle):
def __init__(self):
super().__init__()

View File

@@ -4,12 +4,12 @@ from typing import Dict, List, Optional, Union
from heurams.services.logger import get_logger
from .base import BaseEvaluator
from .base import BasePuzzle
logger = get_logger(__name__)
class MCQPuzzle(BaseEvaluator):
class MCQPuzzle(BasePuzzle):
"""选择题谜题生成器
该类用于生成和管理选择题谜题, 支持多个题目同时生成,

View File

@@ -3,12 +3,12 @@ import random
from heurams.services.logger import get_logger
from .base import BaseEvaluator
from .base import BasePuzzle
logger = get_logger(__name__)
class RecognitionPuzzle(BaseEvaluator):
class RecognitionPuzzle(BasePuzzle):
"""识别占位符"""
def __init__(self) -> None:

View File

@@ -1 +1,123 @@
# Reactor - 记忆程状态机模块
# Reactor - 记忆程状态机模块
Reactor 是 HeurAMS 的记忆流程状态机模块, 和界面 (interface) 的实现是解耦的, 以便后期与其他框架的适配.
得益于 Pickle, 状态机模块支持快照!
## Phaser - 全局阶段控制器
在一次队列记忆流程中, Phaser 代表记忆流程本身.
### 属性
#### 状态属性
其有状态属性:
- unsure - 用于初始化
- *quick_review - 复习逾期的单元
- *recognition - 辨识新单元
- *final_review - 复习所有逾期的和新辨认的单元
- finished - 表示完成
> 逾期的: 指 SM-2 算法间隔显示应该复习的单元
带 * 的属性表示实际的记忆阶段, 由 repo 中 schedule.toml 中 schedule 列表显式声明, 运行过程中可以选择性执行, "空的" Procession 会被直接跳过.
在初始化 Procession 时, 每个 Procession 被赋予一个不重复的状态属性 作为"阶段状态"属性, 以此标识 Procession 的阶段属性, 因为每个 Procession 管理一个阶段下的复习进程.
你可以用 state 属性获取 Phaser 的当前状态.
#### Procession 属性
储存一个顺序列表, 保存所有构造的 Procession.
顺序与 repo 中 schedule.toml 中 schedule 列表中的顺序完全相同
### 初始化
Phaser 接受一个存储 Atom 对象的列表, 作为组织记忆流程的材料
在内部, 根据是否激活将其分为 new_atoms 与 old_atoms.
因此, 如果你传入的列表中有算法上"无所事事"的 Atom, 流程会对其进行"加强复习"
由此创建 Procession.
### 直接输出呈现形式
Phaser 的 __repr__ 定义了此对象"官方的显示"用作直观的调试.
其以 ascii 表格形式输出, 格式也符合 markdown 表格规范, 你可以直接复制到 markdown.
示例:
```text
| Type | State | Processions | Current Procession |
|:-------|:--------|:-----------------------|:---------------------|
| Phaser | unsure | ['新记忆', '总体复习'] | 新记忆 |
```
| Type | State | Processions | Current Procession |
|:-------|:--------|:-----------------------|:---------------------|
| Phaser | unsure | ['新记忆', '总体复习'] | 新记忆 |
### 方法
作为一个 Transition Machine 对象的继承, 其拥有 Machine 对象拥有的所有方法.
除此之外, 它也拥有一些其他方法.
#### current_procession(self)
用于查询当前的 Procession, 并且根据当前 Procession 更新自身状态.
返回一个 Procession 对象, 是当前阶段的 Procession.
内部运作是返回第一个状态不为 finished 的 Procession, 并将自身状态变更为 Procession 的"阶段状态"属性
若所有 Procession 都已完成, 将返回一个"阶段状态"为 finished 的 Procession 占位符对象(它不在 procession 属性中), 并更新自身状态为 finished.
## Procession - 阶段管理器
### 属性
#### 状态属性
其有状态属性:
- active - 标识未完成, 初始化的默认属性
- finished - 完成了
#### 其他属性
- current_atom: 当前记忆原子的引用
- atoms: 队列中所有原子列表
- cursor: 指针, 是当前原子在 atoms 列表中的索引
- phase: "阶段属性"
> 注意区分 "Phaser" 和 "Phase", 其中 "Phase" 表示 "Phaser State".
- name_: 阶段的命名
- state: 当前状态属性
### 初始化
接受一个 atoms 列表与 phase_state (PhaserState Enum 类型)对象
### 直接输出呈现形式
同 Phaser, 但显示数据有所不同
与 Phaser 不同, Procession 显示队列会对过长的 atom.ident 进行缩略(末尾 `>` 符号)
```text
| Type | Name | State | Progress | Queue | Current Atom |
|:-----------|:-------|:--------|:-----------|:-----------------------|:------------------------------|
| Procession | 新记忆 | active | 1 / 2 | ['秦孝公>', '君臣固>'] | 秦孝公据崤函之固, 拥雍州之地, |
```
| Type | Name | State | Progress | Queue | Current Atom |
|:-----------|:-------|:--------|:-----------|:-----------------------|:------------------------------|
| Procession | 新记忆 | active | 1 / 2 | ['秦孝公>', '君臣固>'] | 秦孝公据崤函之固, 拥雍州之地, |
### 方法
作为一个 Transition Machine 对象的继承, 其拥有 Machine 对象拥有的所有方法.
除此之外, 它也拥有一些其他方法.
#### forward(self, step=1)
移动 cursor 并依情况更新 current_atom 和状态属性
无论 Procession 是否处于完成状态, forward 操作都是可逆的, 你可以传入负数, 此时已完成的 Procession 会自动"重启".
#### append(self, atom=None)
追加(回忆失败的)原子(默认为当前原子, 传入 None 会自动转化为当前原子)到队列末端
如果这个原子已经处于队列末端, 不会重复追加, 除非队列只剩下这个原子还没完成(此时最多重复两个)
#### process(self)
返回 cursor 值
#### __len__(self)
返回剩余原子量(而不是原子总量)
可以使用 len 函数调用
获取原子总量请用 len(obj.atoms), 或者 total_length(self) 方法
#### total_length(self)
返回队列原子总量
#### is_empty(self)
判断是否为空队列(传入原子列表对象是空列表的队列)
#### get_fission(self)
获取当前原子的 Fission 对象, 用于单原子调度展开
## Fission - 单原子调度控制器
### 属性
#### 状态属性
- exammode: 测试模式(默认)
- retronly: 仅回顾模式
#### 其他属性
- cursor
- atom
- current_puzzle
- orbital_schedule
- orbital_puzzles
- puzzles
### 初始化
接受 atom 对象和 phase 参数
### 方法
#### get_puzzles(self)

View File

@@ -1,30 +1,30 @@
from functools import reduce
import random
import heurams.kernel.evaluators as puz
import heurams.kernel.puzzles as puz
import heurams.kernel.particles as pt
from heurams.services.logger import get_logger
from .states import PhaserState
from transitions import Machine
from .states import FissionState, PhaserState
logger = get_logger(__name__)
class Fission:
class Fission(Machine):
"""单原子调度展开器"""
def __init__(self, atom: pt.Atom, phase_state=PhaserState.RECOGNITION):
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
self.phase = phase
self.cursor = 0
self.logger = get_logger(__name__)
self.atom = atom
# NOTE: phase 为 PhaserState 枚举实例需要获取其value
phase_value = (
phase_state.value if isinstance(phase_state, PhaserState) else phase_state
)
self.orbital_schedule = atom.registry["orbital"]["phases"][phase_value] # type: ignore
self.orbital_puzzles = atom.registry["nucleon"]["puzzles"]
self.current_puzzle: puz.BasePuzzle
# phase 为 PhaserState 枚举实例, 需要获取其value
phase_value = phase.value
orbital_schedule = atom.registry["orbital"]["phases"][phase_value] # type: ignore
orbital_puzzles = atom.registry["nucleon"]["puzzles"]
self.puzzles = list()
for item, possibility in self.orbital_schedule: # type: ignore
self.min_ratings = []
for item, possibility in orbital_schedule: # type: ignore
self.logger.debug(f"开始处理: {item}")
if not isinstance(possibility, float):
possibility = float(possibility)
@@ -32,7 +32,7 @@ class Fission:
while possibility > 1:
self.puzzles.append(
{
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"puzzle": puz.puzzles[orbital_puzzles[item]["__origin__"]],
"alia": item,
}
)
@@ -41,27 +41,55 @@ class Fission:
if random.random() <= possibility:
self.puzzles.append(
{
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"puzzle": puz.puzzles[orbital_puzzles[item]["__origin__"]],
"alia": item,
}
)
states = [
{"name": FissionState.EXAMMODE.value, "on_enter": "on_exammode"},
{"name": FissionState.RETRONLY.value, "on_enter": "on_retronly"},
]
self.logger.debug(f"orbital 项处理完成: {item}")
transitions = [
{
"trigger": "finish",
"source": FissionState.EXAMMODE.value,
"dest": FissionState.RETRONLY.value,
},
]
Machine.__init__(
self,
states=states,
transitions=transitions,
initial="Evaluator_0",
)
def get_puzzles(self):
if self.state == 'retronly':
return [puz.puzzles['recognition']]
return self.puzzles
def get_current_puzzle(self, forward=0):
if forward:
if len(self.puzzles) <= self.cursor + 1:
return 0
self.cursor += 1
return self.puzzles[self.cursor]
else:
return self.puzzles[self.cursor]
def get_current_puzzle(self):
if self.state == 'retronly':
return puz.puzzles['recognition']
return self.current_puzzle
def report(self, rating):
self.min_ratings[self.cursor] = min(rating, self.min_ratings[self.cursor])
def get_quality(self):
if self.is_state("exammode", self):
return reduce(lambda x,y: min(x, y), self.min_ratings)
return -1
def check_passed(self):
for i in self.puzzles:
if i["finished"] == 0:
return 0
return 1
def forward(self, step=1):
"""将谜题指针向前移动并依情况更新或完成"""
logger.debug("Procession.forward: step=%d, 当前 cursor=%d", step, self.cursor)
self.cursor += step
if self.cursor >= len(self.puzzles):
if self.state != 'retronly':
self.finish()
else:
self.current_puzzle = self.puzzles[self.cursor]

View File

@@ -1,3 +1,4 @@
from click import style
import heurams.kernel.particles as pt
from heurams.kernel.particles.placeholders import AtomPlaceholder
from heurams.services.logger import get_logger
@@ -27,7 +28,7 @@ class Phaser(Machine):
logger.debug("新原子数量=%d, 旧原子数量=%d", len(new_atoms), len(old_atoms))
self.processions = list()
# TODO: 改进为基于配置文件的可复习阶段管理
# TODO: 改进为基于配置文件的可复习阶段
if len(old_atoms):
self.processions.append(
Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习")
@@ -111,7 +112,7 @@ class Phaser(Machine):
for i in self.processions:
i: Procession
if i.state != ProcessionState.FINISHED.value:
# 根据当前procession的phase更新Phaser状态
#if i.phase == PhaserState.UNSURE: 此判断是不必要的 因为没有这种 Procession
if i.phase == PhaserState.QUICK_REVIEW:
self.to_quick_review()
elif i.phase == PhaserState.RECOGNITION:
@@ -139,4 +140,4 @@ class Phaser(Machine):
"Current Procession": "None" if not self.current_procession() else self.current_procession().name_, # type: ignore
},
]
return str(tabu(lst, headers="keys")) + "\n"
return str(tabu(tabular_data=lst, headers="keys", tablefmt="pipe")) + "\n"

View File

@@ -21,27 +21,26 @@ class Procession(Machine):
)
self.current_atom: pt.Atom | None
self.atoms = atoms
self.queue = atoms.copy()
self.current_atom = atoms[0] if atoms else None
self.cursor = 0
self.name_ = name_
self.phase = phase_state
states = [
{"name": ProcessionState.RUNNING.value, "on_enter": "on_running"},
{"name": ProcessionState.ACTIVE.value, "on_enter": "on_active"},
{"name": ProcessionState.FINISHED.value, "on_enter": "on_finished"},
]
transitions = [
{
"trigger": "finish",
"source": ProcessionState.RUNNING.value,
"source": ProcessionState.ACTIVE.value,
"dest": ProcessionState.FINISHED.value,
},
{
"trigger": "restart",
"source": ProcessionState.FINISHED.value,
"dest": ProcessionState.RUNNING.value,
"dest": ProcessionState.ACTIVE.value,
},
]
@@ -49,14 +48,14 @@ class Procession(Machine):
self,
states=states,
transitions=transitions,
initial=ProcessionState.RUNNING.value,
initial=ProcessionState.ACTIVE.value,
)
logger.debug("Procession 初始化完成, 队列长度=%d", len(self.queue))
logger.debug("Procession 初始化完成, 队列长度=%d", len(self.atoms))
def on_running(self):
"""进入RUNNING状态时的回调"""
logger.debug("Procession 进入 RUNNING 状态")
def on_active(self):
"""进入active状态时的回调"""
logger.debug("Procession 进入 active 状态")
def on_finished(self):
"""进入FINISHED状态时的回调"""
@@ -66,21 +65,19 @@ class Procession(Machine):
"""将记忆原子指针向前移动并依情况更新原子(返回 1)或完成队列(返回 0)"""
logger.debug("Procession.forward: step=%d, 当前 cursor=%d", step, self.cursor)
self.cursor += step
if self.cursor >= len(self.queue):
if self.cursor >= len(self.atoms):
if self.state != ProcessionState.FINISHED.value:
self.finish() # 触发状态转换
logger.debug("Procession 已完成")
else:
if self.state != ProcessionState.RUNNING.value:
self.restart() # 确保在RUNNING状态
self.current_atom = self.queue[self.cursor]
if self.state != ProcessionState.ACTIVE.value:
self.restart() # 确保在active状态
self.current_atom = self.atoms[self.cursor]
logger.debug("cursor 更新为: %d", self.cursor)
logger.debug(
"当前原子更新为: %s",
self.current_atom.ident if self.current_atom else "None",
)
return 1 # 成功
return 0
def append(self, atom=None):
"""追加(回忆失败的)原子(默认为当前原子)到队列末端"""
@@ -88,16 +85,16 @@ class Procession(Machine):
atom = self.current_atom
logger.debug("Procession.append: atom=%s", atom.ident if atom else "None")
if not self.queue or self.queue[-1] != atom or len(self) <= 1:
self.queue.append(atom)
logger.debug("原子已追加到队列, 新队列长度=%d", len(self.queue))
if not self.atoms or self.atoms[-1] != atom or len(self) <= 1:
self.atoms.append(atom)
logger.debug("原子已追加到队列, 新队列长度=%d", len(self.atoms))
else:
logger.debug("原子未追加(重复或队列长度<=1)")
def __len__(self):
if not self.queue:
if not self.atoms:
return 0
length = len(self.queue) - self.cursor
length = len(self.atoms) - self.cursor
logger.debug("Procession.__len__: 剩余长度=%d", length)
return length
@@ -106,17 +103,17 @@ class Procession(Machine):
return self.cursor
def total_length(self):
total = len(self.queue)
total = len(self.atoms)
logger.debug("Procession.total_length: %d", total)
return total
def is_empty(self):
empty = len(self.queue) == 0
empty = len(self.atoms) == 0
logger.debug("Procession.is_empty: %s", empty)
return empty
def get_fission(self):
return Fission(atom=self.current_atom, phase_state=self.phase) # type: ignore
return Fission(atom=self.current_atom, phase=self.phase) # type: ignore
def __repr__(self):
from heurams.services.textproc import truncate
@@ -126,9 +123,9 @@ class Procession(Machine):
"Type": "Procession",
"Name": self.name_,
"State": self.state,
"Progress": f"{self.cursor + 1} / {len(self.queue)}",
"Queue": list(map(lambda f: truncate(f.ident), self.queue)),
"Progress": f"{self.cursor + 1} / {len(self.atoms)}",
"Queue": list(map(lambda f: truncate(f.ident), self.atoms)),
"Current Atom": self.current_atom.ident, # type: ignore
}
]
return str(tabu(dic, headers="keys")) + "\n"
return str(tabu(dic, headers="keys", tablefmt='pipe')) + "\n"

View File

@@ -12,10 +12,12 @@ class PhaserState(Enum):
FINAL_REVIEW = "final_review"
FINISHED = "finished"
class ProcessionState(Enum):
RUNNING = "running"
ACTIVE = "active"
FINISHED = "finished"
class FissionState(Enum):
EXAMMODE = "exammode"
RETRONLY = "retronly"
logger.debug("状态枚举定义已加载")