15 KiB
Overall Architecture Overview
graph TB
subgraph "User Interface Layer (TUI)"
TUI[Textual App]
Screens[Application Screens]
Widgets[Puzzle Widgets]
end
subgraph "Kernel Layer"
Reactor[Scheduling Reactor]
Algorithms[Algorithm Modules]
Particles[Data Models]
Puzzles[Puzzle Engine]
RepoLib[Repository System]
Auxiliary[Auxiliary Tools]
end
subgraph "Service Layer"
Config[Config Management ConfigDict]
Logger[Logging System]
Timer[Time Service]
Audio[Audio Service]
TTS[TTS Service]
Favorites[Favorites Management]
Attic[Persistence]
Hasher[Hash Service]
end
subgraph "Provider Layer"
AudioProv[Audio Provider]
TTSProv[TTS Provider]
LLMProv[LLM Provider]
end
subgraph "Data Layer"
RepoDir[TOML/JSON Repository Directory]
ConfigDir[TOML Config Directory]
Logs[Log Files]
end
TUI --> Screens
Screens --> Reactor
Screens --> RepoLib
Screens --> Widgets
Widgets --> Puzzles
Widgets --> Reactor
Reactor --> Algorithms
Reactor --> Particles
Reactor --> Puzzles
Particles --> RepoLib
RepoLib --> Config
RepoLib --> Auxiliary
Auxiliary --> Lict
Auxiliary --> Evalizer
TUI --> Config
TUI --> Logger
TUI --> Audio
TUI --> TTS
Config --> ConfigDir
Audio --> AudioProv
TTS --> TTSProv
Attic --> RepoDir
Data Model
The project uses the physical particle metaphor as its core, decomposing memory units into three models:
Nucleon - Content Layer
Nucleon(ident, payload, common)
- Read-only content container. Compiles and expands
payloadandcommonviaEvalizer(aneval()-based template system). - Contains
puzzlesfield, defining which puzzle types this memory unit supports. - Created by pairing
repo.payloadandrepo.typedef["common"]. - Once created, content cannot be modified (
__setitem__raisesAttributeError).
Electron - State Layer
Electron(ident, algodata, algo_name)
- Wrapper for algorithm state data. Each Electron is bound to one algorithm (
algorithms[algo_name]). algodatais a reference to the corresponding dictionary in the repository'salgodata.lict— modifications are persisted immediately.- Core methods:
activate()(mark as activated),revisor()(rating iteration),is_due()(due check).
Orbital - Strategy Layer
orbital = {
"schedule": ["quick_review", "recognition"],
"routes": {
"quick_review": [["MCQ", "1.0"], ["Cloze", "0.5"]],
"recognition": [["Recognition", "1.0"]],
}
}
- A plain dictionary defining the review phase flow and puzzle selection strategy within each phase.
- Each phase corresponds to a list of
(puzzle_type, probability_coefficient)tuples; coefficients >1 indicate forced repetition count.
Atom - Runtime Assembly
Atom(nucleon, electron, orbital)
- Runtime composition of the three, with
runtimeflags (locked,min_rate,new_activation). - The basic unit operated on by the UI and scheduling layers.
revise()callselectron.revisor(min_rate)whenlockedis true, performing the final rating iteration.
Relationship Diagram:
graph LR
subgraph "Persistent Storage"
Payload[(payload.toml)]
Common[(typedef.toml)]
Algodata[(algodata.json)]
Schedule[(schedule.toml)]
end
subgraph "Runtime Assembly"
Nucleon -->|Content| Atom
Electron -->|State| Atom
Orbital -->|Strategy| Atom
end
Payload -->|Repo| Nucleon
Common -->|Repo| Nucleon
Algodata -->|Repo| Electron
Schedule -->|Repo| Orbital
Scheduling Reactor
The Scheduling Reactor is the core business process engine, designed as a three-layer nested finite state machine (based on the transitions library).
State Enumeration
| State Machine | State | Description |
|---|---|---|
| RouterState | unsure |
Initial state, auto-advances |
quick_review |
Quick review phase | |
recognition |
New memory recognition phase | |
final_review |
Final comprehensive review phase | |
finished |
Complete, execute rating | |
| ProcessionState | active |
In progress |
finished |
Completed | |
| ExpanderState | exammode |
Exam mode (frontal answering) |
retronly |
Retrospective mode (recognition only) |
State Machine Nesting Structure
graph TB
subgraph "Router (Global Router)"
R[Router<br/>State: unsure→quick_review<br/>→recognition→final_review<br/>→finished]
P1[Procession Queue 1: Quick Review]
P2[Procession Queue 2: New Memories]
P3[Procession Queue 3: Final Review]
R --> P1
R --> P2
R --> P3
end
subgraph "Procession (Single-Phase Queue)"
P1 --> E1[Expander Atom A]
P1 --> E2[Expander Atom B]
P1 --> E3[Expander Atom C]
M{forward} --> |Done| Finish((FINISHED))
end
subgraph "Expander (Single Atom Expander)"
E1 --> S[(Orbital Strategy)]
S -->|Probability Expansion| PZ1[Puzzle 1: MCQ]
S -->|Probability Expansion| PZ2[Puzzle 2: Cloze]
PZ1 -->|Rating| RPT[report]
PZ2 -->|Rating| RPT
RPT -->|finish| RETRO[retronly mode]
end
Data Flow Detail
Router.__init__(atoms)
│
├─ Split atoms into new/old
│ ├─ old_atoms → Procession(quick_review) "Initial review"
│ └─ new_atoms → Procession(recognition) "New memories"
│
└─ all atoms → Procession(final_review) "Final review"
│
└─ Procession.forward()
│
├─ cursor >= len(atoms) → finish()
└─ cursor < len(atoms) → next_atom
│
└─ Procession.get_expander()
│
└─ Expander(atom, route)
│
├─ Read orbital.routes[route_value]
├─ Probability expansion → self.puzzles_inf
├─ exammode → display puzzles sequentially
├─ report(rating) → record minimum rating
├─ forward() → next puzzle or finish → retronly
└─ retronly → display Recognition
│
└─ Atom.revise()
│
└─ Electron.revisor(min_rate)
│
└─ Algorithm.revisor(algodata, feedback)
Rating accumulation: The final rating for an atom across multiple puzzles takes the minimum rating (min_rate) across all puzzles, ensuring strict evaluation.
Algorithm System
All algorithms inherit from BaseAlgorithm, implemented in a class-method style, registered via the algorithms dictionary.
| Algorithm | File | Status | Description |
|---|---|---|---|
| SM-2 | sm2.py |
✅ Complete | Classic SuperMemo 1987 algorithm |
| NSP-0 | nsp0.py |
✅ Complete | Non-spaced filtering scheduler |
| SM-15M | sm15m.py |
✅ Complete | SM-15 ported from CoffeeScript |
| FSRS | fsrs.py |
✅ Partial | Optimizer not available |
| Base | base.py |
✅ Base class | Defines AlgodataDict structure and defaults |
Each algorithm provides the following class methods:
| Method | Function |
|---|---|
revisor(algodata, feedback, is_new_activation) |
Iterate memory data based on rating |
is_due(algodata) |
Check if due for review |
get_rating(algodata) |
Get rating information |
nextdate(algodata) |
Get next review timestamp |
check_integrity(algodata) |
Validate algodata data structure integrity |
Algorithm Data Structure (AlgodataDict)
{
"real_rept": int, # Actual review count
"rept": int, # Current repetition count
"interval": int, # Interval in days
"last_date": int, # Last review date
"next_date": int, # Next due date
"is_activated": int, # Whether activated (0/1)
"last_modify": float, # Last modification timestamp
}
Repository System (Repo)
The repository is a directory of TOML/JSON files with no database dependency.
Directory Structure
data/repo/<package_name>/
├── manifest.toml # Meta info: title, author, package, desc
├── typedef.toml # Common metadata, puzzle definitions, annotations
├── payload.toml # Memory items (key=ident)
├── algodata.json # Algorithm state (key=ident)
└── schedule.toml # Orbital/review strategy
Repo Class Design
classDiagram
class Repo {
+dict schedule
+Lict payload
+dict manifest
+dict typedef
+Lict algodata
+Path source
+Lict nucleonic_data_lict
+dict orbitic_data
+Lict electronic_data_lict
+from_repodir(source) ~Repo
+from_dict(dictdata) ~Repo
+create_new_repo() ~Repo
+persist_to_repodir(save_list, source)
+export_to_dict() dict
}
payloadandalgodatauseLict(list+dict hybrid container), supporting dual-mode access._generate_particles_data()automatically converts payload data toNucleon-required format during initialization.- Default save list:
default_save_list = ["algodata"], only persists algorithm state.
Lict Collection
Lict extends MutableSequence, maintaining both list and dictionary access:
lict = Lict()
lict.append(("key1", value1)) # List append
lict["key1"] # Dict access
lict[0] # Index access
lict.keys() # All keys
lict.dicted_data # Plain dict export
Dirty sync mechanism: modifying the list automatically syncs to the dictionary; modifying the dictionary automatically syncs to the list. Used for dual-mode access to payload and algodata.
Configuration System (ConfigDict)
ConfigDict extends UserDict, a singleton TOML lazy-loading configuration manager.
Configuration Directory Convention
data/config/
├── _.toml # Top-level defaults (recursive merge)
├── interface/
│ ├── _.toml # Interface layer defaults
│ ├── global.toml
│ └── puzzles.toml
├── services/
│ ├── _.toml # Services layer defaults
│ ├── audio.toml
│ └── tts.toml
└── repo/
└── _.toml
_.tomlfiles = defaults for that directory level, merged into parent- Suffixed files = lazy-loaded on demand
- Subdirectories = recursive sub-configuration
Context Management
from heurams.context import config_var, ConfigContext
# Global access
config = config_var.get()
algo = config["interface"]["global"]["algorithm"]
# Scope override
with ConfigContext(test_config):
... # Temporarily use test configuration
Provider System (Providers)
Pluggable backend implementations, registered via dictionaries in providers/__init__.py.
| Category | Provider | Description |
|---|---|---|
| TTS | edge_tts |
Microsoft Edge TTS (online) |
basetts |
Stub base class (not implemented) | |
| Audio | playsound |
Cross-platform audio playback |
termux |
Android Termux environment | |
| LLM | openai |
OpenAI compatible API (not fully implemented) |
Selection method: provider field in services/*.toml.
Puzzle System (Puzzles)
The puzzle engine generates evaluation views during review phases:
| Puzzle | File | Description |
|---|---|---|
| MCQ | mcq.py |
Multiple Choice Questions |
| Cloze | cloze.py |
Cloze Deletion |
| Recognition | recognition.py |
Recognition identification |
| Guess | guess.py |
Word meaning guessing |
| Base | base.py |
Abstract base class |
Puzzles are expanded probabilistically by the Orbital strategy in the Expander. Each atom can generate multiple puzzles, and each puzzle is rated independently.
Service Layer
| Service | File | Description |
|---|---|---|
| Config | config.py |
ConfigDict(UserDict) TOML lazy-loading singleton |
| Logger | logger.py |
get_logger(name) → hierarchical logger (heurams.*) |
| Timer | timer.py |
get_daystamp() / get_timestamp(), supports configurable override |
| Audio | audio_service.py |
Audio playback, routes to configured audio provider |
| TTS | tts_service.py |
Text-to-speech, routes to configured TTS provider |
| Favorites | favorite_service.py |
JSON5-persisted favorites manager (singleton) |
| Attic | attic.py |
Structured pickle persistence, supports <DAYSTAMP>/<TIMESTAMP> placeholders |
| Hasher | hasher.py |
MD5 hashing |
| Epath | epath.py |
Dot-notation nested dict access (epath(dct, "a.b.c")) |
| TextProc | textproc.py |
truncate(), domize(), undomize() |
Logging system: Each module creates its own logger via get_logger(__name__). Log files rotate at 10MB, max 5 backups, appended to heurams.log.
Complete Review Flow
sequenceDiagram
participant User as User
participant UI as TUI
participant Router as Router
participant Procession as Procession
participant Expander as Expander
participant Atom as Atom
participant Electron as Electron
participant Algo as Algorithm
User->>UI: Start Review
UI->>Router: Router(atoms)
Router->>Procession: Create review queue
Router->>Procession: Create new memory queue
Router->>Procession: Create final review queue
Procession->>Expander: Expand current atom
Expander->>Expander: Parse orbital strategy
Expander-->>UI: Display puzzle
User->>UI: Rate (1-5)
UI->>Expander: report(rating)
Expander->>Expander: forward() next puzzle
Expander-->>UI: Next puzzle or retrospective
Expander->>Expander: finish() → retronly
Expander-->>UI: Recognition retrospective
User->>UI: Final rating
UI->>Atom: revise()
Atom->>Electron: revisor(min_rate)
Electron->>Algo: revisor(algodata, feedback)
Algo-->>Electron: Update algodata
Algo-->>Atom: Update interval, next_date
Procession->>Procession: forward() next atom
Procession-->>Router: Queue complete
Router->>Router: Switch phase
Router-->>UI: Complete (finished)
UI->>User: Show summary