From 7918e62ca9c04b5669b7c6c6872eb2997bd32e62 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Tue, 7 Apr 2026 06:30:54 -0700 Subject: [PATCH] normalize human language module and sub-module grouping Enhance hierarchy parsing to group Module-N and Module-N.M naming into stable module/sub-module structures under each CEFR level for consistent rendering. Made-with: Cursor --- .../web_server/handlers/course_management.go | 88 +++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/internal/web_server/handlers/course_management.go b/internal/web_server/handlers/course_management.go index 4db0227..0f85dd1 100644 --- a/internal/web_server/handlers/course_management.go +++ b/internal/web_server/handlers/course_management.go @@ -10,6 +10,8 @@ import ( "net/http" "os" "path/filepath" + "regexp" + "sort" "strconv" "strings" @@ -18,6 +20,8 @@ import ( "go.uber.org/zap" ) +var humanLanguageModulePattern = regexp.MustCompile(`(?i)^module-(\d+)(?:\.(\d+))?$`) + // Course Category Handlers type createCourseCategoryReq struct { @@ -955,8 +959,7 @@ func (h *Handler) GetHumanLanguageHierarchy(c *fiber.Ctx) error { if !isValidHumanLanguageCEFRLevel(levelKey) { continue } - - module := humanLanguageModuleRes{ + levelsMap[levelKey] = append(levelsMap[levelKey], humanLanguageModuleRes{ ID: sc.ID, Title: sc.Title, SubModules: []humanLanguageSubModuleRes{ @@ -967,8 +970,7 @@ func (h *Handler) GetHumanLanguageHierarchy(c *fiber.Ctx) error { Practices: sc.Practices, }, }, - } - levelsMap[levelKey] = append(levelsMap[levelKey], module) + }) } levels := make([]humanLanguageLevelRes, 0, 9) @@ -977,9 +979,85 @@ func (h *Handler) GetHumanLanguageHierarchy(c *fiber.Ctx) error { string(domain.SubCourseSubLevelB1), string(domain.SubCourseSubLevelB2), string(domain.SubCourseSubLevelB3), string(domain.SubCourseSubLevelC1), string(domain.SubCourseSubLevelC2), string(domain.SubCourseSubLevelC3), } { + raw := levelsMap[cefr] + moduleBuckets := map[int]*humanLanguageModuleRes{} + fallbackCounter := 1000000 + + for _, item := range raw { + moduleNo := fallbackCounter + subNo := 0 + matched := humanLanguageModulePattern.FindStringSubmatch(strings.TrimSpace(item.Title)) + if len(matched) > 0 { + if parsed, parseErr := strconv.Atoi(matched[1]); parseErr == nil { + moduleNo = parsed + } + if len(matched) > 2 && strings.TrimSpace(matched[2]) != "" { + if parsed, parseErr := strconv.Atoi(matched[2]); parseErr == nil { + subNo = parsed + } + } + } else { + fallbackCounter++ + } + + mod, exists := moduleBuckets[moduleNo] + if !exists { + moduleTitle := item.Title + if moduleNo < 1000000 { + moduleTitle = fmt.Sprintf("Module-%d", moduleNo) + } + mod = &humanLanguageModuleRes{ + ID: item.ID, + Title: moduleTitle, + SubModules: []humanLanguageSubModuleRes{}, + } + moduleBuckets[moduleNo] = mod + } + + subModuleTitle := item.Title + if moduleNo < 1000000 && subNo > 0 { + subModuleTitle = fmt.Sprintf("Sub-Module-%d.%d", moduleNo, subNo) + } else if moduleNo < 1000000 && subNo == 0 { + subModuleTitle = fmt.Sprintf("Sub-Module-%d.1", moduleNo) + } + + sub := humanLanguageSubModuleRes{ + ID: item.ID, + Title: subModuleTitle, + Videos: item.SubModules[0].Videos, + Practices: item.SubModules[0].Practices, + } + mod.SubModules = append(mod.SubModules, sub) + } + + moduleKeys := make([]int, 0, len(moduleBuckets)) + for key := range moduleBuckets { + moduleKeys = append(moduleKeys, key) + } + sort.Ints(moduleKeys) + + groupedModules := make([]humanLanguageModuleRes, 0, len(moduleKeys)) + for _, key := range moduleKeys { + mod := moduleBuckets[key] + sort.SliceStable(mod.SubModules, func(i, j int) bool { + ai := humanLanguageModulePattern.FindStringSubmatch(strings.ReplaceAll(mod.SubModules[i].Title, "Sub-", "")) + aj := humanLanguageModulePattern.FindStringSubmatch(strings.ReplaceAll(mod.SubModules[j].Title, "Sub-", "")) + ival := 0 + jval := 0 + if len(ai) > 2 { + ival, _ = strconv.Atoi(ai[2]) + } + if len(aj) > 2 { + jval, _ = strconv.Atoi(aj[2]) + } + return ival < jval + }) + groupedModules = append(groupedModules, *mod) + } + levels = append(levels, humanLanguageLevelRes{ Level: cefr, - Modules: levelsMap[cefr], + Modules: groupedModules, }) }