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
This commit is contained in:
Yared Yemane 2026-04-07 06:30:54 -07:00
parent 4055ad46f6
commit 7918e62ca9

View File

@ -10,6 +10,8 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
@ -18,6 +20,8 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
var humanLanguageModulePattern = regexp.MustCompile(`(?i)^module-(\d+)(?:\.(\d+))?$`)
// Course Category Handlers // Course Category Handlers
type createCourseCategoryReq struct { type createCourseCategoryReq struct {
@ -955,8 +959,7 @@ func (h *Handler) GetHumanLanguageHierarchy(c *fiber.Ctx) error {
if !isValidHumanLanguageCEFRLevel(levelKey) { if !isValidHumanLanguageCEFRLevel(levelKey) {
continue continue
} }
levelsMap[levelKey] = append(levelsMap[levelKey], humanLanguageModuleRes{
module := humanLanguageModuleRes{
ID: sc.ID, ID: sc.ID,
Title: sc.Title, Title: sc.Title,
SubModules: []humanLanguageSubModuleRes{ SubModules: []humanLanguageSubModuleRes{
@ -967,8 +970,7 @@ func (h *Handler) GetHumanLanguageHierarchy(c *fiber.Ctx) error {
Practices: sc.Practices, Practices: sc.Practices,
}, },
}, },
} })
levelsMap[levelKey] = append(levelsMap[levelKey], module)
} }
levels := make([]humanLanguageLevelRes, 0, 9) 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.SubCourseSubLevelB1), string(domain.SubCourseSubLevelB2), string(domain.SubCourseSubLevelB3),
string(domain.SubCourseSubLevelC1), string(domain.SubCourseSubLevelC2), string(domain.SubCourseSubLevelC3), 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{ levels = append(levels, humanLanguageLevelRes{
Level: cefr, Level: cefr,
Modules: levelsMap[cefr], Modules: groupedModules,
}) })
} }