feat: add learner memory ingestion

This commit is contained in:
user
2026-04-26 16:34:52 +09:00
parent 4a4240fea2
commit 600acf7303
23 changed files with 931 additions and 24 deletions

View File

@@ -9,12 +9,14 @@ import (
"tutor/internal/config"
"tutor/internal/interview"
"tutor/internal/learnermemory"
"tutor/internal/workflows"
)
func TestDiagnosticHTTPFlow(t *testing.T) {
service := interview.NewService(interview.NewMemoryStore(), workflows.NewStubRunner())
handler := NewHandler(config.Config{Environment: "test", ModelKey: "deepseek-v4-flash"}, service)
memory := learnermemory.NewService(learnermemory.NewMemoryStore())
service := interview.NewService(interview.NewMemoryStore(), workflows.NewStubRunner(), memory)
handler := NewHandler(config.Config{Environment: "test", ModelKey: "deepseek-v4-flash"}, service, memory)
routes := handler.Routes()
createBody := bytes.NewBufferString(`{
@@ -73,4 +75,22 @@ func TestDiagnosticHTTPFlow(t *testing.T) {
if len(loaded.Answers) != 1 {
t.Fatalf("answers = %d, want 1", len(loaded.Answers))
}
memoryReq := httptest.NewRequest(http.MethodGet, "/api/v1/learners/user-1/memory", nil)
memoryRec := httptest.NewRecorder()
routes.ServeHTTP(memoryRec, memoryReq)
if memoryRec.Code != http.StatusOK {
t.Fatalf("memory status = %d, body = %s", memoryRec.Code, memoryRec.Body.String())
}
var snapshot learnermemory.Snapshot
if err := json.NewDecoder(memoryRec.Body).Decode(&snapshot); err != nil {
t.Fatalf("decode memory response: %v", err)
}
if snapshot.Profile.UserID != "user-1" {
t.Fatalf("memory profile user = %q", snapshot.Profile.UserID)
}
if len(snapshot.Mastery) == 0 {
t.Fatal("expected mastery entries")
}
}

View File

@@ -6,17 +6,20 @@ import (
"tutor/internal/config"
"tutor/internal/interview"
"tutor/internal/learnermemory"
)
type Handler struct {
cfg config.Config
diagnostic *interview.Service
memory *learnermemory.Service
}
func NewHandler(cfg config.Config, diagnostic *interview.Service) Handler {
func NewHandler(cfg config.Config, diagnostic *interview.Service, memory *learnermemory.Service) Handler {
return Handler{
cfg: cfg,
diagnostic: diagnostic,
memory: memory,
}
}
@@ -26,6 +29,7 @@ func (h Handler) Routes() http.Handler {
mux.HandleFunc("POST /api/v1/diagnostic-sessions", h.createDiagnosticSession)
mux.HandleFunc("GET /api/v1/diagnostic-sessions/{id}", h.getDiagnosticSession)
mux.HandleFunc("POST /api/v1/diagnostic-sessions/{id}/answers", h.submitDiagnosticAnswer)
mux.HandleFunc("GET /api/v1/learners/{userID}/memory", h.getLearnerMemory)
return mux
}

View File

@@ -8,6 +8,7 @@ import (
"tutor/internal/config"
"tutor/internal/interview"
"tutor/internal/learnermemory"
"tutor/internal/workflows"
)
@@ -16,8 +17,9 @@ func TestHealth(t *testing.T) {
Environment: "test",
ModelKey: "deepseek-v4-flash",
}
service := interview.NewService(interview.NewMemoryStore(), workflows.NewStubRunner())
handler := NewHandler(cfg, service)
memory := learnermemory.NewService(learnermemory.NewMemoryStore())
service := interview.NewService(interview.NewMemoryStore(), workflows.NewStubRunner(), memory)
handler := NewHandler(cfg, service, memory)
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
rec := httptest.NewRecorder()

View File

@@ -0,0 +1,27 @@
package httpapi
import (
"errors"
"net/http"
"tutor/internal/learnermemory"
)
func (h Handler) getLearnerMemory(w http.ResponseWriter, r *http.Request) {
if h.memory == nil {
writeError(w, http.StatusNotFound, "learner memory not configured")
return
}
snapshot, err := h.memory.Snapshot(r.PathValue("userID"))
if errors.Is(err, learnermemory.ErrProfileNotFound) {
writeError(w, http.StatusNotFound, "learner memory not found")
return
}
if err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusOK, snapshot)
}