Commit 019ba73326e3
Changed files (4)
dots
.config
claude
skills
Org
tools
dots/.config/claude/skills/Org/tools/tests/batch-functions-test.el
@@ -439,5 +439,76 @@ Priority mapping: '1'=1, '2'=2, '3'=3, '4'=4, '5'=5."
(should (> minutes 0))
(should (= minutes 90)))))) ; 1:30 = 90 minutes
+;;; Tests for Statistics & Analytics
+
+(ert-deftest test-org-batch-get-statistics ()
+ "Test getting comprehensive statistics."
+ (let ((stats (org-batch-get-statistics batch-test-fixture-file)))
+ (should stats)
+ (should (> (alist-get 'total stats) 0))
+ (should (alist-get 'by_state stats))
+ (should (alist-get 'by_priority stats))
+ (should (alist-get 'by_tag stats))
+ ;; Verify we have state counts
+ (let ((by-state (alist-get 'by_state stats)))
+ (should (assoc 'TODO by-state))
+ (should (assoc 'NEXT by-state)))))
+
+(ert-deftest test-org-batch-get-priority-distribution ()
+ "Test getting priority distribution."
+ (let ((distribution (org-batch-get-priority-distribution batch-test-fixture-file)))
+ (should distribution)
+ ;; Should have entries for priorities 1-5
+ (should (assoc 1 distribution))
+ (should (assoc 2 distribution))
+ (should (assoc 3 distribution))
+ (should (assoc 4 distribution))
+ (should (assoc 5 distribution))))
+
+(ert-deftest test-org-batch-get-tag-statistics ()
+ "Test getting tag statistics."
+ (let ((tag-stats (org-batch-get-tag-statistics batch-test-fixture-file)))
+ (should tag-stats)
+ (should (> (length tag-stats) 0))
+ ;; Should be sorted by count (descending)
+ (when (>= (length tag-stats) 2)
+ (should (>= (cdr (nth 0 tag-stats)) (cdr (nth 1 tag-stats)))))))
+
+;;; Tests for Export & Reporting
+
+(ert-deftest test-org-batch-export-csv ()
+ "Test exporting TODOs to CSV."
+ (let ((output-file (make-temp-file "org-export-" nil ".csv")))
+ (unwind-protect
+ (progn
+ (should (org-batch-export-csv batch-test-fixture-file output-file))
+ ;; Verify file was created
+ (should (file-exists-p output-file))
+ ;; Verify it has content
+ (with-temp-buffer
+ (insert-file-contents output-file)
+ (should (> (buffer-size) 0))
+ ;; Should have CSV header
+ (should (string-match-p "heading,state,priority" (buffer-string)))))
+ (when (file-exists-p output-file)
+ (delete-file output-file)))))
+
+(ert-deftest test-org-batch-export-json ()
+ "Test exporting TODOs to JSON."
+ (let ((output-file (make-temp-file "org-export-" nil ".json")))
+ (unwind-protect
+ (progn
+ (should (org-batch-export-json batch-test-fixture-file output-file))
+ ;; Verify file was created
+ (should (file-exists-p output-file))
+ ;; Verify it has valid JSON
+ (with-temp-buffer
+ (insert-file-contents output-file)
+ (should (> (buffer-size) 0))
+ ;; Should be valid JSON (starts with [)
+ (should (string-match-p "^\\[" (buffer-string)))))
+ (when (file-exists-p output-file)
+ (delete-file output-file)))))
+
(provide 'batch-functions-test)
;;; batch-functions-test.el ends here
dots/.config/claude/skills/Org/tools/batch-functions.el
@@ -774,6 +774,135 @@ Returns minutes as integer."
(setq total-minutes (get-text-property (point) :org-clock-minutes))))
(or total-minutes 0))))
+;;; Statistics & Analytics
+
+(defun org-batch-get-statistics (file)
+ "Get comprehensive statistics about TODOs in FILE.
+Returns alist with counts, priorities, tags, and time data."
+ (with-temp-buffer
+ (insert-file-contents file)
+ (org-mode)
+ (let ((total 0)
+ (by-state '())
+ (by-priority '())
+ (by-tag '())
+ (scheduled-count 0)
+ (deadline-count 0)
+ (overdue-count 0))
+ ;; Count all TODOs and gather stats
+ (org-element-map (org-element-parse-buffer) 'headline
+ (lambda (hl)
+ (let ((todo (org-element-property :todo-keyword hl))
+ (priority (org-batch--priority-to-number
+ (org-element-property :priority hl)))
+ (tags (org-element-property :tags hl))
+ (scheduled (org-element-property :scheduled hl))
+ (deadline (org-element-property :deadline hl)))
+ (when todo
+ (setq total (1+ total))
+ ;; Count by state
+ (let ((state-sym (intern todo)))
+ (if (assoc state-sym by-state)
+ (setcdr (assoc state-sym by-state)
+ (1+ (cdr (assoc state-sym by-state))))
+ (push (cons state-sym 1) by-state)))
+ ;; Count by priority
+ (when priority
+ (if (assoc priority by-priority)
+ (setcdr (assoc priority by-priority)
+ (1+ (cdr (assoc priority by-priority))))
+ (push (cons priority 1) by-priority)))
+ ;; Count by tag
+ (dolist (tag tags)
+ (if (assoc tag by-tag #'string=)
+ (setcdr (assoc tag by-tag #'string=)
+ (1+ (cdr (assoc tag by-tag #'string=))))
+ (push (cons tag 1) by-tag)))
+ ;; Count scheduled/deadline
+ (when scheduled (setq scheduled-count (1+ scheduled-count)))
+ (when deadline (setq deadline-count (1+ deadline-count)))
+ ;; Count overdue
+ (when (and deadline (not (member todo '("DONE" "CANX"))))
+ (let ((deadline-date (org-element-property :raw-value deadline))
+ (today (format-time-string "%Y-%m-%d")))
+ (when (string-match "\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)" deadline-date)
+ (let ((dl-str (match-string 0 deadline-date)))
+ (when (string< dl-str today)
+ (setq overdue-count (1+ overdue-count)))))))))))
+ ;; Return comprehensive stats
+ `((total . ,total)
+ (by_state . ,by-state)
+ (by_priority . ,by-priority)
+ (by_tag . ,by-tag)
+ (scheduled_count . ,scheduled-count)
+ (deadline_count . ,deadline-count)
+ (overdue_count . ,overdue-count)))))
+
+(defun org-batch-get-priority-distribution (file)
+ "Get distribution of tasks by priority in FILE.
+Returns alist mapping priority (1-5) to count."
+ (with-temp-buffer
+ (insert-file-contents file)
+ (org-mode)
+ (let ((distribution '((1 . 0) (2 . 0) (3 . 0) (4 . 0) (5 . 0))))
+ (org-element-map (org-element-parse-buffer) 'headline
+ (lambda (hl)
+ (let ((todo (org-element-property :todo-keyword hl))
+ (priority (org-batch--priority-to-number
+ (org-element-property :priority hl))))
+ (when (and todo priority)
+ (let ((entry (assoc priority distribution)))
+ (when entry
+ (setcdr entry (1+ (cdr entry)))))))))
+ distribution)))
+
+(defun org-batch-get-tag-statistics (file)
+ "Get statistics about tag usage in FILE.
+Returns sorted list of (tag . count) pairs."
+ (with-temp-buffer
+ (insert-file-contents file)
+ (org-mode)
+ (let ((tag-counts '()))
+ (org-element-map (org-element-parse-buffer) 'headline
+ (lambda (hl)
+ (let ((tags (org-element-property :tags hl)))
+ (dolist (tag tags)
+ (if (assoc tag tag-counts #'string=)
+ (setcdr (assoc tag tag-counts #'string=)
+ (1+ (cdr (assoc tag tag-counts #'string=))))
+ (push (cons tag 1) tag-counts))))))
+ ;; Sort by count descending
+ (sort tag-counts (lambda (a b) (> (cdr a) (cdr b)))))))
+
+;;; Export & Reporting
+
+(defun org-batch-export-csv (file output-file)
+ "Export TODOs from FILE to CSV format in OUTPUT-FILE.
+Returns t on success."
+ (let ((todos (org-batch-list-todos file)))
+ (with-temp-file output-file
+ ;; CSV header
+ (insert "heading,state,priority,tags,level,scheduled,deadline\n")
+ ;; CSV rows
+ (dolist (todo todos)
+ (insert (format "\"%s\",\"%s\",%s,\"%s\",%s,\"%s\",\"%s\"\n"
+ (or (alist-get 'heading todo) "")
+ (or (alist-get 'todo todo) "")
+ (or (alist-get 'priority todo) "")
+ (or (string-join (alist-get 'tags todo) ";") "")
+ (or (alist-get 'level todo) "")
+ (or (alist-get 'scheduled todo) "")
+ (or (alist-get 'deadline todo) "")))))
+ t))
+
+(defun org-batch-export-json (file output-file)
+ "Export TODOs from FILE to JSON format in OUTPUT-FILE.
+Returns t on success."
+ (let ((todos (org-batch-list-todos file)))
+ (with-temp-file output-file
+ (insert (json-encode todos)))
+ t))
+
;;; Output Functions
(defun org-batch-output-json (success data &optional error)
dots/.config/claude/skills/Org/tools/org-manager
@@ -204,6 +204,28 @@ TIME TRACKING:
Get total time spent on a task (in minutes)
Sums all CLOCK entries for the task
+STATISTICS & ANALYTICS:
+ get-statistics <file>
+ Get comprehensive statistics about all TODOs
+ Returns counts by state, priority, tags, scheduled, overdue
+
+ get-priority-distribution <file>
+ Get distribution of tasks across priorities (1-5)
+ Shows how many tasks at each priority level
+
+ get-tag-statistics <file>
+ Get tag usage statistics
+ Returns list of tags with usage counts, sorted by frequency
+
+EXPORT & REPORTING:
+ export-csv <file> <output-file>
+ Export all TODOs to CSV format
+ Creates spreadsheet-compatible file
+
+ export-json <file> <output-file>
+ Export all TODOs to JSON format
+ Creates machine-readable structured export
+
DENOTE COMMANDS:
denote-create <title> <tags> [--signature=SIG] [--category=CAT] [--directory=DIR] [--content=FILE]
Create a denote-formatted note with proper naming and frontmatter
@@ -892,6 +914,83 @@ cmd_get_clocked_time() {
run_elisp "$elisp"
}
+# Statistics & Analytics commands
+
+cmd_get_statistics() {
+ local file="$1"
+
+ [[ -f "$file" ]] || error "File not found: $file"
+
+ local elisp="(progn
+ (let ((stats (org-batch-get-statistics \"$file\")))
+ (org-batch-output-json t stats))
+ (kill-emacs 0))"
+
+ run_elisp "$elisp"
+}
+
+cmd_get_priority_distribution() {
+ local file="$1"
+
+ [[ -f "$file" ]] || error "File not found: $file"
+
+ local elisp="(progn
+ (let ((distribution (org-batch-get-priority-distribution \"$file\")))
+ (org-batch-output-json t (list :distribution distribution)))
+ (kill-emacs 0))"
+
+ run_elisp "$elisp"
+}
+
+cmd_get_tag_statistics() {
+ local file="$1"
+
+ [[ -f "$file" ]] || error "File not found: $file"
+
+ local elisp="(progn
+ (let ((stats (org-batch-get-tag-statistics \"$file\")))
+ (org-batch-output-json t (list :tag_stats stats)))
+ (kill-emacs 0))"
+
+ run_elisp "$elisp"
+}
+
+# Export commands
+
+cmd_export_csv() {
+ local file="$1"
+ local output="$2"
+
+ [[ -f "$file" ]] || error "File not found: $file"
+ [[ -n "$output" ]] || error "Output file required"
+
+ local elisp="(progn
+ (let ((result (org-batch-export-csv \"$file\" \"$output\")))
+ (if result
+ (org-batch-output-json t (list :exported t :output \"$output\"))
+ (org-batch-output-error \"Export failed\")))
+ (kill-emacs 0))"
+
+ run_elisp "$elisp"
+}
+
+cmd_export_json() {
+ local file="$1"
+ local output="$2"
+
+ [[ -f "$file" ]] || error "File not found: $file"
+ [[ -n "$output" ]] || error "Output file required"
+
+ local elisp="(progn
+ (let ((result (org-batch-export-json \"$file\" \"$output\")))
+ (if result
+ (org-batch-output-json t (list :exported t :output \"$output\"))
+ (org-batch-output-error \"Export failed\")))
+ (kill-emacs 0))"
+
+ run_elisp "$elisp"
+}
+
# Denote commands
cmd_denote_create() {
@@ -1138,6 +1237,21 @@ main() {
get-clocked-time)
cmd_get_clocked_time "$@"
;;
+ get-statistics)
+ cmd_get_statistics "$@"
+ ;;
+ get-priority-distribution)
+ cmd_get_priority_distribution "$@"
+ ;;
+ get-tag-statistics)
+ cmd_get_tag_statistics "$@"
+ ;;
+ export-csv)
+ cmd_export_csv "$@"
+ ;;
+ export-json)
+ cmd_export_json "$@"
+ ;;
denote-create)
cmd_denote_create "$@"
;;
dots/.config/claude/skills/Org/SKILL.md
@@ -112,6 +112,27 @@ Provide reliable, programmatic access to org-mode files using Emacs batch mode a
./tools/org-manager get-clocked-time ~/desktop/org/todos.org "Implement feature X"
```
+#### Statistics & Analytics
+```bash
+# Get comprehensive statistics (counts by state, priority, tags, overdue, etc.)
+./tools/org-manager get-statistics ~/desktop/org/todos.org
+
+# Get priority distribution across all tasks
+./tools/org-manager get-priority-distribution ~/desktop/org/todos.org
+
+# Get tag usage statistics (sorted by frequency)
+./tools/org-manager get-tag-statistics ~/desktop/org/todos.org
+```
+
+#### Export & Reporting
+```bash
+# Export to CSV for spreadsheet analysis
+./tools/org-manager export-csv ~/desktop/org/todos.org /tmp/todos.csv
+
+# Export to JSON for programmatic processing
+./tools/org-manager export-json ~/desktop/org/todos.org /tmp/todos.json
+```
+
#### Denote Operations
```bash
# Create denote-formatted note