Skip to content

Commit ffe2ca2

Browse files
committed
post 2 - fitness
1 parent 0443bc6 commit ffe2ca2

File tree

9 files changed

+154
-0
lines changed

9 files changed

+154
-0
lines changed

content/posts/cult_data_extract.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import csv
2+
import json
3+
from datetime import datetime, timedelta
4+
5+
import requests
6+
7+
# Replace <SECRET> with your actual secret token
8+
headers = {
9+
"accept": "application/json",
10+
"cookie": "", # get from the cult website, any curl call, get the value of header `Cookie`...
11+
"clientversion": "10.08",
12+
"connection": "Keep-Alive",
13+
"content-type": "application/json; charset=utf-8"
14+
}
15+
16+
start_date_str = "2022-04-25"
17+
end_date_str = "2023-08-04"
18+
current_date = datetime.strptime(start_date_str, "%Y-%m-%d")
19+
20+
csv_filename = f"fitness_report.csv"
21+
with open(csv_filename, "w", newline="") as csvfile:
22+
csv_writer = csv.writer(csvfile, delimiter="\t")
23+
csv_writer.writerow(["StartDate", "EndDate", "ClassesAttended", "CaloriesBurnt", "ExercisesDone", "FocusedOn", "NotFocusedOn", "Summary"])
24+
while current_date <= datetime.strptime(end_date_str, "%Y-%m-%d"):
25+
current_date_str = current_date.strftime("%Y-%m-%d")
26+
this_week_end_date = current_date + timedelta(days=6)
27+
this_week_end_date_str = this_week_end_date.strftime("%Y-%m-%d")
28+
29+
url = f"https://www.cult.fit/api/cult/fitnessReport?reportType=WEEKLY&startDate={current_date_str}"
30+
response = requests.get(url, headers=headers)
31+
data = response.json()
32+
33+
try:
34+
widgets = data["widgets"]
35+
classMissedWidgets = [w for w in widgets if w["widgetType"] == "CLASS_MISSED_WIDGET"]
36+
classMissedWidget = classMissedWidgets[0] if len(classMissedWidgets) > 0 else None
37+
if classMissedWidget:
38+
csv_writer.writerow([current_date_str, this_week_end_date_str, 0, 0, "", "", "", classMissedWidget["title"]["value"]])
39+
print(f"Processed week {current_date_str} to {this_week_end_date_str}")
40+
current_date += timedelta(days=7)
41+
continue
42+
fitnessReportSummary = [w for w in widgets if w["widgetType"] == "FITNESS_REPORT_SUMMARY_CARD_WIDGET"][0]
43+
metricWidget = [w for w in widgets if w["widgetType"] == "REPORT_METRIC_DETAIL_WIDGET"][0]
44+
tagViewWidgets = [w for w in widgets if w["widgetType"] == "TAG_VIEW_WIDGET"]
45+
exercisesDoneWidgets = [w for w in tagViewWidgets if "header" not in w]
46+
exercisesDoneWidget = exercisesDoneWidgets[0] if len(exercisesDoneWidgets) > 0 else None
47+
youFocusedOnWidgets = [w for w in tagViewWidgets if "header" in w and w["header"] == "YOU FOCUSSED ON"]
48+
youFocusedOnWidget = youFocusedOnWidgets[0] if len(youFocusedOnWidgets) > 0 else None
49+
start_date = fitnessReportSummary["startDate"]
50+
end_date = fitnessReportSummary["endDate"]
51+
classes_attended = metricWidget["metricSection"]["metricDisplayValue"]
52+
calories_burnt = fitnessReportSummary["caloriesBurnt"]
53+
54+
exercises_done = ""
55+
if exercisesDoneWidget:
56+
exercises_done = "\n".join(tag["title"] + "(" + tag["value"] + ")" for tag in exercisesDoneWidget["tags"] if not tag["disabled"])
57+
focused_on = ""
58+
not_focused_on = ""
59+
summary = ""
60+
if youFocusedOnWidget:
61+
focused_on = "\n".join([tag["title"] for tag in youFocusedOnWidget["tags"] if not tag["disabled"]])
62+
not_focused_on = "\n".join([tag["title"] for tag in youFocusedOnWidget["tags"] if tag["disabled"]])
63+
summary = youFocusedOnWidget["footer"]["dataText"]
64+
csv_writer.writerow([start_date, end_date, classes_attended, calories_burnt, exercises_done, focused_on, not_focused_on, summary])
65+
print(f"Processed week {start_date} to {end_date}")
66+
# Move to the next week
67+
current_date += timedelta(days=7)
68+
except Exception as e:
69+
print(f"Error processing week {current_date_str} to {this_week_end_date_str}")
70+
print(f"data json = {json.dumps(data, indent=4)}")
71+
raise e
72+
73+
74+
print(f"CSV file '{csv_filename}' created successfully.")

