style: 代码格式化

This commit is contained in:
2025-12-13 21:47:37 +08:00
parent a0b327cdbb
commit baa7ac8ee9
64 changed files with 755 additions and 573 deletions

View File

@@ -1,10 +1,10 @@
from .sm2 import SM2Algorithm
__all__ = [
'SM2Algorithm',
"SM2Algorithm",
]
algorithms = {
"SM-2": SM2Algorithm,
"supermemo2": SM2Algorithm,
}
}

View File

@@ -1,12 +1,13 @@
import heurams.services.timer as timer
from typing import TypedDict
class BaseAlgorithm:
algo_name = "BaseAlgorithm"
class AlgodataDict(TypedDict):
efactor: float
real_rept: int
real_rept: int
rept: int
interval: int
last_date: int
@@ -15,31 +16,33 @@ class BaseAlgorithm:
last_modify: float
defaults = {
'real_rept': 0,
'rept': 0,
'interval': 0,
'last_date': 0,
'next_date': 0,
'is_activated': 0,
'last_modify': timer.get_timestamp()
"real_rept": 0,
"rept": 0,
"interval": 0,
"last_date": 0,
"next_date": 0,
"is_activated": 0,
"last_modify": timer.get_timestamp(),
}
@classmethod
def revisor(cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False) -> None:
def revisor(
cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False
) -> None:
"""迭代记忆数据"""
pass
@classmethod
def is_due(cls, algodata) -> int:
"""是否应该复习"""
return 1
@classmethod
def rate(cls, algodata) -> str:
"""获取评分信息"""
return ""
@classmethod
def nextdate(cls, algodata) -> int:
"""获取下一次记忆时间戳"""
return -1
return -1

View File

@@ -1 +1 @@
# FSRS 算法模块, 尚未就绪
# FSRS 算法模块, 尚未就绪

View File

