Align learner progress rollups with practice-scoped lesson counts.
Count only children that have published practices at module and above for LMS and exam prep; keep lesson at 100% after one practice and module at 100% after direct module practice. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a83745fd93
commit
256183ae64
|
|
@ -6,6 +6,21 @@ import (
|
|||
dbgen "Yimaru-Backend/gen/db"
|
||||
)
|
||||
|
||||
// ExamPrepCountPublishedPracticesInLesson returns published practices attached to an exam-prep lesson.
|
||||
func (s *Store) ExamPrepCountPublishedPracticesInLesson(ctx context.Context, lessonID int64) (int32, error) {
|
||||
return s.queries.CountPublishedExamPrepPracticesInLesson(ctx, lessonID)
|
||||
}
|
||||
|
||||
// ExamPrepCountPublishedPracticesInModule returns published practices under an exam-prep module (via lessons).
|
||||
func (s *Store) ExamPrepCountPublishedPracticesInModule(ctx context.Context, moduleID int64) (int32, error) {
|
||||
return s.queries.CountPublishedExamPrepPracticesInModule(ctx, moduleID)
|
||||
}
|
||||
|
||||
// ExamPrepCountPublishedPracticesInUnit returns published practices under an exam-prep unit.
|
||||
func (s *Store) ExamPrepCountPublishedPracticesInUnit(ctx context.Context, unitID int64) (int32, error) {
|
||||
return s.queries.CountPublishedExamPrepPracticesInUnit(ctx, unitID)
|
||||
}
|
||||
|
||||
// ExamPrepUserPracticeProgressInLesson returns published practice completion counts scoped to an exam-prep lesson.
|
||||
func (s *Store) ExamPrepUserPracticeProgressInLesson(ctx context.Context, userID, lessonID int64) (completed, total int32, err error) {
|
||||
total, err = s.queries.CountPublishedExamPrepPracticesInLesson(ctx, lessonID)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,16 @@ func (s *Store) LmsCountPublishedPracticesInLesson(ctx context.Context, lessonID
|
|||
return s.queries.CountPublishedPracticesInLesson(ctx, toPgInt8(&lessonID))
|
||||
}
|
||||
|
||||
// LmsCountPublishedPracticesInModule returns published practices in a module (direct + lesson-attached).
|
||||
func (s *Store) LmsCountPublishedPracticesInModule(ctx context.Context, moduleID int64) (int32, error) {
|
||||
return s.queries.CountPublishedPracticesInModule(ctx, toPgInt8(&moduleID))
|
||||
}
|
||||
|
||||
// LmsCountPublishedPracticesInCourse returns published practices in a course (direct + module + lesson scope).
|
||||
func (s *Store) LmsCountPublishedPracticesInCourse(ctx context.Context, courseID int64) (int32, error) {
|
||||
return s.queries.CountPublishedPracticesInCourse(ctx, toPgInt8(&courseID))
|
||||
}
|
||||
|
||||
// LmsUserPracticeProgressInLesson returns published practice completion counts scoped to a lesson.
|
||||
func (s *Store) LmsUserPracticeProgressInLesson(ctx context.Context, userID, lessonID int64) (completed, total int32, err error) {
|
||||
lessonIDPG := toPgInt8(&lessonID)
|
||||
|
|
|
|||
|
|
@ -332,11 +332,8 @@ func (s *Service) lmsModuleProgress(ctx context.Context, userID, moduleID int64)
|
|||
doneLessons++
|
||||
}
|
||||
}
|
||||
if total == 0 {
|
||||
return 0, false, 0, 0, nil
|
||||
}
|
||||
fraction = float64(doneLessons) / float64(total)
|
||||
return fraction, fraction >= 1, doneLessons, total, nil
|
||||
fraction, done, completed, total = practiceScopeFraction(doneLessons, total)
|
||||
return fraction, done, completed, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) lmsCourseProgress(ctx context.Context, userID, courseID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
|
|
@ -352,25 +349,27 @@ func (s *Service) lmsCourseProgress(ctx context.Context, userID, courseID int64)
|
|||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if len(moduleIDs) == 0 {
|
||||
return 0, false, 0, 0, nil
|
||||
}
|
||||
|
||||
var sum float64
|
||||
var fullDone int32
|
||||
var doneModules int32
|
||||
for _, moduleID := range moduleIDs {
|
||||
moduleFraction, _, _, _, err := s.lmsModuleProgress(ctx, userID, moduleID)
|
||||
practiceCount, err := s.store.LmsCountPublishedPracticesInModule(ctx, moduleID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
sum += moduleFraction
|
||||
if moduleFraction >= 1 {
|
||||
fullDone++
|
||||
if practiceCount == 0 {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
_, moduleDone, _, _, err := s.lmsModuleProgress(ctx, userID, moduleID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if moduleDone {
|
||||
doneModules++
|
||||
}
|
||||
}
|
||||
total = int32(len(moduleIDs))
|
||||
fraction = sum / float64(total)
|
||||
return fraction, fraction >= 1, fullDone, total, nil
|
||||
fraction, done, completed, total = practiceScopeFraction(doneModules, total)
|
||||
return fraction, done, completed, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) lmsProgramProgress(ctx context.Context, userID, programID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
|
|
@ -378,25 +377,27 @@ func (s *Service) lmsProgramProgress(ctx context.Context, userID, programID int6
|
|||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if len(courseIDs) == 0 {
|
||||
return 0, false, 0, 0, nil
|
||||
}
|
||||
|
||||
var sum float64
|
||||
var fullDone int32
|
||||
var doneCourses int32
|
||||
for _, courseID := range courseIDs {
|
||||
courseFraction, _, _, _, err := s.lmsCourseProgress(ctx, userID, courseID)
|
||||
practiceCount, err := s.store.LmsCountPublishedPracticesInCourse(ctx, courseID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
sum += courseFraction
|
||||
if courseFraction >= 1 {
|
||||
fullDone++
|
||||
if practiceCount == 0 {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
_, courseDone, _, _, err := s.lmsCourseProgress(ctx, userID, courseID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if courseDone {
|
||||
doneCourses++
|
||||
}
|
||||
}
|
||||
total = int32(len(courseIDs))
|
||||
fraction = sum / float64(total)
|
||||
return fraction, fraction >= 1, fullDone, total, nil
|
||||
fraction, done, completed, total = practiceScopeFraction(doneCourses, total)
|
||||
return fraction, done, completed, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) hasCompletedDirectModulePractice(ctx context.Context, userID, moduleID int64) (bool, error) {
|
||||
|
|
@ -431,11 +432,17 @@ func (s *Service) examPrepModuleProgress(ctx context.Context, userID, moduleID i
|
|||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if len(lessonIDs) == 0 {
|
||||
return 0, false, 0, 0, nil
|
||||
}
|
||||
|
||||
var doneLessons int32
|
||||
for _, lessonID := range lessonIDs {
|
||||
practiceCount, err := s.store.ExamPrepCountPublishedPracticesInLesson(ctx, lessonID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if practiceCount == 0 {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
lessonFraction, _, _, _, err := s.examPrepLessonProgress(ctx, userID, lessonID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
|
|
@ -444,9 +451,8 @@ func (s *Service) examPrepModuleProgress(ctx context.Context, userID, moduleID i
|
|||
doneLessons++
|
||||
}
|
||||
}
|
||||
total = int32(len(lessonIDs))
|
||||
fraction = float64(doneLessons) / float64(total)
|
||||
return fraction, fraction >= 1, doneLessons, total, nil
|
||||
fraction, done, completed, total = practiceScopeFraction(doneLessons, total)
|
||||
return fraction, done, completed, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) examPrepUnitProgress(ctx context.Context, userID, unitID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
|
|
@ -454,24 +460,27 @@ func (s *Service) examPrepUnitProgress(ctx context.Context, userID, unitID int64
|
|||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if len(moduleIDs) == 0 {
|
||||
return 0, false, 0, 0, nil
|
||||
}
|
||||
var sum float64
|
||||
var fullDone int32
|
||||
|
||||
var doneModules int32
|
||||
for _, moduleID := range moduleIDs {
|
||||
moduleFraction, _, _, _, err := s.examPrepModuleProgress(ctx, userID, moduleID)
|
||||
practiceCount, err := s.store.ExamPrepCountPublishedPracticesInModule(ctx, moduleID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
sum += moduleFraction
|
||||
if moduleFraction >= 1 {
|
||||
fullDone++
|
||||
if practiceCount == 0 {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
_, moduleDone, _, _, err := s.examPrepModuleProgress(ctx, userID, moduleID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if moduleDone {
|
||||
doneModules++
|
||||
}
|
||||
}
|
||||
total = int32(len(moduleIDs))
|
||||
fraction = sum / float64(total)
|
||||
return fraction, fraction >= 1, fullDone, total, nil
|
||||
fraction, done, completed, total = practiceScopeFraction(doneModules, total)
|
||||
return fraction, done, completed, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) examPrepCatalogCourseProgress(ctx context.Context, userID, catalogCourseID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
|
|
@ -479,24 +488,36 @@ func (s *Service) examPrepCatalogCourseProgress(ctx context.Context, userID, cat
|
|||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if len(unitIDs) == 0 {
|
||||
return 0, false, 0, 0, nil
|
||||
}
|
||||
var sum float64
|
||||
var fullDone int32
|
||||
|
||||
var doneUnits int32
|
||||
for _, unitID := range unitIDs {
|
||||
unitFraction, _, _, _, err := s.examPrepUnitProgress(ctx, userID, unitID)
|
||||
practiceCount, err := s.store.ExamPrepCountPublishedPracticesInUnit(ctx, unitID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
sum += unitFraction
|
||||
if unitFraction >= 1 {
|
||||
fullDone++
|
||||
if practiceCount == 0 {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
_, unitDone, _, _, err := s.examPrepUnitProgress(ctx, userID, unitID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
if unitDone {
|
||||
doneUnits++
|
||||
}
|
||||
}
|
||||
total = int32(len(unitIDs))
|
||||
fraction = sum / float64(total)
|
||||
return fraction, fraction >= 1, fullDone, total, nil
|
||||
fraction, done, completed, total = practiceScopeFraction(doneUnits, total)
|
||||
return fraction, done, completed, total, nil
|
||||
}
|
||||
|
||||
// practiceScopeFraction returns done/total for entities that only count children with published practices.
|
||||
func practiceScopeFraction(done, total int32) (fraction float64, complete bool, completed, totalOut int32) {
|
||||
if total == 0 {
|
||||
return 0, false, 0, 0
|
||||
}
|
||||
fraction = float64(done) / float64(total)
|
||||
return fraction, fraction >= 1, done, total
|
||||
}
|
||||
|
||||
func lmsProgressComplete(completed, total int32) bool {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,21 @@ func TestLMSProgressCounts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPracticeScopeFraction(t *testing.T) {
|
||||
fraction, done, completed, total := practiceScopeFraction(1, 5)
|
||||
if fraction != 0.2 || done || completed != 1 || total != 5 {
|
||||
t.Fatalf("practiceScopeFraction(1,5)=(%v,%v,%d,%d), want (0.2,false,1,5)", fraction, done, completed, total)
|
||||
}
|
||||
fraction, done, completed, total = practiceScopeFraction(5, 5)
|
||||
if fraction != 1 || !done || completed != 5 || total != 5 {
|
||||
t.Fatalf("practiceScopeFraction(5,5)=(%v,%v,%d,%d), want (1,true,5,5)", fraction, done, completed, total)
|
||||
}
|
||||
fraction, done, completed, total = practiceScopeFraction(0, 0)
|
||||
if fraction != 0 || done || completed != 0 || total != 0 {
|
||||
t.Fatalf("practiceScopeFraction(0,0)=(%v,%v,%d,%d), want (0,false,0,0)", fraction, done, completed, total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLMSProgressComplete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user