Transit Category Script (NomiScript)
Introduction
Runs on every transaction. When the source account (the split money leaves —
the negative-value split) is Suica and the target account (where it lands —
the positive-value split) is Metro, and the transaction is not categorized
yet (no category tag anywhere on it), the script categorizes it as
transportation by tagging each split.
It uses the SPLIT-ACCOUNT-NAME accessor (the posting account's display name,
serialized into the trigger context) together with SPLIT-VALUE to tell source
from target by sign. Compare with the tag-based
Rust SDK groceries script.
Trigger function
Run for every transaction:
(defun should-apply ()
(= (primary-entity-type) +entity-transaction+))
Helper: collect entity indices by type and parent
(defun entities-for (parent-idx entity-type-val)
(let ((result nil))
(do ((i 0 (+ i 1)))
((>= i (entity-count)) result)
(when (and (= (entity-type i) entity-type-val)
(= (entity-parent-idx i) parent-idx))
(setf result (cons i result))))))
Helper: count splits matching a named account on one side
The source side is the split whose value is negative (money out); the target side is the positive split (money in). Counting keeps the predicate in the index stratum so a comparison never mixes a count with money.
(defun count-source (splits name)
(let ((n 0))
(dolist (s splits)
(when (and (< (split-value s) 0)
(string= (split-account-name s) name))
(setf n (+ n 1))))
n))
(defun count-target (splits name)
(let ((n 0))
(dolist (s splits)
(when (and (> (split-value s) 0)
(string= (split-account-name s) name))
(setf n (+ n 1))))
n))
Helper: is the transaction already categorized?
Count every category tag in the context (on the transaction or any split), so
a transaction that already carries one is left untouched.
(defun category-tag-count ()
(let ((n 0))
(do ((i 0 (+ i 1)))
((>= i (entity-count)) n)
(when (and (= (entity-type i) +entity-tag+)
(string= (tag-name i) "category"))
(setf n (+ n 1))))))
Main logic
Suica → Metro, not yet categorized → tag every split category =
transportation:
(let* ((tx-idx (primary-entity-idx))
(splits (entities-for tx-idx +entity-split+)))
(when (and (> (count-source splits "Suica") 0)
(> (count-target splits "Metro") 0)
(= (category-tag-count) 0))
(dolist (s splits)
(create-tag s "category" "transportation"))))