gets transaction insights kind of working

This commit is contained in:
Bryce
2023-06-13 17:05:10 -07:00
parent 4259425812
commit 3e4b963726
6 changed files with 2045 additions and 140 deletions

1871
data/inference-outcome.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -16,11 +16,22 @@
(into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center ] children)]) (into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center ] children)])
(defn icon-button- [params & children] (defn icon-button- [params & children]
(into (into
[:button (update params :class str " inline-flex items-center justify-center bg-white dark:bg-gray-600 items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center text-gray-500 hover:text-gray-800 rounded-lg dark:text-gray-400 dark:hover:text-gray-100") [:button
[:div.htmx-indicator.flex.items-center
(svg/spinner {:class "inline w-4 h-4 text-white"})] (update params :class
[:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center (into [:div.h-4.w-4] children)]])) #(cond-> %
true (str " inline-flex items-center justify-center items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center rounded-lg ")
(= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700")
(= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 ")
(= :primary-light (:color params)) (str " bg-green-200 hover:bg-green-300 focus:ring-green-200 dark:bg-green-700 dark:hover:bg-green-600 text-gray-800 dark:text-gray-200")
(= :secondary-light (:color params)) (str " bg-blue-200 hover:bg-blue-300 focus:ring-blue-200 dark:bg-blue-700 dark:hover:bg-blue-600 text-gray-800 dark:text-gray-200")
(= :danger-light (:color params)) (str " bg-red-200 hover:bg-red-300 focus:ring-red-200 dark:bg-red-700 dark:hover:bg-red-600 text-gray-800 dark:text-gray-200")
(nil? (:color params))
(str " bg-white dark:bg-gray-600 border-gray-300 dark:border-gray-700 text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-100")))
[:div.htmx-indicator.flex.items-center
(svg/spinner {:class "inline w-4 h-4 text-white"})]
[:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center (into [:div.h-4.w-4] children)]]))
(defn a-icon-button- [params & children] (defn a-icon-button- [params & children]
(into (into

View File

@@ -8,7 +8,8 @@
(defn header- [params & rest] (defn header- [params & rest]
(into [:th.px-4.py-3 {:scope "col" :class (:class params) (into [:th.px-4.py-3 {:scope "col" :class (:class params)
"_" (hiccup/raw (when (:sort-key params ) (format "on click trigger sorted(key:\"%s\")", (:sort-key params))))}] "_" (hiccup/raw (when (:sort-key params ) (format "on click trigger sorted(key:\"%s\")", (:sort-key params))))
:style (:style params)}]
(if (:sort-key params) (if (:sort-key params)
[(into [:a {:href "#"} ] rest)] [(into [:a {:href "#"} ] rest)]
rest))) rest)))
@@ -23,7 +24,7 @@
:class str " border-b dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700")] rest)) :class str " border-b dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700")] rest))
(defn cell- [params & rest] (defn cell- [params & rest]
(into [:td.px-4.py-2 {:class (:class params)}] rest)) (into [:td.px-4.py-2 params ] rest))
(defn right-stack-cell- [params & rest] (defn right-stack-cell- [params & rest]
(cell- params (into [:div.flex.flex-row-reverse.items-center.justify-between (cell- params (into [:div.flex.flex-row-reverse.items-center.justify-between

View File

@@ -35,7 +35,7 @@ curModal.hide();
]]) ]])
(defn modal-card- [params header content footer] (defn modal-card- [params header content footer]
[:div#modal-card [:div#modal-card params
[:div {:class "relative bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white fade-in slide-up duration-300 transition-all modal-content"} [:div {:class "relative bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white fade-in slide-up duration-300 transition-all modal-content"}
[:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600"} header] [:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600"} header]
[:div {:class "p-6 space-y-6"} [:div {:class "p-6 space-y-6"}

View File

@@ -419,3 +419,15 @@
[:path {:d "M22.629,4.572A6.22,6.22,0,0,1,23,6.5v16a1,1,0,0,1-1,1H2a1,1,0,0,1-1-1V6.5a6.22,6.22,0,0,1,.371-1.928L2.629,1.428A1.6,1.6,0,0,1,4,.5H20a1.6,1.6,0,0,1,1.371.928Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}] [:path {:d "M22.629,4.572A6.22,6.22,0,0,1,23,6.5v16a1,1,0,0,1-1,1H2a1,1,0,0,1-1-1V6.5a6.22,6.22,0,0,1,.371-1.928L2.629,1.428A1.6,1.6,0,0,1,4,.5H20a1.6,1.6,0,0,1,1.371.928Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
[:line {:x1 "12", :y1 "6", :x2 "12", :y2 "0.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}] [:line {:x1 "12", :y1 "6", :x2 "12", :y2 "0.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
[:line {:x1 "1.034", :y1 "6", :x2 "22.966", :y2 "6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]]) [:line {:x1 "1.034", :y1 "6", :x2 "22.966", :y2 "6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
(def thumbs-up
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
[:defs]
[:title "like"]
[:path {:d "M19.5,16.065h0a1.5,1.5,0,0,1,0,3h-1a1.5,1.5,0,0,1,0,3H12c-4,0-3-2-11-2v-9H4a7.949,7.949,0,0,0,7.5-8c0-1.581,3-1.781,3,1.219a31.593,31.593,0,0,1-1,5.781h8a1.5,1.5,0,0,1,0,3h-1a1.5,1.5,0,0,1,0,3h-1", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
(def thumbs-down
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
[:defs]
[:title "dislike"]
[:path {:d "M4.5,8h0a1.5,1.5,0,0,1,0-3h1a1.5,1.5,0,0,1,0-3H12c4,0,3,1.87,11,1.87V13H20a7.811,7.811,0,0,0-7.5,7.856c0,1.582-3,1.813-3-1.187A29.774,29.774,0,0,1,10.5,14h-8a1.5,1.5,0,0,1,0-3h1a1.5,1.5,0,0,1,0-3h1", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])

View File

@@ -1,7 +1,6 @@
(ns auto-ap.ssr.transaction.insights (ns auto-ap.ssr.transaction.insights
(:require (:require
[auto-ap.datomic :refer [conn visible-clients]] [auto-ap.datomic :refer [conn visible-clients]]
[auto-ap.shared-views.company.sidebar :refer [company-side-bar]]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.ui :refer [base-page]] [auto-ap.ssr.ui :refer [base-page]]
[auto-ap.ssr.utils :refer [html-response]] [auto-ap.ssr.utils :refer [html-response]]
@@ -10,7 +9,9 @@
[clj-time.coerce :as coerce] [clj-time.coerce :as coerce]
[datomic.api :as dc] [datomic.api :as dc]
[hiccup2.core :as hiccup] [hiccup2.core :as hiccup]
[clj-time.core :as time])) [auto-ap.ssr.components :as com]
[auto-ap.client-routes :as client-routes]
[auto-ap.ssr.svg :as svg]))
(def pull-expr [:transaction/description-original (def pull-expr [:transaction/description-original
:db/id :db/id
@@ -25,77 +26,84 @@
(defn transaction-recommendations [identity selected-client & {:keys [after]}] (defn transaction-recommendations [identity selected-client & {:keys [after]}]
(let [visible-clients (visible-clients identity)] (let [visible-clients (visible-clients identity)]
(->> (->>
(dc/q '[:find (pull ?t pull-expr) (dc/qseq {:query '[:find (pull ?t pull-expr)
:in $ [?c ...] pull-expr :in $ [?c ...] pull-expr
:where [?t :transaction/recommended-account] :where [?t :transaction/recommended-account]
[?t :transaction/client ?c] [?t :transaction/client ?c]
[?t :transaction/approval-status :transaction-approval-status/unapproved] [?t :transaction/approval-status :transaction-approval-status/unapproved]
(not [?t :transaction/vendor])] (not [?t :transaction/vendor])]
(dc/db conn) :args [(dc/db conn)
(if selected-client (if selected-client
[selected-client] [selected-client]
visible-clients) visible-clients)
pull-expr) pull-expr]})
(map first) (map first)
(sort-by :transaction/date) (sort-by :transaction/date)
(reverse) (reverse)
(drop-while (fn [x] (drop-while (fn [x]
(if after (if after
(not= (Long/parseLong after) (:db/id x)) (not= (Long/parseLong after) (:db/id x))
false))) false)))
(#(if after (#(if after
(drop 1 %) (drop 1 %)
%)) %))
(take 10) (take 10)
(into [])))) (into []))))
(defn transaction-row [r & {:keys [hide-actions? class last?]}] (defn transaction-row [r & {:keys [hide-actions? class last?]}]
[:tr (cond-> {:class class} (com/data-grid-row
last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes (cond-> {:class class}
:transaction-insight-rows last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes
:after (:db/id r)) :transaction-insight-rows
:hx-trigger "intersect once" :after (:db/id r))
:hx-indicator "#insight-table" :hx-trigger "intersect once"
:hx-swap "afterend")) :hx-indicator "#insight-table"
[:td {:style {:width "8em"}}(:client/code (:transaction/client r))] :hx-swap "afterend"))
[:td {:style {:width "10em"}} (:bank-account/code (:transaction/bank-account r))] (com/data-grid-cell {} (:client/code (:transaction/client r)))
[:td {:style {:width "12em"}} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local atime/normal-date))] (com/data-grid-cell {} (:bank-account/code (:transaction/bank-account r)))
[:td {:style {:width "30em" :max-width "30em"}} (str (:transaction/description-original r))] (com/data-grid-cell {} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local atime/normal-date)))
[:td {:style {:width "10em"}}
(if (> (:transaction/amount r) 0.0 )
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]
[:td {:style {:width "12em"}}
[:div [:div.tag (:vendor/name (:transaction/recommended-vendor r))]]
[:div [:div.tag (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]]
[:div [:div.tag
{:class (cond
(> (:transaction/account-confidence r) 0.90)
"is-success is-light"
(> (:transaction/account-confidence r) 0.80)
"is-info is-light"
:else (com/data-grid-cell {} (str (:transaction/description-original r)))
"is-warning is-light")} (com/data-grid-cell {}
(str "%" (Math/round (* 100.0 (:transaction/account-confidence r))))]]] (if (> (:transaction/amount r) 0.0)
[:td [:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
(when-not hide-actions? [:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))]))
[:div.buttons (com/data-grid-cell {:style {:width "12em"}}
[:button.button {:hx-post (bidi/path-for ssr-routes/only-routes [:div.flex.gap-2.flex-wrap {:style {:width "12em"}}
:transaction-insight-approve (com/pill {:color :primary} (:vendor/name (:transaction/recommended-vendor r)))
:transaction-id (:db/id r)) (com/pill {:color :secondary} (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r))))
:hx-target "closest tr"} (com/pill {:class (cond
[:i.fa.fa-thumbs-up ]] (> (:transaction/account-confidence r) 0.90)
[:button.button "is-success is-light"
[:i.fa.fa-thumbs-down ]] (> (:transaction/account-confidence r) 0.80)
[:a.button {:hx-get (bidi/path-for ssr-routes/only-routes "is-info is-light"
:transaction-insight-explain
:transaction-id (:db/id r)) :else
:hx-target "#modal-holder" "is-warning is-light")} (str "%" (Math/round (* 100.0 (:transaction/account-confidence r)))))])
:hx-swap "beforeend"} (com/data-grid-right-stack-cell {}
[:i.fa.fa-question ]]])]]) (when-not hide-actions?
[:form.flex.gap-2
[:input {:type :hidden :name "id" :value (:db/id r)}]
(com/icon-button {:hx-post (bidi/path-for ssr-routes/only-routes
:transaction-insight-approve
:transaction-id (:db/id r))
:hx-target "closest tr"
:color :primary-light}
svg/thumbs-up)
(com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
:transaction-insight-approve
:transaction-id (:db/id r))
:hx-swap "closest tr"
:color :danger-light}
svg/thumbs-down)
(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
:transaction-insight-explain
:transaction-id (:db/id r))
:hx-target "#modal-holder"
:hx-swap "outerHTML"}
svg/question)]))))
(defn approve [{:keys [identity session] {:keys [transaction-id]} :route-params}] (defn approve [{:keys [identity session] {:keys [transaction-id]} :route-params}]
(html-response (transaction-row (html-response (transaction-row
@@ -108,15 +116,15 @@
(defn explain [{:keys [identity session] {:keys [transaction-id]} :route-params}] (defn explain [{:keys [identity session] {:keys [transaction-id]} :route-params}]
(let [r (dc/pull (dc/db conn) (let [r (dc/pull (dc/db conn)
pull-expr pull-expr
(Long/parseLong transaction-id)) (Long/parseLong transaction-id))
similar (->> (dc/q '[:find ?date ?do ?amt similar (->> (dc/q '[:find ?date ?do ?amt
:in $ ?tr :in $ ?tr
:where :where
[(iol-ion.query/recent-date 180) ?start-date] [(iol-ion.query/recent-date 180) ?start-date]
[?tr :transaction/client ?c] [?tr :transaction/client ?c]
[?tr :transaction/recommended-account ?a ] [?tr :transaction/recommended-account ?a]
[?tr :transaction/recommended-vendor ?v ] [?tr :transaction/recommended-vendor ?v]
[?t2 :transaction/client ?c] [?t2 :transaction/client ?c]
[?t2 :transaction/date ?date] [?t2 :transaction/date ?date]
[(>= ?date ?start-date)] [(>= ?date ?start-date)]
@@ -130,38 +138,31 @@
(take 5) (take 5)
sort sort
reverse)] reverse)]
(html-response [:div.modal.is-active.wide (html-response
[:div.modal-background {"_" (hiccup/raw "on click remove <#modal-holder div/>")}] (com/modal {}
[:div.modal-card (com/modal-card {:style {:width "900px"}}
[:div.modal-card-head [:div.flex [:div.p-2 "Similar Transactions"]]
[:h1.title "Similar transactions"] [:table.w-full
[:div.tags [:thead
[:div.tag.is-large.is-info.is-light (:vendor/name (:transaction/recommended-vendor r))] [:tr
[:div.tag.is-large.is-info.is-light (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]]] [:td "Date"]
[:div.modal-card-body [:td "Description"]
[:table.table [:td "Amount"]]]
[:thead [:tbody
[:tr [:tr
[:td "Date"] [:th.text-left (some-> r :transaction/date coerce/to-date-time (atime/unparse-local atime/normal-date))]
[:td "Description"] [:th.text-left (-> r :transaction/description-original)]
[:td "Amount"]]] [:th.text-left (if (> (-> r :transaction/amount) 0.0)
[:tbody [:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
[:tr [:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]]
[:th (some-> r :transaction/date coerce/to-date-time (atime/unparse-local atime/normal-date))] (for [[date description amt] similar]
[:th (-> r :transaction/description-original)] [:tr
[:th (if (> (-> r :transaction/amount) 0.0 ) [:td (some-> date coerce/to-date-time (atime/unparse-local atime/normal-date))]
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))] [:td description]
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]] [:td (if (> amt 0.0)
(for [[date description amt] similar] [:div.tag.is-success.is-light (str "$" (Math/round amt))]
[:tr [:div.tag.is-danger.is-light (str "$" (Math/round amt))])]])]]
[:td (some-> date coerce/to-date-time (atime/unparse-local atime/normal-date))] [:div])))))
[:td description]
[:td (if (> amt 0.0 )
[:div.tag.is-success.is-light (str "$" (Math/round amt))]
[:div.tag.is-danger.is-light (str "$" (Math/round amt))])]])]]
]]
[:button.modal-close.is-large {"_" (hiccup/raw "on click remove <#modal-holder div/>")}]])))
(defn transaction-rows* [{:keys [selected-client identity after]}] (defn transaction-rows* [{:keys [selected-client identity after]}]
(let [recommendations (transaction-recommendations identity selected-client :after after)] (let [recommendations (transaction-recommendations identity selected-client :after after)]
@@ -178,28 +179,23 @@
:after (:after route-params)}))) :after (:after route-params)})))
(defn insight-table* [{:keys [selected-client identity]}] (defn insight-table* [{:keys [selected-client identity]}]
[:div#insight-table {:hx-get (bidi/path-for ssr-routes/only-routes (let [recommendations (transaction-recommendations identity selected-client)]
:transaction-insight-table (com/data-grid-card {:id "insight-table"
:request-method :get) :title "Transaction Insights"
:hx-trigger "clientSelected from:body" :route :transaction-insight-table
:hx-swap "outerHTML swap:100ms"} :paginate? false
:total (count recommendations)
[:table.table :action-buttons nil
[:thead :rows (for [r recommendations
[:tr :let [last? (= r (last recommendations))]]
[:td "Client"] (transaction-row r :last? last?))
[:td "Account"] :headers [(com/data-grid-header {} "Client")
[:td "Date"] (com/data-grid-header {} "Account")
[:td "Description"] (com/data-grid-header {} "Date")
[:td "Amount"] (com/data-grid-header {} "Description")
[:td "Vendor / Account"] (com/data-grid-header {} "Amount")
[:td "action"]]] (com/data-grid-header {:style {:width "4em"}} "Vendor / Account")
[:tbody (com/data-grid-header {})]})))
(transaction-rows* {:selected-client selected-client
:identity identity})]]
[:div.container.htmx-indicator
[:div.column.is-4.is-offset-4.has-text-centered
[:div.loader.is-loading.is-active.big.is-centered]]]])
(defn insight-table [{:keys [session identity]}] (defn insight-table [{:keys [session identity]}]
(html-response (insight-table* {:selected-client (html-response (insight-table* {:selected-client
@@ -208,10 +204,24 @@
(defn page [{:keys [identity matched-route session] :as request}] (defn page [{:keys [identity matched-route session] :as request}]
(base-page (base-page
request request
[:div (com/page {:nav (com/admin-aside-nav)
[:h1.title "Transaction Insights"] :active-client (:client (:session request))
(insight-table* {:selected-client :identity (:identity request)
(-> session :client :db/id) :app-params {:hx-get (bidi/path-for ssr-routes/only-routes
:identity identity})] :admin-history)
"Transaction Insights")) :hx-trigger "clientSelected from:body"
:hx-select "#app-contents"
:hx-swap "outerHTML swap:300ms"}}
(com/breadcrumbs {}
[:a {:href (bidi/path-for client-routes/routes
:transactions)}
"Transactions"]
[:a {:href (bidi/path-for ssr-routes/only-routes
:transaction-insights)}
"Insights"])
(insight-table* {:selected-client
(-> session :client :db/id)
:identity identity}))
"Transaction Insights"))