From 5f810ac2c4663047af07b44dfdada5c6a0ab9a12 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Wed, 15 May 2019 18:57:42 -0700 Subject: [PATCH] prioritizes rules. --- src/clj/auto_ap/yodlee/import.clj | 69 +++++++++++-- test/clj/auto_ap/yodlee/import.clj | 157 +++++++++++++++++++++++++---- 2 files changed, 202 insertions(+), 24 deletions(-) diff --git a/src/clj/auto_ap/yodlee/import.clj b/src/clj/auto_ap/yodlee/import.clj index 9358e935..14994a0d 100644 --- a/src/clj/auto_ap/yodlee/import.clj +++ b/src/clj/auto_ap/yodlee/import.clj @@ -121,22 +121,79 @@ [] (partition-all 100 transactions))) -(defn rule-applies? [transaction rule] - (re-find (:transaction-rule/description rule) (:transaction/description-original transaction))) +(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]} ] + (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 + (= (:transaction/client transaction) + (:db/id client)) + true) + (if bank-account + (= (:transaction/bank-account transaction) + (:db/id bank-account)) + true)))) + +(defn more-specific? [left right key] + (cond (and (get left key) + (get right key)) + nil + (get left key) + true + + (get right key) + false + + :else + nil)) + +(defn prioritize-rule [left right] + (first (filter #(not (nil? %)) + [(more-specific? left right :transaction-rule/bank-account) + (more-specific? left right :transaction-rule/client) + (more-specific? left right :transaction-rule/dom-lte) + (more-specific? left right :transaction-rule/dom-gte) + (more-specific? left right :transaction-rule/amount-lte) + (more-specific? left right :transaction-rule/amount-gte) + + true]))) (defn rule-applying-fn [rules] (let [rules (transduce (map (fn [r] (update r :transaction-rule/description #(some-> % re-pattern)))) conj [] - rules)] + (sort prioritize-rule rules))] + (println rules) (fn [transaction] - (let [matching-rules (filter #(rule-applies? transaction %) rules)] + (let [matching-rules (->> rules + (filter #(rule-applies? transaction %) ))] (if-let [top-match (first matching-rules)] (assoc transaction + :transaction/matched-rule (:db/id top-match) :transaction/approval-status (:transaction-rule/transaction-approval-status top-match) - :transaction/vendor (:db/id (:transaction-rule/vendor top-match)) - ) + :transaction/vendor (:db/id (:transaction-rule/vendor top-match))) transaction))))) (defn manual-import [manual-transactions] diff --git a/test/clj/auto_ap/yodlee/import.clj b/test/clj/auto_ap/yodlee/import.clj index f3b1d8c5..9b382b50 100644 --- a/test/clj/auto_ap/yodlee/import.clj +++ b/test/clj/auto_ap/yodlee/import.clj @@ -12,7 +12,7 @@ (m/-main false) (f) (d/release (d/connect uri)) - (d/delete-database uri)) + (d/delete-database uri)) (t/use-fixtures :each wrap-setup) @@ -77,14 +77,14 @@ (let [[result] (sut/transactions->txs [(assoc base-transaction - :description {:original "CHECK 10001" - :simple ""} - :amount {:amount 30.0} - :client-id client-id - :bank-account-id bank-account-id)] - :client-id - :bank-account-id - identity)] + :description {:original "CHECK 10001" + :simple ""} + :amount {:amount 30.0} + :client-id client-id + :bank-account-id bank-account-id)] + :client-id + :bank-account-id + identity)] (t/is (= {:db/id payment-id :payment/status :payment-status/cleared} @@ -139,25 +139,146 @@ (t/is (= :transaction-approval-status/approved (:transaction/approval-status result))))) - (t/testing "Should match if description matches" - (let [apply-rules (sut/rule-applying-fn [{:transaction-rule/description "XXX039" + (t/testing "Should apply vendor and approval status" + (let [apply-rules (sut/rule-applying-fn [{:db/id 1 + :transaction-rule/description "XXX039" :transaction-rule/transaction-approval-status :transaction-approval-status/approved :transaction-rule/vendor {:db/id 123}} - {:transaction-rule/description "OtherMatch" + {:db/id 2 + :transaction-rule/description "OtherMatch" :transaction-rule/transaction-approval-status :transaction-approval-status/requires-feedback :transaction-rule/vendor {:db/id 456}}])] (t/is (= {:transaction/description-original "Hello XXX039", :transaction/vendor 123 - :transaction/approval-status :transaction-approval-status/approved} - (-> {:transaction/description-original "Hello XXX039"} - apply-rules))) + :transaction/approval-status :transaction-approval-status/approved + :transaction/matched-rule 1} + (-> {:transaction/description-original "Hello XXX039"} + apply-rules))) (t/is (= {:transaction/description-original "OtherMatch", :transaction/approval-status :transaction-approval-status/requires-feedback - :transaction/vendor 456} + :transaction/vendor 456 + :transaction/matched-rule 2} (-> {:transaction/description-original "OtherMatch"} apply-rules))) (t/is (= {:transaction/description-original "Hello Not match"} - (-> {:transaction/description-original "Hello Not match"} - apply-rules)))))))) + (-> {:transaction/description-original "Hello Not match"} + apply-rules))))) + + (t/testing "Should match if day of month matches" + (let [apply-rules (sut/rule-applying-fn [{:db/id 123 + :transaction-rule/dom-gte 3 + :transaction-rule/dom-lte 9 + :transaction-rule/transaction-approval-status :transaction-approval-status/approved + :transaction-rule/vendor {:db/id 123}}])] + (t/is (= 123 + (-> {:transaction/date #inst "2019-01-04T00:00:00.000-08:00"} + apply-rules + :transaction/matched-rule))) + (t/is (= 123 + (-> {:transaction/date #inst "2019-01-03T00:00:00.000-08:00"} + apply-rules + :transaction/matched-rule))) + (t/is (= 123 + (-> {:transaction/date #inst "2019-01-09T00:00:00.000-08:00"} + apply-rules + :transaction/matched-rule))) + (t/is (nil? + (-> {:transaction/date #inst "2019-01-01T00:00:00.000-08:00"} + apply-rules + :transaction/matched-rule))) + (t/is (nil? + (-> {:transaction/date #inst "2019-01-10T00:00:00.000-08:00"} + apply-rules + :transaction/matched-rule))))) + + (t/testing "Should match if amount matches" + (let [apply-rules (sut/rule-applying-fn [{:db/id 123 + :transaction-rule/amount-gte 3.0 + :transaction-rule/amount-lte 9.0 + :transaction-rule/transaction-approval-status :transaction-approval-status/approved + :transaction-rule/vendor {:db/id 123}}])] + (t/is (= 123 + (-> {:transaction/amount 4.0} + apply-rules + :transaction/matched-rule))) + (t/is (= 123 + (-> {:transaction/amount 3.0} + apply-rules + :transaction/matched-rule))) + (t/is (= 123 + (-> {:transaction/amount 9.0} + apply-rules + :transaction/matched-rule))) + (t/is (nil? + (-> {:transaction/amount 9.01} + apply-rules + :transaction/matched-rule))) + (t/is (nil? + (-> {:transaction/amount 2.99} + apply-rules + :transaction/matched-rule))))) + + (t/testing "Should match if client matches" + (let [apply-rules (sut/rule-applying-fn [{:db/id 123 + :transaction-rule/client {:db/id 456}}])] + (t/is (= 123 + (-> {:transaction/client 456} + apply-rules + :transaction/matched-rule))) + (t/is (nil? + (-> {:transaction/client 89} + apply-rules + :transaction/matched-rule))))) + (t/testing "Should match if bank-account matches" + (let [apply-rules (sut/rule-applying-fn [{:db/id 123 + :transaction-rule/bank-account {:db/id 456}}])] + (t/is (= 123 + (-> {:transaction/bank-account 456} + apply-rules + :transaction/matched-rule))) + (t/is (nil? + (-> {:transaction/bank-account 89} + apply-rules + :transaction/matched-rule))))) + + (t/testing "Should prioritize rules" + (let [apply-rules (sut/rule-applying-fn (shuffle [{:db/id 2 + :transaction-rule/description "Hello" + :transaction-rule/amount-gte 5.0} + {:db/id 1 + :transaction-rule/description "Hello"} + {:db/id 3 + :transaction-rule/description "Hello" + :transaction-rule/client {:db/id 789}} + {:db/id 4 + :transaction-rule/description "Hello" + :transaction-rule/client {:db/id 789} + :transaction-rule/bank-account {:db/id 456}}]))] + + (t/is (= 4 + (-> {:transaction/bank-account 456 + :transaction/client 789 + :transaction/description-original "Hello" + :transaction/amount 6.0} + apply-rules + :transaction/matched-rule))) + (t/is (= 3 + (-> {:transaction/bank-account 457 + :transaction/client 789 + :transaction/amount 6.0 + :transaction/description-original "Hello"} + apply-rules + :transaction/matched-rule))) + (t/is (= 3 + (-> {:transaction/bank-account 457 + :transaction/client 789 + :transaction/amount 6.0 + :transaction/description-original "Hello"} + apply-rules + :transaction/matched-rule))) + #_(t/is (nil? + (-> {:transaction/bank-account 89} + apply-rules + :transaction/matched-rule))))))))