content/posts/fitness-track.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
+++
2+
title = 'Fitness Track'
3+
date = 2023-09-29T20:48:38+05:30
4+
draft = false
5+
+++
6+
7+
# Fitness Tracking
8+
9+
The post will describe how I track my fitness journey so far.
10+
11+
Slight background: I am on the journey to be the person who goes to gym daily, my goal is to reduce weight, and get lean. I go to [cult](https://www.cult.fit/) for gym.
12+
13+
## What all I track so far?
14+
15+
- Weight (morning and night)
16+
- Number of weeks I had been regular in gym/number of classes attended.
17+
- Occasional BMI and other metrics from [InBody machine](https://www.inbody.in/) installed in the gym I go to.
18+
19+
## How I track all this?
20+
21+
### Weight
22+
23+
I have a simple google sheet which looks like this:
24+
25+
![weight](image.png)
26+
27+
Column D and E are calculated as `Di = Ci - Bi`, `Ei = Bi+1 - Ci` tracking how much weight changed in that day, and the next day.
28+
29+
Until sometime back, this was the interface I used for updating weight everyday: open sheet, add date if not there, add the weight entries. I have a better way which uses this sheet itself as database/backend, gonna come to it at the end.
30+
31+
### Number of weeks I had been regular in gym/number of classes attended.
32+
33+
This data is something I occasionally run a script (I should probably automate this) which calls CULT's API to get the data, and I put that manually into a google sheet then.
34+
Figuring out exact API was done by downloading raw cult apk, pass it through [apk-mitm](https://github.com/shroudedcode/apk-mitm) and then use [http-toolkit](https://httptoolkit.com/) to intercept the traffic while I visit corresponding page in the app to see the API that the app calls..
35+
36+
The script looks something like in [this](./cult_data_extract.py).
37+
38+
The sheet then looks like ![Cult-Fit-Journey](image-1.png) with self-explanatory column names.
39+
40+
Again, this sheet also acts as a backend now, with frontend I will describe at the end on how do I visualize all this.
41+
42+
### Occasional BMI and other metrics from InBody machine installed in the gym I go to.
43+
44+
This is also backed by google sheet, and I have no automated way to get this data, I just manually enter it into the sheet.
45+
46+
From the GYM, I get the data printed on a page, from which I manually enter to the frontend I am gonna describe next.
47+
48+
The sheet by the way looks like:
49+
50+
![InBody](image-2.png)
51+
52+
## Frontend for all this
53+
54+
Entering all this data manually into the google sheet is a hard task, which lead me sometimes missing recording the data. So, I tried to follow the advice that `Atomic Habits` book's second rule of habit formation preaches: `Make it easy`.
55+
56+
So, the simplest way I thought was having an app in which I can quickly crunch in numbers (Ik google sheet also is the same thing, but the interface is hard to enter on the phone), so I used [appsheet](https://www.appsheet.com/) to quickly create a UI on top of the google sheets.
57+
58+
My app looks something like this:
59+
60+
### Landing screen
61+
62+
![landing screen](image-3.png)
63+
64+
This shows the weight entries, and a `+` button to add in new entries, and clicking on anyone brings interface like this: ![edit-mode](image-4.png), where I can edit during night to record night weight.
65+
66+
### InBody Results
67+
68+
Exactly similar to Landing screen, but another tab at the bottom for this.
69+
70+
### Charts - Weight and Classes
71+
72+
The third tab has charts for how weight is progressing and for cult gym classes.
73+
74+
![weight chart](image-5.png)
75+
76+
![gym-classes](image-6.png)
77+
78+
### InBody charts
79+
80+
The last one has inbody charts for metrics: Body Fat Percentage, Total Fat (kg), Muscle Mass (kg), Visceral Fat Level, Waist Hip Ratio.

content/posts/image-1.png

82.6 KB
Loading

content/posts/image-2.png

26.9 KB
Loading

content/posts/image-3.png

72.6 KB
Loading

content/posts/image-4.png

66.5 KB
Loading

content/posts/image-5.png

111 KB
Loading

content/posts/image-6.png

92.6 KB
Loading

content/posts/image.png

45.6 KB
Loading

0 commit comments

Comments
 (0)