1 ;;; planner-rank.el --- Importance vs Urgency for the Emacs planner
4 ;; Copyright (C) 2005 Dryice Dong Liu . All rights reserved.
5 ;; Parts copyright (C) 2005 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 2, or (at your option)
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.
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
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)
58 ;; - planner-timeclock-summary.el integration
63 (require 'planner-deadline
)
65 ;;; USER VARIABLES -----------------------------------------------------------
67 (defgroup planner-rank nil
68 "Importance vs Urgency support for planner.el."
69 :prefix
"planner-rank"
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."
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
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."
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 +-------------+---------+
117 +-------------+---------+"
118 :type
'(repeat integer
)
119 :group
'planner-rank
)
121 (defcustom planner-rank-default-importance
5
122 "Default importance value for newly added rank."
124 :group
'planner-rank
)
126 (defcustom planner-rank-default-urgency
5
127 "Default urgency value for newly added rank."
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."
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."
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'."
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."
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
))
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."
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
)
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)))
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
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
)))
244 (defun planner-rank-calculate-rank-aggressive-rmsd-rest
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
259 (defun planner-rank-calculate-rank-aggressive-rmsd-full
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
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
))
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
)
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.
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
309 (+ (* planner-rank-importance-vs-urgency-factor
310 planner-rank-importance-vs-urgency-factor
)
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
)
318 (if (<= days
(car list
))
319 (setq value
(prog1 (length list
)
321 (setq list
(cdr list
))))
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
332 (defun planner-rank-calculate-priority-from-rank (rank)
333 "Calculate task priority (A,B,C) from RANK."
335 ((>= rank planner-rank-priority-A-valve
) "A")
336 ((>= rank planner-rank-priority-B-valve
) "B")
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
)
350 (setq old-importance
(planner-rank-importance rank-info
))
351 (setq old-urgency
(planner-rank-urgency rank-info
))))
353 (string-to-number (read-string "Importance: " nil nil
354 (number-to-string old-importance
))))
357 (planner-rank-calculate-urgency-from-deadline
358 (planner-deadline-days-left deadline task-info
)))
360 (string-to-number (read-string "Urgency: " nil nil
361 (number-to-string old-urgency
)))))
362 (list new-importance new-urgency
)))
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
)
372 (skip-chars-forward "0123456789 ")
373 (setq status
(char-after))
374 (+ ;(read (current-buffer))
376 ((eq status ?P
) 1000)
377 ((eq status ?D
) 2000)
378 ((eq status ?X
) 3000)
379 ((eq status ?C
) 4000)
381 (cond ((eq ch ?A
) 100)
384 (if (planner-rank-rank (planner-rank-get-task-info))
385 (* 10 (- 10 (planner-rank-rank
386 (planner-rank-get-task-info))))
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
)
397 (skip-chars-forward "0123456789 ")
398 (setq status
(char-after))
399 (+ ;(read (current-buffer))
401 ((eq status ?P
) 1000)
402 ((eq status ?D
) 2000)
403 ((eq status ?X
) 3000)
404 ((eq status ?C
) 4000)
406 (cond ((eq ch ?A
) 100)
409 (if (planner-rank-importance (planner-rank-get-task-info))
410 (* 10 (- 10 (planner-rank-importance (planner-rank-get-task-info))))
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
)
421 (skip-chars-forward "0123456789 ")
422 (setq status
(char-after))
423 (+ ;(read (current-buffer))
425 ((eq status ?P
) 1000)
426 ((eq status ?D
) 2000)
427 ((eq status ?X
) 3000)
428 ((eq status ?C
) 4000)
430 (cond ((eq ch ?A
) 100)
433 (if (planner-rank-urgency (planner-rank-get-task-info))
434 (* 10 (- 10 (planner-rank-urgency (planner-rank-get-task-info))))
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
))
444 (insert (format "Result table for function \`%s\'\n\n\n" test-function
))
449 (insert (format "%3d |" column
))
450 (setq column
(1+ column
)))
451 (insert " -> Importance\n")
453 (insert (format "%4d |" row
))
456 (insert (format "%.2f |" (funcall test-function column row
)))
457 (setq column
(1+ column
)))
460 (insert "\n |\n V\nUrgency")))
462 ;; user visible functions
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
470 (unless (and importance urgency
)
471 (let ((temp (planner-rank-read-importance-and-urgency)))
472 (setq importance
(car temp
))
473 (setq urgency
(cadr temp
))))
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
482 (planner-rank-calculate-priority-from-rank rank
))
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}}"
497 (run-hooks 'planner-rank-change-hook
)))))))
500 (defun planner-rank-update-current-task ()
501 "Re-calculate rank for the current task."
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
)))
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
))))
518 (defun planner-rank-update-all ()
519 "Re-calculate rank for all tasks in the current page."
523 (when (planner-narrow-to-section 'tasks
)
524 (goto-char (point-min))
525 (while (re-search-forward planner-rank-regexp nil t
)
527 (planner-rank-update-current-task))
528 (forward-line 1))))))
530 (provide 'planner-rank
)
532 ;;; planner-rank.el ends here
535 ;; indent-tabs-mode: t