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"
|
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.
|
// 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) {
|
func (s *Store) ExamPrepUserPracticeProgressInLesson(ctx context.Context, userID, lessonID int64) (completed, total int32, err error) {
|
||||||
total, err = s.queries.CountPublishedExamPrepPracticesInLesson(ctx, lessonID)
|
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))
|
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.
|
// 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) {
|
func (s *Store) LmsUserPracticeProgressInLesson(ctx context.Context, userID, lessonID int64) (completed, total int32, err error) {
|
||||||
lessonIDPG := toPgInt8(&lessonID)
|
lessonIDPG := toPgInt8(&lessonID)
|
||||||
|
|
|
||||||
|
|
@ -332,11 +332,8 @@ func (s *Service) lmsModuleProgress(ctx context.Context, userID, moduleID int64)
|
||||||
doneLessons++
|
doneLessons++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if total == 0 {
|
fraction, done, completed, total = practiceScopeFraction(doneLessons, total)
|
||||||
return 0, false, 0, 0, nil
|
return fraction, done, completed, total, nil
|
||||||
}
|
|
||||||
fraction = float64(doneLessons) / float64(total)
|
|
||||||
return fraction, fraction >= 1, doneLessons, total, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) lmsCourseProgress(ctx context.Context, userID, courseID int64) (fraction float64, done bool, completed, total int32, err error) {
|
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 {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
if len(moduleIDs) == 0 {
|
|
||||||
return 0, false, 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var sum float64
|
var doneModules int32
|
||||||
var fullDone int32
|
|
||||||
for _, moduleID := range moduleIDs {
|
for _, moduleID := range moduleIDs {
|
||||||
moduleFraction, _, _, _, err := s.lmsModuleProgress(ctx, userID, moduleID)
|
practiceCount, err := s.store.LmsCountPublishedPracticesInModule(ctx, moduleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
sum += moduleFraction
|
if practiceCount == 0 {
|
||||||
if moduleFraction >= 1 {
|
continue
|
||||||
fullDone++
|
}
|
||||||
|
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, done, completed, total = practiceScopeFraction(doneModules, total)
|
||||||
fraction = sum / float64(total)
|
return fraction, done, completed, total, nil
|
||||||
return fraction, fraction >= 1, fullDone, total, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) lmsProgramProgress(ctx context.Context, userID, programID int64) (fraction float64, done bool, completed, total int32, err error) {
|
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 {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
if len(courseIDs) == 0 {
|
|
||||||
return 0, false, 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var sum float64
|
var doneCourses int32
|
||||||
var fullDone int32
|
|
||||||
for _, courseID := range courseIDs {
|
for _, courseID := range courseIDs {
|
||||||
courseFraction, _, _, _, err := s.lmsCourseProgress(ctx, userID, courseID)
|
practiceCount, err := s.store.LmsCountPublishedPracticesInCourse(ctx, courseID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
sum += courseFraction
|
if practiceCount == 0 {
|
||||||
if courseFraction >= 1 {
|
continue
|
||||||
fullDone++
|
}
|
||||||
|
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, done, completed, total = practiceScopeFraction(doneCourses, total)
|
||||||
fraction = sum / float64(total)
|
return fraction, done, completed, total, nil
|
||||||
return fraction, fraction >= 1, fullDone, total, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) hasCompletedDirectModulePractice(ctx context.Context, userID, moduleID int64) (bool, error) {
|
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 {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
if len(lessonIDs) == 0 {
|
|
||||||
return 0, false, 0, 0, nil
|
|
||||||
}
|
|
||||||
var doneLessons int32
|
var doneLessons int32
|
||||||
for _, lessonID := range lessonIDs {
|
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)
|
lessonFraction, _, _, _, err := s.examPrepLessonProgress(ctx, userID, lessonID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
|
|
@ -444,9 +451,8 @@ func (s *Service) examPrepModuleProgress(ctx context.Context, userID, moduleID i
|
||||||
doneLessons++
|
doneLessons++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total = int32(len(lessonIDs))
|
fraction, done, completed, total = practiceScopeFraction(doneLessons, total)
|
||||||
fraction = float64(doneLessons) / float64(total)
|
return fraction, done, completed, total, nil
|
||||||
return fraction, fraction >= 1, doneLessons, total, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) examPrepUnitProgress(ctx context.Context, userID, unitID int64) (fraction float64, done bool, completed, total int32, err error) {
|
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 {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
if len(moduleIDs) == 0 {
|
|
||||||
return 0, false, 0, 0, nil
|
var doneModules int32
|
||||||
}
|
|
||||||
var sum float64
|
|
||||||
var fullDone int32
|
|
||||||
for _, moduleID := range moduleIDs {
|
for _, moduleID := range moduleIDs {
|
||||||
moduleFraction, _, _, _, err := s.examPrepModuleProgress(ctx, userID, moduleID)
|
practiceCount, err := s.store.ExamPrepCountPublishedPracticesInModule(ctx, moduleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
sum += moduleFraction
|
if practiceCount == 0 {
|
||||||
if moduleFraction >= 1 {
|
continue
|
||||||
fullDone++
|
}
|
||||||
|
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, done, completed, total = practiceScopeFraction(doneModules, total)
|
||||||
fraction = sum / float64(total)
|
return fraction, done, completed, total, nil
|
||||||
return fraction, fraction >= 1, fullDone, total, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) examPrepCatalogCourseProgress(ctx context.Context, userID, catalogCourseID int64) (fraction float64, done bool, completed, total int32, err error) {
|
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 {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
if len(unitIDs) == 0 {
|
|
||||||
return 0, false, 0, 0, nil
|
var doneUnits int32
|
||||||
}
|
|
||||||
var sum float64
|
|
||||||
var fullDone int32
|
|
||||||
for _, unitID := range unitIDs {
|
for _, unitID := range unitIDs {
|
||||||
unitFraction, _, _, _, err := s.examPrepUnitProgress(ctx, userID, unitID)
|
practiceCount, err := s.store.ExamPrepCountPublishedPracticesInUnit(ctx, unitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, 0, 0, err
|
return 0, false, 0, 0, err
|
||||||
}
|
}
|
||||||
sum += unitFraction
|
if practiceCount == 0 {
|
||||||
if unitFraction >= 1 {
|
continue
|
||||||
fullDone++
|
}
|
||||||
|
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, done, completed, total = practiceScopeFraction(doneUnits, total)
|
||||||
fraction = sum / float64(total)
|
return fraction, done, completed, total, nil
|
||||||
return fraction, fraction >= 1, fullDone, 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 {
|
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) {
|
func TestLMSProgressComplete(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user