dimwit 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dimwit-0.3.2 → dimwit-0.3.3}/PKG-INFO +1 -1
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/main.py +23 -0
- dimwit-0.3.3/dimwit/running.py +160 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit.egg-info/PKG-INFO +1 -1
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit.egg-info/SOURCES.txt +1 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/pyproject.toml +1 -1
- {dimwit-0.3.2 → dimwit-0.3.3}/README.md +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/__init__.py +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/air_pollution.py +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/airflow.py +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/legacy.py +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/pomodoro.py +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/sleep.py +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit/weight.py +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit.egg-info/dependency_links.txt +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit.egg-info/requires.txt +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/dimwit.egg-info/top_level.txt +0 -0
- {dimwit-0.3.2 → dimwit-0.3.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dimwit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A package containing various functions and classes for fetching, transforming, and visualising data for personal metrics.
|
|
5
5
|
Author-email: Daniel Soutar <danielsoutar144@gmail.com>
|
|
6
6
|
Project-URL: repository, https://github.com/danielsoutar/dimwit
|
|
@@ -135,6 +135,29 @@ def get_notion_pages_from_db(
|
|
|
135
135
|
return results
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
# Sleep data compression format
|
|
139
|
+
# All distinct years on first line, comma-separated.
|
|
140
|
+
# All distinct months on second line, comma-separated.
|
|
141
|
+
# The start and end days for each month. If there are gaps, parenthesised indices
|
|
142
|
+
# indicate where they are. If a month change occurs, its last day is indicated.
|
|
143
|
+
# Example: 25,(26),31,(7),24.
|
|
144
|
+
# All distinct timezones on the 4th line. If >1, indicate the indices they start
|
|
145
|
+
# from.
|
|
146
|
+
# Example: +01:00,12+00:00
|
|
147
|
+
# Finally each entry has the following:
|
|
148
|
+
# (-/+) indicating if the entry starts and ends on the same day (+ = next day)
|
|
149
|
+
# HH:MM>HH:MM|DURATION
|
|
150
|
+
# 2025
|
|
151
|
+
# 06
|
|
152
|
+
# 19,23
|
|
153
|
+
# +01:00
|
|
154
|
+
# -02:40>06:50|4:10
|
|
155
|
+
# -02:40>06:50|4:10
|
|
156
|
+
# -02:40>06:50|4:10
|
|
157
|
+
# -02:40>06:50|4:10
|
|
158
|
+
# -02:40>06:50|4:10
|
|
159
|
+
|
|
160
|
+
|
|
138
161
|
# This function compresses event-like data (could be generalised to arbitrary
|
|
139
162
|
# data.)
|
|
140
163
|
# Several optimisations are implemented vs baseline of '[datetime+TZ]: [DATA]':
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Trailing running volume by difficulty across a multi-block training plan."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import datetime as dat
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import numpy as np
|
|
8
|
+
from matplotlib.dates import DateFormatter
|
|
9
|
+
from matplotlib.ticker import MultipleLocator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
RECOVERY, EASY, STEADY, THRESHOLD, HARD = range(5)
|
|
13
|
+
DIFFICULTY_NAMES = ["Recovery", "Easy", "Steady", "Threshold", "Hard"]
|
|
14
|
+
# Categorical hues (blue, aqua, yellow, red, violet), fixed order per level.
|
|
15
|
+
DIFFICULTY_COLORS = ["#2a78d6", "#1baf7a", "#eda100", "#e34948", "#4a3aa7"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Run:
|
|
20
|
+
"""One stretch of running at a single difficulty. A session may be several of these."""
|
|
21
|
+
|
|
22
|
+
running_time_mins: float
|
|
23
|
+
difficulty: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# A week is {weekday (Mon=0 .. Sun=6): [Run, ...]}.
|
|
27
|
+
Week = dict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Plan:
|
|
32
|
+
runs: dict # {date: [Run, ...]}
|
|
33
|
+
sections: list # [(start date, label), ...]
|
|
34
|
+
start: dat.date
|
|
35
|
+
end: dat.date # exclusive
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def interval_session(
|
|
39
|
+
sets: float,
|
|
40
|
+
work_mins: float,
|
|
41
|
+
recovery_mins: float,
|
|
42
|
+
work_difficulty: int,
|
|
43
|
+
warm_cool_mins: float = 12.5,
|
|
44
|
+
) -> list:
|
|
45
|
+
"""Warm-up/cool-down (easy) + intervals + recovery jogs between sets, as separate runs."""
|
|
46
|
+
runs = [
|
|
47
|
+
Run(2 * warm_cool_mins, EASY),
|
|
48
|
+
Run(sets * work_mins, work_difficulty),
|
|
49
|
+
]
|
|
50
|
+
if sets > 1:
|
|
51
|
+
runs.append(Run((sets - 1) * recovery_mins, RECOVERY))
|
|
52
|
+
return runs
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def easy_week(*mins: float, difficulty: int = EASY) -> Week:
|
|
56
|
+
"""Runs on Mon/Wed/Fri, and a fourth on Sun if given."""
|
|
57
|
+
return {day: [Run(m, difficulty)] for day, m in zip((0, 2, 4, 6), mins)}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def repeat_each_week(weeks: list, times: int = 2) -> list:
|
|
61
|
+
"""Repeat each week `times` times in a row (weeks 1, 1, 2, 2, ...)."""
|
|
62
|
+
return [week for week in weeks for _ in range(times)]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def build_plan(sections: list, start_date: dat.date) -> Plan:
|
|
66
|
+
"""Lay consecutive `(label, weeks)` sections out from `start_date`, which must be a Monday."""
|
|
67
|
+
if start_date.weekday() != 0:
|
|
68
|
+
raise ValueError(f"start_date must be a Monday, got {start_date:%A}")
|
|
69
|
+
|
|
70
|
+
runs, section_starts, day = {}, [], start_date
|
|
71
|
+
for label, weeks in sections:
|
|
72
|
+
section_starts.append((day, label))
|
|
73
|
+
for w, week in enumerate(weeks):
|
|
74
|
+
for weekday, week_runs in week.items():
|
|
75
|
+
date = day + dat.timedelta(days=7 * w + weekday)
|
|
76
|
+
runs.setdefault(date, []).extend(week_runs)
|
|
77
|
+
day += dat.timedelta(days=7 * len(weeks))
|
|
78
|
+
return Plan(runs, section_starts, start_date, day)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def trailing_volume(plan: Plan, window_days: int = 7) -> np.ndarray:
|
|
82
|
+
"""(difficulty level, day) array of mins run at that level over the trailing window."""
|
|
83
|
+
n_days = (plan.end - plan.start).days
|
|
84
|
+
daily = np.zeros((len(DIFFICULTY_NAMES), n_days))
|
|
85
|
+
for date, runs in plan.runs.items():
|
|
86
|
+
for run in runs:
|
|
87
|
+
daily[run.difficulty, (date - plan.start).days] += run.running_time_mins
|
|
88
|
+
# Value at day t covers days t - window_days + 1 .. t.
|
|
89
|
+
return np.array([np.convolve(row, np.ones(window_days))[:n_days] for row in daily])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def plot_volume_by_difficulty(
|
|
93
|
+
plan: Plan,
|
|
94
|
+
done_date: dat.date = None,
|
|
95
|
+
window_days: int = 7,
|
|
96
|
+
title: str = "Running duration over time",
|
|
97
|
+
figsize: tuple = (10, 5),
|
|
98
|
+
):
|
|
99
|
+
"""Stacked trailing volume by difficulty. `done_date` marks how far you've got."""
|
|
100
|
+
trailing = trailing_volume(plan, window_days)
|
|
101
|
+
n_days = trailing.shape[1]
|
|
102
|
+
days = [plan.start + dat.timedelta(days=i) for i in range(n_days)]
|
|
103
|
+
|
|
104
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
105
|
+
present = [d for d in range(len(DIFFICULTY_NAMES)) if trailing[d].any()]
|
|
106
|
+
ax.stackplot(
|
|
107
|
+
days,
|
|
108
|
+
[trailing[d] for d in present],
|
|
109
|
+
colors=[DIFFICULTY_COLORS[d] for d in present],
|
|
110
|
+
labels=[DIFFICULTY_NAMES[d] for d in present],
|
|
111
|
+
alpha=0.9,
|
|
112
|
+
)
|
|
113
|
+
top = ax.get_ylim()[1] * 0.95
|
|
114
|
+
|
|
115
|
+
# Weeks begin on plan.start (a Monday): labelled every 4 weeks, grid every week.
|
|
116
|
+
ax.set_xlim(days[0], days[-1])
|
|
117
|
+
major_days = list(range(0, n_days, 28))
|
|
118
|
+
if n_days - 1 - major_days[-1] >= 7: # also label the last day, unless it would crowd a tick
|
|
119
|
+
major_days.append(n_days - 1)
|
|
120
|
+
ax.set_xticks([plan.start + dat.timedelta(days=d) for d in major_days])
|
|
121
|
+
ax.set_xticks(
|
|
122
|
+
[plan.start + dat.timedelta(days=7 * k) for k in range(n_days // 7 + 1)],
|
|
123
|
+
minor=True,
|
|
124
|
+
)
|
|
125
|
+
ax.xaxis.set_major_formatter(DateFormatter("%d %b"))
|
|
126
|
+
ax.yaxis.set_major_locator(MultipleLocator(25))
|
|
127
|
+
ax.set_axisbelow(False)
|
|
128
|
+
ax.grid(True, which="both", color="black", alpha=0.12, linewidth=0.6)
|
|
129
|
+
|
|
130
|
+
# Section labels; stagger any that start within 4 weeks of the previous one.
|
|
131
|
+
previous, height = None, 1.0
|
|
132
|
+
for i, (start, label) in enumerate(plan.sections):
|
|
133
|
+
if previous is not None and (start - previous).days < 28:
|
|
134
|
+
height = 0.85 if height == 1.0 else 1.0
|
|
135
|
+
else:
|
|
136
|
+
height = 1.0
|
|
137
|
+
if i:
|
|
138
|
+
ax.axvline(start, color="grey", linestyle="--", linewidth=1)
|
|
139
|
+
ax.text(start + dat.timedelta(days=1), top * height, label, va="top")
|
|
140
|
+
previous = start
|
|
141
|
+
|
|
142
|
+
if done_date is not None:
|
|
143
|
+
weeks_done = (done_date - plan.start).days // 7
|
|
144
|
+
ax.axvline(done_date, color="grey", linestyle=":", linewidth=1, alpha=0.5)
|
|
145
|
+
ax.text(
|
|
146
|
+
done_date + dat.timedelta(days=1),
|
|
147
|
+
top * 0.72,
|
|
148
|
+
f"{weeks_done} weeks done",
|
|
149
|
+
va="top",
|
|
150
|
+
color="grey",
|
|
151
|
+
fontsize=8,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
ax.set_xlabel("Week commencing (grid lines weekly)")
|
|
155
|
+
ax.set_ylabel(f"Trailing {window_days}-day running (mins)")
|
|
156
|
+
ax.legend(loc="center left", bbox_to_anchor=(1.01, 0.5))
|
|
157
|
+
ax.set_title(title)
|
|
158
|
+
fig.tight_layout()
|
|
159
|
+
|
|
160
|
+
return fig, ax
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dimwit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A package containing various functions and classes for fetching, transforming, and visualising data for personal metrics.
|
|
5
5
|
Author-email: Daniel Soutar <danielsoutar144@gmail.com>
|
|
6
6
|
Project-URL: repository, https://github.com/danielsoutar/dimwit
|
|
@@ -14,7 +14,7 @@ dependencies = [
|
|
|
14
14
|
"tqdm>=4.67.1",
|
|
15
15
|
]
|
|
16
16
|
name = "dimwit"
|
|
17
|
-
version = "0.3.
|
|
17
|
+
version = "0.3.3"
|
|
18
18
|
description = "A package containing various functions and classes for fetching, transforming, and visualising data for personal metrics."
|
|
19
19
|
readme = "README.md"
|
|
20
20
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|