Archived
0
0
This commit is contained in:
2025-12-16 21:28:53 +08:00
parent 11d130c3fd
commit 1e534e5fe5
37 changed files with 428 additions and 207 deletions

View File

@@ -2,5 +2,6 @@ print("欢迎使用 HeurAMS 及其组件!")
# 补充日志记录 # 补充日志记录
from heurams.services.logger import get_logger from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
logger.info("欢迎使用 HeurAMS 及其组件!") logger.info("欢迎使用 HeurAMS 及其组件!")

View File

@@ -8,6 +8,7 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class HeurAMSApp(App): class HeurAMSApp(App):
TITLE = "潜进" TITLE = "潜进"
CSS_PATH = "css/main.tcss" CSS_PATH = "css/main.tcss"
@@ -37,8 +38,10 @@ class HeurAMSApp(App):
print("DO NOTHING") print("DO NOTHING")
self.refresh() self.refresh()
def environment_check(): def environment_check():
from pathlib import Path from pathlib import Path
logger.debug("检查环境路径") logger.debug("检查环境路径")
for i in config_var.get()["paths"].values(): for i in config_var.get()["paths"].values():

View File

@@ -34,12 +34,12 @@ class AboutScreen(Screen):
开发人员: 开发人员:
- [@pluvium27](https://github.com/pluvium27) (Wang Zhiyu) - Wang Zhiyu([@pluvium27](https://github.com/pluvium27)): 项目作者
特别感谢: 特别感谢:
- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SuperMemo-2 算法 - [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SuperMemo-2 算法
- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 文献参考 - [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 文献参考
# 参与贡献 # 参与贡献
@@ -47,7 +47,7 @@ class AboutScreen(Screen):
通过我们协力开发的软件为所有人谋取福祉. 通过我们协力开发的软件为所有人谋取福祉.
此项目不是 KDE 软件, 但上述工作不可避免地让我们确立了和 KDE 宣言相同的下列价值观: 上述工作不可避免地让我们确立了下列价值观 (取自 KDE 宣言):
- 开放治理 确保更多人能参与我们的领导和决策进程; - 开放治理 确保更多人能参与我们的领导和决策进程;
@@ -68,8 +68,13 @@ class AboutScreen(Screen):
我们的共同目标是为人人带来高品质的辅助记忆 & 学习软件. 我们的共同目标是为人人带来高品质的辅助记忆 & 学习软件.
不管您来自何方, 我们都欢迎您加入社区并做出贡献. 不管您来自何方, 我们都欢迎您加入社区并做出贡献.
""" """
# """
# 学术数据
# "潜进" 的用户数据可用于科学方面的研究, 我们将在未来版本添加学术数据的收集和展示平台
# """
yield Markdown(about_text, classes="about-markdown") yield Markdown(about_text, classes="about-markdown")
yield Button( yield Button(

View File

@@ -24,8 +24,10 @@ import pathlib
logger = get_logger(__name__) logger = get_logger(__name__)
class DashboardScreen(Screen): class DashboardScreen(Screen):
SUB_TITLE = "仪表盘" SUB_TITLE = "仪表盘"
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
yield ScrollableContainer( yield ScrollableContainer(
@@ -71,6 +73,7 @@ class DashboardScreen(Screen):
nextdate = 0x3F3F3F3F nextdate = 0x3F3F3F3F
for i in electron_dict.values(): for i in electron_dict.values():
i: pt.Electron i: pt.Electron
logger.debug(i, i.is_due())
if i.is_due(): if i.is_due():
is_due = 1 is_due = 1
if i.is_activated(): if i.is_activated():

View File

@@ -18,8 +18,10 @@ class AtomState(Enum):
FAILED = auto() FAILED = auto()
NORMAL = auto() NORMAL = auto()
logger = get_logger(__name__) logger = get_logger(__name__)
class MemScreen(Screen): class MemScreen(Screen):
BINDINGS = [ BINDINGS = [
("q", "pop_screen", "返回"), ("q", "pop_screen", "返回"),
@@ -45,6 +47,7 @@ class MemScreen(Screen):
self.phaser = Phaser(atoms) self.phaser = Phaser(atoms)
# logger.debug(self.phaser.state) # logger.debug(self.phaser.state)
self.procession: Procession = self.phaser.current_procession() # type: ignore self.procession: Procession = self.phaser.current_procession() # type: ignore
self.atom: pt.Atom = self.procession.current_atom
# logger.debug(self.phaser.state) # logger.debug(self.phaser.state)
# self.procession.forward(1) # self.procession.forward(1)
for i in atoms: for i in atoms:
@@ -58,12 +61,12 @@ class MemScreen(Screen):
try: try:
logger.debug(self.phaser.state) logger.debug(self.phaser.state)
logger.debug(self.procession.cursor) logger.debug(self.procession.cursor)
logger.debug(self.procession.current_atom) logger.debug(self.atom)
self.fission = Fission(self.procession.current_atom, self.phaser.state) self.fission = Fission(self.atom, self.phaser.state)
puzzle_debug = next(self.fission.generate()) puzzle_debug = next(self.fission.generate())
#logger.debug(puzzle_debug) # logger.debug(puzzle_debug)
return shim.puzzle2widget[puzzle_debug["puzzle"]]( return shim.puzzle2widget[puzzle_debug["puzzle"]](
atom=self.procession.current_atom, alia=puzzle_debug["alia"] atom=self.atom, alia=puzzle_debug["alia"]
) )
except (KeyError, StopIteration, AttributeError) as e: except (KeyError, StopIteration, AttributeError) as e:
logger.debug(f"调度展开出错: {e}") logger.debug(f"调度展开出错: {e}")
@@ -98,6 +101,7 @@ class MemScreen(Screen):
for i in container.children: for i in container.children:
i.remove() i.remove()
from heurams.interface.widgets.finished import Finished from heurams.interface.widgets.finished import Finished
container.mount(Finished()) container.mount(Finished())
def on_button_pressed(self, event): def on_button_pressed(self, event):
@@ -108,11 +112,12 @@ class MemScreen(Screen):
return return
forwards = 1 if new_rating >= 4 else 0 forwards = 1 if new_rating >= 4 else 0
self.rating = -1 self.rating = -1
logger.debug(f"试图前进: {"允许" if forwards else "禁止"}")
if forwards: if forwards:
ret = self.procession.forward(1) ret = self.procession.forward(1)
if ret == 0: if ret == 0: # 若结束了此次队列
self.procession = self.phaser.current_procession() # type: ignore self.procession = self.phaser.current_procession() # type: ignore
if self.procession == 0: if self.procession == 0: # 若所有队列都结束了
logger.debug(f"记忆进程结束") logger.debug(f"记忆进程结束")
for i in self.atoms: for i in self.atoms:
i: pt.Atom i: pt.Atom
@@ -121,10 +126,10 @@ class MemScreen(Screen):
return return
else: else:
logger.debug(f"建立新队列 {self.procession.phase}") logger.debug(f"建立新队列 {self.procession.phase}")
else: self.load_puzzle()
else: # 若不通过
self.procession.append() self.procession.append()
self.update_display() self.update_display()
self.load_puzzle()
def action_play_voice(self): def action_play_voice(self):
"""朗读当前内容""" """朗读当前内容"""

View File

@@ -123,7 +123,8 @@ class NucleonCreatorScreen(Screen):
# selected 是描述字符串,格式如 "描述 (filename.toml)" # selected 是描述字符串,格式如 "描述 (filename.toml)"
# 提取文件名 # 提取文件名
import re import re
match = re.search(r'\(([^)]+)\)$', selected)
match = re.search(r"\(([^)]+)\)$", selected)
if not match: if not match:
self.notify("模板选择格式无效", severity="error") self.notify("模板选择格式无效", severity="error")
return return
@@ -135,7 +136,7 @@ class NucleonCreatorScreen(Screen):
# 加载模板 # 加载模板
try: try:
with open(template_path, 'r', encoding='utf-8') as f: with open(template_path, "r", encoding="utf-8") as f:
template_data = toml.load(f) template_data = toml.load(f)
except Exception as e: except Exception as e:
self.notify(f"加载模板失败: {e}", severity="error") self.notify(f"加载模板失败: {e}", severity="error")
@@ -159,7 +160,7 @@ class NucleonCreatorScreen(Screen):
# 写入新文件 # 写入新文件
try: try:
with open(nucleon_path, 'w', encoding='utf-8') as f: with open(nucleon_path, "w", encoding="utf-8") as f:
toml.dump(template_data, f) toml.dump(template_data, f)
except Exception as e: except Exception as e:
self.notify(f"保存单元集失败: {e}", severity="error") self.notify(f"保存单元集失败: {e}", severity="error")

View File

@@ -70,7 +70,6 @@ class PreparationScreen(Screen):
def action_go_back(self): def action_go_back(self):
self.app.pop_screen() self.app.pop_screen()
def action_precache(self): def action_precache(self):
from ..screens.precache import PrecachingScreen from ..screens.precache import PrecachingScreen

View File

@@ -1,3 +1,5 @@
from typing import Iterable
from textual.app import ComposeResult
from textual.widget import Widget from textual.widget import Widget
import heurams.kernel.particles as pt import heurams.kernel.particles as pt
@@ -22,3 +24,9 @@ class BasePuzzleWidget(Widget):
markup=markup markup=markup
) )
self.atom = atom self.atom = atom
def compose(self) -> Iterable[Widget]:
return super().compose()
def handler(self, rating) -> None:
pass

View File

@@ -15,6 +15,7 @@ from typing import TypedDict
logger = get_logger(__name__) logger = get_logger(__name__)
class Setting(TypedDict): class Setting(TypedDict):
__origin__: str __origin__: str
__hint__: str __hint__: str
@@ -22,6 +23,7 @@ class Setting(TypedDict):
delimiter: str delimiter: str
min_denominator: str min_denominator: str
class ClozePuzzle(BasePuzzleWidget): class ClozePuzzle(BasePuzzleWidget):
def __init__( def __init__(
@@ -78,7 +80,6 @@ class ClozePuzzle(BasePuzzleWidget):
preview = self.query_one("#inputpreview") preview = self.query_one("#inputpreview")
preview.update(f"当前输入: {self.inputlist}") # type: ignore preview.update(f"当前输入: {self.inputlist}") # type: ignore
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
button_id = event.button.id button_id = event.button.id
@@ -94,9 +95,15 @@ class ClozePuzzle(BasePuzzleWidget):
if len(self.inputlist) >= len(self.puzzle.answer): if len(self.inputlist) >= len(self.puzzle.answer):
is_correct = self.inputlist == self.puzzle.answer is_correct = self.inputlist == self.puzzle.answer
rating = 4 if is_correct else 2 rating = 4 if is_correct else 2
self.handler(rating)
self.screen.rating = rating # type: ignore self.screen.rating = rating # type: ignore
if not is_correct: if not is_correct:
self.inputlist = [] self.inputlist = []
self.update_display() self.update_display()
def handler(self, rating):
if self.atom.lock():
pass
else:
self.atom.minimize(rating)

View File

@@ -14,6 +14,7 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class Setting(TypedDict): class Setting(TypedDict):
__origin__: str __origin__: str
__hint__: str __hint__: str
@@ -49,6 +50,7 @@ class MCQPuzzle(BasePuzzleWidget):
self.alia = alia self.alia = alia
self.hashmap = dict() self.hashmap = dict()
self.cursor = 0 self.cursor = 0
self.atom = atom
self._load() self._load()
def _load(self): def _load(self):
@@ -79,7 +81,7 @@ class MCQPuzzle(BasePuzzleWidget):
yield Button("退格", id="delete") yield Button("退格", id="delete")
def update_display(self, error = 0): def update_display(self, error=0):
# 更新预览标签 # 更新预览标签
preview = self.query_one("#inputpreview") preview = self.query_one("#inputpreview")
preview.update(f"当前输入: {self.inputlist}") # type: ignore preview.update(f"当前输入: {self.inputlist}") # type: ignore
@@ -113,7 +115,7 @@ class MCQPuzzle(BasePuzzleWidget):
rating = 4 if is_correct else 2 rating = 4 if is_correct else 2
self.screen.rating = rating # type: ignore self.screen.rating = rating # type: ignore
self.handler(rating)
# 重置输入(如果回答错误) # 重置输入(如果回答错误)
if not is_correct: if not is_correct:
self.inputlist = [] self.inputlist = []
@@ -138,7 +140,7 @@ class MCQPuzzle(BasePuzzleWidget):
for button in buttons_to_remove: for button in buttons_to_remove:
logger.info(button) logger.info(button)
container.remove_children("#"+button.id) # type: ignore container.remove_children("#" + button.id) # type: ignore
# 添加当前题目的选项按钮 # 添加当前题目的选项按钮
current_question_index = len(self.inputlist) current_question_index = len(self.inputlist)
@@ -150,3 +152,10 @@ class MCQPuzzle(BasePuzzleWidget):
self.hashmap[button_id] = option self.hashmap[button_id] = option
new_button = Button(option, id=button_id) new_button = Button(option, id=button_id)
container.mount(new_button) container.mount(new_button)
def handler(self, rating):
if self.atom.lock():
pass
else:
self.atom.minimize(rating)

View File

@@ -13,7 +13,9 @@ import re
from .base_puzzle_widget import BasePuzzleWidget from .base_puzzle_widget import BasePuzzleWidget
from typing import TypedDict, List from typing import TypedDict, List
from textual.message import Message from textual.message import Message
from heurams.services.logger import get_logger
logger = get_logger(__name__)
class RecognitionConfig(TypedDict): class RecognitionConfig(TypedDict):
__origin__: str __origin__: str
@@ -100,3 +102,11 @@ class Recognition(BasePuzzleWidget):
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "ok": if event.button.id == "ok":
self.screen.rating = 5 # type: ignore self.screen.rating = 5 # type: ignore
self.handler(5)
def handler(self, rating):
if not self.atom.registry["electron"].is_activated() and not self.atom.registry["runtime"]["locked"]:
self.atom.registry["electron"].activate()
logger.debug(f"激活原子 {self.atom}")
self.atom.lock(1)
self.atom.minimize(5)

View File

@@ -33,27 +33,37 @@ class BaseAlgorithm:
cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False
) -> None: ) -> None:
"""迭代记忆数据""" """迭代记忆数据"""
logger.debug("BaseAlgorithm.revisor 被调用algodata keys: %s, feedback: %d, is_new_activation: %s", logger.debug(
list(algodata.keys()) if algodata else [], feedback, is_new_activation) "BaseAlgorithm.revisor 被调用algodata keys: %s, feedback: %d, is_new_activation: %s",
list(algodata.keys()) if algodata else [],
feedback,
is_new_activation,
)
pass pass
@classmethod @classmethod
def is_due(cls, algodata) -> int: def is_due(cls, algodata) -> int:
"""是否应该复习""" """是否应该复习"""
logger.debug("BaseAlgorithm.is_due 被调用algodata keys: %s", logger.debug(
list(algodata.keys()) if algodata else []) "BaseAlgorithm.is_due 被调用algodata keys: %s",
list(algodata.keys()) if algodata else [],
)
return 1 return 1
@classmethod @classmethod
def rate(cls, algodata) -> str: def rate(cls, algodata) -> str:
"""获取评分信息""" """获取评分信息"""
logger.debug("BaseAlgorithm.rate 被调用algodata keys: %s", logger.debug(
list(algodata.keys()) if algodata else []) "BaseAlgorithm.rate 被调用algodata keys: %s",
list(algodata.keys()) if algodata else [],
)
return "" return ""
@classmethod @classmethod
def nextdate(cls, algodata) -> int: def nextdate(cls, algodata) -> int:
"""获取下一次记忆时间戳""" """获取下一次记忆时间戳"""
logger.debug("BaseAlgorithm.nextdate 被调用algodata keys: %s", logger.debug(
list(algodata.keys()) if algodata else []) "BaseAlgorithm.nextdate 被调用algodata keys: %s",
list(algodata.keys()) if algodata else [],
)
return -1 return -1

View File

@@ -41,7 +41,11 @@ class SM2Algorithm(BaseAlgorithm):
Args: Args:
quality (int): 记忆保留率量化参数 quality (int): 记忆保留率量化参数
""" """
logger.debug("SM2.revisor 开始feedback: %d, is_new_activation: %s", feedback, is_new_activation) logger.debug(
"SM2.revisor 开始feedback: %d, is_new_activation: %s",
feedback,
is_new_activation,
)
if feedback == -1: if feedback == -1:
logger.debug("feedback 为 -1跳过更新") logger.debug("feedback 为 -1跳过更新")
@@ -81,7 +85,9 @@ class SM2Algorithm(BaseAlgorithm):
algodata[cls.algo_name]["interval"] = round( algodata[cls.algo_name]["interval"] = round(
algodata[cls.algo_name]["interval"] * algodata[cls.algo_name]["efactor"] algodata[cls.algo_name]["interval"] * algodata[cls.algo_name]["efactor"]
) )
logger.debug("rept>1计算 interval: %d", algodata[cls.algo_name]["interval"]) logger.debug(
"rept>1计算 interval: %d", algodata[cls.algo_name]["interval"]
)
algodata[cls.algo_name]["last_date"] = timer.get_daystamp() algodata[cls.algo_name]["last_date"] = timer.get_daystamp()
algodata[cls.algo_name]["next_date"] = ( algodata[cls.algo_name]["next_date"] = (
@@ -89,16 +95,22 @@ class SM2Algorithm(BaseAlgorithm):
) )
algodata[cls.algo_name]["last_modify"] = timer.get_timestamp() algodata[cls.algo_name]["last_modify"] = timer.get_timestamp()
logger.debug("更新日期: last_date=%d, next_date=%d, last_modify=%f", logger.debug(
"更新日期: last_date=%d, next_date=%d, last_modify=%f",
algodata[cls.algo_name]["last_date"], algodata[cls.algo_name]["last_date"],
algodata[cls.algo_name]["next_date"], algodata[cls.algo_name]["next_date"],
algodata[cls.algo_name]["last_modify"]) algodata[cls.algo_name]["last_modify"],
)
@classmethod @classmethod
def is_due(cls, algodata): def is_due(cls, algodata):
result = algodata[cls.algo_name]["next_date"] <= timer.get_daystamp() result = algodata[cls.algo_name]["next_date"] <= timer.get_daystamp()
logger.debug("SM2.is_due: next_date=%d, current_daystamp=%d, result=%s", logger.debug(
algodata[cls.algo_name]["next_date"], timer.get_daystamp(), result) "SM2.is_due: next_date=%d, current_daystamp=%d, result=%s",
algodata[cls.algo_name]["next_date"],
timer.get_daystamp(),
result,
)
return result return result
@classmethod @classmethod

View File

@@ -5,6 +5,7 @@ Particle 模块 - 粒子对象系统
""" """
from heurams.services.logger import get_logger from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
logger.debug("粒子模块已加载") logger.debug("粒子模块已加载")

View File

@@ -12,6 +12,9 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class AtomRegister_runtime(TypedDict):
locked: bool # 只读锁定标识符
min_rate: int # 最低评分
class AtomRegister(TypedDict): class AtomRegister(TypedDict):
nucleon: Nucleon nucleon: Nucleon
@@ -23,7 +26,7 @@ class AtomRegister(TypedDict):
orbital: Orbital orbital: Orbital
orbital_path: pathlib.Path orbital_path: pathlib.Path
orbital_fmt: str orbital_fmt: str
runtime: dict runtime: AtomRegister_runtime
class Atom: class Atom:
@@ -51,6 +54,7 @@ class Atom:
"orbital": None, "orbital": None,
"orbital_path": None, # 允许设置为 None, 此时使用 nucleon 文件内的推荐配置 "orbital_path": None, # 允许设置为 None, 此时使用 nucleon 文件内的推荐配置
"orbital_fmt": "toml", "orbital_fmt": "toml",
"runtime": {"locked": False, "min_rate": 0x3f3f3f3f}
} }
self.do_eval() self.do_eval()
logger.debug("Atom 初始化完成") logger.debug("Atom 初始化完成")
@@ -65,6 +69,38 @@ class Atom:
logger.error("尝试链接不受支持的键: '%s'", key) logger.error("尝试链接不受支持的键: '%s'", key)
raise ValueError("不受支持的原子元数据链接操作") raise ValueError("不受支持的原子元数据链接操作")
def minimize(self, rating):
"""效果等同于 self.registry['runtime']['min_rate'] = min(rating, self.registry['runtime']['min_rate'])
Args:
rating (int): 评分
"""
self.registry['runtime']['min_rate'] = min(rating, self.registry['runtime']['min_rate'])
def lock(self, locked = -1):
"""锁定, 效果等同于 self.registry['runtime']['locked'] = locked 或者返回是否锁定
"""
if locked == 1:
self.registry['runtime']['locked'] = True
return 1
elif locked == 0:
self.registry['runtime']['locked'] = False
return 1
elif locked == -1:
return self.registry['runtime']["locked"]
return 0
def revise(self):
"""执行最终评分
PuzzleWidget 的 handler 除了测试, 严禁直接执行 Electron 的 revisor 函数, 否则造成逻辑混乱
"""
if self.registry["runtime"]["locked"]:
logger.debug(f"允许总评分: {self.registry['runtime']['min_rate']}")
self.registry["electron"].revisor(self.registry['runtime']["min_rate"])
else:
logger.debug("禁止总评分")
def do_eval(self): def do_eval(self):
""" """
执行并以结果替换当前单元的所有 eval 语句 执行并以结果替换当前单元的所有 eval 语句
@@ -91,7 +127,11 @@ class Atom:
ret = eval_value ret = eval_value
else: else:
ret = str(eval_value) ret = str(eval_value)
logger.debug("eval 执行成功: '%s' -> '%s'", s, str(ret)[:50] + '...' if len(ret) > 50 else ret) logger.debug(
"eval 执行成功: '%s' -> '%s'",
s,
str(ret)[:50] + "..." if len(ret) > 50 else ret,
)
except Exception as e: except Exception as e:
ret = f"此 eval 实例发生错误: {e}" ret = f"此 eval 实例发生错误: {e}"
logger.warning("eval 执行错误: '%s' -> %s", s, e) logger.warning("eval 执行错误: '%s' -> %s", s, e)
@@ -117,13 +157,13 @@ class Atom:
# 如果 nucleon 存在且有 do_eval 方法,调用它 # 如果 nucleon 存在且有 do_eval 方法,调用它
nucleon = self.registry["nucleon"] nucleon = self.registry["nucleon"]
if nucleon is not None and hasattr(nucleon, 'do_eval'): if nucleon is not None and hasattr(nucleon, "do_eval"):
nucleon.do_eval() nucleon.do_eval()
logger.debug("已调用 nucleon.do_eval") logger.debug("已调用 nucleon.do_eval")
# 如果 electron 存在且其 algodata 包含 eval 字符串,遍历它 # 如果 electron 存在且其 algodata 包含 eval 字符串,遍历它
electron = self.registry["electron"] electron = self.registry["electron"]
if electron is not None and hasattr(electron, 'algodata'): if electron is not None and hasattr(electron, "algodata"):
traverse(electron.algodata, eval_with_env) traverse(electron.algodata, eval_with_env)
logger.debug("已处理 electron algodata eval") logger.debug("已处理 electron algodata eval")
@@ -173,7 +213,9 @@ class Atom:
raise KeyError(f"不支持的键: {key}") raise KeyError(f"不支持的键: {key}")
def __setitem__(self, key, value): def __setitem__(self, key, value):
logger.debug("Atom.__setitem__: key='%s', value type: %s", key, type(value).__name__) logger.debug(
"Atom.__setitem__: key='%s', value type: %s", key, type(value).__name__
)
if key in self.registry: if key in self.registry:
self.registry[key] = value self.registry[key] = value
logger.debug("'%s' 已设置", key) logger.debug("'%s' 已设置", key)

View File

@@ -17,7 +17,9 @@ class Electron:
algodata: 算法数据字典, 包含算法的各项参数和设置 algodata: 算法数据字典, 包含算法的各项参数和设置
algo: 使用的算法模块标识 algo: 使用的算法模块标识
""" """
logger.debug("创建 Electron 实例ident: '%s', algo_name: '%s'", ident, algo_name) logger.debug(
"创建 Electron 实例ident: '%s', algo_name: '%s'", ident, algo_name
)
self.algodata = algodata self.algodata = algodata
self.ident = ident self.ident = ident
self.algo = algorithms[algo_name] self.algo = algorithms[algo_name]
@@ -31,11 +33,15 @@ class Electron:
self._default_init(self.algo.defaults) self._default_init(self.algo.defaults)
else: else:
logger.debug("算法数据已存在,跳过默认初始化") logger.debug("算法数据已存在,跳过默认初始化")
logger.debug("Electron 初始化完成algodata keys: %s", list(self.algodata.keys())) logger.debug(
"Electron 初始化完成algodata keys: %s", list(self.algodata.keys())
)
def _default_init(self, defaults: dict): def _default_init(self, defaults: dict):
"""默认初始化包装""" """默认初始化包装"""
logger.debug("Electron._default_init: 使用默认值keys: %s", list(defaults.keys())) logger.debug(
"Electron._default_init: 使用默认值keys: %s", list(defaults.keys())
)
self.algodata[self.algo.algo_name] = defaults.copy() self.algodata[self.algo.algo_name] = defaults.copy()
def activate(self): def activate(self):
@@ -88,10 +94,16 @@ class Electron:
quality (int): 记忆保留率量化参数 (0-5) quality (int): 记忆保留率量化参数 (0-5)
is_new_activation (bool): 是否为初次激活 is_new_activation (bool): 是否为初次激活
""" """
logger.debug("Electron.revisor: ident='%s', quality=%d, is_new_activation=%s", logger.debug(
self.ident, quality, is_new_activation) "Electron.revisor: ident='%s', quality=%d, is_new_activation=%s",
self.ident,
quality,
is_new_activation,
)
self.algo.revisor(self.algodata, quality, is_new_activation) self.algo.revisor(self.algodata, quality, is_new_activation)
logger.debug("revisor 完成,更新后的 algodata: %s", self.algodata.get(self.algo, {})) logger.debug(
"revisor 完成,更新后的 algodata: %s", self.algodata.get(self.algo, {})
)
def __str__(self): def __str__(self):
return ( return (

View File

@@ -40,9 +40,7 @@ def load_nucleon(path: pathlib.Path, fmt="toml"):
logger.debug("处理项目: %s", item) logger.debug("处理项目: %s", item)
lst.append( lst.append(
( (
Nucleon( Nucleon(item, attr, deepcopy(nested_data["__metadata__"])),
item, attr, deepcopy(nested_data["__metadata__"])
),
deepcopy(nested_data["__metadata__"]["orbital"]), deepcopy(nested_data["__metadata__"]["orbital"]),
) )
) )

View File

@@ -14,8 +14,12 @@ class Nucleon:
payload: 记忆内容信息 payload: 记忆内容信息
metadata: 可选元数据信息 metadata: 可选元数据信息
""" """
logger.debug("创建 Nucleon 实例ident: '%s', payload keys: %s, metadata keys: %s", logger.debug(
ident, list(payload.keys()) if payload else [], list(metadata.keys()) if metadata else []) "创建 Nucleon 实例ident: '%s', payload keys: %s, metadata keys: %s",
ident,
list(payload.keys()) if payload else [],
list(metadata.keys()) if metadata else [],
)
self.metadata = metadata self.metadata = metadata
self.payload = payload self.payload = payload
self.ident = ident self.ident = ident
@@ -28,7 +32,9 @@ class Nucleon:
return self.ident return self.ident
if key in self.payload: if key in self.payload:
value = self.payload[key] value = self.payload[key]
logger.debug("返回 payload['%s'], value type: %s", key, type(value).__name__) logger.debug(
"返回 payload['%s'], value type: %s", key, type(value).__name__
)
return value return value
else: else:
logger.error("'%s' 未在 payload 中找到", key) logger.error("'%s' 未在 payload 中找到", key)
@@ -59,7 +65,11 @@ class Nucleon:
ret = str(eval_value) ret = str(eval_value)
else: else:
ret = eval_value ret = eval_value
logger.debug("eval 执行成功: '%s' -> '%s'", s, str(ret)[:50] + '...' if len(ret) > 50 else ret) logger.debug(
"eval 执行成功: '%s' -> '%s'",
s,
str(ret)[:50] + "..." if len(ret) > 50 else ret,
)
except Exception as e: except Exception as e:
ret = f"此 eval 实例发生错误: {e}" ret = f"此 eval 实例发生错误: {e}"
logger.warning("eval 执行错误: '%s' -> %s", s, e) logger.warning("eval 执行错误: '%s' -> %s", s, e)

View File

@@ -5,6 +5,7 @@ Puzzle 模块 - 谜题生成系统
""" """
from heurams.services.logger import get_logger from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
from .base import BasePuzzle from .base import BasePuzzle
@@ -41,7 +42,9 @@ def create_by_dict(config_dict: dict) -> BasePuzzle:
Raises: Raises:
ValueError: 当配置无效时抛出 ValueError: 当配置无效时抛出
""" """
logger.debug("puzzles.create_by_dict: config_dict keys=%s", list(config_dict.keys())) logger.debug(
"puzzles.create_by_dict: config_dict keys=%s", list(config_dict.keys())
)
puzzle_type = config_dict.get("type") puzzle_type = config_dict.get("type")
if puzzle_type == "cloze": if puzzle_type == "cloze":

View File

@@ -14,8 +14,12 @@ class ClozePuzzle(BasePuzzle):
""" """
def __init__(self, text: str, min_denominator: int, delimiter: str = "/"): def __init__(self, text: str, min_denominator: int, delimiter: str = "/"):
logger.debug("ClozePuzzle.__init__: text length=%d, min_denominator=%d, delimiter='%s'", logger.debug(
len(text), min_denominator, delimiter) "ClozePuzzle.__init__: text length=%d, min_denominator=%d, delimiter='%s'",
len(text),
min_denominator,
delimiter,
)
self.text = text self.text = text
self.min_denominator = min_denominator self.min_denominator = min_denominator
self.wording = "填空题 - 尚未刷新谜题" self.wording = "填空题 - 尚未刷新谜题"

View File

@@ -6,6 +6,7 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class MCQPuzzle(BasePuzzle): class MCQPuzzle(BasePuzzle):
"""选择题谜题生成器 """选择题谜题生成器
@@ -37,7 +38,12 @@ class MCQPuzzle(BasePuzzle):
max_riddles_num: 每次生成的最大题目数量, 范围限制在1-5之间 max_riddles_num: 每次生成的最大题目数量, 范围限制在1-5之间
prefix: 题目前缀文本, 会显示在每个题目之前 prefix: 题目前缀文本, 会显示在每个题目之前
""" """
logger.debug("MCQPuzzle.__init__: mapping size=%d, jammer size=%d, max_riddles_num=%d", len(mapping), len(jammer), max_riddles_num) logger.debug(
"MCQPuzzle.__init__: mapping size=%d, jammer size=%d, max_riddles_num=%d",
len(mapping),
len(jammer),
max_riddles_num,
)
self.prefix = prefix self.prefix = prefix
self.mapping = mapping self.mapping = mapping
self.max_riddles_num = max(1, min(max_riddles_num, 5)) self.max_riddles_num = max(1, min(max_riddles_num, 5))

View File

@@ -11,7 +11,7 @@ class Fission:
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION): def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
self.logger = get_logger(__name__) self.logger = get_logger(__name__)
self.atom = atom self.atom = atom
#print(f"{phase.value}") # print(f"{phase.value}")
self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore
self.orbital_puzzles = atom.registry["orbital"]["puzzles"] self.orbital_puzzles = atom.registry["orbital"]["puzzles"]
# print(self.orbital_schedule) # print(self.orbital_schedule)
@@ -39,6 +39,5 @@ class Fission:
print(f"ok:{item}") print(f"ok:{item}")
self.logger.debug(f"orbital 项处理完成: {item}") self.logger.debug(f"orbital 项处理完成: {item}")
def generate(self): def generate(self):
yield from self.puzzles yield from self.puzzles

View File

@@ -9,8 +9,12 @@ class Procession:
"""队列: 标识单次记忆流程""" """队列: 标识单次记忆流程"""
def __init__(self, atoms: list, phase: PhaserState, name: str = ""): def __init__(self, atoms: list, phase: PhaserState, name: str = ""):
logger.debug("Procession.__init__: 原子数量=%d, phase=%s, name='%s'", logger.debug(
len(atoms), phase.value, name) "Procession.__init__: 原子数量=%d, phase=%s, name='%s'",
len(atoms),
phase.value,
name,
)
self.atoms = atoms self.atoms = atoms
self.queue = atoms.copy() self.queue = atoms.copy()
self.current_atom = atoms[0] self.current_atom = atoms[0]

View File

@@ -16,4 +16,5 @@ class ProcessionState(Enum):
RUNNING = auto() RUNNING = auto()
FINISHED = auto() FINISHED = auto()
logger.debug("状态枚举定义已加载") logger.debug("状态枚举定义已加载")

View File

@@ -7,4 +7,6 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
play_by_path: Callable = prov[config_var.get()["services"]["audio"]].play_by_path play_by_path: Callable = prov[config_var.get()["services"]["audio"]].play_by_path
logger.debug("音频服务初始化完成,使用 provider: %s", config_var.get()["services"]["audio"]) logger.debug(
"音频服务初始化完成,使用 provider: %s", config_var.get()["services"]["audio"]
)

View File

@@ -50,7 +50,7 @@ def setup_logging(
filename=log_path, filename=log_path,
maxBytes=max_bytes, maxBytes=max_bytes,
backupCount=backup_count, backupCount=backup_count,
encoding='utf-8' encoding="utf-8",
) )
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
file_handler.setLevel(log_level) file_handler.setLevel(log_level)
@@ -142,7 +142,7 @@ def critical(msg: str, *args, **kwargs) -> None:
def exception(msg: str, *args, **kwargs) -> None: def exception(msg: str, *args, **kwargs) -> None:
"""记录异常信息 (ERROR级别) """ """记录异常信息 (ERROR级别)"""
get_logger().exception(msg, *args, **kwargs) get_logger().exception(msg, *args, **kwargs)

View File

@@ -7,4 +7,6 @@ from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
convert: Callable = TTSs[config_var.get().get("tts_provider")] convert: Callable = TTSs[config_var.get().get("tts_provider")]
logger.debug("TTS服务初始化完成使用 provider: %s", config_var.get().get("tts_provider")) logger.debug(
"TTS服务初始化完成使用 provider: %s", config_var.get().get("tts_provider")
)

View File

@@ -9,8 +9,12 @@ class TestSM2Algorithm(unittest.TestCase):
def setUp(self): def setUp(self):
# 模拟 timer 函数 # 模拟 timer 函数
self.timestamp_patcher = patch('heurams.kernel.algorithms.sm2.timer.get_timestamp') self.timestamp_patcher = patch(
self.daystamp_patcher = patch('heurams.kernel.algorithms.sm2.timer.get_daystamp') "heurams.kernel.algorithms.sm2.timer.get_timestamp"
)
self.daystamp_patcher = patch(
"heurams.kernel.algorithms.sm2.timer.get_daystamp"
)
self.mock_get_timestamp = self.timestamp_patcher.start() self.mock_get_timestamp = self.timestamp_patcher.start()
self.mock_get_daystamp = self.daystamp_patcher.start() self.mock_get_daystamp = self.daystamp_patcher.start()
@@ -46,7 +50,14 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_feedback_less_than_3(self): def test_revisor_feedback_less_than_3(self):
"""测试 feedback < 3 重置 rept 和 interval""" """测试 feedback < 3 重置 rept 和 interval"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 5, "interval": 10, "real_rept": 3}} algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 5,
"interval": 10,
"real_rept": 3,
}
}
SM2Algorithm.revisor(algodata, feedback=2) SM2Algorithm.revisor(algodata, feedback=2)
self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0) self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0)
# rept=0 导致 interval 被设置为 1 # rept=0 导致 interval 被设置为 1
@@ -55,7 +66,14 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_feedback_greater_equal_3(self): def test_revisor_feedback_greater_equal_3(self):
"""测试 feedback >= 3 递增 rept""" """测试 feedback >= 3 递增 rept"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 2, "interval": 6, "real_rept": 2}} algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 2,
"interval": 6,
"real_rept": 2,
}
}
SM2Algorithm.revisor(algodata, feedback=4) SM2Algorithm.revisor(algodata, feedback=4)
self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 3) self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 3)
self.assertEqual(algodata[SM2Algorithm.algo_name]["real_rept"], 3) self.assertEqual(algodata[SM2Algorithm.algo_name]["real_rept"], 3)
@@ -65,7 +83,14 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_new_activation(self): def test_revisor_new_activation(self):
"""测试 is_new_activation 重置 rept 和 efactor""" """测试 is_new_activation 重置 rept 和 efactor"""
algodata = {SM2Algorithm.algo_name: {"efactor": 3.0, "rept": 5, "interval": 20, "real_rept": 5}} algodata = {
SM2Algorithm.algo_name: {
"efactor": 3.0,
"rept": 5,
"interval": 20,
"real_rept": 5,
}
}
SM2Algorithm.revisor(algodata, feedback=5, is_new_activation=True) SM2Algorithm.revisor(algodata, feedback=5, is_new_activation=True)
self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0) self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0)
self.assertEqual(algodata[SM2Algorithm.algo_name]["efactor"], 2.5) self.assertEqual(algodata[SM2Algorithm.algo_name]["efactor"], 2.5)
@@ -74,10 +99,19 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_efactor_calculation(self): def test_revisor_efactor_calculation(self):
"""测试 efactor 计算""" """测试 efactor 计算"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 1, "interval": 6, "real_rept": 1}} algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 1,
"interval": 6,
"real_rept": 1,
}
}
SM2Algorithm.revisor(algodata, feedback=5) SM2Algorithm.revisor(algodata, feedback=5)
# efactor = 2.5 + (0.1 - (5-5)*(0.08 + (5-5)*0.02)) = 2.5 + 0.1 = 2.6 # efactor = 2.5 + (0.1 - (5-5)*(0.08 + (5-5)*0.02)) = 2.5 + 0.1 = 2.6
self.assertAlmostEqual(algodata[SM2Algorithm.algo_name]["efactor"], 2.6, places=6) self.assertAlmostEqual(
algodata[SM2Algorithm.algo_name]["efactor"], 2.6, places=6
)
# 测试 efactor 下限为 1.3 # 测试 efactor 下限为 1.3
algodata[SM2Algorithm.algo_name]["efactor"] = 1.2 algodata[SM2Algorithm.algo_name]["efactor"] = 1.2
@@ -86,7 +120,14 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_interval_calculation(self): def test_revisor_interval_calculation(self):
"""测试 interval 计算规则""" """测试 interval 计算规则"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 0, "interval": 0, "real_rept": 0}} algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 0,
"interval": 0,
"real_rept": 0,
}
}
SM2Algorithm.revisor(algodata, feedback=4) SM2Algorithm.revisor(algodata, feedback=4)
# rept 从 0 递增到 1因此 interval 应为 6 # rept 从 0 递增到 1因此 interval 应为 6
self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 6) self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 6)
@@ -97,7 +138,14 @@ class TestSM2Algorithm(unittest.TestCase):
self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 15) self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 15)
# 单独测试 rept=1 的情况 # 单独测试 rept=1 的情况
algodata2 = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 1, "interval": 0, "real_rept": 0}} algodata2 = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 1,
"interval": 0,
"real_rept": 0,
}
}
SM2Algorithm.revisor(algodata2, feedback=4) SM2Algorithm.revisor(algodata2, feedback=4)
# rept 递增到 2interval = round(0 * 2.5) = 0 # rept 递增到 2interval = round(0 * 2.5) = 0
self.assertEqual(algodata2[SM2Algorithm.algo_name]["interval"], 0) self.assertEqual(algodata2[SM2Algorithm.algo_name]["interval"], 0)
@@ -108,7 +156,10 @@ class TestSM2Algorithm(unittest.TestCase):
self.mock_get_daystamp.return_value = 200 self.mock_get_daystamp.return_value = 200
SM2Algorithm.revisor(algodata, feedback=5) SM2Algorithm.revisor(algodata, feedback=5)
self.assertEqual(algodata[SM2Algorithm.algo_name]["last_date"], 200) self.assertEqual(algodata[SM2Algorithm.algo_name]["last_date"], 200)
self.assertEqual(algodata[SM2Algorithm.algo_name]["next_date"], 200 + algodata[SM2Algorithm.algo_name]["interval"]) self.assertEqual(
algodata[SM2Algorithm.algo_name]["next_date"],
200 + algodata[SM2Algorithm.algo_name]["interval"],
)
self.assertEqual(algodata[SM2Algorithm.algo_name]["last_modify"], 1000.0) self.assertEqual(algodata[SM2Algorithm.algo_name]["last_modify"], 1000.0)
def test_is_due(self): def test_is_due(self):
@@ -131,5 +182,5 @@ class TestSM2Algorithm(unittest.TestCase):
self.assertEqual(SM2Algorithm.nextdate(algodata), 12345) self.assertEqual(SM2Algorithm.nextdate(algodata), 12345)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -23,8 +23,10 @@ class TestAtom(unittest.TestCase):
self.temp_path = pathlib.Path(self.temp_dir.name) self.temp_path = pathlib.Path(self.temp_dir.name)
# 创建默认配置 # 创建默认配置
self.config = ConfigFile(pathlib.Path(__file__).parent.parent.parent.parent / self.config = ConfigFile(
"src/heurams/default/config/config.toml") pathlib.Path(__file__).parent.parent.parent.parent
/ "src/heurams/default/config/config.toml"
)
# 使用 ConfigContext 设置配置 # 使用 ConfigContext 设置配置
self.config_ctx = ConfigContext(self.config) self.config_ctx = ConfigContext(self.config)
@@ -71,7 +73,7 @@ class TestAtom(unittest.TestCase):
atom = Atom("test_eval_trigger") atom = Atom("test_eval_trigger")
nucleon = Nucleon("test_nucleon", {"content": "eval:1+1"}) nucleon = Nucleon("test_nucleon", {"content": "eval:1+1"})
with patch.object(atom, 'do_eval') as mock_do_eval: with patch.object(atom, "do_eval") as mock_do_eval:
atom.link("nucleon", nucleon) atom.link("nucleon", nucleon)
mock_do_eval.assert_called_once() mock_do_eval.assert_called_once()
@@ -89,7 +91,7 @@ class TestAtom(unittest.TestCase):
# 验证文件存在且内容正确 # 验证文件存在且内容正确
self.assertTrue(test_path.exists()) self.assertTrue(test_path.exists())
with open(test_path, 'r') as f: with open(test_path, "r") as f:
data = toml.load(f) data = toml.load(f)
self.assertEqual(data["ident"], "test_nucleon") self.assertEqual(data["ident"], "test_nucleon")
self.assertEqual(data["payload"]["content"], "test") self.assertEqual(data["payload"]["content"], "test")
@@ -106,7 +108,7 @@ class TestAtom(unittest.TestCase):
atom.persist("electron") atom.persist("electron")
self.assertTrue(test_path.exists()) self.assertTrue(test_path.exists())
with open(test_path, 'r') as f: with open(test_path, "r") as f:
data = json.load(f) data = json.load(f)
self.assertIn("supermemo2", data) self.assertIn("supermemo2", data)
@@ -149,10 +151,10 @@ class TestAtom(unittest.TestCase):
def test_do_eval_with_eval_string(self): def test_do_eval_with_eval_string(self):
"""测试 do_eval 处理 eval: 字符串""" """测试 do_eval 处理 eval: 字符串"""
atom = Atom("test_do_eval") atom = Atom("test_do_eval")
nucleon = Nucleon("test_nucleon", { nucleon = Nucleon(
"content": "eval:'hello' + ' world'", "test_nucleon",
"number": "eval:2 + 3" {"content": "eval:'hello' + ' world'", "number": "eval:2 + 3"},
}) )
atom.link("nucleon", nucleon) atom.link("nucleon", nucleon)
# do_eval 应该在链接时自动调用 # do_eval 应该在链接时自动调用
@@ -163,9 +165,9 @@ class TestAtom(unittest.TestCase):
def test_do_eval_with_config_access(self): def test_do_eval_with_config_access(self):
"""测试 do_eval 访问配置""" """测试 do_eval 访问配置"""
atom = Atom("test_eval_config") atom = Atom("test_eval_config")
nucleon = Nucleon("test_nucleon", { nucleon = Nucleon(
"max_riddles": "eval:default['mcq']['max_riddles_num']" "test_nucleon", {"max_riddles": "eval:default['mcq']['max_riddles_num']"}
}) )
atom.link("nucleon", nucleon) atom.link("nucleon", nucleon)
# 配置中 puzzles.mcq.max_riddles_num = 2 # 配置中 puzzles.mcq.max_riddles_num = 2
@@ -195,5 +197,5 @@ class TestAtom(unittest.TestCase):
self.assertEqual(atom_registry.inverse[atom2], "atom2") self.assertEqual(atom_registry.inverse[atom2], "atom2")
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -11,7 +11,9 @@ class TestElectron(unittest.TestCase):
def setUp(self): def setUp(self):
# 模拟 timer.get_timestamp 返回固定值 # 模拟 timer.get_timestamp 返回固定值
self.timestamp_patcher = patch('heurams.kernel.particles.electron.timer.get_timestamp') self.timestamp_patcher = patch(
"heurams.kernel.particles.electron.timer.get_timestamp"
)
self.mock_get_timestamp = self.timestamp_patcher.start() self.mock_get_timestamp = self.timestamp_patcher.start()
self.mock_get_timestamp.return_value = 1234567890.0 self.mock_get_timestamp.return_value = 1234567890.0
@@ -69,7 +71,7 @@ class TestElectron(unittest.TestCase):
self.assertEqual(electron.algodata[electron.algo]["last_modify"], 1234567890.0) self.assertEqual(electron.algodata[electron.algo]["last_modify"], 1234567890.0)
# 修改不存在的字段应记录警告但不引发异常 # 修改不存在的字段应记录警告但不引发异常
with patch('heurams.kernel.particles.electron.logger.warning') as mock_warning: with patch("heurams.kernel.particles.electron.logger.warning") as mock_warning:
electron.modify("unknown_field", 99) electron.modify("unknown_field", 99)
mock_warning.assert_called_once() mock_warning.assert_called_once()
@@ -85,7 +87,7 @@ class TestElectron(unittest.TestCase):
def test_is_due(self): def test_is_due(self):
"""测试 is_due 方法""" """测试 is_due 方法"""
electron = Electron("test_electron") electron = Electron("test_electron")
with patch.object(electron.algo, 'is_due') as mock_is_due: with patch.object(electron.algo, "is_due") as mock_is_due:
mock_is_due.return_value = 1 mock_is_due.return_value = 1
result = electron.is_due() result = electron.is_due()
mock_is_due.assert_called_once_with(electron.algodata) mock_is_due.assert_called_once_with(electron.algodata)
@@ -94,7 +96,7 @@ class TestElectron(unittest.TestCase):
def test_rate(self): def test_rate(self):
"""测试 rate 方法""" """测试 rate 方法"""
electron = Electron("test_electron") electron = Electron("test_electron")
with patch.object(electron.algo, 'rate') as mock_rate: with patch.object(electron.algo, "rate") as mock_rate:
mock_rate.return_value = "good" mock_rate.return_value = "good"
result = electron.rate() result = electron.rate()
mock_rate.assert_called_once_with(electron.algodata) mock_rate.assert_called_once_with(electron.algodata)
@@ -103,7 +105,7 @@ class TestElectron(unittest.TestCase):
def test_nextdate(self): def test_nextdate(self):
"""测试 nextdate 方法""" """测试 nextdate 方法"""
electron = Electron("test_electron") electron = Electron("test_electron")
with patch.object(electron.algo, 'nextdate') as mock_nextdate: with patch.object(electron.algo, "nextdate") as mock_nextdate:
mock_nextdate.return_value = 1234568000 mock_nextdate.return_value = 1234568000
result = electron.nextdate() result = electron.nextdate()
mock_nextdate.assert_called_once_with(electron.algodata) mock_nextdate.assert_called_once_with(electron.algodata)
@@ -112,7 +114,7 @@ class TestElectron(unittest.TestCase):
def test_revisor(self): def test_revisor(self):
"""测试 revisor 方法""" """测试 revisor 方法"""
electron = Electron("test_electron") electron = Electron("test_electron")
with patch.object(electron.algo, 'revisor') as mock_revisor: with patch.object(electron.algo, "revisor") as mock_revisor:
electron.revisor(quality=3, is_new_activation=True) electron.revisor(quality=3, is_new_activation=True)
mock_revisor.assert_called_once_with(electron.algodata, 3, True) mock_revisor.assert_called_once_with(electron.algodata, 3, True)
@@ -173,5 +175,5 @@ class TestElectron(unittest.TestCase):
self.assertEqual(placeholder.algo, algorithms["supermemo2"]) self.assertEqual(placeholder.algo, algorithms["supermemo2"])
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -9,7 +9,9 @@ class TestNucleon(unittest.TestCase):
def test_init(self): def test_init(self):
"""测试初始化""" """测试初始化"""
nucleon = Nucleon("test_id", {"content": "hello", "note": "world"}, {"author": "test"}) nucleon = Nucleon(
"test_id", {"content": "hello", "note": "world"}, {"author": "test"}
)
self.assertEqual(nucleon.ident, "test_id") self.assertEqual(nucleon.ident, "test_id")
self.assertEqual(nucleon.payload, {"content": "hello", "note": "world"}) self.assertEqual(nucleon.payload, {"content": "hello", "note": "world"})
self.assertEqual(nucleon.metadata, {"author": "test"}) self.assertEqual(nucleon.metadata, {"author": "test"})
@@ -58,16 +60,23 @@ class TestNucleon(unittest.TestCase):
def test_do_eval_with_metadata_access(self): def test_do_eval_with_metadata_access(self):
"""测试 do_eval 访问元数据""" """测试 do_eval 访问元数据"""
nucleon = Nucleon("test_id", {"result": "eval:nucleon.metadata.get('value', 0)"}, {"value": 42}) nucleon = Nucleon(
"test_id",
{"result": "eval:nucleon.metadata.get('value', 0)"},
{"value": 42},
)
nucleon.do_eval() nucleon.do_eval()
self.assertEqual(nucleon.payload["result"], "42") self.assertEqual(nucleon.payload["result"], "42")
def test_do_eval_nested(self): def test_do_eval_nested(self):
"""测试 do_eval 处理嵌套结构""" """测试 do_eval 处理嵌套结构"""
nucleon = Nucleon("test_id", { nucleon = Nucleon(
"test_id",
{
"list": ["eval:2*3", "normal"], "list": ["eval:2*3", "normal"],
"dict": {"key": "eval:'hello' + ' world'"} "dict": {"key": "eval:'hello' + ' world'"},
}) },
)
nucleon.do_eval() nucleon.do_eval()
self.assertEqual(nucleon.payload["list"][0], "6") self.assertEqual(nucleon.payload["list"][0], "6")
self.assertEqual(nucleon.payload["list"][1], "normal") self.assertEqual(nucleon.payload["list"][1], "normal")
@@ -95,5 +104,5 @@ class TestNucleon(unittest.TestCase):
self.assertEqual(placeholder.metadata, {}) self.assertEqual(placeholder.metadata, {})
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -19,5 +19,5 @@ class TestBasePuzzle(unittest.TestCase):
self.assertEqual(str(puzzle), "谜题: BasePuzzle") self.assertEqual(str(puzzle), "谜题: BasePuzzle")
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -16,7 +16,7 @@ class TestClozePuzzle(unittest.TestCase):
self.assertEqual(puzzle.wording, "填空题 - 尚未刷新谜题") self.assertEqual(puzzle.wording, "填空题 - 尚未刷新谜题")
self.assertEqual(puzzle.answer, ["填空题 - 尚未刷新谜题"]) self.assertEqual(puzzle.answer, ["填空题 - 尚未刷新谜题"])
@patch('random.sample') @patch("random.sample")
def test_refresh(self, mock_sample): def test_refresh(self, mock_sample):
"""测试 refresh 方法""" """测试 refresh 方法"""
mock_sample.return_value = [0, 2] # 选择索引 0 和 2 mock_sample.return_value = [0, 2] # 选择索引 0 和 2
@@ -47,5 +47,5 @@ class TestClozePuzzle(unittest.TestCase):
self.assertIn("填空题 - 尚未刷新谜题", str_repr) self.assertIn("填空题 - 尚未刷新谜题", str_repr)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -38,8 +38,8 @@ class TestMCQPuzzle(unittest.TestCase):
self.assertEqual(len(puzzle.jammer), 4) self.assertEqual(len(puzzle.jammer), 4)
self.assertEqual(set(puzzle.jammer), {" "}) # 三个空格?实际上循环填充空格 self.assertEqual(set(puzzle.jammer), {" "}) # 三个空格?实际上循环填充空格
@patch('random.sample') @patch("random.sample")
@patch('random.shuffle') @patch("random.shuffle")
def test_refresh(self, mock_shuffle, mock_sample): def test_refresh(self, mock_shuffle, mock_sample):
"""测试 refresh 方法生成题目""" """测试 refresh 方法生成题目"""
mapping = {"q1": "a1", "q2": "a2", "q3": "a3"} mapping = {"q1": "a1", "q2": "a2", "q3": "a3"}
@@ -118,5 +118,5 @@ class TestMCQPuzzle(unittest.TestCase):
self.assertIn("A1, A2", str_repr) self.assertIn("A1, A2", str_repr)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -21,7 +21,7 @@ class TestPhaser(unittest.TestCase):
self.atom_old.registry["electron"].is_activated.return_value = True self.atom_old.registry["electron"].is_activated.return_value = True
# 模拟 Procession 类以避免复杂依赖 # 模拟 Procession 类以避免复杂依赖
self.procession_patcher = patch('heurams.kernel.reactor.phaser.Procession') self.procession_patcher = patch("heurams.kernel.reactor.phaser.Procession")
self.mock_procession_class = self.procession_patcher.start() self.mock_procession_class = self.procession_patcher.start()
def tearDown(self): def tearDown(self):
@@ -110,5 +110,5 @@ class TestPhaser(unittest.TestCase):
self.assertEqual(phaser.state, PhaserState.FINISHED) self.assertEqual(phaser.state, PhaserState.FINISHED)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()