obsidian-tasks icon indicating copy to clipboard operation
obsidian-tasks copied to clipboard

Calculates wrong urgency score for due date

Open Cito opened this issue 2 years ago • 34 comments

Expected Behavior

I have the following two tasks:

- [ ] task 1 - actionable with default prio
- [ ] task 2 - something in ten years with low prio 🔽 📅 2032-01-01

When ordering them by urgency, I expect that task 1 shows up first, and then task 2.

This should work according to the documented urgency scores.

The scores should be

  • 0 (no due date) + 1.95 (default prio) = 1.95 for task 1, and
  • 0.2 (due in future) + 0 (low prio) = 0.2 for task 2,

so task 1 should come first.

Current Behavior

Task 2 is shown first, then task 1.

Steps to Reproduce

Use this to show the tasks above:

sort by urgency

Context (Environment)

  • Obsidian version: 0.14.13
  • Tasks version: 1.5.1

Possible Solution

The problem seems to be that the urgency for the due date is not calculated correctly. The score of 0.2 for a "due date far in the future" is multiplied with the due coefficient of 12, so the actual score is 2.4 instead of 0.2 which is higher than the priority of a task with default priority (1.95). I think the constant 0.2 that appears two times in the function for the calculation of the urgency should be actually 1/60.

Cito avatar May 26 '22 11:05 Cito

Thank you for the detailed report!

The issue is here and in the following line: https://github.com/schemar/obsidian-tasks/blob/09758e51411ca124c4725b00ada023220314eccd/src/Urgency.ts#L26

Exactly as described.

schemar avatar May 29 '22 13:05 schemar

Interestingly, in writing tests for the Urgency code, before fixing this bug, I found that the urgency is wrong for 'today' too:

    it('Due between 7 days ago and in 14 days: Range of 12.0 to 0.2', () => {
        testUrgencyOnDate(
            '2022-10-31', // today's date
            lowPriority.dueDate('2022-10-31'), // due date of task
            9.0, // fails - actual value is 8.8
        );
    });

claremacrae avatar May 31 '22 09:05 claremacrae

I've been able to fix the score for due dates > 14 days in future, as observed by @cito in the report: be25cc694829e49bd720c55e3b6a71c1fd03dbeb

However, @schemar please can you advise, as I am struggling to adjust the code to fix this one:

Due between 7 days ago and in 14 days Range of 12.0 to 0.2 Example for "today": 9.0

Here are the current values given by the code:

    it('Due between 7 days ago and in 14 days: Range of 12.0 to 0.2', () => {
        testUrgencyForDueDate(-7, 12.0);
        testUrgencyForDueDate(0, 8.8); // documentation says: 9.0 for "today"
        testUrgencyForDueDate(1, 8.34286);
        testUrgencyForDueDate(6, 6.05714);
        testUrgencyForDueDate(13, 2.85714);
        testUrgencyForDueDate(14, 2.4); // documentation says: 0.2
    });

When I put the documented values in to a chart:

day	score
-7	12
0	9
14	0.2

I don't get a linear chart:

image

But the code is implementing a linear calculation.

I could split this calculation in to two, so it uses 2 different gradients, in order to honour the documented values.

But am I missing something?

claremacrae avatar May 31 '22 13:05 claremacrae

@claremacrae Did you notice that there is a 0.2 constant in the "else if" branch as well? As far as I see, it should suffice to change both of these contants to 1/60.

Cito avatar May 31 '22 14:05 Cito

@Cito Thank you, I did, and initially ruled it out because it gave the score for 'today' as '6.6', not the documented 9.0

However, I've tried it again, and these tests pass:

    it('Due between 7 days ago and in 14 days: Range of 12.0 to 0.2', () => {
        testUrgencyForDueDate(-7, 12.0);
        testUrgencyForDueDate(0, 6.6); // documentation says: 9.0 for "today"
        testUrgencyForDueDate(14, 0.2);
    });

In other words, as you say, it fixes the value for 'in 14 days'. 🎉

So if I were to update the docs to say that for today, the score is 6.6, then it's all good. @schemar FYI.

I charted a number of the values for the new calculation:

image

So it was linear except for the value for 7 days overdue, which I didn't spot in my earlier comments about non-linear charts.

claremacrae avatar May 31 '22 14:05 claremacrae

I don't quite remember. I modeled it after Taskwarrior, which I deem a fantastic task tracking app.

Their implementation of urgency can be found at this link:

