REF remove lots of useless dead code

This commit is contained in:
Nathan Dwarshuis 2022-03-26 17:29:26 -04:00
parent a58a1dec70
commit 43a2ac7ebd
1 changed files with 49 additions and 620 deletions

View File

@ -2055,57 +2055,56 @@ removed from, added to, or edited within the DAG respectively."
(dag-edit-nodes to-remove to-insert dag))))
(plist-put org-x-dag :dag dag*)))
(defun org-x-dag-id->parent-class (id parent)
(-let* (((cfile cgroup) (org-x-dag-id->file-group id))
((pfile pgroup) (org-x-dag-id->file-group parent)))
(cl-case cgroup
;; the only allowed links are local
((:lifetime :survival)
(unless (equal cfile pfile)
:ill-foreign))
;; should only link locally or a lifetime goal
(:endpoint
(unless (or (equal cfile pfile) (eq pgroup :lifetime))
:ill-foreign))
;; should only link to an endpoint or lifetime goal
(:quarterly
(cond
((memq pgroup '(:endpoint :lifetime)) nil)
((equal pfile cfile) :ill-local)
(t :ill-foreign)))
;; should only link to a quarterly plan
(:weekly
(cond
((eq pgroup :quarterly) nil)
((equal pfile cfile) :ill-local)
(t :ill-foreign)))
;; should only link to a weekly plan or an action
(:daily
(cond
((memq pgroup '(nil :weekly)) nil)
((equal pfile cfile) :ill-local)
(t :ill-foreign)))
;; actions can only be linked to goal files, and nothing else
(t
(cond
((memq pgroup '(:lifetime :endpoint :survival)) nil)
((equal pfile cfile) :ill-local)
(t :ill-foreign))))))
;; TODO this will also include broken links, which isn't totally wrong but these
;; should be filtered out as including them is suboptimal (note: I figureed out
;; they were here because the broken links dag code is wrong)
(defun org-x-dag-filter-links (relations)
(cl-flet
((flatten-relation
(rel)
(-let (((c . ps) rel))
(--map (list c it) ps))))
(-let (((&alist :ill-foreign :ill-local)
(->> (-mapcat #'flatten-relation relations)
(--group-by (apply #'org-x-dag-id->parent-class it)))))
(list ill-foreign ill-local))))
;; (defun org-x-dag-id->parent-class (id parent)
;; (-let* (((cfile cgroup) (org-x-dag-id->file-group id))
;; ((pfile pgroup) (org-x-dag-id->file-group parent)))
;; (cl-case cgroup
;; ;; the only allowed links are local
;; ((:lifetime :survival)
;; (unless (equal cfile pfile)
;; :ill-foreign))
;; ;; should only link locally or a lifetime goal
;; (:endpoint
;; (unless (or (equal cfile pfile) (eq pgroup :lifetime))
;; :ill-foreign))
;; ;; should only link to an endpoint or lifetime goal
;; (:quarterly
;; (cond
;; ((memq pgroup '(:endpoint :lifetime)) nil)
;; ((equal pfile cfile) :ill-local)
;; (t :ill-foreign)))
;; ;; should only link to a quarterly plan
;; (:weekly
;; (cond
;; ((eq pgroup :quarterly) nil)
;; ((equal pfile cfile) :ill-local)
;; (t :ill-foreign)))
;; ;; should only link to a weekly plan or an action
;; (:daily
;; (cond
;; ((memq pgroup '(nil :weekly)) nil)
;; ((equal pfile cfile) :ill-local)
;; (t :ill-foreign)))
;; ;; actions can only be linked to goal files, and nothing else
;; (t
;; (cond
;; ((memq pgroup '(:lifetime :endpoint :survival)) nil)
;; ((equal pfile cfile) :ill-local)
;; (t :ill-foreign))))))
;; ;; TODO this will also include broken links, which isn't totally wrong but these
;; ;; should be filtered out as including them is suboptimal (note: I figureed out
;; ;; they were here because the broken links dag code is wrong)
;; (defun org-x-dag-filter-links (relations)
;; (cl-flet
;; ((flatten-relation
;; (rel)
;; (-let (((c . ps) rel))
;; (--map (list c it) ps))))
;; (-let (((&alist :ill-foreign :ill-local)
;; (->> (-mapcat #'flatten-relation relations)
;; (--group-by (apply #'org-x-dag-id->parent-class it)))))
;; (list ill-foreign ill-local))))
(defun org-x-dag-id->illegal-parents (which id)
(ht-get (plist-get org-x-dag which) id))
@ -2127,576 +2126,6 @@ removed from, added to, or edited within the DAG respectively."
(-when-let (e (org-x-dag-id->created-epoch id))
(<= e (float-time))))
(defun org-x-dag-id->0th-status (id)
(cl-flet*
((check-done
(id kw)
(cond
((member kw org-x-done-keywords)
(unless (org-x-dag-id->is-closed-p id)
"DONE/CANC headlines must be closed"))
(t
(when (org-x-dag-id->is-closed-p id)
"closed headlines must be marked DONE/CANC"))))
(check-todo-or-done
(id kw)
(or (check-done id kw)
(unless (equal kw org-x-kw-todo)
"keyword must be TODO or DONE")))
(check-not-scheduled
(id)
(when (org-x-dag-id->planning-timestamp :scheduled id)
"scheduled timestamps not allowed"))
(check-not-deadlined
(id)
(when (org-x-dag-id->planning-timestamp :deadline id)
"deadlined timestamps not allowed"))
(check-level
(level id)
(unless (= level (org-x-dag-id->level id))
(format "headline must have level %d" level))))
(cond
;; all nodes must have legal nodes and have a creation timestamp
((org-x-dag-id->any-illegal-p id)
"has illegal links")
((not (org-x-dag-id->created-in-past-p id))
"must have creation timestamp in the past")
(t
(-let (((_ group) (org-x-dag-id->file-group id))
(kw (org-x-dag-id->todo id)))
(cl-case group
;; lifetime/survival nodes
;; - can only be marked TODO (they never end)
;; - cannot be marked with CLOSED/SCHEDULED/DEADLINE
((:lifetime :survival)
(cond
((not (equal kw org-x-kw-todo))
"keyword must be TODO")
((org-x-dag-id->metaprop :planning id)
"planning element not allowed")))
;; endpoint nodes
;; - cannot be SCHEDULED
;; - can only be TODO or DONE/CANC with CLOSED
(:endpoint
(or (check-not-scheduled id)
(check-todo-or-done id kw)))
;; quarterly plan nodes
;; - can only be level 4 headlines
;; - cannot be SCHEDULED
;; - can only be TODO or DONE/CANC with CLOSED
;; - if DEADLINE, timestamp must start on/after the quarter
(:quarterly
(or (check-level 4 id)
(check-not-scheduled id)
(check-todo-or-done id kw)
(-when-let (d (org-x-dag-id->planning-datetime :deadline id))
(when (->> (org-x-dag-id->tags nil id)
(org-x-dag-quarter-tags-to-date)
(org-x-dag-datetime< d))
"deadline occurs after quarter start"))))
;; weekly plan nodes
;; - can only level 4
;; - cannot be SCHEDULED or DEADLINE
;; - can only be TODO or DONE/CANC with CLOSED
(:weekly
(or (check-level 4 id)
(check-not-scheduled id)
(check-not-deadlined id)
(check-todo-or-done id kw)))
;; daily plan nodes
;; - can only be level 4 headlines
;; - can only be TODO or DONE/CANC with CLOSED
;; - must be SCHEDULED with long timestamp on the indicated day
(:daily
(or (check-level 4 id)
(check-todo-or-done id kw)
(-let (((date time) (->> (org-x-dag-id->planning-datetime id)
(org-x-dag-datetime-split))))
(cond
((not (and date time))
"must have HH:MM scheduled timestamp")
((->> (org-x-dag-id->tags nil id)
(org-x-dag-daily-tags-to-date)
(org-x-dag-datetime= date))
"timestamp must be within the calendar date")))))
;; action nodes
;; - closed keywords must be marked DONE/CANC
(t (check-done id kw))))))))
;; type Status = Either String (Enum, a)
;; data NodeData a = Status { props :: [something], status = Status a }
(defun org-x-dag-status-valid (id data)
`(:valid :id ,id :data ,data))
(defun org-x-dag-status-error (id msg)
`(:error :id ,id :msg ,msg))
(defmacro org-x-dag-status>>= (status form)
"Treat STATUS like an Either monad.
FORM should take the data of a valid status bound to 'it' and
return another status."
(declare (indent 1))
`(pcase ,status
(`(:error . ,d) `(:error ,@d))
(`(:valid . ,it) ,form)))
(defmacro org-x-dag-status-with-data (status form)
(declare (indent 1))
`(org-x-dag-status>>= status
(-let (((&plist :id it-id :data it-data) it))
,form)))
(defmacro org-x-dag-status-from-data (status form)
(declare (indent 1))
`(pcase ,status
(`(:error . ,_) nil)
(`(:valid . ,d)
(-let (((&plist :id it-id :data it-data) d))
,form))))
(defun org-x-dag-action-status (id action-type code)
;; action types and their codes
;;
;; :task
;; - :archivable
;; - :complete
;; - :active
;;
;; :project
;; - :archivable
;; - :complete
;; - :stuck
;; - :held
;; - :wait
;; - :active
;;
;; :iterator
;; - :archivable
;; - :complete
;; - :empty
;; - :active
;; - :future
;;
;; :iterator-task/project
;; - :archivable
;; - :complete
;; - :active
;; - :future
(org-x-dag-status-valid id (list :code code :action-type action-type)))
(defun org-x-dag-endpoint-status (id deadline code)
(org-x-dag-status-valid id (list :code code :deadline deadline)))
;; (defun org-x-dag-id->task-status (id)
;; (cl-flet
;; ((to-valid
;; (code)
;; (org-x-dag-action-status id :task code))
;; (to-error
;; (msg)
;; (org-x-dag-status-error id msg)))
;; (let ((kw (org-x-dag-id->todo id)))
;; (-if-let (c (org-x-dag-id->planning-epoch :closed id))
;; (if (member kw org-x-done-keywords)
;; (->> (if (org-x-dag-time-is-archivable-p c) :archivable :complete)
;; (to-valid))
;; (to-error "Closed id must be marked DONE/CANC"))
;; (cond
;; ((member kw org-x-done-keywords)
;; (to-error "Done id's must have closed timestamp"))
;; (t
;; (to-valid :active)))))))
(defun org-x-dag-status-is-error-p (status)
(eq (car status) :error))
(defmacro org-x-dag-id->with-child-statuses (id child-statuses codelist
to-valid-form rank-form
childless-form)
(declare (indent 3))
(let ((c (make-symbol "--child-statuses"))
(i (make-symbol "--id")))
`(-if-let (,c ,child-statuses)
(let* ((,i ,id)
(ranks (--map (org-x-dag-status-from-data it ,rank-form)
,child-statuses)))
(if (-any #'null ranks)
(org-x-dag-status-error ,i "Child-dependent error")
(pcase (nth (-max ranks) ',codelist)
(`nil (error "Invalid index returned during child translation"))
(`(:error ,msg) (org-x-dag-status-error ,i msg))
(`(:valid ,it) ,to-valid-form))))
,childless-form)))
(defmacro org-x-dag-id->with-action-child-statuses (id child-statuses action-type
codelist trans-fun
childless-form)
(declare (indent 3))
`(org-x-dag-id->with-child-statuses ,id ,child-statuses ,codelist
(org-x-dag-action-status ,id ,action-type it)
(-let (((&plist :action-type a :code c) it-data))
(funcall ,trans-fun it-id a c))
,childless-form))
(defmacro org-x-dag-id->with-endpoint-child-statuses (id child-statuses deadline
codelist trans-fun
childless-form)
(declare (indent 3))
`(org-x-dag-id->with-child-statuses ,id ,child-statuses ,codelist
(org-x-dag-endpoint-status ,id ,deadline it)
(-let (((&plist :deadline d :code c) it-data))
(funcall ,trans-fun it-id ,deadline d c))
,childless-form))
(defmacro org-x-dag-id->with-nested-buffer-children (id child-form node-form)
(declare (indent 1))
;; Distinguish between immediate children and all deeper children;
;; assume that the car of anything returned by
;; `org-x-dag-id->action-status' is the parent of all the status
;; objects in the cdr, so map these together
`(let* ((results (->> (org-x-dag-id->buffer-children ,id)
(--map ,child-form)))
(it (-map #'car results)))
(->> (apply #'append results)
(cons ,node-form))))
(defun org-x-dag-id->action-status (id in-iteratorp)
(cl-flet
((iterator-status
(id kw closed c-statuses)
(cond
((not (org-x-dag-id->node-property "ARCHIVE" id))
(org-x-dag-status-error id "Iterator must have ARCHIVE set"))
((not (org-x-dag-id->node-property org-x-prop-time-shift id))
(->> (format "Iterator must have %s set" org-x-prop-time-shift)
(org-x-dag-status-error id)))
((equal kw org-x-done-keywords)
(org-x-dag-id->with-action-child-statuses id c-statuses :iterator
((:valid :complete)
(:error "Complete iterators can only have complete child nodes"))
(lambda (_ type code)
(when (memq type '(:iterator-task :iterator-project))
(pcase code
(:complete 0)
(:active 1))))
(org-x-dag-action-status id :iterator :complete)))
((equal kw org-x-kw-todo)
(org-x-dag-id->with-action-child-statuses id c-statuses :iterator
((:valid :empty)
(:valid :active))
(lambda (_ type code)
(when (memq type '(:iterator-task :iterator-project))
(pcase code
(:complete 0)
(:active 1))))
(org-x-dag-action-status id :iterator :empty)))
(t
(funcall to-error "Iterator can only have DONE/CANC/TODO keywords"))))
(iterator-project-status
(id kw closed c-statuses)
(cond
((equal kw org-x-kw-done)
(org-x-dag-id->with-action-child-statuses id c-statuses :iterator-project
((:valid :complete)
(:error "Complete iterator projects can only have complete children"))
(lambda (_ type code)
(when (memq type '(:iterator-task :iterator-project))
(pcase code
(:complete 0)
(:active 1))))
(org-x-dag-action-status id :iterator-task :complete)))
((equal kw org-x-kw-todo)
(org-x-dag-id->with-action-child-statuses id c-statuses :iterator-project
((:error "Open iterator projects must have at least one active child")
(:valid :active))
(lambda (_ type code)
(when (memq type '(:iterator-project :iterator-task))
(pcase code
(:complete 0)
(:active 1))))
(if (or (org-x-dag-id->planning-epoch :scheduled id)
(org-x-dag-id->planning-epoch :deadline id))
(org-x-dag-action-status id :iterator-task :active)
(->> "Iterator tasks must be scheduled or deadlined"
(org-x-dag-status-error id)))))
(t
(->> (format "Iterated action has invalid keyword: %s" kw)
(org-x-dag-status-error id)))))
(project-status
(id kw closed c-statuses)
(cond
((equal kw org-x-kw-done)
(->>
(org-x-dag-id->with-action-child-statuses id c-statuses :project
((:valid :complete)
(:error "DONE projects can only have complete child nodes"))
;; TODO figure out what to do when a key doesn't match, it
;; should make an error but not sure if it should be a
;; runtime error or a returned status error
(lambda (_ type code)
(pcase `(,type ,code)
(`(,_ :complete) 0)
(`(:project ,(or :stuck :held :wait :active)) 1)
(`(:task :active) 1)
(`(:iterator ,(or :active :future :empty) 1))
(e (error "Invalid task/code combination: %S" e))))
(to-valid-closed id :task closed))
(check-archivable id closed)))
((equal kw org-x-kw-todo)
(org-x-dag-id->with-action-child-statuses id c-statuses :project
((:error "TODO projects should have at least one active child")
(:valid :stuck)
(:valid :held)
(:valid :wait)
(:valid :active))
(lambda (id type code)
(pcase `(,type ,code)
(`(,_ :complete) 0)
(`(:project :stuck) 1)
(`(:project :held) 2)
(`(:project :wait) 3)
(`(:project :active) 4)
(`(:task ,_)
(let ((kw (org-x-dag-id->todo id)))
(cond
((equal kw org-x-kw-hold) 2)
((equal kw org-x-kw-wait) 3)
((equal kw org-x-kw-next) 4)
((equal kw org-x-kw-todo)
(if (org-x-dag-id->planning-timestamp :scheduled id) 4 1))
(t
(error "Could not translate task with keyword: %s" kw)))))
(`(:iterator :empty) 1)
(`(:iterator :active) 4)
(e (error "Invalid task/code combination: %S" e))))
(org-x-dag-action-status id :task :active)))
(t
(if c-statuses
(if (equal kw org-x-kw-hold)
(org-x-dag-action-status id :project :held)
(->> (format "Projects cannot be marked %s" kw)
(org-x-dag-status-error)))
(org-x-dag-action-status id :task :active))))))
(-let ((kw (org-x-dag-id->todo id))
(closed (org-x-dag-id->planning-timestamp :closed id))
(iteratorp (org-x-dag-id->is-iterator-p id)))
(org-x-dag-id->with-nested-buffer-children id
(org-x-dag-id->action-status it iteratorp)
;; first check for a bunch of errors that are equally valid at any
;; level in the DAG
(-if-let (general-error
(cond
((org-x-dag-id->any-illegal-p id)
"Action has illegal links")
((not (org-x-dag-id->created-in-past-p id))
"Action must have creation timestamp in the past")
((and closed (not (member kw org-x-done-keywords)))
"Closed actions must be marked with DONE/CANC")
((and (not closed) (member kw org-x-done-keywords))
"DONE/CANC actions must have closed timestamp")
((and it (org-x-dag-id->planning-timestamp :scheduled id))
"Actions with children cannot be scheduled")
((and it (org-x-dag-id->planning-timestamp :deadline id))
"Actions with children cannot be deadlined")
((and iteratorp in-iteratorp)
"Iterators cannot be nested")))
(org-x-dag-status-error id general-error)
(cond
;; CANC actions work the same regardless of action type
((equal kw org-x-kw-canc)
(let ((type (cond
(iteratorp :iterator)
(in-iteratorp
(if it :iterator-project :iterator-task))
(t
(if it :project :task)))))
(org-x-dag-action-status id type :complete)))
;; assuming none of the general checks above matched, try more
;; specific checks depending on if the current action is an iterator
;; (and if we are inside one)
(iteratorp
(iterator-status id kw closed it))
(in-iteratorp
(iterator-project-status id kw closed it))
(t
(project-status id kw closed it))))))))
(defun org-x-dag-status-check-todo-or-done (id to-error alt-fun)
(declare (indent 2))
(org-x-dag-status-check-done id kw to-error
(lambda (id)
(let ((kw (org-x-dag-id->todo id)))
(if (equal kw org-x-kw-todo)
(to-error "Keyword must be marked TODO/DONE")
(funcall alt-fun id))))))
(defun org-x-dag-id->illegal-link-error (id)
(when (org-x-dag-id->any-illegal-p id)
"Node has illegal links"))
(defun org-x-dag-done-closed-error (kw closed)
(cond
((and closed (not (member kw org-x-done-keywords)))
"Closed nodes must be marked with DONE/CANC")
((and (not closed) (member kw org-x-done-keywords))
"DONE/CANC nodes must have closed timestamp")))
(defun org-x-dag-id->created-error (id)
(unless (org-x-dag-id->node-property org-x-prop-created id)
"Node must have creation timestamp"))
;; TODO it might make more sense to make a 'status' independent of time, such
;; that as time changes, we don't need to constantly recalculate this
(defun org-x-dag-id->endpoint-status (id)
(let ((kw (org-x-dag-id->todo id))
(closed (org-x-dag-id->planning-timestamp :closed id)))
(org-x-dag-id->with-nested-buffer-children id
(org-x-dag-id->endpoint-status it)
(-if-let (general-error
(or (org-x-dag-id->illegal-link-error id)
(org-x-dag-id->created-error id)
(org-x-dag-done-closed-error kw closed)
(cond
((org-x-dag-id->planning-timestamp :scheduled id)
"Endpoint goals cannot be scheduled")
((member kw (list org-x-kw-next org-x-kw-hold org-x-kw-wait))
"Endpoint goal has invalid keyword: %s" kw))))
(org-x-dag-status-error id general-error)
(if (equal kw org-x-kw-canc)
(org-x-dag-endpoint-status id nil :complete)
(let ((deadline (org-x-dag-id->planning-epoch :deadline id)))
(if (equal kw org-x-kw-done)
(org-x-dag-id->with-endpoint-child-statuses id it deadline
((:valid :complete)
(:error "Closed endpoint goals must only have closed children"))
(lambda (_ _ _ code)
(pcase code
(:complete 0)
(:active 1)))
(org-x-dag-endpoint-status id nil :complete))
(org-x-dag-id->with-endpoint-child-statuses id it deadline
((:error "Open endpoint goals must have at least one open child")
(:valid :active)
(:error "Child deadline cannot occur after this deadline"))
(lambda (_ this-deadline child-deadline code)
(if (and this-deadline child-deadline
(< deadline child-deadline))
2
(pcase code
(:active 1)
(:complete 2))))
(org-x-dag-endpoint-status id deadline :active)))))))))
(defun org-x-dag-toplevel-goal-status (id)
(org-x-dag-status-valid id :toplevel-goal))
(defun org-x-dag-id->toplevel-goal-status (id)
(-if-let (err (or (org-x-dag-id->illegal-link-error id)
(org-x-dag-id->created-error id)
(when (org-x-dag-id->metaprop :planning id)
"Toplevel goals cannot have planning elements.")
(unless (equal org-x-kw-todo (org-x-dag-id->todo id))
"Toplevel goals can only be TODO")))
(org-x-dag-status-error id general-error)
(org-x-dag-toplevel-goal-status id)))
(defun org-x-dag-qtp-status (id code deadline)
(org-x-dag-status-valid id (list :code code :deadline)))
(defun org-x-dag-id->qtp-status (id)
(let ((kw (org-x-dag-id->todo id))
(closed (org-x-dag-id->planning-timestamp :closed id)))
(-if-let (err (or (org-x-dag-id->illegal-link-error id)
(org-x-dag-id->created-error id)
(org-x-dag-done-closed-error kw closed)
(unless (eq 4 (org-x-dag-id->level id))
"Quarterly plans cannot have children")
(when (org-x-dag-id->planning-timestamp :scheduled id)
"Quarterly plans cannot be scheduled")))
(org-x-dag-status-error id general-error)
(if (member kw org-x-done-keywords)
(org-x-dag-qtp-status id :complete)
(-if-let (deadline (org-x-dag-id->planning-datetime :deadline id))
(if (org-ml-time-is-long)
(org-x-dag-status-error id "Quarterly plan deadlines must be short")
(let ((qdate (->> (org-x-dag-id->tags nil id)
(org-x-dag-quarter-tags-to-date))))
(if (org-x-dag-datetime< deadline qdate)
(->> "Quarterly plan deadlines must start within/after quarter"
(org-x-dag-status-error id))
(org-x-dag-qtp-status id :active deadline))))
(org-x-dag-qtp-status id :active nil))))))
(defun org-x-dag-wkp-status (id code)
(org-x-dag-status-valid id (list :code code)))
(defun org-x-dag-id->wkp-status (id)
(let ((kw (org-x-dag-id->todo id))
(closed (org-x-dag-id->planning-timestamp :closed id)))
(-if-let (err (or (org-x-dag-id->illegal-link-error id)
(org-x-dag-id->created-error id)
(org-x-dag-done-closed-error kw closed)
(unless (eq 4 (org-x-dag-id->level id))
"Weekly plans cannot have children")
(when (org-x-dag-id->planning-timestamp :scheduled id)
"Quarterly plans cannot be scheduled")
(when (org-x-dag-id->planning-timestamp :deadline id)
"Quarterly plans cannot be deadlined")))
(org-x-dag-status-error id general-error)
(if (member kw org-x-done-keywords)
(org-x-dag-qtp-status id :complete)
(org-x-dag-qtp-status id :active)))))
(defun org-x-dag-wkp-status (id code scheduled)
(org-x-dag-status-valid id (list :code code :scheduled scheduled)))
(defun org-x-dag-id->dlp-status (id)
(let ((kw (org-x-dag-id->todo id))
(closed (org-x-dag-id->planning-timestamp :closed id)))
(-if-let (err (or (org-x-dag-id->illegal-link-error id)
(org-x-dag-id->created-error id)
(org-x-dag-done-closed-error kw closed)
(unless (eq 4 (org-x-dag-id->level id))
"Daily plans cannot have children")
(when (org-x-dag-id->planning-timestamp :deadline id)
"Daily plans cannot be deadlined")))
(org-x-dag-status-error id general-error)
(if (member kw org-x-done-keywords)
(org-x-dag-wlp-status id :complete nil)
(let ((scheduled (org-x-dag-id->planning-datetime :scheduled id)))
(if (not (and scheduled (org-ml-time-is-long scheduled)))
(->> "Daily plans must have long scheduled timestamp"
(org-x-dag-status-error id))
;; ASSUME this won't fail (likewise for quarterly plans)
(let ((ddate (->> (org-x-dag-id->tags nil id)
(org-x-dag-daily-tags-to-date))))
(if (org-x-dag-datetime= ddate (-take 3 scheduled))
(org-x-dag-wkp-status id :active scheduled)
(->> "Daily plan scheduled timestamp at wrong date"
(org-x-dag-status-error id))))))))))
(defun org-x-dag-id->file-level-status (id)
"Return file-level status of ID and its children.
Assume ID is a toplevel headline. Return an alist where the car
of each cell is the ID and the cdr is its status."
(-let (((_ group) (org-x-dag-id->file-group id)))
(cl-case group
(:action
(org-x-dag-id->action-status id))
(:endpoint
(org-x-dag-id->endpoint-status id)))))
;; (defun org-x-dag-file-level-status (ids)
;; (->> (--filter (org-x-dag-id->is-toplevel-p it) ids)
;; ;; TODO this function doesn't exist yet
;; (--filter (org-x-dag-id->error id))))
;; TODO there is a HUGE DIFFERENCE between a 'key' (the things in the hash table
;; the look things up) and a 'node' (which is a cons cell, the car of which is a
;; 'key' and the cdr of which is a 'relation'). These names suck, but the point