Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic sugarwod import for lifts #81

Merged
merged 4 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{:paths ["src" "resources" "target/resources"]
:deps {com.biffweb/biff {:git/url "https://github.com/jacobobryant/biff", :sha "3ff1256", :tag "v0.7.4"}
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"}
org.clojure/clojure {:mvn/version "1.11.1"}
org.slf4j/slf4j-simple {:mvn/version "2.0.0-alpha5"}
clojure.java-time/clojure.java-time {:mvn/version "1.3.0"}
djblue/portal {:mvn/version "0.40.0"}}}
:deps {com.biffweb/biff {:git/url "https://github.com/jacobobryant/biff", :sha "3ff1256", :tag "v0.7.4"}
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"}
net.clojars.wkok/openai-clojure {:mvn/version "0.11.0"}
cheshire/cheshire {:mvn/version "5.12.0"}
metosin/malli {:mvn/version "0.13.0"}
org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/data.csv {:mvn/version "1.0.1"}
org.slf4j/slf4j-simple {:mvn/version "2.0.0-alpha5"}
clojure.java-time/clojure.java-time {:mvn/version "1.3.0"}
djblue/portal {:mvn/version "0.40.0"}}}
33 changes: 32 additions & 1 deletion src/com/spicy/app.clj
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
(ns com.spicy.app
(:require
[clojure.java.io :as io]
[com.biffweb :as biff :refer [q]]
[com.spicy.admin.core :as admin]
[com.spicy.middleware :as mid]
[com.spicy.movements.core :as movements]
[com.spicy.results.core :as results]
[com.spicy.results.ui :as r]
[com.spicy.settings :as settings]
[com.spicy.sugarwod.core :as sugar.core]
[com.spicy.sugarwod.csv :as csv]
[com.spicy.ui :as ui]
[com.spicy.workouts.core :as workouts]
[com.spicy.workouts.ui :as w]))
Expand Down Expand Up @@ -80,10 +84,37 @@
[:a.link {:href "https://biffweb.com"} "Biff"] "."]))


(defn import-page
[ctx]
(ui/page
ctx
(ui/panel [:h1.text-2xl.font-bold.mb-8
"Import your data"]
[:h2.text-xl.font-bold.mb-4
"SugarWOD"]
(biff/form {:method :post :enctype "multipart/form-data"}
[:input {:type :file :name :file :id :file}]
[:button.btn {:type "submit"} "Submit"]))))


(defn process-import
[{:keys [params session] :as ctx}]
(let [user (:uid session)
data (csv/parse-sugar-csv (io/reader (-> params :file :tempfile)))
tx-data (sugar.core/sugar-movements->tx-data (assoc ctx :user user) data)]
(biff/submit-tx ctx tx-data))
{:status 303
:headers {"location" "/app/import"}})