https://github.com/GothenburgBitFactory/taskwarrior/blob/4c179a427d542005f1da36ab02206072078684fd/src/Task.cpp#L2029

It covers more fields than us, e.g. tags or annotations. But due, etc. I tried to model similar, if I recall correctly.

This is their implementation for the due date:

https://github.com/GothenburgBitFactory/taskwarrior/blob/4c179a427d542005f1da36ab02206072078684fd/src/Task.cpp#L2238

From that, I would say the documentation is wrong. 2.4 is "correct" when due in more than two weeks, for example.

However, we are obviously free to decide on whatever values/formulas we prefer.

schemar avatar May 31 '22 14:05 schemar

To help with commenting the code, where did the calculation 1/60 come from?

claremacrae avatar May 31 '22 14:05 claremacrae

Here is their documentation with their default coefficients:

https://taskwarrior.org/docs/urgency.html

schemar avatar May 31 '22 14:05 schemar

Thanks for the info, Martin.

For context, I was doing this because I thought it would be super easy to do 'Filter by task Urgency score' #557.

I'm unsure about that plan now, as I'm not sure about spending time reworking the Urgency calculation, but we definitely shouldn't publish a 'filter by urgency' until we are OK with the urgency values, to avoid breaking loads of searches in future...

claremacrae avatar May 31 '22 14:05 claremacrae

I guess it's two different decisions.

First is whether to fix the 2nd half of this bug, to fix the discontinuity in urgency scores at around 14 days.

This will fix the issues in sorting by urgency for future tasks - as recording in this issue.

Second is whether to implement filtering by urgency. I could do that now, or hold off, if anyone would like to look at making Tasks' urgency more like Taskwarrior.

I'm thinking to go ahead and do the first one anyway, and update the docs for now of what the value for 'today' is.

claremacrae avatar May 31 '22 14:05 claremacrae

where did the calculation 1/60 come from

The original constant (from TaskWarrior) was 0.2. However, in our function, after the calculation of the score, it is multiplied with the dueCoefficient = 12. So to get the original constant of 0.2, you need to divide it by 12 to compensate the following multiplication, and 0.2 / 12 = 1 / 60.

Cito avatar May 31 '22 15:05 Cito

So maybe it should not be hardcoded as 1/60, but set to 0.2/dueCoefficient.

Cito avatar May 31 '22 15:05 Cito

So maybe it should not be hardcoded as 1/60, but set to 0.2/dueCoefficient.

Great minds think alike! 364d7256156014e18060e478dab15feabebf8532

claremacrae avatar May 31 '22 15:05 claremacrae

Just as a note: in Taskwarrior, the value is 0.2 * 12, not 0.2. If I read their code correctly.

schemar avatar May 31 '22 15:05 schemar

Just as a note: in Taskwarrior, the value is 0.2 * 12, not 0.2. If I read their code correctly.

Ah, ok, they too have that dueCoefficient. That would mean the formular is correct, but that score table is wrong, since some of the values (the 12.0 score) already contain the multiplier, and some (the 0.2 score) do not. I thought that table was the "source of truth", not the formula.

Cito avatar May 31 '22 15:05 Cito

But then, if we fix the table, not the formula, we would have the original problem again that the actionable task with default prio would be ranked lower than the task due in ten years with low prio. This is a bit similar to #698 in that the fact that something is due or starts in ten years is overrated in comparison to other task attributes like priority. But maybe it's then a problem of TaskWarrior as well. I'll check that later.

Cito avatar May 31 '22 15:05 Cito

I think you summarized it correctly, @Cito. @claremacrae and I have a parallel discussion in #711.

Now it's up to us to decide whether this is actually a bug or a feature :tm: :yum:

Happy to change the formula, but it cannot be objectively correct. It's a matter of preference. For example, I think a due task that is due in a week (even with low prio) must catch my attention. After all, it has a due date, meaning it is not allowed to do the task after. However, if it also starts after today, then maybe it's less critical. Though I still want to be made aware that this is upcoming. If not, I can filter by starts before tomorrow.

schemar avatar May 31 '22 16:05 schemar

For example, I think a due task that is due in a week (even with low prio) must catch my attention. After all, it has a due date, meaning it is not allowed to do the task after.

Certainly. The problem appears however with tasks that are due much later ("over my horizon"). Like preparing for your moms 80s birthday, when she is still 78, or changing the tires for winter when you just changed them for summer. These tasks have a due date, but I don't want to worry about them now, it does not make sense to give them any urgency. Still, I might already want to note them down so I do not forget that important birthday.

