style: 格式化代码
This commit is contained in:
@@ -20,4 +20,4 @@ python 代指您使用的解释器, 在某些发行版中可能是 python3, 而
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -27,6 +27,7 @@ user_data = workdir / "data"
|
||||
if not user_data.exists():
|
||||
logger.info("初始化数据目录: %s", user_data)
|
||||
import shutil
|
||||
|
||||
shutil.copytree(default_data, user_data)
|
||||
else:
|
||||
(workdir / "data" / "config").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -7,6 +7,7 @@ import pickle
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def start_debug_server(app):
|
||||
logger = get_logger("zmq_debug")
|
||||
context = zmq.Context()
|
||||
|
||||
@@ -16,7 +16,6 @@ from textual.widgets import (
|
||||
Label,
|
||||
ListItem,
|
||||
ListView,
|
||||
Markdown,
|
||||
Static,
|
||||
)
|
||||
|
||||
@@ -117,7 +116,13 @@ class FavoriteManagerScreen(Screen):
|
||||
# 创建列表项, 包含移除按钮
|
||||
container = Horizontal(
|
||||
Label(display_text, classes="favorite-content"),
|
||||
Button("移除", id=f"remove-{button_key}", variant="error", flat=True, classes="favorite-item-btn"),
|
||||
Button(
|
||||
"移除",
|
||||
id=f"remove-{button_key}",
|
||||
variant="error",
|
||||
flat=True,
|
||||
classes="favorite-item-btn",
|
||||
),
|
||||
classes="favorite-item",
|
||||
)
|
||||
return ListItem(container)
|
||||
|
||||
@@ -140,7 +140,11 @@ class MemScreen(Screen):
|
||||
|
||||
if config_var.get()["interface"]["global"]["persist_to_file"]:
|
||||
self.repo.persist_to_repodir()
|
||||
container.mount(Finished(is_saved=config_var.get()["interface"]["global"]["persist_to_file"]))
|
||||
container.mount(
|
||||
Finished(
|
||||
is_saved=config_var.get()["interface"]["global"]["persist_to_file"]
|
||||
)
|
||||
)
|
||||
|
||||
def on_button_pressed(self, event):
|
||||
event.stop()
|
||||
@@ -162,6 +166,7 @@ class MemScreen(Screen):
|
||||
play_by_path(path)
|
||||
else:
|
||||
from heurams.services.tts_service import convertor
|
||||
|
||||
convertor(self.atom.registry["nucleon"]["tts_text"], path)
|
||||
play_by_path(path)
|
||||
|
||||
@@ -224,6 +229,7 @@ class MemScreen(Screen):
|
||||
|
||||
def action_go_back_notif(self):
|
||||
self.notify("确定吗? 按下大写 Q 以返回")
|
||||
|
||||
def action_go_back(self):
|
||||
self.app.pop_screen()
|
||||
|
||||
|
||||
@@ -81,10 +81,10 @@ class ClozePuzzle(BasePuzzleWidget):
|
||||
btnid = f"sel000-{h}"
|
||||
logger.debug(f"建立按钮 {btnid}")
|
||||
self.btn_shortcuts[f"{c}"] = btnid
|
||||
btns.append(Button(f"{i}", id=f"{btnid}", classes='cloze-option-btn'))
|
||||
for i in range((len(btns)+1)//2):
|
||||
btns.append(Button(f"{i}", id=f"{btnid}", classes="cloze-option-btn"))
|
||||
for i in range((len(btns) + 1) // 2):
|
||||
if 2 * i + 1 + 1 <= len(btns):
|
||||
yield Horizontal(btns[i], btns[len(btns) - 1 - i], classes='hori')
|
||||
yield Horizontal(btns[i], btns[len(btns) - 1 - i], classes="hori")
|
||||
else:
|
||||
yield btns[i]
|
||||
s.focus()
|
||||
@@ -136,7 +136,7 @@ class ClozePuzzle(BasePuzzleWidget):
|
||||
self.atom.minimize(rating)
|
||||
|
||||
def on_key(self, event: Key) -> None:
|
||||
#self.notify(event.key)
|
||||
# self.notify(event.key)
|
||||
if event.key in self.btn_shortcuts:
|
||||
btn_id = self.btn_shortcuts.get(event.key)
|
||||
btn_id = "#" + btn_id
|
||||
|
||||
@@ -11,4 +11,4 @@ for _finder, _name, _ispkg in pkgutil.iter_modules(__path__):
|
||||
continue
|
||||
importlib.import_module(f".{_name}", __package__)
|
||||
|
||||
algorithms = BaseAlgorithm.get_registry()
|
||||
algorithms = BaseAlgorithm.get_registry()
|
||||
|
||||
@@ -7,6 +7,7 @@ logger = get_logger(__name__)
|
||||
|
||||
_registry: dict[str, type["BaseAlgorithm"]] = {}
|
||||
|
||||
|
||||
class BaseAlgorithm:
|
||||
algo_name = "BaseAlgorithm"
|
||||
desc = "算法基类"
|
||||
|
||||
@@ -3,7 +3,7 @@ FSRS 算法模块 — 基于 py-fsrs 的现代间隔重复调度器
|
||||
|
||||
基于: https://github.com/open-spaced-repetition/py-fsrs
|
||||
"""
|
||||
import json
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from datetime import datetime, timezone, timedelta
|
||||
@@ -20,9 +20,10 @@ from .base import BaseAlgorithm
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# 全局 Scheduler 状态文件路径
|
||||
_SCHEDULER_STATE_FILE = pathlib.Path(
|
||||
config_var.get()["global"]["paths"]["misc"]
|
||||
) / "fsrs_scheduler_state.json"
|
||||
_SCHEDULER_STATE_FILE = (
|
||||
pathlib.Path(config_var.get()["global"]["paths"]["misc"])
|
||||
/ "fsrs_scheduler_state.json"
|
||||
)
|
||||
|
||||
|
||||
def _get_global_scheduler():
|
||||
@@ -78,10 +79,10 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
|
||||
class AlgodataDict(TypedDict):
|
||||
# FSRS 特有字段
|
||||
fsrs_state: int # State 枚举值: 1=Learning, 2=Review, 3=Relearning
|
||||
fsrs_step: int # 当前学习步进索引, -1 表示 None (Review 状态)
|
||||
fsrs_state: int # State 枚举值: 1=Learning, 2=Review, 3=Relearning
|
||||
fsrs_step: int # 当前学习步进索引, -1 表示 None (Review 状态)
|
||||
fsrs_stability: float # 稳定性(秒), 0.0 表示尚未计算
|
||||
fsrs_difficulty: float # 难度 [1.0, 10.0], 0.0 表示尚未计算
|
||||
fsrs_difficulty: float # 难度 [1.0, 10.0], 0.0 表示尚未计算
|
||||
# 标准 BaseAlgorithm 兼容字段
|
||||
real_rept: int
|
||||
rept: int
|
||||
@@ -92,7 +93,7 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
last_modify: float
|
||||
|
||||
defaults = {
|
||||
"fsrs_state": 1, # State.Learning
|
||||
"fsrs_state": 1, # State.Learning
|
||||
"fsrs_step": 0,
|
||||
"fsrs_stability": 0.0,
|
||||
"fsrs_difficulty": 0.0,
|
||||
@@ -136,9 +137,7 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
|
||||
# last_review
|
||||
last_date = data.get("last_date", 0)
|
||||
card.last_review = (
|
||||
_daystamp_to_datetime(last_date) if last_date > 0 else None
|
||||
)
|
||||
card.last_review = _daystamp_to_datetime(last_date) if last_date > 0 else None
|
||||
|
||||
return card
|
||||
|
||||
@@ -160,9 +159,7 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
if card.last_review
|
||||
else data.get("last_date", 0)
|
||||
)
|
||||
data["next_date"] = (
|
||||
_datetime_to_daystamp(card.due) if card.due else 0
|
||||
)
|
||||
data["next_date"] = _datetime_to_daystamp(card.due) if card.due else 0
|
||||
data["interval"] = max(0, data["next_date"] - data["last_date"])
|
||||
data["last_modify"] = get_timestamp()
|
||||
return algodata
|
||||
@@ -209,8 +206,7 @@ class FSRSAlgorithm(BaseAlgorithm):
|
||||
algodata[cls.algo_name]["rept"] += 1
|
||||
|
||||
logger.debug(
|
||||
"FSRS.revisor 完成: stability=%s, difficulty=%s, state=%s, "
|
||||
"next_date=%d",
|
||||
"FSRS.revisor 完成: stability=%s, difficulty=%s, state=%s, " "next_date=%d",
|
||||
card.stability,
|
||||
card.difficulty,
|
||||
card.state,
|
||||
|
||||
@@ -4,6 +4,7 @@ SM-15M — 基于 sm.js 的间隔重复算法
|
||||
基于: https://github.com/slaypni/sm.js
|
||||
原始 CoffeeScript (c) 2014 Kazuaki Tanida, MIT 许可证
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import math
|
||||
@@ -19,7 +20,6 @@ from heurams.services.timer import (
|
||||
get_timestamp_ms,
|
||||
daystamp_to_datetime,
|
||||
datetime_to_daystamp,
|
||||
get_now_datetime,
|
||||
)
|
||||
|
||||
from .base import BaseAlgorithm
|
||||
@@ -111,7 +111,7 @@ def power_law_model(a, b):
|
||||
"""y = a * x^b"""
|
||||
|
||||
def y_func(x):
|
||||
return a * (x ** b)
|
||||
return a * (x**b)
|
||||
|
||||
def x_func(y):
|
||||
if a == 0 or b == 0:
|
||||
@@ -134,7 +134,7 @@ def fixed_point_power_law_regression(points, fixed_point):
|
||||
sum_sqX = sum(x * x for x in X)
|
||||
b = sumXY / sum_sqX if sum_sqX else 0
|
||||
|
||||
return power_law_model(q / (p ** b), b)
|
||||
return power_law_model(q / (p**b), b)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -161,7 +161,7 @@ class FI_G:
|
||||
def _register_point(self, fi, grade):
|
||||
self.points.append([fi, grade + self.GRADE_OFFSET])
|
||||
if len(self.points) > self.MAX_POINTS_COUNT:
|
||||
self.points = self.points[-self.MAX_POINTS_COUNT:]
|
||||
self.points = self.points[-self.MAX_POINTS_COUNT :]
|
||||
self._graph = None
|
||||
|
||||
def update(self, grade, item, now):
|
||||
@@ -203,7 +203,7 @@ class ForgettingCurve:
|
||||
is_remembered = grade >= THRESHOLD_RECALL
|
||||
self.points.append([uf, self.REMEMBERED if is_remembered else self.FORGOTTEN])
|
||||
if len(self.points) > self.MAX_POINTS_COUNT:
|
||||
self.points = self.points[-self.MAX_POINTS_COUNT:]
|
||||
self.points = self.points[-self.MAX_POINTS_COUNT :]
|
||||
self._curve = None
|
||||
|
||||
def retention(self, uf):
|
||||
@@ -253,9 +253,9 @@ class ForgettingCurves:
|
||||
pts = []
|
||||
for i in range(21):
|
||||
v = MIN_AF + NOTCH_AF * i
|
||||
y = math.exp(
|
||||
-1.0 / (10 + 1 * (a + 1)) * (i - a ** 0.6)
|
||||
) * (self.REMEMBERED - self.sm.requested_fi)
|
||||
y = math.exp(-1.0 / (10 + 1 * (a + 1)) * (i - a**0.6)) * (
|
||||
self.REMEMBERED - self.sm.requested_fi
|
||||
)
|
||||
pts.append([v, min(self.REMEMBERED, y)])
|
||||
partial = [[0, self.REMEMBERED]] + pts
|
||||
row.append(ForgettingCurve(partial))
|
||||
@@ -427,7 +427,9 @@ class Item:
|
||||
now = datetime.datetime.now()
|
||||
af_idx = self.lapse if self.repetition == 0 else self.af_index()
|
||||
of_val = self.sm.ofm.of(self.repetition, af_idx)
|
||||
self.of = max(1.0, (of_val - 1) * (self.interval(now) / self.optimum_interval) + 1)
|
||||
self.of = max(
|
||||
1.0, (of_val - 1) * (self.interval(now) / self.optimum_interval) + 1
|
||||
)
|
||||
self.optimum_interval = round(self.optimum_interval * self.of)
|
||||
self.previous_date = now
|
||||
self.due_date = now + datetime.timedelta(milliseconds=self.optimum_interval)
|
||||
@@ -443,7 +445,7 @@ class Item:
|
||||
estimated_af = max(MIN_AF, min(MAX_AF, corrected_uf))
|
||||
self._afs.append(estimated_af)
|
||||
if len(self._afs) > self.MAX_AFS_COUNT:
|
||||
self._afs = self._afs[-self.MAX_AFS_COUNT:]
|
||||
self._afs = self._afs[-self.MAX_AFS_COUNT :]
|
||||
wsum = sum(af * (i + 1) for i, af in enumerate(self._afs))
|
||||
wtotal = sum(range(1, len(self._afs) + 1))
|
||||
self.af(wsum / wtotal if wtotal else estimated_af)
|
||||
@@ -600,7 +602,10 @@ class SM:
|
||||
# Global state management
|
||||
# ============================================================================
|
||||
|
||||
_GLOBAL_STATE_FILE = pathlib.Path(config_var.get()["global"]["paths"]["misc"]) / "sm15m_global_state.json"
|
||||
_GLOBAL_STATE_FILE = (
|
||||
pathlib.Path(config_var.get()["global"]["paths"]["misc"])
|
||||
/ "sm15m_global_state.json"
|
||||
)
|
||||
|
||||
|
||||
def _get_global_sm():
|
||||
@@ -702,7 +707,8 @@ class SM15MAlgorithm(BaseAlgorithm):
|
||||
last_date = data.get("last_date", 0)
|
||||
item.previous_date = (
|
||||
daystamp_to_datetime(last_date).replace(tzinfo=None)
|
||||
if last_date > 0 else None
|
||||
if last_date > 0
|
||||
else None
|
||||
)
|
||||
|
||||
next_date_ms = data.get("next_date_ms", 0)
|
||||
@@ -743,9 +749,7 @@ class SM15MAlgorithm(BaseAlgorithm):
|
||||
data["last_date"] = datetime_to_daystamp(item.previous_date)
|
||||
data["next_date_ms"] = int(item.due_date.timestamp() * 1000)
|
||||
data["next_date"] = datetime_to_daystamp(item.due_date)
|
||||
data["interval"] = max(
|
||||
0, data["next_date"] - (data.get("last_date", 0) or 0)
|
||||
)
|
||||
data["interval"] = max(0, data["next_date"] - (data.get("last_date", 0) or 0))
|
||||
data["last_modify"] = get_timestamp()
|
||||
return algodata
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from heurams.services.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def play_by_path(path: pathlib.Path):
|
||||
logger.debug("playsound_audio.play_by_path: 开始播放 %s", path)
|
||||
try:
|
||||
|
||||
@@ -43,9 +43,9 @@ def get_timestamp_ms() -> int:
|
||||
|
||||
def daystamp_to_datetime(daystamp: int) -> datetime.datetime:
|
||||
"""将日戳转换为 UTC datetime (当日午夜)"""
|
||||
return datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(
|
||||
days=daystamp
|
||||
)
|
||||
return datetime.datetime(
|
||||
1970, 1, 1, tzinfo=datetime.timezone.utc
|
||||
) + datetime.timedelta(days=daystamp)
|
||||
|
||||
|
||||
def datetime_to_daystamp(dt: datetime.datetime) -> int:
|
||||
|
||||
Reference in New Issue
Block a user