diff --git a/src/clj/auto_ap/rule_matching.clj b/src/clj/auto_ap/rule_matching.clj new file mode 100644 index 00000000..b15bad22 --- /dev/null +++ b/src/clj/auto_ap/rule_matching.clj @@ -0,0 +1,130 @@ +(ns auto-ap.rule-matching + (:require [auto-ap.yodlee.core :as client] + [auto-ap.utils :refer [by]] + [datomic.api :as d] + [auto-ap.datomic :refer [uri remove-nils]] + [auto-ap.datomic.accounts :as a] + [clj-time.coerce :as coerce] + [digest :refer [sha-256]] + [auto-ap.datomic.checks :as d-checks] + [auto-ap.datomic.transactions :as d-transactions] + [auto-ap.datomic.clients :as d-clients] + [auto-ap.time :as time] + [auto-ap.datomic.transaction-rules :as tr])) + +(defn rule-applies? [transaction {:keys [:transaction-rule/description + :transaction-rule/dom-gte :transaction-rule/dom-lte + :transaction-rule/amount-gte :transaction-rule/amount-lte + :transaction-rule/client :transaction-rule/bank-account + :transaction-rule/yodlee-merchant]} ] + (let [transaction-dom (some-> transaction + :transaction/date + .toInstant + (.atZone (java.time.ZoneId/of "US/Pacific")) + (.get java.time.temporal.ChronoField/DAY_OF_MONTH))] + (and + (if description + (re-find description (:transaction/description-original transaction)) + true) + (if dom-gte + (>= transaction-dom dom-gte) + true) + (if dom-lte + (<= transaction-dom dom-lte) + true) + (if amount-gte + (>= (:transaction/amount transaction) amount-gte) + true) + (if amount-lte + (<= (:transaction/amount transaction) amount-lte) + true) + (if client + (or + (= (:transaction/client transaction) + (:db/id client)) + (= (:db/id (:transaction/client transaction)) + (:db/id client))) + true) + (if yodlee-merchant + (= (:yodlee-merchant/yodlee-id (:transaction/yodlee-merchant transaction)) + (:yodlee-merchant/yodlee-id yodlee-merchant)) + true) + (if bank-account + (or + (= (:db/id (:transaction/bank-account transaction)) + (:db/id bank-account)) + (= (:transaction/bank-account transaction) + (:db/id bank-account))) + true)))) + +(defn rule-priority [rule] + (or + (->> [[:transaction-rule/bank-account 0] + [:transaction-rule/client 1] + [:transaction-rule/dom-lte 2] + [:transaction-rule/dom-gte 2] + [:transaction-rule/amount-lte 3] + [:transaction-rule/amount-gte 3] + [:transaction-rule/description 4] + [:transaction-rule/yodlee-merchant 5]] + (filter (fn [[key]] + (get rule key))) + (map second) + first) + 6)) + +(defn get-matching-rules-by-priority [rules-by-priority transaction] + (loop [[rule-set & rules] rules-by-priority] + (if rule-set + (let [matching-rules (into [] (filter #(rule-applies? transaction %) rule-set))] + (if (seq matching-rules) + matching-rules + (recur rules))) + []))) + +(defn group-rules-by-priority [rules] + (->> rules + (map (fn [r] (update r :transaction-rule/description #(some-> % re-pattern)))) + (group-by rule-priority) + (sort-by first) + (map second))) + +(defn get-matching-rules [transaction all-rules] + (->> all-rules + (map (fn [r] (update r :transaction-rule/description #(some-> % re-pattern)))) + (filter #(rule-applies? transaction %)))) + +(defn apply-rule [transaction rule valid-locations] + (assoc transaction + :transaction/matched-rule (:db/id rule) + :transaction/approval-status (:transaction-rule/transaction-approval-status rule) + :transaction/accounts (mapcat + (fn [tra] + (if (= "Shared" (:transaction-rule-account/location tra)) + (map + (fn [location] + {:transaction-account/account (:db/id (:transaction-rule-account/account tra)) + :transaction-account/amount (Math/abs (* (/ 1.0 (count valid-locations)) + (:transaction-rule-account/percentage tra) + (:transaction/amount transaction))) + :transaction-account/location location}) + + + valid-locations) + [{:transaction-account/account (:db/id (:transaction-rule-account/account tra)) + :transaction-account/amount (Math/abs (* (:transaction-rule-account/percentage tra) + (:transaction/amount transaction))) + :transaction-account/location (:transaction-rule-account/location tra)}])) + (:transaction-rule/accounts rule)) + :transaction/vendor (:db/id (:transaction-rule/vendor rule)))) + +(defn rule-applying-fn [rules] + (let [rules-by-priority (group-rules-by-priority rules)] + (fn [transaction valid-locations] + (if (:transaction/payment transaction) + transaction + (let [matching-rules (get-matching-rules-by-priority rules-by-priority transaction )] + (if-let [top-match (and (= (count matching-rules) 1) (first matching-rules))] + (apply-rule transaction top-match valid-locations) + + transaction))))))