Files

73 lines
3.0 KiB
JavaScript

export function scoreDecay(task, settings, allTasks) {
const now = Date.now();
const ageMs = now - task.createdAt.getTime();
const ageDays = ageMs / (1000 * 60 * 60 * 24);
// Priority weight: 1 = lowest, 4 = highest; invert so low-priority tasks decay faster
const priorityWeight = (5 - task.priority) * 15; // 15-60 range
// Project activity: if many tasks in same project are completed recently, project is active
const projectTasks = allTasks.filter(t => t.projectId === task.projectId && t.projectId !== null);
const recentProjectActivity = projectTasks.filter(t => t.completedAt && (now - t.completedAt.getTime()) < 30 * 24 * 60 * 60 * 1000).length;
const projectActivity = Math.min(30, recentProjectActivity * 5); // 0-30
// Label density: more labels = more mental overhead = higher decay
const labelDensity = task.labels.length * 8;
// Due urgency: if overdue, add urgency; if far future, reduce decay
let dueUrgency = 0;
if (task.dueDate) {
const dueMs = task.dueDate.getTime() - now;
if (dueMs < 0) {
dueUrgency = 20; // overdue
}
else if (dueMs < 7 * 24 * 60 * 60 * 1000) {
dueUrgency = -10; // due soon, reduce decay
}
}
// Auto-archive: if older than autoArchiveDays, cap at 100
let score = Math.round((Math.min(ageDays, settings.autoArchiveDays) / settings.autoArchiveDays) * 50 +
priorityWeight +
projectActivity +
labelDensity +
dueUrgency);
score = Math.max(0, Math.min(100, score));
let explanation = `Age: ${Math.round(ageDays)} days`;
if (priorityWeight > 30)
explanation += `; Low priority (${task.priority})`;
if (projectActivity > 10)
explanation += `; Active project`;
if (labelDensity > 0)
explanation += `; ${task.labels.length} labels`;
if (dueUrgency > 0)
explanation += `; Overdue`;
if (dueUrgency < 0)
explanation += `; Due soon`;
return {
score,
ageDays: Math.round(ageDays * 10) / 10,
priorityWeight,
projectActivity,
labelDensity,
dueUrgency,
explanation,
};
}
export function getReviewTasks(tasks, settings) {
const scored = tasks
.filter(t => !t.completedAt)
.filter(t => {
const ageDays = (Date.now() - t.createdAt.getTime()) / (1000 * 60 * 60 * 24);
return ageDays >= settings.decayThresholdDays;
})
.filter(t => !settings.dismissedTasks.includes(t.id))
.filter(t => !settings.archivedTasks.includes(t.id))
.map(t => ({ task: t, score: scoreDecay(t, settings, tasks) }))
.sort((a, b) => b.score.score - a.score.score);
return scored.slice(0, settings.maxTasksPerReview);
}
export function getAutoArchiveCandidates(tasks, settings) {
return tasks.filter(t => {
if (t.completedAt)
return false;
const ageDays = (Date.now() - t.createdAt.getTime()) / (1000 * 60 * 60 * 24);
return ageDays >= settings.autoArchiveDays && !settings.archivedTasks.includes(t.id);
});
}