## Overall Architecture Overview ```mermaid 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 `payload` and `common` via `Evalizer` (an `eval()`-based template system). - Contains `puzzles` field, defining which puzzle types this memory unit supports. - Created by pairing `repo.payload` and `repo.typedef["common"]`. - Once created, content cannot be modified (`__setitem__` raises `AttributeError`). ### Electron - State Layer ``` Electron(ident, algodata, algo_name) ``` - Wrapper for algorithm state data. Each Electron is bound to one algorithm (`algorithms[algo_name]`). - `algodata` is a **reference** to the corresponding dictionary in the repository's `algodata.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 `runtime` flags (`locked`, `min_rate`, `new_activation`). - The basic unit operated on by the UI and scheduling layers. - `revise()` calls `electron.revisor(min_rate)` when `locked` is true, performing the final rating iteration. **Relationship Diagram**: ```mermaid 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 ```mermaid graph TB subgraph "Router (Global Router)" R[Router
State: unsure→quick_review
→recognition→final_review
→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) ```python { "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// ├── 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 ```mermaid 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 } ``` - `payload` and `algodata` use `Lict` (list+dict hybrid container), supporting dual-mode access. - `_generate_particles_data()` automatically converts payload data to `Nucleon`-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: ```python 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 ``` - `_.toml` files = defaults for that directory level, merged into parent - Suffixed files = lazy-loaded on demand - Subdirectories = recursive sub-configuration ### Context Management ```python 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 ``/`` 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 ```mermaid 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 ```