feat: add time-of-day component to urgencyScore formula

This commit is contained in:
Loukas Andreadelis 2026-04-28 17:34:40 +03:00 committed by callumalpass
parent 35dba2b974
commit 5593582bf8
4 changed files with 45 additions and 4 deletions

View file

@ -29,6 +29,8 @@ Example:
- Enabled lint checks that mirror Obsidian community plugin review findings, including dynamic code execution, Promise handling, deprecated APIs, unsafe stringification, directive comments, Node built-in imports, and explicit `any` usage.
- Clarified privacy documentation for optional integrations that make periodic background network refreshes.
- Cleaned up internal Obsidian and Bases compatibility adapters used by search and grouped views.
- Updated default Base urgency scoring so timed tasks earlier in the day sort above later tasks with the same priority and date.
- Thanks to @loukandr for the contribution.
## Fixed

View file

@ -95,7 +95,7 @@ These formulas work with either due date or scheduled date, useful for finding t
| Formula | Description | Expression |
|---------|-------------|------------|
| `priorityWeight` | Numeric weight for priority sorting (lower = higher priority) | `if(priority=="none",0,if(priority=="low",1,if(priority=="normal",2,if(priority=="high",3,999))))` |
| `urgencyScore` | Combines priority and next date proximity (due or scheduled, higher = more urgent) | `if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext))` |
| `urgencyScore` | Combines priority, next date proximity, and time-of-day (due or scheduled, higher = more urgent) | `if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) / 86400000) - (number(date(formula.nextDate)) / 86400000).floor())))` |
### Display formulas

View file

@ -381,9 +381,10 @@ function generateAllFormulas(plugin: TaskNotesPlugin): Record<string, string> {
// === SORTING/SCORING FORMULAS ===
// Urgency score: combines priority weight and days until next date (due or scheduled)
// Higher score = more urgent. Overdue tasks get bonus, no date gets just priority
urgencyScore: `if(!${dueProperty} && !${scheduledProperty}, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext))`,
// Urgency score: combines priority weight, days until next date (due or scheduled), and time-of-day.
// Higher = more urgent. The 0..1 time-of-day term ranks earlier-in-day tasks above later same-day
// tasks at the same priority. Date-only values fall back to midnight.
urgencyScore: `if(!${dueProperty} && !${scheduledProperty}, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) / 86400000) - (number(date(formula.nextDate)) / 86400000).floor())))`,
// === DISPLAY FORMULAS ===

View file

@ -104,4 +104,42 @@ describe("defaultBasesFiles", () => {
expect(template).not.toMatch(/date\(due\) <= today\(\) \+ "7d"/);
expect(template).not.toMatch(/date\(scheduled\) <= today\(\) \+ "7d"/);
});
it("includes a time-of-day component in urgencyScore so earlier values rank higher", () => {
// Without the time-of-day term, two tasks at the same priority and same date but
// different times scored identically and tie-broke on file-iteration order. The
// 0..1 boost (1 - hourFraction(nextDate)) ranks earlier values above later ones
// while staying smaller than the priority and days components so cross-day order
// is preserved.
const template = generateBasesFileTemplate("open-tasks-view", createMockPlugin() as any);
expect(template).toContain(
`urgencyScore: 'if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) / 86400000) - (number(date(formula.nextDate)) / 86400000).floor())))'`
);
// Guard against the time-naive form returning
expect(template).not.toMatch(
/urgencyScore: 'if\(!due && !scheduled, formula\.priorityWeight, formula\.priorityWeight \+ max\(0, 10 - formula\.daysUntilNext\)\)'/
);
});
it("time-of-day boost is monotonic and bounded in [0, 1]", () => {
// Verifies the math invariant the formula relies on, independent of YAML shape.
// boost = 1 - fractional_day(ms_since_epoch). A given Date earlier in its day
// must yield a strictly larger boost than the same date later in the day.
const boost = (iso: string) => {
const ms = Date.parse(iso);
const dayMs = ms / 86_400_000;
return 1 - (dayMs - Math.floor(dayMs));
};
expect(boost("2026-04-28T00:00:00Z")).toBe(1);
expect(boost("2026-04-28T09:00:00Z")).toBeCloseTo(0.625, 3);
expect(boost("2026-04-28T17:00:00Z")).toBeCloseTo(0.292, 3);
expect(boost("2026-04-28T23:59:59Z")).toBeGreaterThan(0);
expect(boost("2026-04-28T23:59:59Z")).toBeLessThan(1 / 86_400);
// Monotonic: earlier in day → larger boost.
expect(boost("2026-04-28T09:00:00Z")).toBeGreaterThan(boost("2026-04-28T17:00:00Z"));
});
});