@@ -2,12 +2,13 @@ from .base import BaseAlgorithm
import heurams.services.timer as timer
from typing import TypedDict
class SM2Algorithm(BaseAlgorithm):
algo_name = "SM-2"
class AlgodataDict(TypedDict):
efactor: float
real_rept: int
real_rept: int
rept: int
interval: int
last_date: int
@@ -16,66 +17,72 @@ class SM2Algorithm(BaseAlgorithm):
last_modify: float
defaults = {
'efactor': 2.5,
'real_rept': 0,
'rept': 0,
'interval': 0,
'last_date': 0,
'next_date': 0,
'is_activated': 0,
'last_modify': timer.get_timestamp()
"efactor": 2.5,
"real_rept": 0,
"rept": 0,
"interval": 0,
"last_date": 0,
"next_date": 0,
"is_activated": 0,
"last_modify": timer.get_timestamp(),
}
@classmethod
def revisor(cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False):
"""SM-2 算法迭代决策机制实现
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
quality 由主程序评估
Args:
quality (int): 记忆保留率量化参数
@classmethod
def revisor(
cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False
):
"""SM-2 算法迭代决策机制实现
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
quality 由主程序评估
Args:
quality (int): 记忆保留率量化参数
"""
if feedback == -1:
return
algodata[cls.algo_name]['efactor'] = algodata[cls.algo_name]['efactor'] + (
algodata[cls.algo_name]["efactor"] = algodata[cls.algo_name]["efactor"] + (
0.1 - (5 - feedback) * (0.08 + (5 - feedback) * 0.02)
)
algodata[cls.algo_name]['efactor'] = max(1.3, algodata[cls.algo_name]['efactor'])
algodata[cls.algo_name]["efactor"] = max(
1.3, algodata[cls.algo_name]["efactor"]
)
if feedback < 3:
algodata[cls.algo_name]['rept'] = 0
algodata[cls.algo_name]['interval'] = 0
algodata[cls.algo_name]["rept"] = 0
algodata[cls.algo_name]["interval"] = 0
else:
algodata[cls.algo_name]['rept'] += 1
algodata[cls.algo_name]["rept"] += 1
algodata[cls.algo_name]['real_rept'] += 1
algodata[cls.algo_name]["real_rept"] += 1
if is_new_activation:
algodata[cls.algo_name]['rept'] = 0
algodata[cls.algo_name]['efactor'] = 2.5
algodata[cls.algo_name]["rept"] = 0
algodata[cls.algo_name]["efactor"] = 2.5
if algodata[cls.algo_name]['rept'] == 0:
algodata[cls.algo_name]['interval'] = 1
elif algodata[cls.algo_name]['rept'] == 1:
algodata[cls.algo_name]['interval'] = 6
if algodata[cls.algo_name]["rept"] == 0:
algodata[cls.algo_name]["interval"] = 1
elif algodata[cls.algo_name]["rept"] == 1:
algodata[cls.algo_name]["interval"] = 6
else:
algodata[cls.algo_name]['interval'] = round(
algodata[cls.algo_name]['interval'] * algodata[cls.algo_name]['efactor']
algodata[cls.algo_name]["interval"] = round(
algodata[cls.algo_name]["interval"] * algodata[cls.algo_name]["efactor"]
)
algodata[cls.algo_name]['last_date'] = timer.get_daystamp()
algodata[cls.algo_name]['next_date'] = timer.get_daystamp() + algodata[cls.algo_name]['interval']
algodata[cls.algo_name]['last_modify'] = timer.get_timestamp()
algodata[cls.algo_name]["last_date"] = timer.get_daystamp()
algodata[cls.algo_name]["next_date"] = (
timer.get_daystamp() + algodata[cls.algo_name]["interval"]
)
algodata[cls.algo_name]["last_modify"] = timer.get_timestamp()
@classmethod
def is_due(cls, algodata):
return (algodata[cls.algo_name]['next_date'] <= timer.get_daystamp())
return algodata[cls.algo_name]["next_date"] <= timer.get_daystamp()
@classmethod
def rate(cls, algodata):
return str(algodata[cls.algo_name]['efactor'])
return str(algodata[cls.algo_name]["efactor"])
@classmethod
def nextdate(cls, algodata) -> int:
return algodata[cls.algo_name]['next_date']
return algodata[cls.algo_name]["next_date"]

View File

@@ -21,4 +21,4 @@ __all__ = [
"load_nucleon",
"load_electron",
"atom_registry",
]
]

View File

@@ -9,6 +9,7 @@ import json
import bidict
from heurams.context import config_var
class AtomRegister(TypedDict):
nucleon: Nucleon
nucleon_path: pathlib.Path
@@ -21,7 +22,8 @@ class AtomRegister(TypedDict):
orbital_fmt: str
runtime: dict
class Atom():
class Atom:
"""
统一处理一系列对象的所有信息与持久化:
关联电子 (算法数据)
@@ -30,11 +32,11 @@ class Atom():
以及关联路径
"""
def __init__(self, ident = ""):
def __init__(self, ident=""):
self.ident = ident
atom_registry[ident] = self
# self.is_evaled = False
self.registry: AtomRegister = { # type: ignore
self.registry: AtomRegister = { # type: ignore
"nucleon": None,
"nucleon_path": None,
"nucleon_fmt": "toml",
@@ -42,7 +44,7 @@ class Atom():
"electron_path": None,
"electron_fmt": "json",
"orbital": None,
"orbital_path": None, # 允许设置为 None, 此时使用 nucleon 文件内的推荐配置
"orbital_path": None, # 允许设置为 None, 此时使用 nucleon 文件内的推荐配置
"orbital_fmt": "toml",
}
self.do_eval()
@@ -53,16 +55,17 @@ class Atom():
self.do_eval()
else:
raise ValueError("不受支持的原子元数据链接操作")
def do_eval(self):
"""
执行并以结果替换当前单元的所有 eval 语句
TODO: 带有限制的 eval, 异步/多线程执行避免堵塞
"""
# eval 环境设置
def eval_with_env(s: str):
try:
nucleon = self.registry['nucleon']
nucleon = self.registry["nucleon"]
default = config_var.get()["puzzles"]
metadata = nucleon.metadata
except:
@@ -72,7 +75,7 @@ class Atom():
except Exception as e:
ret = f"此 eval 实例发生错误: {e}"
return ret
def traverse(data, modifier):
if isinstance(data, dict):
for key, value in data.items():
@@ -89,10 +92,9 @@ class Atom():
if data.startswith("eval:"):
return modifier(data[5:])
return data
traverse(self.registry["nucleon"], eval_with_env)
traverse(self.registry["orbital"], eval_with_env)
def persist(self, key):
path: pathlib.Path | None = self.registry[key + "_path"]
@@ -109,7 +111,7 @@ class Atom():
raise KeyError("不受支持的持久化格式")
else:
raise TypeError("对未初始化的路径对象操作")
def __getitem__(self, key):
if key in self.registry:
return self.registry[key]
@@ -124,5 +126,6 @@ class Atom():
@staticmethod
def placeholder():
return (Electron.placeholder(), Nucleon.placeholder(), {})
atom_registry: bidict.bidict[str, Atom] = bidict.bidict()

View File

@@ -2,12 +2,13 @@ import heurams.services.timer as timer
from heurams.context import config_var
from heurams.kernel.algorithms import algorithms
class Electron:
"""电子: 记忆分析元数据及算法"""
def __init__(self, ident: str, algodata: dict = {}, algo_name: str = "supermemo2"):
"""初始化电子对象 (记忆数据)
Args:
ident: 算法的唯一标识符, 用于区分不同的算法实例, 使用 algodata[ident] 获取
algodata: 算法数据字典, 包含算法的各项参数和设置
@@ -28,34 +29,34 @@ class Electron:
def activate(self):
"""激活此电子"""
self.algodata[self.algo]['is_activated'] = 1
self.algodata[self.algo]['last_modify'] = timer.get_timestamp()
self.algodata[self.algo]["is_activated"] = 1
self.algodata[self.algo]["last_modify"] = timer.get_timestamp()
def modify(self, var: str, value):
"""修改 algodata[algo] 中子字典数据"""
if var in self.algodata[self.algo]:
self.algodata[self.algo][var] = value
self.algodata[self.algo]['last_modify'] = timer.get_timestamp()
self.algodata[self.algo]["last_modify"] = timer.get_timestamp()
else:
print(f"警告: '{var}' 非已知元数据字段")
def is_due(self):
"""是否应该复习"""
return self.algo.is_due(self.algodata)
def is_activated(self):
return self.algodata[self.algo]['is_activated']
return self.algodata[self.algo]["is_activated"]
def rate(self):
"评价"
return self.algo.rate(self.algodata)
def nextdate(self) -> int:
return self.algo.nextdate(self.algodata)
def revisor(self, quality: int = 5, is_new_activation: bool = False):
"""算法迭代决策机制实现
Args:
quality (int): 记忆保留率量化参数 (0-5)
is_new_activation (bool): 是否为初次激活
@@ -93,7 +94,7 @@ class Electron:
if key == "ident":
raise AttributeError("ident 应为只读")
self.algodata[self.algo][key] = value
self.algodata[self.algo]['last_modify'] = timer.get_timestamp()
self.algodata[self.algo]["last_modify"] = timer.get_timestamp()
def __len__(self):
"""仅返回当前算法的配置数量"""
@@ -102,4 +103,4 @@ class Electron:
@staticmethod
def placeholder():
"""生成一个电子占位符"""
return Electron("电子对象样例内容", {})
return Electron("电子对象样例内容", {})

View File

@@ -6,17 +6,18 @@ import toml
import json
from copy import deepcopy
def load_nucleon(path: pathlib.Path, fmt = "toml"):
def load_nucleon(path: pathlib.Path, fmt="toml"):
with open(path, "r") as f:
dictdata = dict()
dictdata = toml.load(f) # type: ignore
dictdata = toml.load(f) # type: ignore
lst = list()
nested_data = dict()
# 修正 toml 解析器的不管嵌套行为
for key, value in dictdata.items():
if "__metadata__" in key: # 以免影响句号
if '.' in key:
parts = key.split('.')
if "__metadata__" in key: # 以免影响句号
if "." in key:
parts = key.split(".")
current = nested_data
for part in parts[:-1]:
if part not in current:
@@ -29,23 +30,31 @@ def load_nucleon(path: pathlib.Path, fmt = "toml"):
for item, attr in nested_data.items():
if item == "__metadata__":
continue
lst.append((Nucleon(hasher.hash(item), attr, deepcopy(nested_data['__metadata__'])), deepcopy(nested_data["__metadata__"]["orbital"])))
lst.append(
(
Nucleon(
hasher.hash(item), attr, deepcopy(nested_data["__metadata__"])
),
deepcopy(nested_data["__metadata__"]["orbital"]),
)
)
return lst
def load_electron(path: pathlib.Path, fmt = "json") -> dict:
def load_electron(path: pathlib.Path, fmt="json") -> dict:
"""从文件路径加载电子对象
Args:
path (pathlib.Path): 路径
fmt (str): 文件格式(可选, 默认 json)
Returns:
dict: 键名是电子对象名称, 值是电子对象
"""
with open(path, "r") as f:
dictdata = dict()
dictdata = json.load(f) # type: ignore
dictdata = json.load(f) # type: ignore
dic = dict()
for item, attr in dictdata.items():
dic[item] = (Electron(hasher.hash(item), attr))
return dic
dic[item] = Electron(hasher.hash(item), attr)
return dic

View File

@@ -29,12 +29,13 @@ class Nucleon:
def __hash__(self):
return hash(self.ident)
def do_eval(self):
"""
执行并以结果替换当前单元的所有 eval 语句
TODO: 带有限制的 eval, 异步/多线程执行避免堵塞
"""
# eval 环境设置
def eval_with_env(s: str):
try:
@@ -43,7 +44,7 @@ class Nucleon:
except Exception as e:
ret = f"此 eval 实例发生错误: {e}"
return ret
def traverse(data, modifier):
if isinstance(data, dict):
for key, value in data.items():
@@ -60,9 +61,10 @@ class Nucleon:
if data.startswith("eval:"):
return modifier(data[5:])
return data
traverse(self.payload, eval_with_env)
traverse(self.metadata, eval_with_env)
@staticmethod
def placeholder():
"""生成一个占位原子核"""

View File

@@ -1,14 +1,16 @@
from typing import TypedDict
class OrbitalSchedule(TypedDict):
quick_review: list
recognition: list
final_review: list
class Orbital(TypedDict):
schedule: OrbitalSchedule
puzzles: dict
"""一份示例
["__metadata__.orbital.puzzles"] # 谜题定义
@@ -20,4 +22,4 @@ class Orbital(TypedDict):
quick_review = [["FillBlank", "1.0"], ["SelectMeaning", "0.5"], ["recognition", "1.0"]]
recognition = [["recognition", "1.0"]]
final_review = [["FillBlank", "0.7"], ["SelectMeaning", "0.7"], ["recognition", "1.0"]]
"""
"""

