package engine // UrgencyCoefficients holds the configurable coefficients for urgency calculation type UrgencyCoefficients struct { Due float64 PriorityH float64 PriorityM float64 PriorityD float64 PriorityL float64 Active float64 Age float64 AgeMax int Tags float64 Project float64 Waiting float64 UrgentTag string // Configurable urgent tag name (default: "next") UrgentCoeff float64 // Coefficient for urgent tag } // CalculateUrgency computes the urgency score for a task func (t *Task) CalculateUrgency(coeffs *UrgencyCoefficients) float64 { urgency := 0.0 // Due date contribution urgency += t.urgencyDue(coeffs.Due) // Priority contribution urgency += t.urgencyPriority(coeffs) // Age contribution urgency += t.urgencyAge(coeffs.Age, coeffs.AgeMax) // Active task contribution urgency += t.urgencyActive(coeffs.Active) // Waiting task penalty urgency += t.urgencyWaiting(coeffs.Waiting) // Tags contribution urgency += t.urgencyTags(coeffs) // Project contribution urgency += t.urgencyProject(coeffs.Project) return urgency } // urgencyDue calculates urgency contribution from due date // Linear scale: overdue=12.0, today=10.0, 7 days=6.0, 14+ days=2.0 func (t *Task) urgencyDue(coeff float64) float64 { if t.Due == nil { return 0.0 } now := timeNow() due := *t.Due // Calculate days until due (negative if overdue) duration := due.Sub(now) daysUntil := duration.Hours() / 24.0 var scale float64 if daysUntil < 0 { // Overdue - maximum urgency scale = 12.0 } else if daysUntil < 1 { // Due today scale = 10.0 } else if daysUntil <= 7 { // Due within a week - linear from 10.0 to 6.0 scale = 10.0 - (daysUntil * 0.571) // (10-6)/7 = 0.571 } else if daysUntil <= 14 { // Due within two weeks - linear from 6.0 to 2.0 scale = 6.0 - ((daysUntil - 7.0) * 0.571) // (6-2)/7 = 0.571 } else { // Due further out - low urgency scale = 2.0 } return scale * coeff } // urgencyPriority calculates urgency contribution from priority // Order: High > Medium > Default > Low func (t *Task) urgencyPriority(coeffs *UrgencyCoefficients) float64 { switch t.Priority { case PriorityHigh: return coeffs.PriorityH case PriorityMedium: return coeffs.PriorityM case PriorityDefault: return coeffs.PriorityD case PriorityLow: return coeffs.PriorityL default: return 0.0 } } // urgencyAge calculates urgency contribution from task age // Age increases linearly up to maxDays, then caps func (t *Task) urgencyAge(coeff float64, maxDays int) float64 { now := timeNow() ageDuration := now.Sub(t.Created) ageDays := int(ageDuration.Hours() / 24.0) if ageDays > maxDays { ageDays = maxDays } ageRatio := float64(ageDays) / float64(maxDays) return ageRatio * coeff } // urgencyActive calculates urgency contribution for started tasks func (t *Task) urgencyActive(coeff float64) float64 { if t.Start != nil { return coeff } return 0.0 } // urgencyWaiting calculates urgency penalty for waiting tasks func (t *Task) urgencyWaiting(coeff float64) float64 { if t.Wait != nil { now := timeNow() if t.Wait.After(now) { return coeff // Should be negative coefficient } } return 0.0 } // urgencyTags calculates urgency contribution from tags // Special urgent tag (configurable, default "next") has high coefficient // Regular tags contribute with diminishing returns (0.8 for 1, 0.9 for 2, 1.0 for 3+) func (t *Task) urgencyTags(coeffs *UrgencyCoefficients) float64 { tagCount := len(t.Tags) if tagCount == 0 { return 0.0 } // Check for special urgent tag first for _, tag := range t.Tags { if tag == coeffs.UrgentTag { return coeffs.UrgentCoeff } } // Apply general tag coefficient with multiplier based on count var multiplier float64 switch tagCount { case 1: multiplier = 0.8 case 2: multiplier = 0.9 default: multiplier = 1.0 } return multiplier * coeffs.Tags } // urgencyProject calculates urgency contribution from having a project func (t *Task) urgencyProject(coeff float64) float64 { if t.Project != nil { return coeff } return 0.0 } // BuildUrgencyCoefficients creates UrgencyCoefficients from config func BuildUrgencyCoefficients(cfg *Config) *UrgencyCoefficients { return &UrgencyCoefficients{ Due: cfg.UrgencyDue, PriorityH: cfg.UrgencyPriorityH, PriorityM: cfg.UrgencyPriorityM, PriorityD: cfg.UrgencyPriorityD, PriorityL: cfg.UrgencyPriorityL, Active: cfg.UrgencyActive, Age: cfg.UrgencyAge, AgeMax: cfg.UrgencyAgeMax, Tags: cfg.UrgencyTags, Project: cfg.UrgencyProject, Waiting: cfg.UrgencyWaiting, UrgentTag: cfg.UrgencyUrgentTag, UrgentCoeff: cfg.UrgencyUrgentCoeff, } }