You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
gitea 3c5e13edd3
readme
3 months ago
android first 3 months ago
lib first 3 months ago
packages first 3 months ago
.gitignore first 3 months ago
.metadata first 3 months ago
README.md readme 3 months ago
analysis_options.yaml first 3 months ago
pubspec.lock first 3 months ago
pubspec.yaml first 3 months ago

README.md

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:

dependencies:
  practice_engine:
    path: packages/practice_engine  # For local development
    # Or use pub.dev if published:
    # practice_engine: ^1.0.0

Then run:

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).

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.

// 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

// 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).

// 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.

// Reset all learning progress (streaks, priority, known status)
final resetDeck = DeckService.resetDeck(
  deck: deck,
  resetAttemptCounts: false, // Keep attempt history if true
);

Complete Example

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:

# 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:

dart test

Follow the existing code style and maintain test coverage for new features.

Powered by TurnKey Linux.