Boost Azure Demo
This commit is contained in:
375
Backend/internal/handlers/api/assignments/handler_helpers.go
Normal file
375
Backend/internal/handlers/api/assignments/handler_helpers.go
Normal file
@@ -0,0 +1,375 @@
|
||||
package assignments
|
||||
|
||||
import (
|
||||
"boostai-backend/internal/assignmentgen"
|
||||
"boostai-backend/internal/handlers/api/shared"
|
||||
"boostai-backend/internal/sqlc"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
func mapAssignment(assignment sqlc.Assignment) AssignmentResponse {
|
||||
return AssignmentResponse{
|
||||
ID: assignment.ID,
|
||||
ClassroomID: assignment.ClassroomID,
|
||||
TeacherID: assignment.TeacherID,
|
||||
Title: assignment.Title,
|
||||
Instructions: shared.TextPointer(assignment.Instructions),
|
||||
PassThreshold: shared.NumericPointer(assignment.PassThreshold),
|
||||
Status: string(assignment.Status),
|
||||
DueAt: shared.TimePointer(assignment.DueAt),
|
||||
PublishedAt: shared.TimePointer(assignment.PublishedAt),
|
||||
CreatedAt: shared.TimePointer(assignment.CreatedAt),
|
||||
UpdatedAt: shared.TimePointer(assignment.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func parseQuestionTopicValue(value string) (sqlc.QuestionTopic, error) {
|
||||
switch strings.TrimSpace(strings.ToLower(value)) {
|
||||
case string(sqlc.QuestionTopicPlaceValue):
|
||||
return sqlc.QuestionTopicPlaceValue, nil
|
||||
case string(sqlc.QuestionTopicArithmetic):
|
||||
return sqlc.QuestionTopicArithmetic, nil
|
||||
case string(sqlc.QuestionTopicNegativeNumbers):
|
||||
return sqlc.QuestionTopicNegativeNumbers, nil
|
||||
case string(sqlc.QuestionTopicBidmas):
|
||||
return sqlc.QuestionTopicBidmas, nil
|
||||
case string(sqlc.QuestionTopicFractions):
|
||||
return sqlc.QuestionTopicFractions, nil
|
||||
case string(sqlc.QuestionTopicAlgebra):
|
||||
return sqlc.QuestionTopicAlgebra, nil
|
||||
case string(sqlc.QuestionTopicGeometry):
|
||||
return sqlc.QuestionTopicGeometry, nil
|
||||
case string(sqlc.QuestionTopicData):
|
||||
return sqlc.QuestionTopicData, nil
|
||||
default:
|
||||
return "", fmt.Errorf("primary_topic must be one of place_value, arithmetic, negative_numbers, bidmas, fractions, algebra, geometry, or data")
|
||||
}
|
||||
}
|
||||
|
||||
func parseQuestionDifficultyValue(value string) (sqlc.QuestionDifficulty, error) {
|
||||
switch strings.TrimSpace(strings.ToLower(value)) {
|
||||
case string(sqlc.QuestionDifficultyEasy):
|
||||
return sqlc.QuestionDifficultyEasy, nil
|
||||
case string(sqlc.QuestionDifficultyMedium):
|
||||
return sqlc.QuestionDifficultyMedium, nil
|
||||
case string(sqlc.QuestionDifficultyHard):
|
||||
return sqlc.QuestionDifficultyHard, nil
|
||||
default:
|
||||
return "", fmt.Errorf("difficulty must be one of easy, medium, or hard")
|
||||
}
|
||||
}
|
||||
|
||||
func parseQuestionStatusValue(value string) (sqlc.QuestionStatus, error) {
|
||||
switch strings.TrimSpace(strings.ToLower(value)) {
|
||||
case string(sqlc.QuestionStatusDraft):
|
||||
return sqlc.QuestionStatusDraft, nil
|
||||
case string(sqlc.QuestionStatusPublished):
|
||||
return sqlc.QuestionStatusPublished, nil
|
||||
case string(sqlc.QuestionStatusArchived):
|
||||
return sqlc.QuestionStatusArchived, nil
|
||||
default:
|
||||
return "", fmt.Errorf("question_status must be one of draft, published, or archived")
|
||||
}
|
||||
}
|
||||
|
||||
func mapAssignmentGenerationWeaknessSummary(summary assignmentgen.WeaknessSummary) mixedPlanWeaknessSummaryResponse {
|
||||
topicScores := make(map[string]float64, len(summary.TopicScores))
|
||||
for topic, score := range summary.TopicScores {
|
||||
topicScores[string(topic)] = score
|
||||
}
|
||||
|
||||
return mixedPlanWeaknessSummaryResponse{
|
||||
TopicScores: topicScores,
|
||||
WeakTags: append([]string(nil), summary.WeakTags...),
|
||||
RecentIssues: append([]string(nil), summary.RecentIssues...),
|
||||
}
|
||||
}
|
||||
|
||||
func questionTopicPointer(topic sqlc.NullQuestionTopic) *string {
|
||||
if !topic.Valid {
|
||||
return nil
|
||||
}
|
||||
value := string(topic.QuestionTopic)
|
||||
return &value
|
||||
}
|
||||
|
||||
func questionDifficultyPointer(difficulty sqlc.NullQuestionDifficulty) *string {
|
||||
if !difficulty.Valid {
|
||||
return nil
|
||||
}
|
||||
value := string(difficulty.QuestionDifficulty)
|
||||
return &value
|
||||
}
|
||||
|
||||
func personalizedRatioValue(value *float64) float64 {
|
||||
if value == nil || *value == 0 {
|
||||
return 0.30
|
||||
}
|
||||
return *value
|
||||
}
|
||||
|
||||
func int64Value(value *int64) int64 {
|
||||
if value == nil {
|
||||
return 0
|
||||
}
|
||||
return *value
|
||||
}
|
||||
|
||||
func int64Pointer(value int64) *int64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
func stringPointer(value string) *string {
|
||||
return &value
|
||||
}
|
||||
|
||||
func trimmedPointerValue(value *string) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(*value)
|
||||
}
|
||||
|
||||
func mapAssignmentQuestion(question sqlc.ListQuestionsForAssignmentRow) AssignmentQuestionResponse {
|
||||
return AssignmentQuestionResponse{
|
||||
AssignmentID: question.AssignmentID,
|
||||
QuestionID: question.QuestionID,
|
||||
Position: question.Position,
|
||||
AuthorTeacherID: question.AuthorTeacherID,
|
||||
Title: question.Title,
|
||||
Prompt: question.Prompt,
|
||||
Subject: shared.TextPointer(question.Subject),
|
||||
Source: shared.TextPointer(question.Source),
|
||||
QuestionStatus: string(question.Status),
|
||||
QuestionCreatedAt: shared.TimePointer(question.CreatedAt),
|
||||
QuestionUpdatedAt: shared.TimePointer(question.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func mapAssignmentStudentQuestionDetail(row sqlc.ListQuestionDetailsForAssignmentStudentRow, studentID int64) AssignmentStudentQuestionDetailResponse {
|
||||
var answerStatus *string
|
||||
if row.AnswerStatus.Valid {
|
||||
status := string(row.AnswerStatus.AnswerStatus)
|
||||
answerStatus = &status
|
||||
}
|
||||
|
||||
var passStatus *string
|
||||
if row.PassStatus.Valid {
|
||||
status := string(row.PassStatus.AssignmentPassStatus)
|
||||
passStatus = &status
|
||||
}
|
||||
|
||||
var passStatusOverride *string
|
||||
if row.PassStatusOverride.Valid {
|
||||
status := string(row.PassStatusOverride.AssignmentPassStatus)
|
||||
passStatusOverride = &status
|
||||
}
|
||||
|
||||
var nextStepOutcome *string
|
||||
if row.NextStepOutcome.Valid {
|
||||
outcome := string(row.NextStepOutcome.AssignmentNextStepOutcome)
|
||||
nextStepOutcome = &outcome
|
||||
}
|
||||
|
||||
var reviewNeedsAttention *bool
|
||||
if row.AnswerID.Valid {
|
||||
reviewNeedsAttention = shared.BoolPointer(row.ReviewNeedsAttention)
|
||||
}
|
||||
|
||||
return AssignmentStudentQuestionDetailResponse{
|
||||
AssignmentID: row.AssignmentID,
|
||||
StudentID: studentID,
|
||||
QuestionID: row.QuestionID,
|
||||
Position: row.Position,
|
||||
Title: row.Title,
|
||||
Prompt: row.Prompt,
|
||||
Subject: shared.TextPointer(row.Subject),
|
||||
Source: shared.TextPointer(row.Source),
|
||||
QuestionTags: row.QuestionTags,
|
||||
QuestionStatus: string(row.QuestionStatus),
|
||||
CorrectAnswer: shared.TextPointer(row.CorrectAnswer),
|
||||
AssignmentAiFeedback: shared.TextPointer(row.AssignmentAiFeedback),
|
||||
AssignmentTeacherFeedback: shared.TextPointer(row.AssignmentTeacherFeedback),
|
||||
OverallScore: shared.NumericPointer(row.OverallScore),
|
||||
PassThreshold: shared.NumericPointer(row.PassThreshold),
|
||||
NextStepOutcome: nextStepOutcome,
|
||||
PassStatusOverride: passStatusOverride,
|
||||
PassStatus: passStatus,
|
||||
AnswerID: shared.Int64Pointer(row.AnswerID),
|
||||
AnswerText: shared.TextPointer(row.AnswerText),
|
||||
SolveMode: shared.TextPointer(row.SolveMode),
|
||||
WorkingSteps: shared.TextPointer(row.WorkingSteps),
|
||||
IsCorrect: shared.BoolPointer(row.IsCorrect),
|
||||
AiFeedback: shared.TextPointer(row.AiFeedback),
|
||||
TeacherFeedback: shared.TextPointer(row.TeacherFeedback),
|
||||
AnswerStatus: answerStatus,
|
||||
ReviewNeedsAttention: reviewNeedsAttention,
|
||||
ReviewIssueReason: shared.TextPointer(row.ReviewIssueReason),
|
||||
ReviewCorrectnessScore: shared.NumericPointer(row.ReviewCorrectnessScore),
|
||||
ReviewUnderstandingScore: shared.NumericPointer(row.ReviewUnderstandingScore),
|
||||
ReviewQuestionScore: shared.NumericPointer(row.ReviewQuestionScore),
|
||||
ReviewConfidence: shared.NumericPointer(row.ReviewConfidence),
|
||||
ReviewTags: row.ReviewTags,
|
||||
SubmittedAt: shared.TimePointer(row.SubmittedAt),
|
||||
ReviewedAt: shared.TimePointer(row.ReviewedAt),
|
||||
AnswerCreatedAt: shared.TimePointer(row.AnswerCreatedAt),
|
||||
AnswerUpdatedAt: shared.TimePointer(row.AnswerUpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func mapAssignmentReviewSummary(summary sqlc.GetAssignmentReviewSummaryRow) AssignmentReviewSummaryResponse {
|
||||
return AssignmentReviewSummaryResponse{
|
||||
AssignmentID: summary.AssignmentID,
|
||||
TotalQuestions: summary.TotalQuestions,
|
||||
TotalAssigned: summary.TotalAssigned,
|
||||
NotStarted: summary.NotStarted,
|
||||
InProgress: summary.InProgress,
|
||||
Submitted: summary.Submitted,
|
||||
Reviewed: summary.Reviewed,
|
||||
}
|
||||
}
|
||||
|
||||
func mapAssignmentReviewQueueItem(row sqlc.ListAssignmentReviewQueueRow) AssignmentReviewQueueItemResponse {
|
||||
var nextStepOutcome *string
|
||||
if row.NextStepOutcome.Valid {
|
||||
outcome := string(row.NextStepOutcome.AssignmentNextStepOutcome)
|
||||
nextStepOutcome = &outcome
|
||||
}
|
||||
|
||||
return AssignmentReviewQueueItemResponse{
|
||||
AssignmentID: row.AssignmentID,
|
||||
StudentID: row.StudentID,
|
||||
NextStepOutcome: nextStepOutcome,
|
||||
StudentName: row.StudentName,
|
||||
StudentEmail: row.StudentEmail,
|
||||
TotalQuestions: row.TotalQuestions,
|
||||
AnsweredQuestions: row.AnsweredQuestions,
|
||||
ReviewedQuestions: row.ReviewedQuestions,
|
||||
SubmittedQuestions: row.SubmittedQuestions,
|
||||
InProgressQuestions: row.InProgressQuestions,
|
||||
ReviewStatus: string(row.ReviewStatus),
|
||||
LatestSubmittedAt: shared.TimePointer(row.LatestSubmittedAt),
|
||||
LatestReviewedAt: shared.TimePointer(row.LatestReviewedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func buildAssignmentCloseReadiness(queue []sqlc.ListAssignmentReviewQueueRow) assignmentCloseReadiness {
|
||||
blockers := make([]string, 0)
|
||||
if len(queue) == 0 {
|
||||
return assignmentCloseReadiness{
|
||||
CanClose: false,
|
||||
Blockers: []string{"No students have been assigned yet."},
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range queue {
|
||||
name := strings.TrimSpace(item.StudentName)
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("Student %d", item.StudentID)
|
||||
}
|
||||
|
||||
switch {
|
||||
case item.SubmittedQuestions > 0 || item.ReviewStatus == sqlc.AnswerStatusSubmitted:
|
||||
blockers = append(blockers, fmt.Sprintf("%s still has submitted work waiting for review.", name))
|
||||
case item.InProgressQuestions > 0 || item.ReviewStatus == sqlc.AnswerStatusInProgress:
|
||||
blockers = append(blockers, fmt.Sprintf("%s still has work in progress.", name))
|
||||
case item.AnsweredQuestions == 0 || item.ReviewStatus == sqlc.AnswerStatusNotStarted:
|
||||
blockers = append(blockers, fmt.Sprintf("%s has not started this assignment yet.", name))
|
||||
case !item.NextStepOutcome.Valid:
|
||||
blockers = append(blockers, fmt.Sprintf("%s still needs a next-step decision.", name))
|
||||
}
|
||||
}
|
||||
|
||||
return assignmentCloseReadiness{
|
||||
CanClose: len(blockers) == 0,
|
||||
Blockers: blockers,
|
||||
}
|
||||
}
|
||||
|
||||
func parseStoredRedoPlan(value string) (storedRedoPlan, error) {
|
||||
var payload storedRedoPlan
|
||||
if err := json.Unmarshal([]byte(value), &payload); err != nil {
|
||||
return storedRedoPlan{}, err
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func mapWeaknessSummary(studentID int64, summary weaknessSummary) StudentWeaknessSummaryResponse {
|
||||
return StudentWeaknessSummaryResponse{
|
||||
StudentID: studentID,
|
||||
TopicScores: summary.TopicScores,
|
||||
WeakTags: summary.WeakTags,
|
||||
RecentIssues: summary.RecentIssues,
|
||||
}
|
||||
}
|
||||
|
||||
func planningScore(isCorrect pgtype.Bool, understanding pgtype.Numeric) float64 {
|
||||
understandingValue := 0.0
|
||||
if value := shared.NumericPointer(understanding); value != nil {
|
||||
understandingValue = *value
|
||||
}
|
||||
correctnessValue := 0.0
|
||||
if isCorrect.Valid && isCorrect.Bool {
|
||||
correctnessValue = 1.0
|
||||
}
|
||||
return (correctnessValue + understandingValue) / 2
|
||||
}
|
||||
|
||||
func roundToOneDecimal(value float64) float64 {
|
||||
return math.Round(value*10) / 10
|
||||
}
|
||||
|
||||
func allowedQuestionTopics() []string {
|
||||
return []string{
|
||||
string(sqlc.QuestionTopicPlaceValue),
|
||||
string(sqlc.QuestionTopicArithmetic),
|
||||
string(sqlc.QuestionTopicNegativeNumbers),
|
||||
string(sqlc.QuestionTopicBidmas),
|
||||
string(sqlc.QuestionTopicFractions),
|
||||
string(sqlc.QuestionTopicAlgebra),
|
||||
string(sqlc.QuestionTopicGeometry),
|
||||
string(sqlc.QuestionTopicData),
|
||||
}
|
||||
}
|
||||
|
||||
func emptyStringPointer(value string) *string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
return &value
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func isValidAssignmentPassStatus(value string) bool {
|
||||
switch value {
|
||||
case string(sqlc.AssignmentPassStatusPending), string(sqlc.AssignmentPassStatusPass), string(sqlc.AssignmentPassStatusNoPass):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isValidAssignmentNextStepOutcome(value string) bool {
|
||||
switch value {
|
||||
case "redo", "accept", "support":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func pointerToFloat64(value float64) *float64 {
|
||||
return &value
|
||||
}
|
||||
Reference in New Issue
Block a user