learning

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("learning: not found")

ErrNotFound is returned when a requested entity does not exist.

Functions

func ComputeConceptStatus

func ComputeConceptStatus(reviewCount int, intervalDays float64) string

ComputeConceptStatus maps review metrics to a human-readable learning stage. "new": review_count=0; "learning": 1-4; "reviewing": 5-14; "mastered": review_count>=15 AND interval_days>30. Exported so both Postgres and SQLite stores can use the same logic.

func NextState

func NextState(s CardState, rating Rating) (stability, difficulty float64, intervalDays int)

NextState returns the updated stability, difficulty, and interval in days after performing a review with the given rating.

Types

type CardState

type CardState struct {
	Stability   float64
	Difficulty  float64
	ReviewCount int
}

CardState holds the current FSRS parameters for a flashcard.

type ConceptForReview

type ConceptForReview struct {
	ID          uuid.UUID
	Title       string
	Content     string
	ReviewCount int
	Stability   float64
}

ConceptForReview holds the fields needed by the AI reviewer to evaluate whether a concept has been mastered or is no longer helpful.

type ConceptHistoryRow

type ConceptHistoryRow struct {
	ID              uuid.UUID  `json:"id"`
	Title           string     `json:"title"`
	Tags            []string   `json:"tags"`
	ReviewCount     int        `json:"review_count"`
	FirstReviewedAt *time.Time `json:"first_reviewed_at,omitempty"`
	LastReviewedAt  *time.Time `json:"last_reviewed_at,omitempty"`
	IntervalDays    float64    `json:"interval_days"`
	NextReviewAt    *time.Time `json:"next_review_at,omitempty"`
	Status          string     `json:"status"` // "new"|"learning"|"reviewing"|"mastered"
}

ConceptHistoryRow is returned by ReviewHistory. It combines a concept record with its associated review_schedule statistics.

type DueReview

type DueReview struct {
	ConceptID   uuid.UUID `json:"concept_id"`
	ScheduleID  uuid.UUID `json:"schedule_id"`
	Title       string    `json:"title"`
	Content     string    `json:"content"`
	Stability   float64   `json:"stability"`
	Difficulty  float64   `json:"difficulty"`
	DueDate     time.Time `json:"due_date"`
	ReviewCount int       `json:"review_count"`
}

DueReview represents a concept with its associated review schedule.

type LearningStatsResult

type LearningStatsResult struct {
	TotalReviews  int `json:"total_reviews"`
	Reviews7d     int `json:"reviews_7d"`
	Mastered      int `json:"mastered"`
	TotalConcepts int `json:"total_concepts"`
	StreakDays    int `json:"streak_days"`
}

LearningStatsResult is the response shape for GET /api/learning/stats.

type Rating

type Rating int

Rating represents the quality of a review response.

const (
	// Again means the user forgot the concept entirely.
	Again Rating = 1
	// Hard means the user recalled with significant difficulty.
	Hard Rating = 2
	// Good means the user recalled with some effort.
	Good Rating = 3
	// Easy means the user recalled effortlessly.
	Easy Rating = 4
)

type Store

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

Store handles all database operations for the Learning bounded context.

func NewStore

func NewStore(pool *pgxpool.Pool, workspaceID *uuid.UUID) *Store

NewStore returns a Store backed by the given connection pool scoped to the optional workspace. nil workspaceID = legacy unscoped mode.

func (*Store) CountDueReviews

func (s *Store) CountDueReviews(ctx context.Context) (int, error)

CountDueReviews returns the total number of concepts currently due for review.

func (*Store) CreateConcept

func (s *Store) CreateConcept(ctx context.Context, title, content string, tags []string) (*db.Concept, error)

CreateConcept inserts a concept and its initial review schedule.

func (*Store) DueReviews

func (s *Store) DueReviews(ctx context.Context, limit int) ([]DueReview, error)

DueReviews returns concepts whose review is due, up to the given limit.

func (*Store) GetScheduleState

func (s *Store) GetScheduleState(ctx context.Context, scheduleID uuid.UUID) (CardState, error)

GetScheduleState returns the current CardState for scheduleID, scoped to the store's configured workspace. See StoreIface.GetScheduleState (Ω7 fix) for the full rationale. Raw SQL (not sqlc) matches the pattern already used by ReviewHistory/LearningStats in this same file.

func (*Store) LearningStats

func (s *Store) LearningStats(ctx context.Context) (*LearningStatsResult, error)

LearningStats returns aggregate learning statistics. interval is approximated as stability * 9 (FSRS target-90% formula). StreakDays counts consecutive days (ending today) on which at least one review was submitted, based on last_review_at in review_schedule.

func (*Store) ListConcepts

