focus-flow/lib/src/backlog.dart

115 lines
3.1 KiB
Dart

import 'models.dart';
/// Derived backlog filters for a unified backlog list.
enum BacklogFilter {
inbox,
pushed,
criticalMissed,
wishlist,
stale,
noRewardSet,
}
/// Sort options for a unified backlog list.
enum BacklogSortKey {
priority,
rewardVsEffort,
age,
project,
timesPushed,
}
/// Read-only backlog projection over the unified task list.
class BacklogView {
const BacklogView({
required this.tasks,
required this.now,
this.staleAfter = const Duration(days: 31),
});
final List<Task> tasks;
final DateTime now;
final Duration staleAfter;
List<Task> get backlogTasks {
return tasks.where((task) => task.isBacklog).toList(growable: false);
}
List<Task> filter(BacklogFilter filter) {
return backlogTasks.where((task) => _matchesFilter(task, filter)).toList(
growable: false,
);
}
List<Task> sorted(BacklogSortKey sortKey) {
final sortedTasks = [...backlogTasks];
sortedTasks.sort((a, b) => _compareTasks(a, b, sortKey));
return List<Task>.unmodifiable(sortedTasks);
}
bool _matchesFilter(Task task, BacklogFilter filter) {
return switch (filter) {
BacklogFilter.inbox => task.projectId == 'inbox',
BacklogFilter.pushed =>
task.stats.manuallyPushedCount > 0 || task.stats.autoPushedCount > 0,
BacklogFilter.criticalMissed =>
task.type == TaskType.critical && task.stats.missedCount > 0,
BacklogFilter.wishlist => task.backlogTags.contains(BacklogTag.wishlist),
BacklogFilter.stale => now.difference(task.updatedAt) >= staleAfter,
BacklogFilter.noRewardSet => task.reward == RewardLevel.notSet,
};
}
int _compareTasks(Task a, Task b, BacklogSortKey sortKey) {
return switch (sortKey) {
BacklogSortKey.priority =>
_priorityRank(b.priority).compareTo(_priorityRank(a.priority)),
BacklogSortKey.rewardVsEffort =>
_rewardVsEffortScore(b).compareTo(_rewardVsEffortScore(a)),
BacklogSortKey.age => a.createdAt.compareTo(b.createdAt),
BacklogSortKey.project => a.projectId.compareTo(b.projectId),
BacklogSortKey.timesPushed => _timesPushed(b).compareTo(_timesPushed(a)),
};
}
}
int _priorityRank(PriorityLevel? priority) {
return switch (priority) {
PriorityLevel.veryLow => 0,
PriorityLevel.low => 1,
PriorityLevel.medium || null => 2,
PriorityLevel.high => 3,
PriorityLevel.veryHigh => 4,
};
}
int _rewardRank(RewardLevel reward) {
return switch (reward) {
RewardLevel.notSet => 0,
RewardLevel.veryLow => 1,
RewardLevel.low => 2,
RewardLevel.medium => 3,
RewardLevel.high => 4,
RewardLevel.veryHigh => 5,
};
}
int _difficultyRank(DifficultyLevel difficulty) {
return switch (difficulty) {
DifficultyLevel.notSet => 0,
DifficultyLevel.veryEasy => 1,
DifficultyLevel.easy => 2,
DifficultyLevel.medium => 3,
DifficultyLevel.hard => 4,
DifficultyLevel.veryHard => 5,
};
}
int _rewardVsEffortScore(Task task) {
return _rewardRank(task.reward) - _difficultyRank(task.difficulty);
}
int _timesPushed(Task task) {
return task.stats.manuallyPushedCount + task.stats.autoPushedCount;
}