# Practice Engine A pure Dart package implementing spaced repetition, weighted selection, and practice tracking for question decks. This package provides a complete solution for building quiz and flashcard applications with intelligent question selection based on learning progress. ## What It Does The Practice Engine helps you build applications where users practice questions (like flashcards or quizzes) with an intelligent system that: - **Adapts to learning progress**: Questions you struggle with appear more frequently, while mastered questions appear less often - **Uses spaced repetition**: Even "known" questions occasionally reappear to reinforce memory - **Tracks comprehensive statistics**: Monitor progress, streaks, and overall deck mastery - **Provides flexible configuration**: Customize behavior per deck to match different learning styles The engine is designed to be pure Dart (no Flutter dependencies), making it suitable for any Dart application including CLI tools, web apps, and mobile applications. ## Features - **Spaced Repetition**: Known questions have low but increasing probability of appearing based on time since last seen - **Weighted Random Selection**: Questions are selected based on priority points and spaced repetition logic - **Priority Scoring**: Questions gain priority when answered incorrectly and lose it when answered correctly - **Question Streak Logic**: Tracks consecutive correct answers to determine when a question is "known" - **Attempt/Quiz Logic**: Create and process practice attempts with comprehensive result tracking - **Deck Progress Tracking**: Track overall deck progress with practice percentage - **Deck Configuration**: Customizable settings per deck (streak threshold, attempt size, priority changes, etc.) - **Manual Overrides**: Manually mark questions as known or needs practice - **Reset Behavior**: Reset deck state with optional attempt count preservation ## Installation Add this package to your `pubspec.yaml`: ```yaml dependencies: practice_engine: path: packages/practice_engine # For local development # Or use pub.dev if published: # practice_engine: ^1.0.0 ``` Then run: ```bash dart pub get ``` ## Requirements - Dart SDK >= 3.0.0 ## Usage ### Creating a Deck A deck contains questions and configuration. Each question tracks its learning state (streaks, priority, known status). ```dart import 'package:practice_engine/practice_engine.dart'; // Configure deck behavior final config = DeckConfig( requiredConsecutiveCorrect: 3, // Need 3 correct in a row to mark as "known" defaultAttemptSize: 10, // Default questions per practice session priorityIncreaseOnIncorrect: 5, // Add 5 priority points when wrong priorityDecreaseOnCorrect: 2, // Remove 2 priority points when correct immediateFeedbackEnabled: true, // Show feedback immediately ); // Create questions final questions = [ Question( id: 'q1', prompt: 'What is 2+2?', answers: ['3', '4', '5'], correctAnswerIndex: 1, // Answer at index 1 is correct ), Question( id: 'q2', prompt: 'What is the capital of France?', answers: ['London', 'Paris', 'Berlin'], correctAnswerIndex: 1, ), // ... more questions ]; // Create the deck final deck = Deck( id: 'math-deck', title: 'Math Basics', description: 'Basic arithmetic questions', questions: questions, config: config, ); ``` ### Creating and Processing an Attempt An attempt represents a practice session. The engine intelligently selects questions based on priority and spaced repetition. ```dart // Create attempt service (optionally with seed for reproducible randomness) final attemptService = AttemptService(seed: 42); // Create a new practice attempt final attempt = attemptService.createAttempt(deck: deck); // Or specify custom attempt size: // final attempt = attemptService.createAttempt(deck: deck, attemptSize: 5); // Display questions to user and collect answers // answers maps questionId -> userSelectedAnswerIndex final answers = { 'q1': 1, // User selected answer at index 1 'q2': 0, // User selected answer at index 0 // ... }; // Process the attempt and get results final result = attemptService.processAttempt( deck: deck, attempt: attempt, answers: answers, ); // Access the updated deck (with learning progress applied) final updatedDeck = result.updatedDeck; // Access attempt results print('Score: ${result.result.percentageCorrect}%'); print('Correct: ${result.result.correctCount}/${result.result.totalQuestions}'); print('Deck progress: ${updatedDeck.practicePercentage.toStringAsFixed(1)}%'); ``` ### Accessing Deck Statistics ```dart // Get overall deck statistics print('Total questions: ${deck.numberOfQuestions}'); print('Known questions: ${deck.knownCount}'); print('Practice percentage: ${deck.practicePercentage}%'); print('Current attempt index: ${deck.currentAttemptIndex}'); // Access individual question state final question = deck.questions.firstWhere((q) => q.id == 'q1'); print('Consecutive correct: ${question.consecutiveCorrect}'); print('Priority points: ${question.priorityPoints}'); print('Total attempts: ${question.totalAttempts}'); print('Is known: ${question.isKnown}'); ``` ### Manual Overrides Manually control question state when needed (e.g., user wants to mark something as learned). ```dart // Mark question as known (resets streak, sets isKnown=true) final updatedDeck = DeckService.markQuestionAsKnown( deck: deck, questionId: 'q1', ); // Mark question as needs practice (resets streak, sets isKnown=false, adds priority) final updatedDeck2 = DeckService.markQuestionAsNeedsPractice( deck: deck, questionId: 'q1', ); ``` ### Resetting a Deck Reset learning progress while optionally preserving attempt history. ```dart // Reset all learning progress (streaks, priority, known status) final resetDeck = DeckService.resetDeck( deck: deck, resetAttemptCounts: false, // Keep attempt history if true ); ``` ### Complete Example ```dart import 'package:practice_engine/practice_engine.dart'; void main() { // Setup final config = const DeckConfig( requiredConsecutiveCorrect: 2, defaultAttemptSize: 5, ); final questions = [ Question(id: 'q1', prompt: '2+2?', answers: ['3', '4'], correctAnswerIndex: 1), Question(id: 'q2', prompt: '3+3?', answers: ['5', '6'], correctAnswerIndex: 1), Question(id: 'q3', prompt: '4+4?', answers: ['7', '8'], correctAnswerIndex: 1), ]; var deck = Deck( id: 'example', title: 'Example Deck', description: 'Simple math', questions: questions, config: config, ); // Practice session final service = AttemptService(); final attempt = service.createAttempt(deck: deck); // Simulate user answers (all correct) final answers = { for (var q in attempt.questions) q.id: q.correctAnswerIndex }; final result = service.processAttempt( deck: deck, attempt: attempt, answers: answers, ); deck = result.updatedDeck; print('Score: ${result.result.percentageCorrect}%'); print('Deck progress: ${deck.practicePercentage.toStringAsFixed(1)}%'); } ``` ## Architecture The package is organized into three main layers: - **Models** (`lib/models/`): Immutable data classes representing the domain - `Deck`: Container for questions and configuration - `Question`: Individual question with learning state - `Attempt`: A practice session - `AttemptResult`: Results from processing an attempt - `DeckConfig`: Configuration settings - **Algorithms** (`lib/algorithms/`): Pure functions for calculations - `WeightedSelector`: Selects questions based on priority and spaced repetition - `SpacedRepetition`: Calculates probability for known questions - `PriorityManager`: Manages priority point calculations - **Services** (`lib/logic/`): Business logic and orchestration - `DeckService`: Deck-level operations (resets, manual overrides) - `AttemptService`: Attempt creation and processing All models are immutable and use `copyWith` for updates, ensuring predictable state management. ## Testing ### Running Tests From the `packages/practice_engine` directory: ```bash # Run all tests dart test # Run tests with coverage dart test --coverage=coverage # Run a specific test file dart test test/deck_test.dart # Run tests in watch mode (requires dart_test package) dart test --watch ``` ### Test Coverage The package includes comprehensive test coverage for: - Deck creation and statistics - Question state management - Attempt creation and processing - Priority and spaced repetition algorithms - Manual overrides - Reset functionality - Configuration validation Test files are located in the `test/` directory and mirror the structure of the `lib/` directory. ## Project Structure ``` practice_engine/ ├── lib/ │ ├── algorithms/ # Core algorithms (selection, repetition, priority) │ ├── logic/ # Business logic services │ ├── models/ # Data models │ └── practice_engine.dart # Main export file ├── test/ # Test files ├── coverage/ # Test coverage reports ├── pubspec.yaml # Package configuration └── README.md # This file ``` ## Integration This package is designed to be used as a library. To use it in your application: 1. **Flutter Apps**: Add to `pubspec.yaml` and import as shown in usage examples 2. **Dart CLI Tools**: Same as Flutter, just import and use 3. **Web Apps**: Compatible with Dart web compilation The package has no external dependencies beyond the Dart SDK, making it lightweight and easy to integrate. ## Contributing When contributing, ensure all tests pass: ```bash dart test ``` Follow the existing code style and maintain test coverage for new features.