Files

175 lines
4.5 KiB
Go
Raw Permalink Normal View History

2026-04-26 16:34:52 +09:00
package learnermemory
import (
"errors"
"sync"
"tutor/internal/workflows"
)
var ErrProfileNotFound = errors.New("learner profile not found")
type Store interface {
UpsertProfile(Profile) (Profile, error)
GetProfile(string) (Profile, error)
UpsertMastery(ConceptMastery) error
AddMisconception(Misconception) error
AddIntervention(Intervention) error
AddReviewSchedule(ReviewSchedule) error
Snapshot(string) (Snapshot, error)
}
type MemoryStore struct {
mu sync.RWMutex
profiles map[string]Profile
mastery map[string]map[string]ConceptMastery
misconceptions map[string][]Misconception
interventions map[string][]Intervention
reviewSchedules map[string][]ReviewSchedule
}
func NewMemoryStore() *MemoryStore {
return &MemoryStore{
profiles: make(map[string]Profile),
mastery: make(map[string]map[string]ConceptMastery),
misconceptions: make(map[string][]Misconception),
interventions: make(map[string][]Intervention),
reviewSchedules: make(map[string][]ReviewSchedule),
}
}
func (s *MemoryStore) UpsertProfile(profile Profile) (Profile, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.profiles[profile.UserID] = cloneProfile(profile)
return profile, nil
}
func (s *MemoryStore) GetProfile(userID string) (Profile, error) {
s.mu.RLock()
defer s.mu.RUnlock()
profile, ok := s.profiles[userID]
if !ok {
return Profile{}, ErrProfileNotFound
}
return cloneProfile(profile), nil
}
func (s *MemoryStore) UpsertMastery(mastery ConceptMastery) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.mastery[mastery.UserID]; !ok {
s.mastery[mastery.UserID] = make(map[string]ConceptMastery)
}
s.mastery[mastery.UserID][mastery.Concept.ID] = cloneMastery(mastery)
return nil
}
func (s *MemoryStore) AddMisconception(misconception Misconception) error {
s.mu.Lock()
defer s.mu.Unlock()
s.misconceptions[misconception.UserID] = append(
s.misconceptions[misconception.UserID],
cloneMisconception(misconception),
)
return nil
}
func (s *MemoryStore) AddIntervention(intervention Intervention) error {
s.mu.Lock()
defer s.mu.Unlock()
s.interventions[intervention.UserID] = append(
s.interventions[intervention.UserID],
cloneIntervention(intervention),
)
return nil
}
func (s *MemoryStore) AddReviewSchedule(schedule ReviewSchedule) error {
s.mu.Lock()
defer s.mu.Unlock()
s.reviewSchedules[schedule.UserID] = append(
s.reviewSchedules[schedule.UserID],
cloneReviewSchedule(schedule),
)
return nil
}
func (s *MemoryStore) Snapshot(userID string) (Snapshot, error) {
s.mu.RLock()
defer s.mu.RUnlock()
profile, ok := s.profiles[userID]
if !ok {
return Snapshot{}, ErrProfileNotFound
}
snapshot := Snapshot{
Profile: cloneProfile(profile),
Mastery: make([]ConceptMastery, 0, len(s.mastery[userID])),
Misconceptions: cloneMisconceptions(s.misconceptions[userID]),
Interventions: cloneInterventions(s.interventions[userID]),
ReviewSchedule: cloneReviewSchedules(s.reviewSchedules[userID]),
}
for _, mastery := range s.mastery[userID] {
snapshot.Mastery = append(snapshot.Mastery, cloneMastery(mastery))
}
return snapshot, nil
}
func cloneProfile(profile Profile) Profile {
profile.Stack = append([]string(nil), profile.Stack...)
profile.Preferences = append([]string(nil), profile.Preferences...)
return profile
}
func cloneMastery(mastery ConceptMastery) ConceptMastery {
mastery.Evidence = append([]workflows.EvidenceRef(nil), mastery.Evidence...)
return mastery
}
func cloneMisconception(misconception Misconception) Misconception {
misconception.Evidence = append([]workflows.EvidenceRef(nil), misconception.Evidence...)
return misconception
}
func cloneIntervention(intervention Intervention) Intervention {
intervention.Evidence = append([]workflows.EvidenceRef(nil), intervention.Evidence...)
return intervention
}
func cloneReviewSchedule(schedule ReviewSchedule) ReviewSchedule {
schedule.Evidence = append([]workflows.EvidenceRef(nil), schedule.Evidence...)
return schedule
}
func cloneMisconceptions(items []Misconception) []Misconception {
cloned := make([]Misconception, len(items))
for i, item := range items {
cloned[i] = cloneMisconception(item)
}
return cloned
}
func cloneInterventions(items []Intervention) []Intervention {
cloned := make([]Intervention, len(items))
for i, item := range items {
cloned[i] = cloneIntervention(item)
}
return cloned
}
func cloneReviewSchedules(items []ReviewSchedule) []ReviewSchedule {
cloned := make([]ReviewSchedule, len(items))
for i, item := range items {
cloned[i] = cloneReviewSchedule(item)
}
return cloned
}