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

@@ -17,7 +17,18 @@
(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
(update params :class
#(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 [:div.htmx-indicator.flex.items-center
(svg/spinner {:class "inline w-4 h-4 text-white"})] (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)]])) [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center (into [:div.h-4.w-4] children)]]))

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
@@ -26,17 +27,17 @@
(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)
@@ -52,50 +53,57 @@
(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
(cond-> {:class class}
last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes
:transaction-insight-rows :transaction-insight-rows
:after (:db/id r)) :after (:db/id r))
:hx-trigger "intersect once" :hx-trigger "intersect once"
:hx-indicator "#insight-table" :hx-indicator "#insight-table"
:hx-swap "afterend")) :hx-swap "afterend"))
[:td {:style {:width "8em"}}(:client/code (:transaction/client r))] (com/data-grid-cell {} (:client/code (:transaction/client r)))
[:td {:style {:width "10em"}} (:bank-account/code (:transaction/bank-account r))] (com/data-grid-cell {} (:bank-account/code (:transaction/bank-account r)))
[:td {:style {:width "12em"}} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local atime/normal-date))] (com/data-grid-cell {} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local atime/normal-date)))
[:td {:style {:width "30em" :max-width "30em"}} (str (:transaction/description-original r))]
[:td {:style {:width "10em"}} (com/data-grid-cell {} (str (:transaction/description-original r)))
(if (> (:transaction/amount r) 0.0 ) (com/data-grid-cell {}
(if (> (:transaction/amount r) 0.0)
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))] [:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])] [:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))]))
[:td {:style {:width "12em"}} (com/data-grid-cell {:style {:width "12em"}}
[:div [:div.tag (:vendor/name (:transaction/recommended-vendor r))]] [:div.flex.gap-2.flex-wrap {:style {:width "12em"}}
[:div [:div.tag (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]] (com/pill {:color :primary} (:vendor/name (:transaction/recommended-vendor r)))
[:div [:div.tag (com/pill {:color :secondary} (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r))))
{:class (cond (com/pill {:class (cond
(> (:transaction/account-confidence r) 0.90) (> (:transaction/account-confidence r) 0.90)
"is-success is-light" "is-success is-light"
(> (:transaction/account-confidence r) 0.80) (> (:transaction/account-confidence r) 0.80)
"is-info is-light" "is-info is-light"
:else :else
"is-warning is-light")} "is-warning is-light")} (str "%" (Math/round (* 100.0 (:transaction/account-confidence r)))))])
(str "%" (Math/round (* 100.0 (:transaction/account-confidence r))))]]] (com/data-grid-right-stack-cell {}
[:td
(when-not hide-actions? (when-not hide-actions?
[:div.buttons [:form.flex.gap-2
[:button.button {:hx-post (bidi/path-for ssr-routes/only-routes [: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-insight-approve
:transaction-id (:db/id r)) :transaction-id (:db/id r))
:hx-target "closest tr"} :hx-target "closest tr"
[:i.fa.fa-thumbs-up ]] :color :primary-light}
[:button.button svg/thumbs-up)
[:i.fa.fa-thumbs-down ]] (com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
[:a.button {:hx-get (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-insight-explain
:transaction-id (:db/id r)) :transaction-id (:db/id r))
:hx-target "#modal-holder" :hx-target "#modal-holder"
:hx-swap "beforeend"} :hx-swap "outerHTML"}
[:i.fa.fa-question ]]])]]) 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
@@ -115,8 +123,8 @@
: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,16 +138,11 @@
(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
[:div.tag.is-large.is-info.is-light (:vendor/name (:transaction/recommended-vendor r))]
[:div.tag.is-large.is-info.is-light (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]]]
[:div.modal-card-body
[:table.table
[:thead [:thead
[:tr [:tr
[:td "Date"] [:td "Date"]
@@ -147,21 +150,19 @@
[:td "Amount"]]] [:td "Amount"]]]
[:tbody [:tbody
[:tr [:tr
[:th (some-> r :transaction/date coerce/to-date-time (atime/unparse-local atime/normal-date))] [:th.text-left (some-> r :transaction/date coerce/to-date-time (atime/unparse-local atime/normal-date))]
[:th (-> r :transaction/description-original)] [:th.text-left (-> r :transaction/description-original)]
[:th (if (> (-> r :transaction/amount) 0.0 ) [:th.text-left (if (> (-> r :transaction/amount) 0.0)
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))] [:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]] [:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]]
(for [[date description amt] similar] (for [[date description amt] similar]
[:tr [:tr
[:td (some-> date coerce/to-date-time (atime/unparse-local atime/normal-date))] [:td (some-> date coerce/to-date-time (atime/unparse-local atime/normal-date))]
[:td description] [:td description]
[:td (if (> amt 0.0 ) [:td (if (> amt 0.0)
[:div.tag.is-success.is-light (str "$" (Math/round amt))] [:div.tag.is-success.is-light (str "$" (Math/round amt))]
[:div.tag.is-danger.is-light (str "$" (Math/round amt))])]])]] [:div.tag.is-danger.is-light (str "$" (Math/round amt))])]])]]
[:div])))))
]]
[: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
@@ -209,9 +205,23 @@
(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))
:identity (:identity request)
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
:admin-history)
: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 (insight-table* {:selected-client
(-> session :client :db/id) (-> session :client :db/id)
:identity identity})] :identity identity}))
"Transaction Insights")) "Transaction Insights"))