app

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 25, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package app — export_pack.go implements the export use case.

Exports all questions for a given topic from the database back to the canonical pack file format (YAML or JSON). Questions are sorted deterministically by created_at ASC, then by hash for tie-breaking, ensuring byte-stable output for the same data.

Package app contains application use cases for golearn.

Package app — selection_mode.go defines the question selection modes.

Modes control how questions are chosen for a practice session:

  • Balanced: unseen → weak → random fill (default, formerly "practice")
  • ByDifficulty: filter to a chosen difficulty bucket, then apply Balanced logic
  • Weakest: focus on weakest tag or weakest questions

Package app — selector.go implements the question selection policy.

Strategy (MVP):

  1. Unseen questions (zero prior attempts) — shuffled
  2. Weak questions (highest wrong rate) — sorted descending
  3. Random fill from remaining — shuffled

No duplicates within a session. Capped at n. Uses a supplied random source for test determinism.

Package app — selector_difficulty.go implements the By Difficulty selection mode.

Filters candidate questions to the chosen difficulty bucket, then applies the Balanced selector logic (unseen → weak → random fill) within that set.

Package app — selector_weakest.go implements the Weakest selection mode.

Two sub-modes:

  • Weakest by Tag: find the tag with lowest accuracy, select questions from that tag using Balanced logic within the tag set.
  • Weakest by Questions: rank all questions by wrong rate and select the weakest ones directly. Falls back to Balanced fill if not enough.

Package app — session.go implements the session lifecycle use cases: StartSession, GetNextSessionQuestion, RecordAttempt, EndSession.

The SessionEngine holds in-memory state for the current session's selected question queue and cursor position.

Index

Constants

View Source
const DefaultMinAttempts = 3

DefaultMinAttempts is the minimum number of attempts a question must have before it is considered for weakest-question selection.

View Source
const DefaultMinTagAttempts = 5

DefaultMinTagAttempts is the minimum number of attempts required for a tag to be included in tag-based stats and weakest-tag selection.

Variables

ValidModes lists all accepted selection modes.

Functions

func EnsureDefaultUser

func EnsureDefaultUser(userRepo ports.UserRepository) ([]domain.User, error)

EnsureDefaultUser lists existing users and seeds a "local" profile if none exist. Returns the full user list (always ≥ 1 element).

func ModeDisplayName

func ModeDisplayName(m SelectionMode) string

ModeDisplayName returns the user-facing label for a mode.

func ModeLabel

func ModeLabel(mode SelectionMode, params ModeParams) string

ModeLabel builds a one-line context string for display in the quiz header.

func ModeParamsJSON

func ModeParamsJSON(p ModeParams) string

ModeParamsJSON encodes mode params to a JSON string.

func ResolveUser

func ResolveUser(userRepo ports.UserRepository, configuredUserID int64) (*domain.User, bool, error)

ResolveUser finds the current user from the repository. It first tries the configuredUserID, then falls back to the "local" handle, then to the first available user. Returns the resolved user, whether it matched the configured ID, and any error.

func SelectByDifficulty

func SelectByDifficulty(
	questions []domain.Question,
	stats map[int64]ports.QuestionStats,
	n int,
	difficulty string,
	rng *rand.Rand,
) (selected []domain.Question, note string)

SelectByDifficulty picks up to n questions matching the given difficulty, then applies Balanced selection within that filtered set. Returns the selected questions and a note string (non-empty if N was reduced).

func SelectQuestions

func SelectQuestions(
	questions []domain.Question,
	stats map[int64]ports.QuestionStats,
	n int,
	rng *rand.Rand,
) []domain.Question

SelectQuestions picks up to n questions for a session, prioritizing unseen and weak questions. rng controls shuffle order (use a seeded *rand.Rand for deterministic tests).

func SelectRandomQuestions

func SelectRandomQuestions(
	questions []domain.Question,
	n int,
	rng *rand.Rand,
) []domain.Question

SelectRandomQuestions picks up to n questions in random order, independent of prior attempt statistics.

Types

type CurrentUserProvider

type CurrentUserProvider interface {
	CurrentUserID() int64
	SetCurrentUserID(id int64)
}

CurrentUserProvider provides read/write access to the active local user profile.

type ExportService

type ExportService struct {
	// contains filtered or unexported fields
}

ExportService handles the export use case.

func NewExportService