With other words, the problem is how the urgency declines. I think there should be an exponential or quadratic decay of urgency, and tasks due in over one year should not have any real boost in ranking compared to other tasks any more. In these cases the priority is more relevant. Currently, the urgency declines too slowly (linearly) and the end point of the decline is not zero as it should be, but a positive value. Mathematically speaking, when the due dates approaches infinity, the urgency should approach zero, which should be the same as a task without due date.

The underlying issue is that of the Eisenhower-Matrix. We need to fold a twodimensional plane of importance (priority) and urgency (regarding time) into one linear scale (unfortunately called urgency as well). Any algorithm to do that is of course not trivial and as you said also a matter of preference. However, most productivity systems recommend making importance the driving factor, not urgency. The problem is that in the current formula, urgency (measured by due date) has a too high weight compared with the importance (measured by priority).

The "boundary conditions" are very clear however. A task with no start date should be treated the same as one with a start date of -infinity (or any date earlier than today), and a task with no end date should be treated the same as one with a due date of +infinity (or any date later than a year or so).

Cito avatar May 31 '22 17:05 Cito

I really don't think this is going to be "solved" in any philosophical sense. Simply because different people are categorising radically differently. For example:

a) I make less than 1% of my tasks "High Priority". That is reserved for life or death type things -- submitting my tax return, remembering wife's birthday gift, renewing insurance. Other people might be roughly equalising priority scales across tasks.

b) I only apply a deadline to perhaps 2% of tasks at all, and the rest have just a threshold date. Something with a deadline will involve a clear crisis if that is missed. Others might be putting deadlines to any and all tasks (or many of them).

To make things worse, priorities might vary a lot depending on where "I am" (at work, at home, weekend, weekday, at the golf club).

My "weights" would be personal to me, and would make no sense whatever to many other people. I suppose if you want to be snazzy you could have a slider of some sort which would fiddle the key parameters to taste.

I think it is not worth over-egging this particular pudding. At the end of the day it is a fairly open system, and people will create different queries for different things.

aubreyz avatar May 31 '22 19:05 aubreyz

Ok, I have now looked into TaskWarrior and found that it has the same quirk. Adding these tasks:

task add actionable with default prio
task add some low prio task in ten years priority:L due:2032-01-01

Then I get the following list:

some low prio task in ten years - urgency=4.2
actionable with default prio    - urgency=0

I think this is ridiculous. The urgency value is 0.2 x 12 = 2.4 + 1,8 (for "low priority").

Note that in TaskWarrior "low priority" is considered higher than "no priority", while in Obsidian Tasks it's the other way around. Not sure whether this difference in behavior with TaskWarrior was intended or not. Anyway, the task in ten years is always considered more urgent because the "floor value" for due dates of 2.4 which is higher than the score for low or no priority, no matter how you order them.

Again, I think the fundamental problem here (and in TaskWarrior) is that the score for the due date does not approach zero when the due date moves into the future, but a positive value which is even higher than the score steps for priority. As explained above, I do not think this is reasonable.

There is this open issue in TaskWarrior where someone seems to be unlucky with the formula as well.

Cito avatar May 31 '22 19:05 Cito

@aubreyz Yes, the exact weights are debatable. But there are some things that are not debatable, e.g. the urgency should go down as the due date moves to the future, and not go up, and finally approach zero for tasks way out in the future, and not approach a significant postive value.

At the end of the day it is a fairly open system, ...

Actually, no it is not. You (currently) cannot configure these weights to your needs. And even if you could, people would not want to waste time figuring out the optimal values and learning how to configure the software. They simply expect that the default algorithm is somewhat reasonable. Only less than 1% of users would fiddle with the weights I guess.

To make things worse, priorities might vary a lot depending on where "I am"

Still, the low priority task in ten years will always be uninteresting to me and should be at the bottom of the list for at least the next 9 years, and the task "mow the lawn" without any due date should always be ranked higher until then. That's simply what you expect from any reasonable implementation of an urgency score.

Btw, the "where I am" would correspond to the "context" in GTD. This is simply another filter that you would apply to your tasks. It must not even mean "where I am physically", but can also mean "in which mindset I am", or "how much energy I have". This would be according to GTD the primary filter, but it would be nice to see the tasks in the current context sorted in a reasonable way using a computed score built from priority and urgency.

Cito avatar May 31 '22 19:05 Cito

task add actionable with default prio
task add some low prio task in ten years priority:L due:2032-01-01

Then I get the following list:

some low prio task in ten years - urgency=4.2
actionable with default prio       - urgency=0

