Added functions to show distribution of donations by account.

This commit is contained in:
Simon Brooke 2026-09-28 09:57:31 +01:00
parent b396015e34
commit 74afa02516
2 changed files with 65 additions and 11 deletions

View file

@ -5,6 +5,7 @@
[com.widdindustries/cljc.java-time "0.1.22"] [com.widdindustries/cljc.java-time "0.1.22"]
[org.xerial/sqlite-jdbc "3.7.15-M1"]] [org.xerial/sqlite-jdbc "3.7.15-M1"]]
:description "FIXME: write description" :description "FIXME: write description"
:jvm-opts ["-Xmx4g"]
:license {:name "GNU General Public License,version 2.0 or (at your option) an :license {:name "GNU General Public License,version 2.0 or (at your option) an
y later version" y later version"
:url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"}

View file

@ -2,14 +2,15 @@
(:require [clojure.string :refer [capitalize]] (:require [clojure.string :refer [capitalize]]
[cljc.java-time.local-date :as ld] [cljc.java-time.local-date :as ld]
[com.hypirion.clj-xchart :refer [xy-chart]] [com.hypirion.clj-xchart :refer [xy-chart]]
[korma.core :refer [fields join select where]] [korma.core :refer [as-sql fields join select where]]
[korma.db :refer [defdb sqlite3]])) [korma.db :refer [defdb sqlite3]]))
;; create db ;; create db
(defdb db (sqlite3 (def db-spec {:classname "org.sqlite.JDBC"
{:classname "org.sqlite.JDBC"
:subprotocol "sqlite" :subprotocol "sqlite"
:subname "resources/app.db"})) :subname "resources/app.db"})
(defdb db (sqlite3 db-spec))
(defmacro date? [x] (defmacro date? [x]
`(instance? java.time.LocalDate ~x)) `(instance? java.time.LocalDate ~x))
@ -22,11 +23,13 @@
returned." returned."
([start-date] ([start-date]
(let [sd (cond (date? start-date) start-date (let [sd (cond (date? start-date) start-date
(string? start-date) (ld/parse start-date))] (string? start-date) (ld/parse start-date)
:else (ld/parse "2024-01-01"))]
(total-donations-by-weeks sd (ld/plus-days sd 7)))) (total-donations-by-weeks sd (ld/plus-days sd 7))))
([start-date end-date] ([start-date end-date]
(let [sd (cond (date? start-date) start-date (let [sd (cond (date? start-date) start-date
(string? start-date) (ld/parse start-date)) (string? start-date) (ld/parse start-date)
:else (ld/parse "2024-01-01"))
ed (cond (date? end-date) end-date ed (cond (date? end-date) end-date
(string? end-date) (ld/parse end-date) (string? end-date) (ld/parse end-date)
:else (ld/now))] :else (ld/now))]
@ -62,7 +65,6 @@
[(map #(datetime->date (first %)) data) [(map #(datetime->date (first %)) data)
(map #(:n (nth % 1)) data)]}))) (map #(:n (nth % 1)) data)]})))
(defn chart-total-donations-by-weeks (defn chart-total-donations-by-weeks
"Create and return an XY chart of data on total donations made between "Create and return an XY chart of data on total donations made between
`start-date` and `end-date`. Arguments may be supplied as `start-date` and `end-date`. Arguments may be supplied as
@ -76,3 +78,54 @@
`java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`." `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`."
[start-date end-date] [start-date end-date]
(chart-donations-by-weeks start-date end-date :n)) (chart-donations-by-weeks start-date end-date :n))
(defn enhance-account-with-donations
"Take this `account_map`, which must at least have a valid value for the
key `:campaign_url` and return a similar map including the key `:donations`,
whose value is a float representing the total donations received by that
campaign URL between the dates `sd` (exclusive) and `ed` (inclusive). Date
arguments should be supplied as `java.time.LocalDate` objects."
[account-map sd ed]
(assoc account-map :donations
(reduce + 0 (map :amount
(select 'campaign_donations
(fields :amount)
(where (and
{:campaign_url [= (:campaign_url account-map)]}
{:created_at [> sd]}
{:created_at [<= ed]})))))))
(defn accounts-with-donations-by-date-range
"Return a list of maps, each representing one account from the `gaza.onl`
database, with a key `:donations`, whose value is a float representing the
total donations received by that account between the dates `start-date`
(exclusive) and `end-date` (inclusive). Date arguments should be supplied
as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`."
[start-date end-date]
(let [sd (cond (date? start-date) start-date
(string? start-date) (ld/parse start-date))
ed (cond (date? end-date) end-date
(string? end-date) (ld/parse end-date)
:else (ld/now))
accounts (select 'accounts
(fields :id :display_name :created_at :campaign_url)
(where {:created_at [< sd]}))]
(map #(enhance-account-with-donations % sd ed) accounts)))
(defn chart-donations-by-accounts-in-date-range
"Return a chart of donations received by individual accounts between
`start-date` (exclusive) and `end-date` (inclusive). Arguments should be
supplied as `java.time.LocalDate` objects, or as strings in the format
`yyyy-mm-dd`. Accounts are identified in the chart only by rank order."
[start-date end-date]
(let [data (accounts-with-donations-by-date-range start-date end-date)]
(xy-chart {(format "Donations between\n%s and\n%s" start-date end-date)
[(range (count data)) (sort (map :donations data))]})))
;; this does not work and I don't know why
;; (as-sql '(select 'campaign_donations
;; (fields :amount :campaign_url :created_at)
;; (where (and
;; {:campaign_url [= "https://www.chuffed.org/project/128261-photographer-from-gaza-rebuilding-life-dreams"]}
;; {:created_at [> "2024-04-01"]}
;; {:created_at [<= "2o24-04-30"]}))))