Boost Azure Demo
This commit is contained in:
171
Backend/internal/assignmentgen/personalization_weakness.go
Normal file
171
Backend/internal/assignmentgen/personalization_weakness.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package assignmentgen
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"boostai-backend/internal/sqlc"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
func buildWeaknessSummary(rows []sqlc.ListStudentPlanningPerformanceRow) WeaknessSummary {
|
||||
topicTotals := make(map[sqlc.QuestionTopic]float64)
|
||||
topicCounts := make(map[sqlc.QuestionTopic]int)
|
||||
tagStats := make(map[string]*tagWeaknessStats)
|
||||
recentIssues := make([]string, 0, 5)
|
||||
seenIssues := make(map[string]struct{})
|
||||
|
||||
for _, row := range rows {
|
||||
score := planningScore(row.IsCorrect.Bool, numericFloat64OrZero(row.ReviewUnderstandingScore))
|
||||
accumulateTopicScore(topicTotals, topicCounts, row, score)
|
||||
accumulateTagStats(tagStats, row, score)
|
||||
recentIssues = appendRecentIssue(recentIssues, seenIssues, row.ReviewIssueReason.String)
|
||||
if len(recentIssues) >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return WeaknessSummary{
|
||||
TopicScores: buildTopicScores(topicTotals, topicCounts),
|
||||
WeakTags: collectWeakTags(tagStats),
|
||||
RecentIssues: recentIssues,
|
||||
}
|
||||
}
|
||||
|
||||
type tagWeaknessStats struct {
|
||||
total float64
|
||||
count int
|
||||
flaggedCount int
|
||||
}
|
||||
|
||||
type scoredTag struct {
|
||||
tag string
|
||||
score float64
|
||||
flaggedCount int
|
||||
}
|
||||
|
||||
func accumulateTopicScore(
|
||||
topicTotals map[sqlc.QuestionTopic]float64,
|
||||
topicCounts map[sqlc.QuestionTopic]int,
|
||||
row sqlc.ListStudentPlanningPerformanceRow,
|
||||
score float64,
|
||||
) {
|
||||
if !row.Topic.Valid {
|
||||
return
|
||||
}
|
||||
|
||||
topicTotals[row.Topic.QuestionTopic] += score
|
||||
topicCounts[row.Topic.QuestionTopic]++
|
||||
}
|
||||
|
||||
func accumulateTagStats(tagStats map[string]*tagWeaknessStats, row sqlc.ListStudentPlanningPerformanceRow, score float64) {
|
||||
for _, rawTag := range row.QuestionTags {
|
||||
tag := strings.TrimSpace(strings.ToLower(rawTag))
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
stats, ok := tagStats[tag]
|
||||
if !ok {
|
||||
stats = &tagWeaknessStats{}
|
||||
tagStats[tag] = stats
|
||||
}
|
||||
|
||||
stats.total += score
|
||||
stats.count++
|
||||
if row.ReviewNeedsAttention {
|
||||
stats.flaggedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appendRecentIssue(recentIssues []string, seenIssues map[string]struct{}, rawIssue string) []string {
|
||||
issue := strings.TrimSpace(rawIssue)
|
||||
if issue == "" {
|
||||
return recentIssues
|
||||
}
|
||||
if _, exists := seenIssues[issue]; exists {
|
||||
return recentIssues
|
||||
}
|
||||
|
||||
seenIssues[issue] = struct{}{}
|
||||
return append(recentIssues, issue)
|
||||
}
|
||||
|
||||
func buildTopicScores(topicTotals map[sqlc.QuestionTopic]float64, topicCounts map[sqlc.QuestionTopic]int) map[sqlc.QuestionTopic]float64 {
|
||||
topicScores := make(map[sqlc.QuestionTopic]float64, len(topicTotals))
|
||||
for topic, total := range topicTotals {
|
||||
count := topicCounts[topic]
|
||||
if count == 0 {
|
||||
continue
|
||||
}
|
||||
topicScores[topic] = roundToOneDecimal((total / float64(count)) * 100)
|
||||
}
|
||||
return topicScores
|
||||
}
|
||||
|
||||
func collectWeakTags(tagStats map[string]*tagWeaknessStats) []string {
|
||||
if len(tagStats) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
weakCandidates := make([]scoredTag, 0, len(tagStats))
|
||||
for tag, stats := range tagStats {
|
||||
if stats == nil || stats.count == 0 {
|
||||
continue
|
||||
}
|
||||
average := (stats.total / float64(stats.count)) * 100
|
||||
if average >= 70 && stats.flaggedCount == 0 {
|
||||
continue
|
||||
}
|
||||
weakCandidates = append(weakCandidates, scoredTag{
|
||||
tag: tag,
|
||||
score: roundToOneDecimal(average),
|
||||
flaggedCount: stats.flaggedCount,
|
||||
})
|
||||
}
|
||||
|
||||
sort.SliceStable(weakCandidates, func(i, j int) bool {
|
||||
if weakCandidates[i].score == weakCandidates[j].score {
|
||||
if weakCandidates[i].flaggedCount == weakCandidates[j].flaggedCount {
|
||||
return weakCandidates[i].tag < weakCandidates[j].tag
|
||||
}
|
||||
return weakCandidates[i].flaggedCount > weakCandidates[j].flaggedCount
|
||||
}
|
||||
return weakCandidates[i].score < weakCandidates[j].score
|
||||
})
|
||||
|
||||
limit := 6
|
||||
if len(weakCandidates) < limit {
|
||||
limit = len(weakCandidates)
|
||||
}
|
||||
|
||||
weakTags := make([]string, 0, limit)
|
||||
for idx := 0; idx < limit; idx++ {
|
||||
weakTags = append(weakTags, weakCandidates[idx].tag)
|
||||
}
|
||||
|
||||
return weakTags
|
||||
}
|
||||
|
||||
func planningScore(isCorrect bool, understandingScore float64) float64 {
|
||||
correctness := 0.0
|
||||
if isCorrect {
|
||||
correctness = 1.0
|
||||
}
|
||||
return (correctness + understandingScore) / 2
|
||||
}
|
||||
|
||||
func roundToOneDecimal(value float64) float64 {
|
||||
return math.Round(value*10) / 10
|
||||
}
|
||||
|
||||
func numericFloat64OrZero(value pgtype.Numeric) float64 {
|
||||
floatValue, err := value.Float64Value()
|
||||
if err != nil || !floatValue.Valid {
|
||||
return 0
|
||||
}
|
||||
return floatValue.Float64
|
||||
}
|
||||
Reference in New Issue
Block a user