I think this is ridiculous. The urgency value is 0.2 x 12 = 2.4 + 1,8 (for "low priority").

Well with my mind-set adding a very distant deadline to a task without a deadline should not alter the urgency at all. So this makes perfect sense to me and is not "ridiculous". The fact is that it is a low priority, with a deadline that does not factor in at this point in time.

A deadline is a date after which something becomes urgent - but it makes no difference at all to the need to do that thing now (until that date comes close). A deadline date is not a thinking-threshold date, which is exactly why that is a separate parameter.

I can't say you are "wrong" - which is the whole point.

IMPORTANT EDIT: I mis-read the direction of your test, but am not going to edit the above. Yes in my mindset it would be ridiculous if a deadline date 10 years in the future had any weighting whatever.

aubreyz avatar May 31 '22 19:05 aubreyz

Yes in my mindset it would be ridiculous if a deadline date 10 years in the future had any weighting whatever.

Exactly, that is the point I wanted to make. Currently, it makes a significant difference when I put a deadline in 10 years, which is what I called ridiculous. (In addition, the "low" priority was considered higher than "no priority" in TaskWarrior which made it look even more ridiculous. In Obsidian Tasks it makes more sense, because low priority here is really lower than no explicit priority.)

You could even argue that a task due in ten years is significantly less urgent than a task with no deadline, because you explictly said that it is only due in ten years, i.e. you explicitly told the system that it is not urgent. Whereas the task with no explicit deadline may have some urgency after all, and may be relevant in the foreseeable future. Otherwise, you would not have put it in the system.

On the other hand, if I have a task with a deadline in a week or a month, I would rank it higher than a task with the same priority and no deadline, since it may be wise to start it now even if there is still some time. The other task without deadline can usually wait.

Cito avatar May 31 '22 19:05 Cito

but it makes no difference at all to the need to do that thing now (until that date comes close).

That's why I think an exponentially curve falling down to zero with a half-life of a week or so would be the best to model real life tasks with due date.

Cito avatar May 31 '22 19:05 Cito

OK thanks for the suggestions, everyone. Much appreciated.

Just for context, I picked this up this morning as I thought it would be an easy thing to add tests for and then fix, and it would be a quick enabler for an easy small feature.

As it's turned out to be more complicated and less certain than I first thought, I'm putting it down for now. Will mull it over another day.

At least the things I learned from writing the tests will still be helpful on other features.

claremacrae avatar May 31 '22 20:05 claremacrae

but it makes no difference at all to the need to do that thing now (until that date comes close).

That's why I think an exponentially curve falling down to zero with a half-life of a week or so would be the best to model real life tasks with due date.

See my edit in my earlier message. So yes I do agree with you,

but others who have a different concept of what a "due date" means to them might have a different opinion. A different interpretation/use of a due date might be that things without a due date never have to be "done" ever. In some different mindset people might apply this to things that I call "ticklers" - not tasks at all, but just reminders for a forgetful future-me that "I have 30 tins of cat food stored in the roof so should not buy more". When I see the "task" I will rechedule it again according to how much I think my brain has degenerated.

There is no reconciling these differences

aubreyz avatar May 31 '22 20:05 aubreyz

but others who have a different concept of what a "due date" means

Yes, and I think this is the crucial mistake that most todo apps make, namely not clarifying what a field is exactly meant to contain. Many todo apps only have one date field, which can mean anything then. In that case, the app has no chance to do any reasonable processing based on the field and you get very little value from the app.

But I think Obsidian Tasks is in a much better position here since it has different fields with different meanings:

  • "due date" means the latests date when you can work on the task (deadline)
  • "start date" means the first date when you can work on a task
  • "scheduled date" means the date where you plan to work on a task or want to look at it again

No due date means there is no fixed deadline, and no start date means you can start immediately. Most tasks will not have a start date, and few tasks will have a deadline. Most important is the scheduled date, which is not really fixed but can and should be changed over the course of time, it must be "re-negotiated" during your weekly or monthly review, depending on which other tasks come in.

Of course users are free to give these dates a different meaning, but then they should not expect the software to make resonable use of them.

Cito avatar May 31 '22 20:05 Cito

@claremacrae Thanks a lot for your help. I'm sure we will eventually come up with something reasonable. It needs a little time to think it through, but I it will be worthwile, and would make Obsidian Tasks stand out, because most other apps do not support such computed scores for automatic ranking, or they also have similar problems. My Life Organized is one of the few apps which does it, but unfortunately it also does it wrong in that it considers tasks with no due date like tasks due today (instead of like tasks due far in the future), and similar for tasks with no start date. Such seemingly small mistakes in the computed score formula can make the whole automatic sorting feature unusable.

