8f6db4672a
- Add urgency calculation based on multiple factors: * Due date (linear scale: overdue=12.0, today=10.0, week=6.0, 2weeks=2.0) * Priority (H=6.0, M=3.9, D=1.8, L=0.0) * Age (0-2.0 over 365 days) * Active status (+4.0 boost) * Waiting status (-3.0 penalty) * Tags (+1.0 with count modifier) * Project assignment (+1.0) * Configurable urgent tag (default 'next', +15.0) - Replace priority column with urgency in all reports * Display as decimal with 1 decimal place * 4-tier color coding: ≥10 (bright red), ≥5 (red), ≥2 (yellow), <2 (cyan) * Minimal format color-coded by urgency - Add default urgency sorting to all reports * list, minimal, active, ready, overdue reports sort by urgency * newest/oldest keep date-based sorting - Implement 'next' report * Shows most urgent ready tasks * Configurable limit (default 5) * Only includes tasks ready to work on (no future wait/scheduled) - Add urgency display to info command * Shows urgency score alongside priority - All urgency coefficients configurable via config * Adjusted defaults for Opal's simpler model (no blocking/annotations) * Configurable urgent tag name (not hardcoded to 'next') Priority order maintained: High > Medium > Default > Low
191 lines
4.7 KiB
Go
191 lines
4.7 KiB
Go
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,
|
|
}
|
|
}
|