package roadmap import ( "fmt" "strconv" "strings" "time" ) // parseRow parses a single row into a ParsedItem func (p *Parser) parseRow(row []string, columns []DetectedColumn, rowNum int) ParsedItem { item := ParsedItem{ RowNumber: rowNum, IsValid: true, Data: RoadmapItemInput{}, } values := make(map[string]string) for i, col := range columns { if i < len(row) && col.MappedTo != "" { values[col.MappedTo] = strings.TrimSpace(row[i]) } } if title, ok := values["title"]; ok && title != "" { item.Data.Title = title } else { item.IsValid = false item.Errors = append(item.Errors, "Titel/Title ist erforderlich") } if desc, ok := values["description"]; ok { item.Data.Description = desc } if cat, ok := values["category"]; ok && cat != "" { item.Data.Category = p.parseCategory(cat) if item.Data.Category == "" { item.Warnings = append(item.Warnings, fmt.Sprintf("Unbekannte Kategorie: %s", cat)) item.Data.Category = ItemCategoryTechnical } } if prio, ok := values["priority"]; ok && prio != "" { item.Data.Priority = p.parsePriority(prio) if item.Data.Priority == "" { item.Warnings = append(item.Warnings, fmt.Sprintf("Unbekannte Priorität: %s", prio)) item.Data.Priority = ItemPriorityMedium } } if status, ok := values["status"]; ok && status != "" { item.Data.Status = p.parseStatus(status) if item.Data.Status == "" { item.Warnings = append(item.Warnings, fmt.Sprintf("Unbekannter Status: %s", status)) item.Data.Status = ItemStatusPlanned } } if ctrl, ok := values["control_id"]; ok { item.Data.ControlID = ctrl } if reg, ok := values["regulation_ref"]; ok { item.Data.RegulationRef = reg } if gap, ok := values["gap_id"]; ok { item.Data.GapID = gap } if effort, ok := values["effort_days"]; ok && effort != "" { if days, err := strconv.Atoi(effort); err == nil { item.Data.EffortDays = &days } } if assignee, ok := values["assignee"]; ok { item.Data.AssigneeName = assignee } if dept, ok := values["department"]; ok { item.Data.Department = dept } if startStr, ok := values["planned_start"]; ok && startStr != "" { if start := p.parseDate(startStr); start != nil { item.Data.PlannedStart = start } } if endStr, ok := values["planned_end"]; ok && endStr != "" { if end := p.parseDate(endStr); end != nil { item.Data.PlannedEnd = end } } if notes, ok := values["notes"]; ok { item.Data.Notes = notes } return item } // parseCategory converts a string to ItemCategory func (p *Parser) parseCategory(s string) ItemCategory { s = strings.ToLower(strings.TrimSpace(s)) switch { case strings.Contains(s, "tech"): return ItemCategoryTechnical case strings.Contains(s, "org"): return ItemCategoryOrganizational case strings.Contains(s, "proz") || strings.Contains(s, "process"): return ItemCategoryProcessual case strings.Contains(s, "dok") || strings.Contains(s, "doc"): return ItemCategoryDocumentation case strings.Contains(s, "train") || strings.Contains(s, "schul"): return ItemCategoryTraining default: return "" } } // parsePriority converts a string to ItemPriority func (p *Parser) parsePriority(s string) ItemPriority { s = strings.ToLower(strings.TrimSpace(s)) switch { case strings.Contains(s, "crit") || strings.Contains(s, "krit") || s == "1": return ItemPriorityCritical case strings.Contains(s, "high") || strings.Contains(s, "hoch") || s == "2": return ItemPriorityHigh case strings.Contains(s, "med") || strings.Contains(s, "mitt") || s == "3": return ItemPriorityMedium case strings.Contains(s, "low") || strings.Contains(s, "nied") || s == "4": return ItemPriorityLow default: return "" } } // parseStatus converts a string to ItemStatus func (p *Parser) parseStatus(s string) ItemStatus { s = strings.ToLower(strings.TrimSpace(s)) switch { case strings.Contains(s, "plan") || strings.Contains(s, "offen") || strings.Contains(s, "open"): return ItemStatusPlanned case strings.Contains(s, "progress") || strings.Contains(s, "lauf") || strings.Contains(s, "arbeit"): return ItemStatusInProgress case strings.Contains(s, "block") || strings.Contains(s, "wart"): return ItemStatusBlocked case strings.Contains(s, "complet") || strings.Contains(s, "done") || strings.Contains(s, "fertig") || strings.Contains(s, "erledigt"): return ItemStatusCompleted case strings.Contains(s, "defer") || strings.Contains(s, "zurück") || strings.Contains(s, "verschob"): return ItemStatusDeferred default: return "" } } // parseDate attempts to parse various date formats func (p *Parser) parseDate(s string) *time.Time { s = strings.TrimSpace(s) if s == "" { return nil } formats := []string{ "2006-01-02", "02.01.2006", "2.1.2006", "02/01/2006", "2/1/2006", "01/02/2006", "1/2/2006", "2006/01/02", time.RFC3339, } for _, format := range formats { if t, err := time.Parse(format, s); err == nil { return &t } } return nil } // ValidateAndEnrich validates parsed items and enriches them with mappings func (p *Parser) ValidateAndEnrich(items []ParsedItem, controls []string, regulations []string, gaps []string) []ParsedItem { controlSet := make(map[string]bool) for _, c := range controls { controlSet[strings.ToLower(c)] = true } regSet := make(map[string]bool) for _, r := range regulations { regSet[strings.ToLower(r)] = true } gapSet := make(map[string]bool) for _, g := range gaps { gapSet[strings.ToLower(g)] = true } for i := range items { item := &items[i] if item.Data.ControlID != "" { if controlSet[strings.ToLower(item.Data.ControlID)] { item.MatchedControl = item.Data.ControlID item.MatchConfidence = 1.0 } else { item.Warnings = append(item.Warnings, fmt.Sprintf("Control '%s' nicht im Katalog gefunden", item.Data.ControlID)) } } if item.Data.RegulationRef != "" { if regSet[strings.ToLower(item.Data.RegulationRef)] { item.MatchedRegulation = item.Data.RegulationRef } } if item.Data.GapID != "" { if gapSet[strings.ToLower(item.Data.GapID)] { item.MatchedGap = item.Data.GapID } else { item.Warnings = append(item.Warnings, fmt.Sprintf("Gap '%s' nicht im Mapping gefunden", item.Data.GapID)) } } } return items }