Btw, I also advocate renaming the "urgency" score to something else, because it is not only based on urgency (dates) but also on importance (priority), and not try to emulate TaskWarrior but we should come up with our own more reasonable formula.

Cito avatar May 31 '22 20:05 Cito

Let me try to wrap up the discussion so far:

Obsidian Tasks currently tries to mimic the Urgency Score of Taskwarrior.

One exception is that low prio in Obsidian Tasks is lower than default (=no) prio, while in Taskwarrior, low prio is higher than default prio. I am not sure whether this is intentional, but I like how Obsidian Tasks does it - it is more intuitive and it spares you from adding a prio for most "normal" tasks. You will only need to do if the task has lower or higher prio than "normal".

One problem that we noticed above is that the current documentation of Obsidian Tasks on this page is wrong regarding the "more than 14 days until due" score. It is actually (in the implementation and in Taskwarrior) not 0.2, but 0.2 * 12 = 2.4. This documentation problem will be fixed in #753.

However, fixing the documentation does not solve the actual issue posted here at the top.

The root cause is that the score calculation of Taskwarrior is fundamentally flawed in my view. I already demonstrated this above with the following example. When you add the following two tasks in Taskwarrior:

task add actionable with default prio
task add some low prio task in ten years priority:L due:2032-01-01

Then Taskwarrior will show the following list of tasks:

some low prio task in ten years - urgency=4.2
actionable with default prio    - urgency=0

I think we all agree that this order of tasks is not reasonable and not what anybody would expect.

In Obsidian Tasks the scores are a bit less absurd because of the above mentioned difference between the order of low and default prio, but the order is still not what you expect because of the positive score given to tasks with a due date in the future, even in the very far future. There is also a related issue #698 showing a similar problem for start dates.

My suggestion therefore is to use a different formula than Taskwarrior.

As a simple alternative formula, I suggest the following:

  • Scores for Priority: H = 2, M = 1, N = 0, L = -1
  • Additional score for due date: increase from 0 to 3 in the 14 days before the task is due
  • Additional score for start date: increase from -3 to 0 in the 7 days before the task starts
  • Additional score for scheduled date: increase from 0 to 3 in the 7 days before the task is scheduled

I know this is not perfect, but certainly better than the current formula.

export class Urgency {
  private tatic readonly msPerDay = 1e3 * 60 * 60 * 24;

  static calculate(task: Task): number {
    let urgency = 0;
    switch (task.priority) {
      case "1":
        urgency += 2;
        break;
      case "2":
        urgency += 1;
        break;
      case "4":
        urgency -= 1;
        break;
    }
    const msPerDay = Urgency.msPerDay;
    if (task.dueDate !== null) {
      const t = window.moment().diff(task.dueDate) / msPerDay;
      if (t >= 0) {
        urgency += 3;
      } else if (t > -14) {
        urgency += 4 ** (t/14 + 1) - 1;
      }
    }
    if (task.scheduledDate !== null) {
      const t = window.moment().diff(task.scheduledDate) / msPerDay;
      if (t >= 0) {
        urgency += 3;
      } else if (t > -7) {
        urgency += 4 ** (t/7 + 1) - 1;
      }
    }
    if (task.startDate !== null) {
      const t = window.moment().diff(task.startDate) / msPerDay;
      if (t <= -7) {
        urgency -= 3;
      } else if (t < 0) {
        urgency += 4 ** (t/7 + 1) - 4;
      }
    }
    return urgency;
  }
};

I think this formula is very simple to document and to understand, and yields more reasonable results than the existing one.

Cito avatar Jun 11 '22 17:06 Cito

Let me try to wrap up the discussion so far:

Thank you @Cito!

As a simple alternative formula, I suggest the following:

Scores for Priority: H = 2, M = 1, N = 0, L = -1 Additional score for due date: increase from 0 to 3 in the 14 days before the task is due Additional score for start date: increase from -3 to 0 in the 7 days before the task starts Additional score for scheduled date: increase from 0 to 3 in the 7 days before the task is scheduled I know this is not perfect, but certainly better than the current formula.

I'm not sure about this. I would need to see some charts to see how the numbers change over time and add up.

How would it emphasise overdue tasks, which I think is quite an important feature of the current scoring?

claremacrae avatar Jun 12 '22 12:06 claremacrae