feat: add progression readiness api

This commit is contained in:
user
2026-04-26 16:39:19 +09:00
parent 600acf7303
commit a413f1ef15
16 changed files with 637 additions and 15 deletions

View File

@@ -10,13 +10,15 @@ import (
"tutor/internal/config"
"tutor/internal/interview"
"tutor/internal/learnermemory"
"tutor/internal/progression"
"tutor/internal/workflows"
)
func TestDiagnosticHTTPFlow(t *testing.T) {
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)
progress := progression.NewService(memory)
handler := NewHandler(config.Config{Environment: "test", ModelKey: "deepseek-v4-flash"}, service, memory, progress)
routes := handler.Routes()
createBody := bytes.NewBufferString(`{
@@ -93,4 +95,20 @@ func TestDiagnosticHTTPFlow(t *testing.T) {
if len(snapshot.Mastery) == 0 {
t.Fatal("expected mastery entries")
}
readinessReq := httptest.NewRequest(http.MethodGet, "/api/v1/learners/user-1/readiness-map", nil)
readinessRec := httptest.NewRecorder()
routes.ServeHTTP(readinessRec, readinessReq)
if readinessRec.Code != http.StatusOK {
t.Fatalf("readiness status = %d, body = %s", readinessRec.Code, readinessRec.Body.String())
}
challengeReq := httptest.NewRequest(http.MethodGet, "/api/v1/learners/user-1/next-challenge", nil)
challengeRec := httptest.NewRecorder()
routes.ServeHTTP(challengeRec, challengeReq)
if challengeRec.Code != http.StatusOK {
t.Fatalf("challenge status = %d, body = %s", challengeRec.Code, challengeRec.Body.String())
}
}

View File

@@ -7,19 +7,27 @@ import (
"tutor/internal/config"
"tutor/internal/interview"
"tutor/internal/learnermemory"
"tutor/internal/progression"
)
type Handler struct {
cfg config.Config
diagnostic *interview.Service
memory *learnermemory.Service
progress *progression.Service
}
func NewHandler(cfg config.Config, diagnostic *interview.Service, memory *learnermemory.Service) Handler {
func NewHandler(
cfg config.Config,
diagnostic *interview.Service,
memory *learnermemory.Service,
progress *progression.Service,
) Handler {
return Handler{
cfg: cfg,
diagnostic: diagnostic,
memory: memory,
progress: progress,
}
}
@@ -30,6 +38,8 @@ func (h Handler) Routes() http.Handler {
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)
mux.HandleFunc("GET /api/v1/learners/{userID}/readiness-map", h.getReadinessMap)
mux.HandleFunc("GET /api/v1/learners/{userID}/next-challenge", h.getNextChallenge)
return mux
}

View File

@@ -9,6 +9,7 @@ import (
"tutor/internal/config"
"tutor/internal/interview"
"tutor/internal/learnermemory"
"tutor/internal/progression"
"tutor/internal/workflows"
)
@@ -19,7 +20,8 @@ func TestHealth(t *testing.T) {
}
memory := learnermemory.NewService(learnermemory.NewMemoryStore())
service := interview.NewService(interview.NewMemoryStore(), workflows.NewStubRunner(), memory)
handler := NewHandler(cfg, service, memory)
progress := progression.NewService(memory)
handler := NewHandler(cfg, service, memory, progress)
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
rec := httptest.NewRecorder()

View File

@@ -0,0 +1,46 @@
package httpapi
import (
"errors"
"net/http"
"tutor/internal/learnermemory"
)
func (h Handler) getReadinessMap(w http.ResponseWriter, r *http.Request) {
if h.progress == nil {
writeError(w, http.StatusNotFound, "progression not configured")
return
}
readiness, err := h.progress.ReadinessMap(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, readiness)
}
func (h Handler) getNextChallenge(w http.ResponseWriter, r *http.Request) {
if h.progress == nil {
writeError(w, http.StatusNotFound, "progression not configured")
return
}
challenge, err := h.progress.NextChallenge(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, challenge)
}