func NewExportService(topics ports.TopicRepository, questions ports.QuestionRepository) *ExportService

NewExportService creates an export service with the given dependencies.

func (*ExportService) Export

func (s *ExportService) Export(topicSlug, outPath, format string) error

Export writes the canonical pack file for the given topic slug. format must be "yaml" or "json". outPath is the destination file.

func (*ExportService) ExportToBytes

func (s *ExportService) ExportToBytes(topicSlug, format string) ([]byte, error)

ExportToBytes returns the serialized pack as bytes (for testing).

type ImportResult

type ImportResult struct {
	FilesProcessed int
	Inserted       int
	Duplicates     int
	Invalid        int
	Errors         []string
}

ImportResult summarizes the outcome of an import operation.

type ImportService

type ImportService struct {
	// contains filtered or unexported fields
}

ImportService handles the import use case.

func NewImportService

func NewImportService(reader ports.PackReader, topics ports.TopicRepository, questions ports.QuestionRepository) *ImportService

NewImportService creates an import service with the given dependencies.

func (*ImportService) Import

func (s *ImportService) Import(path string) (*ImportResult, error)

Import loads packs from a file or directory, validates, normalises, and persists.

func (*ImportService) ImportFromFS

func (s *ImportService) ImportFromFS(fsys fs.FS) (*ImportResult, error)

ImportFromFS imports all YAML pack files from an fs.FS (e.g. an embedded file system). This is the same pipeline as [Import] but reads from the provided FS instead of the host filesystem.

type ModeParams

type ModeParams struct {
	Difficulty string         `json:"difficulty,omitempty"`
	WeakestSub WeakestSubMode `json:"weakest_sub,omitempty"`
	WeakestTag string         `json:"weakest_tag,omitempty"` // resolved tag name
}

ModeParams is the serialisable form of mode-specific parameters, persisted as JSON in the sessions table.

type SelectionMode

type SelectionMode string

SelectionMode enumerates supported question selection strategies.

const (
	// ModeBalanced selects questions using unseen → weak → random fill.
	ModeBalanced SelectionMode = "balanced"
	// ModeRandom selects questions in random order, ignoring prior attempts.
	ModeRandom SelectionMode = "random"
	// ModeByDifficulty filters to a chosen difficulty, then applies Balanced.
	ModeByDifficulty SelectionMode = "by_difficulty"
	// ModeWeakest focuses on the user's weakest tag or questions.
	ModeWeakest SelectionMode = "weakest"
)

type SessionConfig

type SessionConfig struct {
	TopicSlug string
	N         int
	Mode      SelectionMode

	// ByDifficulty params — only used when Mode == ModeByDifficulty.
	Difficulty string // "easy", "medium", or "hard"

	// Weakest params — only used when Mode == ModeWeakest.
	WeakestSub WeakestSubMode
}

SessionConfig holds all parameters needed to start a session.

type SessionEngine

type SessionEngine struct {
	// contains filtered or unexported fields
}

SessionEngine manages the lifecycle of a practice session.

func NewSessionEngine

func NewSessionEngine(
	topics ports.TopicRepository,
	questions ports.QuestionRepository,
	sessions ports.SessionRepository,
	attempts ports.AttemptRepository,
	userCtx CurrentUserProvider,
	rng *rand.Rand,
) *SessionEngine

NewSessionEngine creates a session engine with the given dependencies. If rng is nil, a time-seeded source is used.

func (*SessionEngine) ActiveMode

func (e *SessionEngine) ActiveMode() SelectionMode

ActiveMode returns the resolved selection mode for the current session.

func (*SessionEngine) ActiveModeParams

func (e *SessionEngine) ActiveModeParams() ModeParams

ActiveModeParams returns the resolved mode parameters for the current session.

func (*SessionEngine) EndSession

func (e *SessionEngine) EndSession() error

EndSession marks the session as finished.

func (*SessionEngine) EndSessionByID

func (e *SessionEngine) EndSessionByID(sessionID int64) error

EndSessionByID marks a specific session as finished and unloads in-memory state.

func (*SessionEngine) GetNextSessionQuestion

func (e *SessionEngine) GetNextSessionQuestion() *SessionQuestion

GetNextSessionQuestion returns the next session question in the queue, or nil when all questions have been served.

func (*SessionEngine) GetNextSessionQuestionForSession

func (e *SessionEngine) GetNextSessionQuestionForSession(sessionID int64) (*SessionQuestion, error)

