Files

114 lines
2.9 KiB
Go
Raw Permalink Normal View History

2026-04-26 16:34:52 +09:00
package learnermemory
import (
"errors"
"fmt"
"strings"
"sync/atomic"
"time"
"tutor/internal/workflows"
)
type Service struct {
store Store
ids atomic.Uint64
}
func NewService(store Store) *Service {
return &Service{store: store}
}
func (s *Service) EnsureProfile(input ProfileInput) (Profile, error) {
if strings.TrimSpace(input.UserID) == "" {
return Profile{}, errors.New("user_id is required")
}
if strings.TrimSpace(input.TargetRole) == "" {
return Profile{}, errors.New("target_role is required")
}
profile := Profile{
UserID: input.UserID,
TargetRole: input.TargetRole,
Stack: append([]string(nil), input.Stack...),
InterviewTimeline: input.InterviewTimeline,
Preferences: append([]string(nil), input.Preferences...),
UpdatedAt: time.Now().UTC(),
}
return s.store.UpsertProfile(profile)
}
func (s *Service) ApplyCandidate(candidate workflows.MemoryUpdateCandidate) error {
if strings.TrimSpace(candidate.UserID) == "" {
return errors.New("candidate user_id is required")
}
for _, update := range candidate.Updates {
if len(update.Evidence) == 0 {
continue
}
if strings.TrimSpace(update.Concept.ID) == "" {
continue
}
if err := s.applyUpdate(candidate.UserID, update); err != nil {
return err
}
}
return nil
}
func (s *Service) Snapshot(userID string) (Snapshot, error) {
if strings.TrimSpace(userID) == "" {
return Snapshot{}, errors.New("user_id is required")
}
return s.store.Snapshot(userID)
}
func (s *Service) applyUpdate(userID string, update workflows.MemoryUpdate) error {
now := time.Now().UTC()
switch update.Kind {
case workflows.MemoryConceptMastery:
return s.store.UpsertMastery(ConceptMastery{
UserID: userID,
Concept: update.Concept,
State: update.ProposedState,
Evidence: append([]workflows.EvidenceRef(nil), update.Evidence...),
UpdatedAt: now,
})
case workflows.MemoryMisconception:
return s.store.AddMisconception(Misconception{
ID: s.nextID("misconception"),
UserID: userID,
Concept: update.Concept,
Label: update.Summary,
Description: update.Summary,
Evidence: append([]workflows.EvidenceRef(nil), update.Evidence...),
UpdatedAt: now,
})
case workflows.MemoryIntervention:
return s.store.AddIntervention(Intervention{
ID: s.nextID("intervention"),
UserID: userID,
Concept: update.Concept,
Summary: update.Summary,
Evidence: append([]workflows.EvidenceRef(nil), update.Evidence...),
UpdatedAt: now,
})
case workflows.MemoryReviewSchedule:
return s.store.AddReviewSchedule(ReviewSchedule{
ID: s.nextID("review"),
UserID: userID,
Concept: update.Concept,
Reason: update.Summary,
Evidence: append([]workflows.EvidenceRef(nil), update.Evidence...),
UpdatedAt: now,
})
default:
return nil
}
}
func (s *Service) nextID(prefix string) string {
return fmt.Sprintf("%s-%d", prefix, s.ids.Add(1))
}