New milestone MIL-008 with story US-001.06, and the design documents updated for the level choice: BC-001, PP-001, US-001, UC-001, DM-001, DICT-001, SSD-001, OC-001, SD-001, DCD-001 and the wireframe. Review records RC-030 to RC-040, all Go (autonomous mode, not independent). Task: MIL-008#1
9.5 KiB
9.5 KiB
Design Class Diagram: Flash Card
Metadata
| Key | Value |
|---|---|
| ID | DCD-001 |
| CrossReference | DM-001, SD-001, DICT-001, ADR-0002 |
Version History
| Date | Status | Author | Reviewer | Change | Commit |
|---|---|---|---|---|---|
| 2026-10-14 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | c3fbcbc |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added Level, Deck.within, the level argument and the level screen | pending |
Purpose and Scope
The design classes of the program in src/flash_card/, in four layers (domain, application, infrastructure, presentation) with the presentation pattern MVVM (ADR-0002). The classes refine the concepts of DM-001; names are the IT terms of DICT-001. Methods trace to the operation contracts of OC-001 and the sequence diagrams of SD-001 (the SSD messages are in SSD-001).
Diagram
@startuml
package domain {
class Card <<frozen>> {
+front : str
+back : str
}
class LanguagePair <<frozen>> {
+front_code : str
+back_code : str
}
class Level <<frozen>> {
+limit : int | None
+first(cards : Sequence[Card]) : tuple[Card, ...]
}
class Vocabulary <<frozen>> {
+language_pair : LanguagePair
+cards : tuple[Card, ...]
}
class Deck {
+language_pair : LanguagePair
-_cards : list[Card]
+from_vocabulary(vocabulary : Vocabulary) : Deck
+cards : tuple[Card, ...]
+is_empty : bool
+within(cards : Sequence[Card]) : tuple[Card, ...]
+draw(choose, exclude : Card | None, among : Sequence[Card] | None) : Card
+remove(card : Card) : None
}
interface VocabularyRepository {
+load() : Vocabulary
}
interface ProgressRepository {
+load() : Deck | None
+save(deck : Deck) : None
}
class VocabularyError
class VocabularyFormatError
class VocabularyNotFoundError
class EmptyDeckError
}
package application {
class CardDto <<frozen>> {
+front_text : str
+back_text : str
+front_language : str
+back_language : str
}
interface SpeechPort {
+speak(text : str, language_code : str) : None
}
class StudySession {
-_deck : Deck
-_progress : ProgressRepository
-_speech : SpeechPort
-_level_cards : tuple[Card, ...]
+current_card : CardDto | None
+remaining : int
+next_card() : CardDto | None
+mark_known() : None
}
class open_study_session <<function>>
}
package infrastructure {
class CsvVocabularyRepository {
-_path : Path
+load() : Vocabulary
}
class CsvProgressRepository {
-_path : Path
+load() : Deck | None
+save(deck : Deck) : None
}
class GttsSpeech {
+speak(text : str, language_code : str) : None
}
class NullSpeech {
+speak(text : str, language_code : str) : None
}
}
package presentation {
class LevelChoice <<frozen>> {
+label : str
+level : Level
}
class LevelViewModel {
+title : str
+choices : tuple[LevelChoice, ...]
}
class LevelView {
+choose(level : Level) : None
}
class FlashCardViewModel {
-_session : StudySession
+side : Side
+language_name : str
+text : str
+next_card() : None
+flip_card() : None
+is_known() : None
}
class FlashCardView {
+flip_timer : str | None
+next_card() : None
+flip_card() : None
+is_known() : None
}
class "app.main" as Main <<function>>
}
Vocabulary "1" *-- "1..*" Card
Vocabulary "1" --> "1" LanguagePair
Level ..> Card : selects the first
Deck ..> Level : filtered by the cards of
Deck "1" o-- "0..*" Card
Deck ..> Vocabulary : built from
Deck ..> EmptyDeckError : raises
VocabularyFormatError --|> VocabularyError
VocabularyNotFoundError --|> VocabularyError
EmptyDeckError --|> VocabularyError
StudySession "1" --> "1" Deck
StudySession "1" --> "1" ProgressRepository
StudySession "1" --> "1" SpeechPort
StudySession ..> CardDto : returns
open_study_session ..> VocabularyRepository
open_study_session ..> ProgressRepository
open_study_session ..> Level
open_study_session ..> StudySession : creates
CsvVocabularyRepository ..|> VocabularyRepository
CsvProgressRepository ..|> ProgressRepository
GttsSpeech ..|> SpeechPort
NullSpeech ..|> SpeechPort
FlashCardViewModel "1" --> "1" StudySession
FlashCardView "1" --> "1" FlashCardViewModel
LevelViewModel "1" *-- "3" LevelChoice
LevelChoice "1" --> "1" Level
LevelView "1" --> "1" LevelViewModel
Main ..> LevelView : composes
Main ..> CsvVocabularyRepository : composes
Main ..> CsvProgressRepository : composes
Main ..> GttsSpeech : composes
Main ..> FlashCardView : composes
@enduml
Class Table
| Class | Refines (Domain Model concept) | Responsibility | Attributes | Operations |
|---|---|---|---|---|
Card |
Flash Card | Holds a word and its translation | front, back |
none |
LanguagePair |
Word List (first and second language) | Names the two languages of a word list by code | front_code, back_code |
none |
Level |
Level | Names how many of the first words of the word list a session uses and selects them | limit |
first |
Vocabulary |
Word List | Holds all cards of a language pair as delivered | language_pair, cards |
none |
Deck |
Words To Learn | Holds the cards still to learn and picks and removes cards | language_pair, _cards |
from_vocabulary, within, draw, remove |
VocabularyRepository |
Word List | Port: load the delivered word list | none | load |
ProgressRepository |
Words To Learn | Port: load and save the words to learn | none | load, save |
StudySession |
Study Session | Runs one session: draws cards of the level, marks cards known, saves progress, asks for speech | _deck, _progress, _speech, _level_cards, _current |
next_card, mark_known |
CardDto |
Flash Card | Plain data that crosses to the presentation layer | four text fields | none |
SpeechPort |
(none: external service) | Port: speak a text in a language | none | speak |
CsvVocabularyRepository, CsvProgressRepository |
Word List, Words To Learn | Adapters that read and write the semicolon separated CSV file | _path |
load, save |
GttsSpeech, NullSpeech |
(none) | Adapters for speech: the gTTS service, and silence | none | speak |
FlashCardViewModel |
Study Session | Exposes the side, language name and text of the card shown, and the three commands | _session, side, language_name, text |
next_card, flip_card, is_known |
LevelChoice, LevelViewModel |
Level | Plain data and view model of the level screen: the title and the three choices with their labels | title, choices |
none |
LevelView |
(none: user interface) | Draws the level screen and forwards the chosen level | buttons | choose |
FlashCardView |
(none: user interface) | Draws the window, forwards the buttons, owns the 3000 ms timer | flip_timer, widgets |
next_card, flip_card, is_known |
Method Traceability
| Method signature | Operation Contract / SD message |
|---|---|
open_study_session(vocabulary_repository, progress_repository, speech, choose, level) -> StudySession |
OC-001 chooseLevel, SD-001 sequence chooseLevel (loads the words to learn and the level's cards) |
Level.first(cards) -> tuple[Card, ...], Deck.within(cards) -> tuple[Card, ...] |
OC-001 chooseLevel postcondition (the level's cards); SD-001 |
LevelViewModel.choices -> tuple[LevelChoice, ...] |
OC-001 startStudySession; SD-001 sequence startStudySession |
LevelView.choose(level) -> None |
OC-001 chooseLevel (forwards the level to the composition root) |
StudySession.next_card() -> CardDto | None |
OC-001 chooseLevel (first card), markUnknown; SD-001 |
StudySession.mark_known() -> None |
OC-001 markKnown (remove and save); SD-001 |
FlashCardViewModel.next_card() -> None |
OC-001 markUnknown; SD-001 |
FlashCardViewModel.is_known() -> None |
OC-001 markKnown; SD-001 |
FlashCardViewModel.flip_card() -> None |
SSD-001 timer turn-over (no contract: started by the system's timer) |
FlashCardView.next_card(), .is_known(), .flip_card() |
the same three operations, forwarded from buttons and the timer; names are the assignment's |
Pattern Annotations
| Pattern | Classes | Rationale |
|---|---|---|
| Repository | VocabularyRepository, ProgressRepository, CSV adapters |
The domain does not know the CSV file |
| Ports and adapters | SpeechPort, GttsSpeech, NullSpeech |
Speech is an optional extra; the core runs without it |
| Null Object | NullSpeech |
No if speech: checks in the use case |
| MVVM | FlashCardView, FlashCardViewModel, LevelView, LevelViewModel |
Behaviour is tested without Tkinter (ADR-0002) |
| Composition root | app.main |
The one place that names the adapters and shows the level screen |
| Value object | Level, Card, LanguagePair |
Immutable data with its own rule (Level.first) |
Dependency Check
Imports point inward: domain imports nothing from the other packages; application imports domain; infrastructure imports application and domain; presentation imports application and domain, and app.main additionally imports infrastructure. There is no cycle. constants.py imports nothing from the packages.