(def plugin
{:static {"/about/" about-page}
:routes ["/app" {:middleware [mid/wrap-signed-in]}
:routes ["/app" {:middleware [mid/wrap-signed-in
mid/wrap-session-user]}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clj-kondo] reported by reviewdog 🐶
Unresolved var: mid/wrap-session-user

["" {:get app}]
["/import" {:get import-page
:post process-import}]
admin/routes
workouts/routes
results/routes
movements/routes]
Expand Down
5 changes: 3 additions & 2 deletions src/com/spicy/numbers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

(defn parse-int
[s]
(Integer/parseInt (re-find #"\A-?\d+" s)))
(if (int? s)
s
(Integer/parseInt (re-find #"\A-?\d+" s))))


(defn safe-parse-int
Expand All @@ -12,4 +14,3 @@
(parse-int s)
(catch Exception e
(prn "Error while parsing int: " e))))

56 changes: 56 additions & 0 deletions src/com/spicy/sugarwod/core.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
(ns com.spicy.sugarwod.core
(:require
[com.biffweb :as biff]
[com.spicy.sugarwod.transform :as t]))


(defn sugar-lift?
[{:keys [barbell-lift] :as _sugar-record}]
(not-empty barbell-lift))


(defn sugar-movements->tx-data
[{:keys [user] :as ctx} sugarwod-csv-data]
(let [spicy-lifts (biff/q (:biff/db ctx)
'{:find (pull m [*])
:where [[m :movement/name]
[m :movement/type :strength]]})
sugar-lifts (filter sugar-lift? sugarwod-csv-data)]
(filterv seq (flatten
(map
(partial t/transform-sugar-strength->tx {:user user :movements spicy-lifts})
sugar-lifts)))))


(comment
(require '[com.spicy.portal :as p])
(require '[com.spicy.repl :as r])
(require '[com.spicy.sugarwod.csv :as s])
(require '[clojure.java.io :as io])

(p/open-portal)

(s/decode-record {:description "Hang Power Clean for load: #1: 8 reps #2: 8 reps #3: 8 reps #4: 8 reps #5: 8 reps #6: 8 reps"
:date "08/12/2017"
:score-type "Load"
:set-details "[{\"success\":true,\"load\":115},{\"success\":true,\"load\":115},{\"success\":true,\"load\":115},{\"success\":true,\"load\":125},{\"success\":true,\"load\":125},{\"success\":true,\"load\":125}]"
:barbell-lift "Hang Power Clean"
:best-result-raw "125"
:title "Hang Power Clean 6x8"
:rx-or-scaled "RX"
:notes "Barbell Cycling, trying to bounce the bar out of the power position." :best-result-display "125"
:pr ""})

(def e (io/reader (io/resource "sugarwod_workouts.csv")))

(def data
(s/parse-sugar-csv e))

(tap> data)


(let [ctx (r/get-context)]
(tap>
(sugar-movements->tx-data (assoc ctx :user r/user-b) data)))

(biff/add-libs))
45 changes: 45 additions & 0 deletions src/com/spicy/sugarwod/csv.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
(ns com.spicy.sugarwod.csv
(:require
[camel-snake-kebab.core :as csk]
[cheshire.core :as json]
[clojure.data.csv :as csv]))


(def BarbellLiftEnums
[:enum "" "Shoulder Press" "Bench Press" "Split Jerk" "Push Press" "Sotts Press" "Clean & Jerk" "Power Clean" "Hang Power Snatch" "Pendlay Row" "Good Morning" "Clean Pull" "Deadlift" "Romanian Deadlift" "Squat Clean Thruster" "Back Pause Squat" "Hang Squat Clean" "Back Squat" "Thruster" "Sumo Deadlift" "Squat Snatch" "Box Squat" "Power Snatch" "Front Squat" "Muscle Snatch" "Power Clean & Jerk" "Snatch" "Hang Squat Snatch" "Muscle Clean" "Overhead Squat" "Hang Clean" "Hang Power Clean" "Squat Clean" "Push Jerk" "Front Pause Squat" "Clean"])


(def SugarRecord
[:map
[[:barbell-lift BarbellLiftEnums]
[:best-result-display :int]
[:best-result-raw :int]
[:date inst?]
[:description :string]
[:notes :string]
[:pr [:enum "PR"]]
[:rx-or-scaled [:enum "RX" "SCALED"]]
[:score-type [:enum "Load" "Rounds + Reps" "Other / Text" "Reps" "Time"]]
[:set-details :string]
[:title :string]]])


(defn csv-data->maps
[csv-data]
(map zipmap
(->> (first csv-data) ; First row is the header
(map csk/->kebab-case-keyword) ; Drop if you want string keys instead
repeat)
(rest csv-data)))


(defn decode-record
[r]
(assoc r :set-details (json/decode (:set-details r) keyword)))


(defn parse-sugar-csv
[csv]
(->> (csv/read-csv csv)
csv-data->maps
(map decode-record)))
142 changes: 142 additions & 0 deletions src/com/spicy/sugarwod/transform.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
(ns com.spicy.sugarwod.transform
(:require
[clojure.string :as string]
[com.spicy.calendar :as c]
[com.spicy.numbers :as n]
[java-time.api :as jt]))


(defn transformer
"Takes a translation map data to transform"
[transform-map raw-data]
(reduce-kv (fn [m k v]
(let [resolved
(cond
(vector? v) (if (fn? (last v))
((last v) (get-in raw-data (butlast v)))
(get-in raw-data v))
(fn? v) (v raw-data)
(map? v) (transformer v raw-data)
:else v)]
(assoc m k resolved)))
{}
transform-map))


(def sugar-lift->spicy-lift
{"Shoulder Press" "strict press"
"Bench Press" "bench press"
"Split Jerk" "split jerk"
"Push Press" "push press"
"Sotts Press" "sotts press"
"Clean & Jerk" "clean and jerk"
"Power Clean" "power clean"
"Hang Power Snatch" "hang power snatch"
"Pendlay Row" "pendlay row"
"Good Morning" "good morning"
"Clean Pull" "clean pull"
"Deadlift" "deadlift"
"Romanian Deadlift" "romainian deadlift"
"Squat Clean Thruster" "squat clean thruster (cluster)"
"Back Pause Squat" "back pause squat"
"Hang Squat Clean" "hang squat clean"
"Back Squat" "back squat"
"Thruster" "thruster"
"Sumo Deadlift" "sumo deadlift"
"Squat Snatch" "squat snatch"
"Box Squat" "box squat"
"Power Snatch" "power snatch"
"Front Squat" "front squat"
"Muscle Snatch" "muscle snatch"
"Power Clean & Jerk" "power clean and jerk"
"Snatch" "snatch"
"Hang Squat Snatch" "hang squat snatch"
"Muscle Clean" "muscle clean"
"Overhead Squat" "overhead squat"
"Hang Clean" "hang clean"
"Hang Power Clean" "hang power clean"
"Squat Clean" "squat clean"
"Push Jerk" "push jerk"
"Front Pause Squat" "front pause squat"
"Clean" "clean"})
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zacjones93 if you have a movement that isnt in this conversion, we need to add it here... I manually had to go through all the movements that I've logged and created this map.



(defn sugar-lift->xt-id
[movements lift-str]
(:xt/id (first (filter #(= (get sugar-lift->spicy-lift lift-str) (:movement/name %)) movements))))


(defmulti title->reps
(fn [title]
(cond
(seq (re-find #"^(\D+)\s+(\d+x\d+)+$" title)) :constant
(seq (re-find #"^(\D+)\s+((?:\d+-)+\d+)$" title)) :variable)))


(defmethod title->reps :constant
[title]
(when-let [[_ _ reps-str] (re-find #"^(\D+)\s+(\d+x\d+)+$" title)]
(let [[sets reps] (string/split reps-str #"x")]
(repeat (n/parse-int sets) (n/parse-int reps)))))


(defmethod title->reps :variable
[title]
(when-let [[_ _ reps-str] (re-find #"^(\D+)\s+((?:\d+-)+\d+)$" title)]
(let [reps (string/split reps-str #"-")]
(map n/parse-int reps))))


(defn ->instant
[date-str]
(->> date-str
(jt/local-date "MM/dd/yyyy")
c/->instant
java.util.Date/from))


(def sugar-set->spicy-set
{:db/doc-type :strength-set
:result-set/status [:success (fn [success?] (if success? :pass :fail))]
:result-set/weight [:load n/parse-int]})


(defn map-set-details
[{:keys [type-id] :as _opts}]
(fn [{:keys [set-details title] :as _sugar-record}]
(let [reps (title->reps title)
map-fn (fn [idx set]
(-> (transformer sugar-set->spicy-set set)
(assoc :result-set/number (inc idx)
:result-set/parent type-id
:db/op :create
:result-set/reps (nth reps idx))))]
(into [] (map-indexed map-fn set-details)))))


(defn sugar-strength->spicy-strength
[{:keys [type-id user movements] :as opts}]
{:strength-result {:result/movement [:barbell-lift (partial sugar-lift->xt-id movements)]
:result/notes [:notes]
:result/set-count [:set-details count]
:db/doc-type :strength-result
:db/op :create
:xt/id type-id}
:result {:result/date [:date ->instant]
:db/doc-type :result
:db/op :create
:result/type type-id
:result/user user}
:sets (map-set-details opts)})


(defn transform-sugar-strength->tx
[{:keys [_user _movements] :as ctx} sugar-record]
(try
(let [{:keys [strength-result result sets]}
(transformer
(sugar-strength->spicy-strength (assoc ctx :type-id (random-uuid)))
sugar-record)]
(concat [result strength-result] sets))
(catch Exception _e
nil)))
Loading