GetNextSessionQuestionForSession returns next question for a specific session.

func (*SessionEngine) LoadSession

func (e *SessionEngine) LoadSession(sessionID int64) error

LoadSession loads a persisted session into engine memory and sets it active. It enables resuming sessions across engine instances.

func (*SessionEngine) ModeNote

func (e *SessionEngine) ModeNote() string

ModeNote returns any informational note from the selector (e.g., reduced N).

func (*SessionEngine) QueueLength

func (e *SessionEngine) QueueLength() int

QueueLength returns the total number of questions selected for this session.

func (*SessionEngine) QueueLengthForSession

func (e *SessionEngine) QueueLengthForSession(sessionID int64) (int, error)

QueueLengthForSession returns question count for a specific session.

func (*SessionEngine) RecordAttempt

func (e *SessionEngine) RecordAttempt(
	questionID int64,
	selectedChoiceIDs []string,
	skipped bool,
	latencyMs int,
) (bool, error)

RecordAttempt evaluates correctness and persists the attempt.

func (*SessionEngine) RecordAttemptForSession

func (e *SessionEngine) RecordAttemptForSession(
	sessionID int64,
	questionID int64,
	selectedChoiceIDs []string,
	skipped bool,
	latencyMs int,
) (bool, error)

RecordAttemptForSession evaluates correctness and persists an attempt for a specific session.

func (*SessionEngine) StartSession

func (e *SessionEngine) StartSession(topicSlug string, n int, mode string) (int64, error)

StartSession validates the topic, selects questions, persists a session row, and returns the session ID. mode should be "balanced" (or legacy "practice"). This method uses Balanced mode for backward compatibility.

func (*SessionEngine) StartSessionWithConfig

func (e *SessionEngine) StartSessionWithConfig(cfg SessionConfig) (int64, error)

StartSessionWithConfig validates the topic, selects questions using the specified mode, persists a session row, and returns the session ID.

func (*SessionEngine) WithStatsRepo

func (e *SessionEngine) WithStatsRepo(sr ports.StatsRepository) *SessionEngine

WithStatsRepo sets an optional stats repository for weakest-by-tag selection.

type SessionQuestion

type SessionQuestion struct {
	Question        *domain.Question
	ShuffledChoices []domain.Choice
}

SessionQuestion holds session-scoped display state for one question. It preserves a pointer to the original question while allowing per-session shuffled presentation of choices.

type UserContext

type UserContext struct {
	// contains filtered or unexported fields
}

UserContext stores the current user ID in memory.

func NewUserContext

func NewUserContext(initialUserID int64) *UserContext

NewUserContext creates a UserContext with an initial user ID.

func (*UserContext) CurrentUserID

func (c *UserContext) CurrentUserID() int64

CurrentUserID returns the active user ID.

func (*UserContext) SetCurrentUserID

func (c *UserContext) SetCurrentUserID(id int64)

SetCurrentUserID updates the active user ID.

type WeakestResult

type WeakestResult struct {
	Questions []domain.Question
	Note      string // informational note (e.g., fallback notice)
	Tag       string // the resolved weakest tag, if tag mode was used
}

WeakestResult holds the output of a weakest selection.

func SelectWeakestByQuestions

func SelectWeakestByQuestions(
	questions []domain.Question,
	stats map[int64]ports.QuestionStats,
	n int,
	minAttempts int,
	rng *rand.Rand,
) WeakestResult

SelectWeakestByQuestions selects questions ranked by wrong rate. Candidates must have attempts >= minAttempts. If not enough weak questions exist, fills remainder with Balanced selection.

func SelectWeakestByTag

func SelectWeakestByTag(
	questions []domain.Question,
	stats map[int64]ports.QuestionStats,
	tagStats []ports.TagStat,
	n int,
	rng *rand.Rand,
) WeakestResult

SelectWeakestByTag selects questions from the weakest tag. tagStats must already be filtered by minAttempts. If no valid tag is found, falls back to SelectWeakestByQuestions.

type WeakestSubMode

type WeakestSubMode string

WeakestSubMode distinguishes between tag-based and question-based weakest.

const (
	// WeakestByTag selects questions from the weakest tag.
	WeakestByTag WeakestSubMode = "by_tag"
	// WeakestByQuestion selects individual questions with the highest wrong rate.
	WeakestByQuestion WeakestSubMode = "by_question"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL