Finally commit patch from Bradley Kuhn at the SFLC to allow - to be used as date...
[planner-el.git] / planner-rank.el
blob10ca9b9f087a9ab8fc5cbb0833e7b6a0e43f1ca4
1 ;;; planner-rank.el --- Importance vs Urgency for the Emacs planner
2 ;;
4 ;; Copyright (C) 2005, 2008 Dryice Dong Liu . All rights reserved.
5 ;; Parts copyright (C) 2005, 2008 Free Software Foundation, Inc.
6 ;; Parts copyright (C) 2006, 2007 Software Freedom Law Center
8 ;; Keywords: emacs planner important emergent rank deadline
9 ;; Author: Dryice Liu <dryice AT liu DOT com DOT cn>
10 ;; Description: calculate task rank from importance and urgency
12 ;; This file is part of Planner. It is not part of GNU Emacs.
14 ;; Planner is free software; you can redistribute it and/or modify it
15 ;; under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 3, or (at your option)
17 ;; any later version.
19 ;; Planner is distributed in the hope that it will be useful, but
20 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 ;; General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with Planner; see the file COPYING. If not, write to the
26 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
27 ;; Boston, MA 02110-1301, USA.
29 ;;; Commentary:
31 ;; This file models Franklin Covey's Urgency and Importance
32 ;; principle. Where for both Urgency and Importance, you can measure
33 ;; them with number 0-9. 9 means "I should have done this from
34 ;; Emergency/Important point of view" while 0 means "I can forget this
35 ;; from Emergency/Important point of view".
37 ;; If you use the planner-deadline feature, the Urgency value is
38 ;; calculated from how many days left to meet the deadline. You can
39 ;; manipulate `planner-rank-deadline-urgency-map-list' if you have
40 ;; speal needs.
42 ;; The rank is calculated from Urgency and Importance. You can choose
43 ;; your favoriate algorithm by setting
44 ;; `planner-rank-rank-calculate-function'. Took a look at the
45 ;; planner-rank-calculate-rank-* functions to see which one you like
46 ;; most. Call `planner-rank-test-algorithm' to get the result tables
47 ;; and see how it works.
49 ;; The three planner-sort-tasks-by-* functions in this file is
50 ;; suitable for `planner-sort-tasks-key-function'.
52 ;; if you want to set rank for every new task, add
54 ;; (add-hook 'planner-create-task-from-buffer-hook 'planner-rank-change)
56 ;; to your .emacs.
58 ;;; TODO
59 ;; - planner-timeclock-summary.el integration
61 ;;; Code:
63 (require 'planner)
64 (require 'planner-deadline)
66 ;;; USER VARIABLES -----------------------------------------------------------
68 (defgroup planner-rank nil
69 "Importance vs Urgency support for planner.el."
70 :prefix "planner-rank"
71 :group 'planner)
73 (defcustom planner-rank-change-hook nil
74 "Functions to run after `planner-rank-change'.
75 Point will be on the same line as the task."
76 :type 'hook
77 :group 'planner-rank)
79 (defcustom planner-rank-priority-A-valve 6
80 "\"A\" \"B\" task valve.
81 Tasks with rank greater than or equal to this value will be in
82 priority A."
83 :type 'number
84 :group 'planner-rank)
86 (defcustom planner-rank-priority-B-valve 3
87 "\"B\" \"C\" task valve.
88 Tasks with rank greater than or equal to this value and less than
89 `planner-rank-priority-A-valve' will be in priority B. Tasks with
90 rank less than this value will be in priority C."
91 :type 'number
92 :group 'planner-rank)
94 (defcustom planner-rank-deadline-urgency-map-list
95 '(-1 0 3 7 14 21 30 90 365)
96 "Deadline to Urgency map.
97 If there is a deadline for the task, the urgency value is
98 calculated from how many days left and this list. E.g, there's n
99 days left, if n is less or equal than nth 0 of this list, it will
100 get urgency 9. If it's greater than nth 0, but less or equal than
101 nth 1, it will be urgency 8. It's the user's responsibility to
102 keep the list in order.
104 The meaning of the default value:
105 +-------------+---------+
106 |Days left |Urgency |
107 +-------------+---------+
108 |overdue |9 |
109 |today |8 |
110 |3 days |7 |
111 |a week |6 |
112 |2 weeks |5 |
113 |3 weeks |4 |
114 |this month |3 |
115 |this quarter |2 |
116 |this year |1 |
117 |longer |0 |
118 +-------------+---------+"
119 :type '(repeat integer)
120 :group 'planner-rank)
122 (defcustom planner-rank-default-importance 5
123 "Default importance value for newly added rank."
124 :type 'integer
125 :group 'planner-rank)
127 (defcustom planner-rank-default-urgency 5
128 "Default urgency value for newly added rank."
129 :type 'integer
130 :group 'planner-rank)
132 (defcustom planner-rank-importance-vs-urgency-factor 1.5
133 "The weight of importance vs urgency.
134 How much do you think importance is more \"important\" than
135 urgency. This will be used in
136 planner-rank-calculate-rank-weighted-* functions."
137 :type 'number
138 :group 'planner-rank)
140 (defcustom planner-rank-rank-calculate-function
141 'planner-rank-calculate-rank-weighted-rmsd
142 "Define the function called to calculate rank.
143 The function should take two arguments: the importance and the
144 urgency value, and return the calculated rank value."
145 :type 'function
146 :group 'planner-rank)
148 (defcustom planner-rank-test-buffer "*Planner Rank Test Result*"
149 "Buffer name for timeclock reports from `planner-rank-test-algorithm'."
150 :type 'string
151 :group 'planner-rank)
153 ;;;_+ Internal variables and utility functions
155 (add-hook 'planner-deadline-change-hook 'planner-rank-update-current-task)
157 (defconst planner-rank-regexp
158 "\\(\\s-*{{Rank:\\s-+\\([0-9][\\.\\-][0-9]+\\)\\s-+-\\s-+I=\\([0-9]\\)\\s-+U=\\([0-9]\\)}}\\)"
159 "Regular expression for rank data.
160 Regexp group 1 is the whole rank string.
161 Regexp group 2 is the rank value.
162 Regexp group 3 is the importance value.
163 Regexp group 4 is the urgency value.")
165 (defun planner-rank-get-task-info ()
166 "Get the rank of the current task.
167 Return nil if there's no rank string in the current task."
168 (save-excursion
169 (let ((string (buffer-substring (planner-line-beginning-position)
170 (planner-line-end-position))))
171 (when (string-match planner-rank-regexp string)
172 (list (string-to-number (planner-match-string-no-properties 2 string))
173 (string-to-number (planner-match-string-no-properties 3 string))
174 (string-to-number
175 (planner-match-string-no-properties 4 string)))))))
177 (defsubst planner-rank-rank (info)
178 "Return the rank of a task given INFO." (nth 0 info))
179 (defsubst planner-rank-importance (info)
180 "Return the importance of a task given INFO." (nth 1 info))
181 (defsubst planner-rank-urgency (info)
182 "Return the urgency of a task given INFO." (nth 2 info))
184 (defun planner-rank-calculate-base (arg)
185 "Get the base of importance or urgency.
186 ARG is the basic priority."
187 (cond
188 ((>= arg planner-rank-priority-A-valve)
189 planner-rank-priority-A-valve)
190 ((>= arg planner-rank-priority-B-valve)
191 planner-rank-priority-B-valve)
192 (t 0)))
194 (defun planner-rank-calculate-rank-average (importance urgency)
195 "Calculate the rank from IMPORTANCE and URGENCY.
197 The algorithm is a simple average.
198 Eval the following sexp to see how it works:
200 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-average\)"
201 (/ (+ importance urgency) 2.0))
203 (defun planner-rank-calculate-rank-average-aggressive (importance urgency)
204 "Calculate the rank from IMPORTANCE and URGENCY.
206 The algorithm is a simple average. Plus making sure the result is
207 close to the higher value.
208 Eval the following sexp to see how it works:
210 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-average-aggressive\)"
211 (let ((average (/ (+ importance urgency) 2.0)))
212 (max average
213 (planner-rank-calculate-base importance)
214 (planner-rank-calculate-base urgency))))
216 (defun planner-rank-calculate-rmsd (a b)
217 "Return the root mean square deviation of A and B."
218 (sqrt (/ (+ (* a a) (* b b)) 2.0)))
220 (defun planner-rank-calculate-rank-rmsd (importance urgency)
221 "Calculate the rank from IMPORTANCE and URGENCY.
223 The algorithm is the root mean square deviation of importance and
224 urgency. Eval the following sexp to see how it works:
226 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-rmsd\)"
227 (planner-rank-calculate-rmsd importance urgency))
229 (defun planner-rank-calculate-rank-rmsd-aggressive (importance
230 urgency)
231 "Calculate the rank from IMPORTANCE and URGENCY.
233 The algorithm is the RMDS first, if it's smaller than the base of the
234 bigger arg, return sqrt(RMDS) + base.
235 Eval the following sexp to see how it works:
237 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-rmsd-aggressive\)"
238 (let ((base (planner-rank-calculate-base
239 (max importance urgency)))
240 (rmsd (planner-rank-calculate-rmsd importance urgency)))
241 (if (< rmsd base)
242 (+ base (sqrt rmsd))
243 rmsd)))
245 (defun planner-rank-calculate-rank-aggressive-rmsd-rest
246 (importance urgency)
247 "Calculate the rank from IMPORTANCE and URGENCY.
249 First we make sure the result is bigger than base, then we add
250 (sqrt(rmsd rest smaller)).
251 Eval the following sexp to see how it works:
253 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-aggressive-rmsd-rest\)"
254 (let* ((bigger (max importance urgency))
255 (smaller (min importance urgency))
256 (base (planner-rank-calculate-base bigger)))
257 (+ base (sqrt (planner-rank-calculate-rmsd smaller
258 (- bigger base))))))
260 (defun planner-rank-calculate-rank-aggressive-rmsd-full
261 (importance urgency)
262 "Calculate the rank from IMPORTANCE and URGENCY.
264 First we make sure the result is bigger than base, then we add
265 (sqrt(rmsd bigger smaller)).
266 Eval the following sexp to see how it works:
268 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-aggressive-rmsd-full\)"
269 (let* ((bigger (max importance urgency))
270 (smaller (min importance urgency))
271 (base (planner-rank-calculate-base bigger)))
272 (+ base (sqrt (planner-rank-calculate-rmsd smaller bigger)))))
274 (defun planner-rank-calculate-rank-average-base-rmsd (importance
275 urgency)
276 "Calculate the rank from IMPORTANCE and URGENCY.
278 The average of two base plus rmsd.
279 Eval the following sexp to see how it works:
281 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-average-base-rmsd\)"
282 (+ (/ (+ (planner-rank-calculate-base importance)
283 (planner-rank-calculate-base urgency))
284 2.0)
285 (sqrt (planner-rank-calculate-rmsd importance urgency))))
287 (defun planner-rank-calculate-rank-weighted-average (importance urgency)
288 "Calculate the rank from IMPORTANCE and URGENCY.
290 The weighted average.
291 Eval the following sexp to see how it works:
293 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-weighted-average\)"
294 (/ (+ (* importance planner-rank-importance-vs-urgency-factor)
295 urgency)
296 (+ planner-rank-importance-vs-urgency-factor 1.0)))
298 (defun planner-rank-calculate-rank-weighted-rmsd (importance urgency)
299 "Calculate the rank from IMPORTANCE and URGENCY.
301 The weighted rmsd.
302 Eval the following sexp to see how it works:
304 \(planner-rank-test-algorithm 'planner-rank-calculate-rank-weighted-rmsd\)"
305 (sqrt (/ (+ (* planner-rank-importance-vs-urgency-factor
306 planner-rank-importance-vs-urgency-factor
307 importance
308 importance)
309 (* urgency urgency))
310 (+ (* planner-rank-importance-vs-urgency-factor
311 planner-rank-importance-vs-urgency-factor)
312 1.0))))
314 (defun planner-rank-calculate-urgency-from-deadline (days)
315 "Calculate urgency from how many DAYS are left."
316 (let ((list planner-rank-deadline-urgency-map-list)
317 value)
318 (while list
319 (if (<= days (car list))
320 (setq value (prog1 (length list)
321 (setq list nil)))
322 (setq list (cdr list))))
323 value))
325 (defun planner-rank-calculate-rank (importance urgency)
326 "Make sure the rank is not greater than 9, just in case.
327 This is based on IMPORTANCE and URGENCY."
328 (min (funcall planner-rank-rank-calculate-function
329 importance urgency)
333 (defun planner-rank-calculate-priority-from-rank (rank)
334 "Calculate task priority (A,B,C) from RANK."
335 (cond
336 ((>= rank planner-rank-priority-A-valve) "A")
337 ((>= rank planner-rank-priority-B-valve) "B")
338 (t "C")))
340 (defun planner-rank-read-importance-and-urgency ()
341 "Read importance and urgency.
342 Use old value as default if available."
343 (let ((rank-info (planner-rank-get-task-info))
344 (task-info (planner-current-task-info))
345 (old-importance planner-rank-default-importance)
346 (old-urgency planner-rank-default-urgency)
347 (deadline (planner-deadline-get-current-deadline))
348 new-importance new-urgency)
349 (if rank-info
350 (progn
351 (setq old-importance (planner-rank-importance rank-info))
352 (setq old-urgency (planner-rank-urgency rank-info))))
353 (setq new-importance
354 (string-to-number (read-string "Importance: " nil nil
355 (number-to-string old-importance))))
356 (if deadline
357 (setq new-urgency
358 (planner-rank-calculate-urgency-from-deadline
359 (planner-deadline-days-left deadline task-info)))
360 (setq new-urgency
361 (string-to-number (read-string "Urgency: " nil nil
362 (number-to-string old-urgency)))))
363 (list new-importance new-urgency)))
365 ;;;###autoload
366 (defun planner-sort-tasks-by-rank ()
367 "Sort tasks by status (_PDXC), priority (ABC), and rank.
368 Suitable for `planner-sort-tasks-key-function'"
369 (skip-chars-forward "#ABC")
370 (let ((case-fold-search t)
371 (ch (char-before))
372 status)
373 (skip-chars-forward "0123456789 ")
374 (setq status (char-after))
375 (+ ;(read (current-buffer))
376 (cond
377 ((eq status ?P) 1000)
378 ((eq status ?D) 2000)
379 ((eq status ?X) 3000)
380 ((eq status ?C) 4000)
381 (t 0))
382 (cond ((eq ch ?A) 100)
383 ((eq ch ?B) 200)
384 ((eq ch ?C) 300))
385 (if (planner-rank-rank (planner-rank-get-task-info))
386 (* 10 (- 10 (planner-rank-rank
387 (planner-rank-get-task-info))))
388 99))))
390 ;;;###autoload
391 (defun planner-sort-tasks-by-importance ()
392 "Sort tasks by status (_PDXC), priority (ABC), and importance.
393 Suitable for `planner-sort-tasks-key-function'"
394 (skip-chars-forward "#ABC")
395 (let ((case-fold-search t)
396 (ch (char-before))
397 status)
398 (skip-chars-forward "0123456789 ")
399 (setq status (char-after))
400 (+ ;(read (current-buffer))
401 (cond
402 ((eq status ?P) 1000)
403 ((eq status ?D) 2000)
404 ((eq status ?X) 3000)
405 ((eq status ?C) 4000)
406 (t 0))
407 (cond ((eq ch ?A) 100)
408 ((eq ch ?B) 200)
409 ((eq ch ?C) 300))
410 (if (planner-rank-importance (planner-rank-get-task-info))
411 (* 10 (- 10 (planner-rank-importance (planner-rank-get-task-info))))
412 99))))
414 ;;;###autoload
415 (defun planner-sort-tasks-by-urgency ()
416 "Sort tasks by status (_PDXC), priority (ABC), and urgency.
417 Suitable for `planner-sort-tasks-key-function'"
418 (skip-chars-forward "#ABC")
419 (let ((case-fold-search t)
420 (ch (char-before))
421 status)
422 (skip-chars-forward "0123456789 ")
423 (setq status (char-after))
424 (+ ;(read (current-buffer))
425 (cond
426 ((eq status ?P) 1000)
427 ((eq status ?D) 2000)
428 ((eq status ?X) 3000)
429 ((eq status ?C) 4000)
430 (t 0))
431 (cond ((eq ch ?A) 100)
432 ((eq ch ?B) 200)
433 ((eq ch ?C) 300))
434 (if (planner-rank-urgency (planner-rank-get-task-info))
435 (* 10 (- 10 (planner-rank-urgency (planner-rank-get-task-info))))
436 99))))
438 ;; for developer
440 (defun planner-rank-test-algorithm (test-function)
441 "Make a test result table for the given TEST-FUNCTION."
442 (interactive "aTest function: ")
443 (switch-to-buffer (get-buffer-create planner-rank-test-buffer))
444 (erase-buffer)
445 (insert (format "Result table for function \`%s\'\n\n\n" test-function))
446 (let ((row 0)
447 (column 0))
448 (insert " |")
449 (while (< column 10)
450 (insert (format "%3d |" column))
451 (setq column (1+ column)))
452 (insert " -> Importance\n")
453 (while (< row 10)
454 (insert (format "%4d |" row))
455 (setq column 0)
456 (while (< column 10)
457 (insert (format "%.2f |" (funcall test-function column row)))
458 (setq column (1+ column)))
459 (insert "\n")
460 (setq row (1+ row)))
461 (insert "\n |\n V\nUrgency")))
463 ;; user visible functions
465 ;;;###autoload
466 (defun planner-rank-change (&optional importance urgency)
467 "Change the IMPORTANCE and URGENCY of the current task.
468 If there's deadline available, calculate urgency instead of asking
469 the user."
470 (interactive)
471 (unless (and importance urgency)
472 (let ((temp (planner-rank-read-importance-and-urgency)))
473 (setq importance (car temp))
474 (setq urgency (cadr temp))))
475 (save-excursion
476 (save-match-data
477 (let* ((info (planner-current-task-info))
478 (old-description (planner-task-description info))
479 (old-priority (planner-task-priority info))
480 (rank (planner-rank-calculate-rank
481 importance urgency))
482 (new-priority
483 (planner-rank-calculate-priority-from-rank rank))
484 new-description)
485 (if info
486 (progn
487 (if (not (string= old-priority new-priority))
488 (planner-set-task-priority new-priority))
489 (if (string-match planner-rank-regexp old-description)
490 (setq new-description (replace-match "" t t old-description))
491 (setq new-description old-description))
492 (planner-edit-task-description
493 (format "%s {{Rank: %.2f - I=%d U=%d}}"
494 new-description
495 rank
496 importance
497 urgency))
498 (run-hooks 'planner-rank-change-hook)))))))
500 ;;;###autoload
501 (defun planner-rank-update-current-task ()
502 "Re-calculate rank for the current task."
503 (interactive)
504 (let* ((task-info (save-match-data (planner-current-task-info)))
505 (status (planner-task-status task-info))
506 (deadline (planner-deadline-get-current-deadline))
507 (rank-info (planner-rank-get-task-info))
508 (importance (planner-rank-importance rank-info))
509 (urgency (planner-rank-urgency rank-info)))
510 (when (and deadline
511 task-info
512 (not (equal status "X"))
513 (not (equal status "C")))
514 (setq urgency (planner-rank-calculate-urgency-from-deadline
515 (planner-deadline-days-left deadline task-info)))
516 (planner-rank-change importance urgency))))
518 ;;;###autoload
519 (defun planner-rank-update-all ()
520 "Re-calculate rank for all tasks in the current page."
521 (interactive)
522 (save-excursion
523 (save-restriction
524 (when (planner-narrow-to-section 'tasks)
525 (goto-char (point-min))
526 (while (re-search-forward planner-rank-regexp nil t)
527 (save-match-data
528 (planner-rank-update-current-task))
529 (forward-line 1))))))
531 (provide 'planner-rank)
533 ;;; planner-rank.el ends here
535 ;; Local Variables:
536 ;; indent-tabs-mode: t
537 ;; tab-width: 8
538 ;; End: