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