Rank captured tasks by what he actually said (#129)
Ordering is computed, not generated. Asking a 1.7B which of his tasks
matters most produces a fluent opinion with no basis in anything, and a
confidently wrong priority is worse than none — same posture as the
behaviour profile in internal/memory, which counts instead of summarising.
internal/tasks is a pure package (no ipc, no store, no cgo) holding the
score, the order and the Russian rendering, so the spoken list and the
/tasks page cannot drift. Four signals, all of them things he stated:
deadline (overdue > today > tomorrow > this week), stated urgency, age
with a cap so nothing rots at the bottom, and confirmed work always
ahead of mail-derived candidates. A task with no due date and no weight
scores nothing and carries no reason string — inventing a "потому что"
about a priority he never set is the failure mode this avoids.
Capture now picks up urgency he says out loud ("добавь в задачи срочно
оплатить интернет"), stripping the marker from the task text, and the web
add form offers the same three rungs. Ranking is a read: it sorts and
renders, never writes, schedules or announces.
This commit is contained in:
@@ -141,6 +141,45 @@ func TestQueryTasksRecitesTheLiveList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The stated urgency rides through capture as a weight, so the ranker can use
|
||||
// it later (Vikunja #129). "срочно" is not part of the task text.
|
||||
func TestCaptureTaskCarriesStatedUrgency(t *testing.T) {
|
||||
api := &taskAPI{created: true}
|
||||
h := taskHandler(api)
|
||||
if _, ok := h.captureTaskFromNote(context.Background(), router.Decision{
|
||||
Utterance: "добавь в задачи срочно оплатить интернет",
|
||||
}); !ok {
|
||||
t.Fatal("expected a capture")
|
||||
}
|
||||
got := api.captured[0]
|
||||
if got.Text != "оплатить интернет" {
|
||||
t.Errorf("text = %q, want the urgency word out of the task", got.Text)
|
||||
}
|
||||
if got.Weight == 0 {
|
||||
t.Error("weight = 0 — he said срочно and it was dropped")
|
||||
}
|
||||
}
|
||||
|
||||
// The recital is ordered by the ranker, not by insertion: a deadline he named
|
||||
// comes before undated work.
|
||||
func TestQueryTasksRecitesInPriorityOrder(t *testing.T) {
|
||||
due := taskNow()
|
||||
api := &taskAPI{tasks: []ipc.Task{
|
||||
{ID: 1, Text: "купить молоко", Status: "open", CreatedTs: taskNow()},
|
||||
{ID: 2, Text: "оплатить интернет", Status: "open", CreatedTs: taskNow(), Due: &due},
|
||||
}}
|
||||
h := taskHandler(api)
|
||||
reply, _ := h.queryTasks(context.Background(), &queryTurn{
|
||||
dec: router.Decision{Utterance: "какие у меня задачи?"},
|
||||
})
|
||||
if strings.Index(reply, "оплатить интернет") > strings.Index(reply, "купить молоко") {
|
||||
t.Errorf("reply = %q, want the dated task first", reply)
|
||||
}
|
||||
if !strings.Contains(reply, "сегодня") {
|
||||
t.Errorf("reply = %q, want the reason named", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryTasksEmptyList(t *testing.T) {
|
||||
h := taskHandler(&taskAPI{})
|
||||
reply, ok := h.queryTasks(context.Background(), &queryTurn{
|
||||
|
||||
Reference in New Issue
Block a user