func (s *Store) ListConcepts(ctx context.Context, limit int) ([]db.Concept, error)

ListConcepts returns up to limit concepts ordered by created_at DESC, scoped to the configured workspace.

func (*Store) ListForAIReview

func (s *Store) ListForAIReview(ctx context.Context, minReviewCount int) ([]ConceptForReview, error)

ListForAIReview returns active concepts that have at least minReviewCount completed reviews, ordered by review count descending.

func (*Store) ReviewHistory

func (s *Store) ReviewHistory(ctx context.Context) ([]ConceptHistoryRow, error)

ReviewHistory returns all concepts joined with their review schedule, sorted by last_review_at DESC NULLS LAST (un-reviewed concepts appear last). interval_days is approximated as stability * 9 (the FSRS target-90%-retention formula used in NextState). Status is computed in Go from review_count and the derived interval.

func (*Store) ReviewedSince

func (s *Store) ReviewedSince(ctx context.Context, since time.Time, limit int) ([]DueReview, error)

ReviewedSince returns DueReview entries whose last_review_at >= since, scoped to the configured workspace. Used by the timeline aggregator.

func (*Store) SoftPruneDecayed

func (s *Store) SoftPruneDecayed(ctx context.Context, cutoff time.Time, strengthThreshold float64) (int64, error)

SoftPruneDecayed implements decay.PrunerStore for the concepts table. It sets archived_at=NOW() on concepts that are:

  • not already archived (archived_at IS NULL)
  • older than cutoff (created_at < cutoff)
  • Ebbinghaus strength < strengthThreshold

Decisions table is NEVER touched by this method.

func (*Store) SubmitReview

func (s *Store) SubmitReview(ctx context.Context, scheduleID uuid.UUID, currentState CardState, rating Rating) error

SubmitReview applies the FSRS algorithm and updates the review schedule.

func (*Store) UpdateConceptStatus

func (s *Store) UpdateConceptStatus(ctx context.Context, id uuid.UUID, status string) error

UpdateConceptStatus sets the status column of the given concept. Valid values are "active", "mastered", and "not_helpful".

func (*Store) WithTx

func (s *Store) WithTx(tx pgx.Tx) *Store

WithTx returns a Store bound to tx, preserving the workspace scope, for use in multi-store transactions (e.g. atomically materializing a concept while resolving a pending proposal).

type StoreIface

type StoreIface interface {
	CreateConcept(ctx context.Context, title, content string, tags []string) (*db.Concept, error)
	DueReviews(ctx context.Context, limit int) ([]DueReview, error)
	// GetScheduleState returns the current CardState for scheduleID, scoped
	// to the store's configured workspace. Returns ErrNotFound when no
	// review_schedule row matches. Ω7 fix (mcp-surface spec, backend-
	// security-design.md §2.1 — LLM tool input is adversarial): submit_review
	// used to accept stability/difficulty/review_count as caller-supplied
	// "current state" params instead of the MCP handler reading them from
	// here; an omitted/zero review_count silently routed a mature schedule
	// through SubmitReview's initial-review branch and overwrote it with a
	// fresh, much shorter interval. SubmitReview's own currentState
	// parameter is unchanged (still used directly by internal/handler's HTTP
	// path and internal/scheduler) — this is an additive method, not a
	// signature change to SubmitReview.
	GetScheduleState(ctx context.Context, scheduleID uuid.UUID) (CardState, error)
	SubmitReview(ctx context.Context, scheduleID uuid.UUID, currentState CardState, rating Rating) error
	CountDueReviews(ctx context.Context) (int, error)
	ListForAIReview(ctx context.Context, minReviewCount int) ([]ConceptForReview, error)
	UpdateConceptStatus(ctx context.Context, id uuid.UUID, status string) error
	// ReviewHistory returns all concepts with their review summary, sorted by
	// last_reviewed_at DESC NULLS LAST (new concepts appear last).
	ReviewHistory(ctx context.Context) ([]ConceptHistoryRow, error)
	// LearningStats returns aggregate stats across all concepts and reviews.
	LearningStats(ctx context.Context) (*LearningStatsResult, error)
	// ListConcepts returns up to limit concepts ordered by created_at DESC,
	// scoped to the configured workspace.
	ListConcepts(ctx context.Context, limit int) ([]db.Concept, error)
	// ReviewedSince returns review_schedule rows where last_review_at >= since,
	// scoped to the configured workspace. Used by the timeline aggregator.
	ReviewedSince(ctx context.Context, since time.Time, limit int) ([]DueReview, error)
}

StoreIface is the backend-agnostic contract for the Learning (FSRS) bounded context.

Jump to

Keyboard shortcuts

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