View File

@@ -1,6 +1,7 @@
from heurams.context import config_var
import pathlib
def probe_by_filename(filename):
"""探测指定文件 (无扩展名) 的所有信息"""
paths: dict = config_var.get().get("paths")
@@ -8,17 +9,18 @@ def probe_by_filename(filename):
result = {}
for item, attr in paths.items():
for i in formats:
attr: pathlib.Path = pathlib.Path(attr) / filename + '.' + i
attr: pathlib.Path = pathlib.Path(attr) / filename + "." + i
if attr.exists():
result[item.replace("_dir", "")] = str(attr)
return result
def probe_all(is_stem = 1):
def probe_all(is_stem=1):
"""依据目录探测所有信息
Args:
is_stem (boolean): 是否**删除**文件扩展名
Returns:
dict: 有三项, 每一项的键名都是文件组类型, 值都是文件组列表, 只包含文件名
"""
@@ -35,7 +37,9 @@ def probe_all(is_stem = 1):
result[item.replace("_dir", "")].append(str(i.name))
return result
if __name__ == "__main__":
import os
print(os.getcwd())
print(probe_all())

View File

@@ -10,10 +10,10 @@ from .mcq import MCQPuzzle
from .recognition import RecognitionPuzzle
__all__ = [
'BasePuzzle',
'ClozePuzzle',
'MCQPuzzle',
'RecognitionPuzzle',
"BasePuzzle",
"ClozePuzzle",
"MCQPuzzle",
"RecognitionPuzzle",
]
puzzles = {
@@ -23,6 +23,7 @@ puzzles = {
"base": BasePuzzle,
}
@staticmethod
def create_by_dict(config_dict: dict) -> BasePuzzle:
"""
@@ -37,19 +38,19 @@ def create_by_dict(config_dict: dict) -> BasePuzzle:
Raises:
ValueError: 当配置无效时抛出
"""
puzzle_type = config_dict.get('type')
puzzle_type = config_dict.get("type")
if puzzle_type == 'cloze':
if puzzle_type == "cloze":
return puzzles["cloze"](
text=config_dict['text'],
min_denominator=config_dict.get('min_denominator', 7)
text=config_dict["text"],
min_denominator=config_dict.get("min_denominator", 7),
)
elif puzzle_type == 'mcq':
elif puzzle_type == "mcq":
return puzzles["mcq"](
mapping=config_dict['mapping'],
jammer=config_dict.get('jammer', []),
max_riddles_num=config_dict.get('max_riddles_num', 2),
prefix=config_dict.get('prefix', '')
mapping=config_dict["mapping"],
jammer=config_dict.get("jammer", []),
max_riddles_num=config_dict.get("max_riddles_num", 2),
prefix=config_dict.get("prefix", ""),
)
else:
raise ValueError(f"未知的谜题类型: {puzzle_type}")
raise ValueError(f"未知的谜题类型: {puzzle_type}")

View File

@@ -1,9 +1,9 @@
# base.py
class BasePuzzle:
"""谜题基类"""
def refresh(self):
raise NotImplementedError("谜题对象未实现 refresh 方法")
def __str__(self):
return f"谜题: {type(self).__name__}"
return f"谜题: {type(self).__name__}"

View File

@@ -1,9 +1,10 @@
from .base import BasePuzzle
import random
class ClozePuzzle(BasePuzzle):
"""填空题谜题生成器
Args:
text: 原始字符串(需要 delimiter 分割句子, 末尾应有 delimiter)
min_denominator: 最小概率倒数(如占所有可生成填空数的 1/7 中的 7, 若期望值小于 1, 则取 1)

View File

@@ -2,15 +2,12 @@
from .base import BasePuzzle
import random
class MCQPuzzle(BasePuzzle):
"""选择题谜题生成器"""
def __init__(
self,
mapping: dict,
jammer: list,
max_riddles_num: int = 2,
prefix: str = ""
self, mapping: dict, jammer: list, max_riddles_num: int = 2, prefix: str = ""
):
self.prefix = prefix
self.mapping = mapping
@@ -29,18 +26,16 @@ class MCQPuzzle(BasePuzzle):
self.answer = ["无答案"]
self.options = []
return
num_questions = min(self.max_riddles_num, len(self.mapping))
questions = random.sample(list(self.mapping.items()), num_questions)
puzzles = []
answers = []
all_options = []
for question, correct_answer in questions:
options = [correct_answer]
available_jammers = [
j for j in self.jammer if j != correct_answer
]
available_jammers = [j for j in self.jammer if j != correct_answer]
if len(available_jammers) >= 3:
selected_jammers = random.sample(available_jammers, 3)
else:
@@ -50,14 +45,14 @@ class MCQPuzzle(BasePuzzle):
puzzles.append(question)
answers.append(correct_answer)
all_options.append(options)
question_texts = []
for i, puzzle in enumerate(puzzles):
question_texts.append(f"{self.prefix}:\n {i+1}. {puzzle}")
self.wording = question_texts
self.answer = answers
self.options = all_options
def __str__(self):
return f"{self.wording}\n正确答案: {', '.join(self.answer)}"
return f"{self.wording}\n正确答案: {', '.join(self.answer)}"

View File

@@ -2,11 +2,12 @@
from .base import BasePuzzle
import random
class RecognitionPuzzle(BasePuzzle):
"""识别占位符"""
def __init__(self) -> None:
super().__init__()
def refresh(self):
pass
pass

View File

@@ -3,10 +3,4 @@ from .procession import Procession
from .fission import Fission
from .phaser import Phaser
__all__ = [
"PhaserState",
"ProcessionState",
"Procession",
"Fission",
"Phaser"
]
__all__ = ["PhaserState", "ProcessionState", "Procession", "Fission", "Phaser"]

View File

@@ -3,27 +3,34 @@ import heurams.kernel.puzzles as puz
import random
from .states import PhaserState
class Fission():
class Fission:
"""裂变器: 单原子调度展开器"""
def __init__(self, atom: pt.Atom, phase = PhaserState.RECOGNITION):
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
self.atom = atom
self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore
self.orbital_puzzles = atom.registry["orbital"]["puzzles"]
#print(self.orbital_schedule)
self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore
self.orbital_puzzles = atom.registry["orbital"]["puzzles"]
# print(self.orbital_schedule)
self.puzzles = list()
for item, possibility in self.orbital_schedule: # type: ignore
for item, possibility in self.orbital_schedule: # type: ignore
if not isinstance(possibility, float):
possibility = float(possibility)
while possibility > 1:
self.puzzles.append({
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"alia": item
})
self.puzzles.append(
{
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"alia": item,
}
)
possibility -= 1
if random.random() <= possibility:
self.puzzles.append({
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"alia": item
})
self.puzzles.append(
{
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"alia": item,
}
)
def generate(self):
yield from self.puzzles

View File

@@ -4,8 +4,10 @@ import heurams.kernel.particles as pt
from .states import PhaserState, ProcessionState
from .procession import Procession
class Phaser():
class Phaser:
"""移相器: 全局调度阶段管理器"""
def __init__(self, atoms: list[pt.Atom]) -> None:
new_atoms = list()
old_atoms = list()
@@ -17,10 +19,14 @@ class Phaser():
old_atoms.append(i)
self.processions = list()
if len(old_atoms):
self.processions.append(Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习"))
self.processions.append(
Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习")
)
if len(new_atoms):
self.processions.append(Procession(new_atoms,PhaserState.RECOGNITION, "新记忆"))
self.processions.append(Procession(atoms,PhaserState.FINAL_REVIEW, "总体复习"))
self.processions.append(
Procession(new_atoms, PhaserState.RECOGNITION, "新记忆")
)
self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习"))
def current_procession(self):
for i in self.processions:
@@ -29,4 +35,4 @@ class Phaser():
self.state = i.phase
return i
self.state = PhaserState.FINISHED
return 0
return 0

View File

@@ -1,8 +1,10 @@
import heurams.kernel.particles as pt
from .states import PhaserState, ProcessionState
class Procession():
class Procession:
"""队列: 标识单次记忆流程"""
def __init__(self, atoms: list, phase: PhaserState, name: str = ""):
self.atoms = atoms
self.queue = atoms.copy()
@@ -12,7 +14,7 @@ class Procession():
self.phase = phase
self.state: ProcessionState = ProcessionState.RUNNING
def forward(self, step = 1):
def forward(self, step=1):
self.cursor += step
if self.cursor == len(self.queue):
self.state = ProcessionState.FINISHED
@@ -20,22 +22,22 @@ class Procession():
self.state = ProcessionState.RUNNING
try:
self.current_atom = self.queue[self.cursor]
return 1 # 成功
return 1 # 成功
except IndexError as e:
print(f"{e}")
return 0
def append(self, atom = None):
def append(self, atom=None):
if atom == None:
atom = self.current_atom
if self.queue[len(self.queue) - 1] != atom or len(self) <= 1:
self.queue.append(atom)
self.queue.append(atom)
def __len__(self):
return (len(self.queue) - self.cursor)
return len(self.queue) - self.cursor
def process(self):
return (self.cursor)
return self.cursor
def total_length(self):
return len(self.queue)

View File

@@ -1,5 +1,6 @@
from enum import Enum, auto
class PhaserState(Enum):
UNSURE = "unsure"
QUICK_REVIEW = "quick_review"
@@ -7,6 +8,7 @@ class PhaserState(Enum):
FINAL_REVIEW = "final_review"
FINISHED = "finished"
class ProcessionState(Enum):
RUNNING = auto()
FINISHED = auto()
FINISHED = auto()