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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dimwit
3
- Version: 0.3.2
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.2
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
@@ -6,6 +6,7 @@ dimwit/airflow.py
6
6
  dimwit/legacy.py
7
7
  dimwit/main.py
8
8
  dimwit/pomodoro.py
9
+ dimwit/running.py
9
10
  dimwit/sleep.py
10
11
  dimwit/weight.py
11
12
  dimwit.egg-info/PKG-INFO
@@ -14,7 +14,7 @@ dependencies = [
14
14
  "tqdm>=4.67.1",
15
15
  ]
16
16
  name = "dimwit"
17
- version = "0.3.2"
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