drunken-chunk 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/bin/index.js +49 -0
- package/drunken_chunk/__init__.py +2 -0
- package/drunken_chunk/analytics.py +172 -0
- package/drunken_chunk/cli.py +834 -0
- package/drunken_chunk/db.py +245 -0
- package/drunken_chunk/profile.py +232 -0
- package/drunken_chunk/reminder.py +386 -0
- package/drunken_chunk/shortcut.py +43 -0
- package/drunken_chunk/utils.py +122 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antigravity Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the software, and to permit persons to whom the software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# π§ Drunken Chunk (drunken-chunk)
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](#-supported-platforms--native-mechanics)
|
|
6
|
+
[](#-technical-implementation-highlights)
|
|
7
|
+
|
|
8
|
+
A beautiful, premium, cross-platform CLI-based water tracking tool, smart background reminder service, and behavioral analytics dashboard. Written in 100% pure Python with **zero external dependencies**, Drunken Chunk leverages native OS mechanisms to keep you healthy, hydrated, and productive.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## π Table of Contents
|
|
13
|
+
|
|
14
|
+
- [β¨ Features](#-features)
|
|
15
|
+
- [π₯οΈ Supported Platforms & Native Mechanics](#%EF%B8%8F-supported-platforms--native-mechanics)
|
|
16
|
+
- [π Installation & Quick Start](#-installation--quick-start)
|
|
17
|
+
- [Prerequisites](#prerequisites)
|
|
18
|
+
- [Global Installation (Recommended)](#global-installation-recommended)
|
|
19
|
+
- [Local Development & Source Running](#local-development--source-running)
|
|
20
|
+
- [π οΈ CLI Commands & Usage](#%EF%B8%8F-cli-commands--usage)
|
|
21
|
+
- [1. Initialization (`init`)](#1-initialization-init)
|
|
22
|
+
- [2. Interactive Console Logging (`log` / `log-custom`)](#2-interactive-console-logging-log--log-custom)
|
|
23
|
+
- [3. Dashboard Progress (`status`)](#3-dashboard-progress-status)
|
|
24
|
+
- [4. Vessel Shortcuts (`shortcut`)](#4-vessel-shortcuts-shortcut)
|
|
25
|
+
- [5. Historical Log View (`history`)](#5-historical-log-view-history)
|
|
26
|
+
- [6. Behavioral Analytics (`analyze`)](#6-behavioral-analytics-analyze)
|
|
27
|
+
- [7. Reminder Controls (`start` / `stop` / `snooze`)](#7-reminder-controls-start--stop--snooze)
|
|
28
|
+
- [8. Startup Integration (`autostart`)](#8-startup-integration-autostart)
|
|
29
|
+
- [π Project Directory Layout](#-project-directory-layout)
|
|
30
|
+
- [βοΈ How It Works under the Hood](#%EF%B8%8F-how-it-works-under-the-hood)
|
|
31
|
+
- [π€ Contributing](#-contributing)
|
|
32
|
+
- [π License](#-license)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## β¨ Features
|
|
37
|
+
|
|
38
|
+
* **Smart Reminder Service**: Notifies you to drink water based on your waking hours and consumption frequency. Unlike rigid timer tools, Drunken Chunk is **context-aware**βlogging a drink automatically pushes your next notification out by one full interval.
|
|
39
|
+
* **Rich Terminal UI**: Beautiful, premium gradient styling, custom ASCII banners, and dynamic progress bars without requiring external packages like `rich` or `colorama`.
|
|
40
|
+
* **Platform-Native Notifications**: Zero-dependency notifications that natively slide in on Windows (WinRT toast alerts), macOS (AppleScript alerts), and Linux (D-Bus `notify-send` alerts).
|
|
41
|
+
* **Behavioral Telemetry**: Tracks your hydration rates across weekdays, details diurnal drinking spreads (morning, afternoon, evening), calculates target consistency indices, and surfaces actionable sleep/health recommendations.
|
|
42
|
+
* **SQL Log Storage**: Leverages a local, lightweight SQLite database to manage user dimensions, drink vessel shortcuts, logging history, and daemon state.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## π₯οΈ Supported Platforms & Native Mechanics
|
|
47
|
+
|
|
48
|
+
Drunken Chunk is built to operate with lightweight, native OS components instead of heavy third-party system libraries:
|
|
49
|
+
|
|
50
|
+
| Platform | Native Desktop Notifications | Background Daemon Detaching | Autostart Boot Entry |
|
|
51
|
+
| :--- | :--- | :--- | :--- |
|
|
52
|
+
| πͺ **Windows** | PowerShell WinRT XML API Toast | `DETACHED_PROCESS` + `CREATE_NO_WINDOW` flags | Windows Startup batch launcher script (`shell:startup`) |
|
|
53
|
+
| π **macOS** | AppleScript `display notification` | `start_new_session` (`setsid`) background process | User Launch Agent (`~/Library/LaunchAgents/*.plist`) |
|
|
54
|
+
| π§ **Linux** | Desktop D-Bus `notify-send` utility | `start_new_session` (`setsid`) background process | XDG Desktop Entry (`~/.config/autostart/*.desktop`) |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## π Installation & Quick Start
|
|
59
|
+
|
|
60
|
+
### Prerequisites
|
|
61
|
+
* Python **3.8** or newer installed.
|
|
62
|
+
* Standard command-line terminal environment.
|
|
63
|
+
|
|
64
|
+
### Global Installation (Recommended)
|
|
65
|
+
You can install Drunken Chunk in editable mode or globally from source. Open your terminal in this repository folder and run:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install -e .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Once installed, the main command is linked globally. Test the installation by running:
|
|
72
|
+
```bash
|
|
73
|
+
drunken-chunk --help
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Local Development & Source Running
|
|
77
|
+
If you prefer not to install the package globally, you can invoke the CLI using the platform-specific execution wrappers located at the repository root:
|
|
78
|
+
|
|
79
|
+
* **Windows PowerShell/Command Prompt:**
|
|
80
|
+
```cmd
|
|
81
|
+
drunken-chunk.bat --help
|
|
82
|
+
```
|
|
83
|
+
* **macOS / Linux terminal:**
|
|
84
|
+
Ensure the wrapper script has execute permissions:
|
|
85
|
+
```bash
|
|
86
|
+
chmod +x drunken-chunk
|
|
87
|
+
./drunken-chunk --help
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## π οΈ CLI Commands & Usage
|
|
93
|
+
|
|
94
|
+
### 1. Initialization (`init`)
|
|
95
|
+
Sets up your personal physical profile and hydration constraints. Drunken Chunk will calculate a customized daily intake target based on your metabolic demands (weight, height, and BMI metadata).
|
|
96
|
+
```bash
|
|
97
|
+
drunken-chunk init
|
|
98
|
+
```
|
|
99
|
+
During initialization, you will be prompted for:
|
|
100
|
+
1. Your name.
|
|
101
|
+
2. Weight in kg (for metabolic calculation).
|
|
102
|
+
3. Height in cm (for BMI classification).
|
|
103
|
+
4. Wake-up and Sleep times (defines **Quiet Hours** to prevent middle-of-the-night notifications).
|
|
104
|
+
5. Notification interval in minutes.
|
|
105
|
+
6. Optional custom target override (e.g. 3000 ml).
|
|
106
|
+
|
|
107
|
+
> [!NOTE]
|
|
108
|
+
> **Automatic Setup Trigger**: If you run any other CLI subcommand on a fresh database before running `init`, the program will automatically launch the setup wizard first to ensure your database is initialized.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### 2. Interactive Console Logging (`log` / `log-custom`)
|
|
113
|
+
Log your water intake instantly using direct integers, custom shortcuts, or relative timestamps.
|
|
114
|
+
|
|
115
|
+
* **Log by Volume (milliliters):**
|
|
116
|
+
```bash
|
|
117
|
+
drunken-chunk log 350
|
|
118
|
+
```
|
|
119
|
+
* **Log using a Vessel Shortcut:**
|
|
120
|
+
```bash
|
|
121
|
+
drunken-chunk log glass
|
|
122
|
+
```
|
|
123
|
+
* **Log in the Past (Relative/Absolute):**
|
|
124
|
+
Useful if you forgot to log a drink earlier:
|
|
125
|
+
```bash
|
|
126
|
+
drunken-chunk log-custom 250 "15m ago" # 15 minutes ago
|
|
127
|
+
drunken-chunk log-custom 500 "2h ago" # 2 hours ago
|
|
128
|
+
drunken-chunk log-custom 300 "14:30" # At exactly 2:30 PM today
|
|
129
|
+
```
|
|
130
|
+
* **Undo Last Log Entry:**
|
|
131
|
+
Quickly undo your last recorded log if you made a typo:
|
|
132
|
+
```bash
|
|
133
|
+
drunken-chunk undo
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### 3. Dashboard Progress (`status`)
|
|
139
|
+
Presents a status breakdown including your hydration progress bar, current intake totals, hydration level metrics, and notification service status.
|
|
140
|
+
```bash
|
|
141
|
+
drunken-chunk status
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
* **Compact Prompts & Status Bars (`--mini`):**
|
|
145
|
+
Perfect for loading into status widgets (like tmux, polybar, or zsh prompts):
|
|
146
|
+
```bash
|
|
147
|
+
drunken-chunk status --mini
|
|
148
|
+
```
|
|
149
|
+
*Output Example:* `[ββββββββββββββ] 66.7%`
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### 4. Vessel Shortcuts (`shortcut`)
|
|
154
|
+
Configure convenient, memorable shortcuts for drink vessels to speed up logging.
|
|
155
|
+
|
|
156
|
+
* **List active vessel shortcuts:**
|
|
157
|
+
```bash
|
|
158
|
+
drunken-chunk shortcut list
|
|
159
|
+
```
|
|
160
|
+
* **Add or update a shortcut (e.g., custom `mug` holding 300ml):**
|
|
161
|
+
```bash
|
|
162
|
+
drunken-chunk shortcut add mug 300
|
|
163
|
+
```
|
|
164
|
+
* **Remove a shortcut:**
|
|
165
|
+
```bash
|
|
166
|
+
drunken-chunk shortcut remove bottle
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### 5. Historical Log View (`history`)
|
|
172
|
+
Review your logged intake and check if targets were reached over the past week.
|
|
173
|
+
```bash
|
|
174
|
+
drunken-chunk history --days 7
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### 6. Behavioral Analytics (`analyze`)
|
|
180
|
+
Executes behavior analysis on your historical SQL registry to map habits and discover dehydration triggers.
|
|
181
|
+
```bash
|
|
182
|
+
drunken-chunk analyze
|
|
183
|
+
```
|
|
184
|
+
Surfaces telemetry analytics for:
|
|
185
|
+
* **Target Consistency**: Hit rates and consistency indices over the last fortnight.
|
|
186
|
+
* **Temporal Spread**: Diurnal breakdown showing what percentage of water you log in the Morning, Afternoon, Evening, and Night.
|
|
187
|
+
* **Weekday Variance**: Visualizes which days of the week your intake drops.
|
|
188
|
+
* **Intake Frequency Gaps**: Calculates the average time gap between drinks to determine if you are "chugging" vs "sipping".
|
|
189
|
+
* **Personalized Health Warnings**: Actionable alerts based on drinking patterns (e.g. drinking too close to bed).
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### 7. Reminder Controls (`start` / `stop` / `snooze`)
|
|
194
|
+
Manage the background notification process.
|
|
195
|
+
|
|
196
|
+
* **Start the background daemon:**
|
|
197
|
+
```bash
|
|
198
|
+
drunken-chunk start
|
|
199
|
+
```
|
|
200
|
+
* **Stop the background daemon:**
|
|
201
|
+
```bash
|
|
202
|
+
drunken-chunk stop
|
|
203
|
+
```
|
|
204
|
+
* **Snooze reminders (e.g., temporarily silence popups for 45 minutes):**
|
|
205
|
+
```bash
|
|
206
|
+
drunken-chunk snooze 45
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### 8. Startup Integration (`autostart`)
|
|
212
|
+
Configure the background hydration daemon to boot up automatically when your computer starts.
|
|
213
|
+
|
|
214
|
+
* **Toggle or check autostart registration:**
|
|
215
|
+
```bash
|
|
216
|
+
drunken-chunk autostart status
|
|
217
|
+
drunken-chunk autostart enable
|
|
218
|
+
drunken-chunk autostart disable
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## π Project Directory Layout
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
drunken-chunk/
|
|
227
|
+
βββ drunken_chunk/
|
|
228
|
+
β βββ __init__.py
|
|
229
|
+
β βββ analytics.py # Telemetry calculations and diurnal spreading analysis
|
|
230
|
+
β βββ cli.py # CLI argument parser and routing commands
|
|
231
|
+
β βββ db.py # SQLite database setup and profile/log queries
|
|
232
|
+
β βββ profile.py # Metabolic calculations and BMI indexing
|
|
233
|
+
β βββ reminder.py # Cross-platform background daemon and notification dispatching
|
|
234
|
+
β βββ shortcut.py # Shortcut validation and registry updates
|
|
235
|
+
β βββ utils.py # ANSI colors, banners, and table visual formatters
|
|
236
|
+
βββ setup.py # Package metadata and installation script
|
|
237
|
+
βββ drunken-chunk # Linux & macOS shell executable wrapper
|
|
238
|
+
βββ drunken-chunk.bat # Windows cmd batch executable wrapper
|
|
239
|
+
βββ README.md # User manual and documentation
|
|
240
|
+
βββ .gitignore
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## βοΈ How It Works under the Hood
|
|
246
|
+
|
|
247
|
+
* **Data Isolation**: User profiles, custom shortcuts, daemon states, and hydration registries are stored securely in a local directory `~/.drunken_chunk/drunken_chunk.db`.
|
|
248
|
+
* **Zero-Library Color Sequences**: Windows Console colors are configured using standard ANSI escapes combined with native Win32 `ctypes` bindings that enable Virtual Terminal Processing on Windows consoles. Non-Windows systems support standard ANSI styling natively.
|
|
249
|
+
* **Low Overhead Polling Loop**: The reminder daemon process runs a lightweight background loop polling every 20 seconds. It reads the database directly to check if a notification is due and goes to sleep without loading CPU resources.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## π€ Contributing
|
|
254
|
+
|
|
255
|
+
Contributions are welcome to make Drunken Chunk even better! If you have suggestions or bug fixes:
|
|
256
|
+
1. Fork this repository.
|
|
257
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`).
|
|
258
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`).
|
|
259
|
+
4. Push to the branch (`git push origin feature/amazing-feature`).
|
|
260
|
+
5. Open a Pull Request.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## π License
|
|
265
|
+
|
|
266
|
+
Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function getPythonCommand() {
|
|
7
|
+
const cmds = process.platform === 'win32' ? ['python', 'py'] : ['python3', 'python'];
|
|
8
|
+
for (const cmd of cmds) {
|
|
9
|
+
try {
|
|
10
|
+
const res = spawnSync(cmd, ['--version'], { stdio: 'ignore' });
|
|
11
|
+
if (res.status === 0) {
|
|
12
|
+
return cmd;
|
|
13
|
+
}
|
|
14
|
+
} catch (e) {
|
|
15
|
+
// Ignore error and try next
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Fallback to python
|
|
19
|
+
return process.platform === 'win32' ? 'python' : 'python3';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pythonCmd = getPythonCommand();
|
|
23
|
+
|
|
24
|
+
// The module root is one folder up from `bin/index.js`
|
|
25
|
+
const packageRoot = path.dirname(__dirname);
|
|
26
|
+
|
|
27
|
+
// Set up PYTHONPATH environment variable so python can find the `drunken_chunk` module
|
|
28
|
+
const env = { ...process.env };
|
|
29
|
+
env.PYTHONPATH = env.PYTHONPATH
|
|
30
|
+
? `${packageRoot}${path.delimiter}${env.PYTHONPATH}`
|
|
31
|
+
: packageRoot;
|
|
32
|
+
|
|
33
|
+
// Run the python module
|
|
34
|
+
const args = ['-m', 'drunken_chunk.cli', ...process.argv.slice(2)];
|
|
35
|
+
|
|
36
|
+
const child = spawn(pythonCmd, args, {
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
env: env
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
child.on('close', (code) => {
|
|
42
|
+
process.exit(code !== null ? code : 1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on('error', (err) => {
|
|
46
|
+
console.error(`Failed to start the drunken-chunk command: ${err.message}`);
|
|
47
|
+
console.error(`Please ensure Python 3.8+ is installed on your system.`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import collections
|
|
3
|
+
import statistics
|
|
4
|
+
|
|
5
|
+
from .db import get_all_logs, get_profile
|
|
6
|
+
from .profile import calculate_recommended_intake
|
|
7
|
+
|
|
8
|
+
def get_day_name(date_str):
|
|
9
|
+
return datetime.strptime(date_str, "%Y-%m-%d").strftime("%A")
|
|
10
|
+
|
|
11
|
+
def analyze_drinking_behavior():
|
|
12
|
+
"""
|
|
13
|
+
Analyzes historical drinking behavior.
|
|
14
|
+
Returns a dict with processed analytics metrics or None if not enough data.
|
|
15
|
+
"""
|
|
16
|
+
logs = get_all_logs()
|
|
17
|
+
profile = get_profile()
|
|
18
|
+
|
|
19
|
+
if not profile:
|
|
20
|
+
return {"error": "Profile not configured. Please run 'drunken-chunk init'."}
|
|
21
|
+
|
|
22
|
+
if not logs:
|
|
23
|
+
return {"error": "No water logs recorded yet. Start logging with 'drunken-chunk log'!"}
|
|
24
|
+
|
|
25
|
+
target = profile['custom_target'] if profile['custom_target'] else calculate_recommended_intake(profile['weight'], profile['height'])
|
|
26
|
+
|
|
27
|
+
# 1. Basic Stats
|
|
28
|
+
total_consumed = sum(log['amount'] for log in logs)
|
|
29
|
+
total_entries = len(logs)
|
|
30
|
+
|
|
31
|
+
# Group logs by date
|
|
32
|
+
daily_totals = collections.defaultdict(int)
|
|
33
|
+
daily_entries = collections.defaultdict(list)
|
|
34
|
+
|
|
35
|
+
for log in logs:
|
|
36
|
+
# timestamp format: "YYYY-MM-DD HH:MM:SS"
|
|
37
|
+
dt = datetime.strptime(log['timestamp'], "%Y-%m-%d %H:%M:%S")
|
|
38
|
+
date_str = dt.strftime("%Y-%m-%d")
|
|
39
|
+
daily_totals[date_str] += log['amount']
|
|
40
|
+
daily_entries[date_str].append(dt)
|
|
41
|
+
|
|
42
|
+
unique_days_logged = len(daily_totals)
|
|
43
|
+
avg_daily_consumption = total_consumed / unique_days_logged
|
|
44
|
+
|
|
45
|
+
days_met_target = sum(1 for amount in daily_totals.values() if amount >= target)
|
|
46
|
+
target_hit_rate = (days_met_target / unique_days_logged) * 100
|
|
47
|
+
|
|
48
|
+
# 2. Weekday Analysis
|
|
49
|
+
# To get proper averages, count unique weekday occurrences in the logged period
|
|
50
|
+
weekday_occurrences = collections.defaultdict(set)
|
|
51
|
+
weekday_totals = collections.defaultdict(int)
|
|
52
|
+
|
|
53
|
+
for date_str, amount in daily_totals.items():
|
|
54
|
+
day_name = get_day_name(date_str)
|
|
55
|
+
weekday_totals[day_name] += amount
|
|
56
|
+
weekday_occurrences[day_name].add(date_str)
|
|
57
|
+
|
|
58
|
+
weekday_averages = {}
|
|
59
|
+
for day in ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]:
|
|
60
|
+
occurrences = len(weekday_occurrences[day])
|
|
61
|
+
if occurrences > 0:
|
|
62
|
+
weekday_averages[day] = weekday_totals[day] / occurrences
|
|
63
|
+
else:
|
|
64
|
+
weekday_averages[day] = 0.0
|
|
65
|
+
|
|
66
|
+
# 3. Time of Day Analysis
|
|
67
|
+
time_blocks = {
|
|
68
|
+
"Early Morning (5am - 9am)": 0,
|
|
69
|
+
"Late Morning (9am - 12pm)": 0,
|
|
70
|
+
"Afternoon (12pm - 5pm)": 0,
|
|
71
|
+
"Evening (5pm - 9pm)": 0,
|
|
72
|
+
"Night (9pm - 5am)": 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for log in logs:
|
|
76
|
+
dt = datetime.strptime(log['timestamp'], "%Y-%m-%d %H:%M:%S")
|
|
77
|
+
hour = dt.hour
|
|
78
|
+
if 5 <= hour < 9:
|
|
79
|
+
time_blocks["Early Morning (5am - 9am)"] += log['amount']
|
|
80
|
+
elif 9 <= hour < 12:
|
|
81
|
+
time_blocks["Late Morning (9am - 12pm)"] += log['amount']
|
|
82
|
+
elif 12 <= hour < 17:
|
|
83
|
+
time_blocks["Afternoon (12pm - 5pm)"] += log['amount']
|
|
84
|
+
elif 17 <= hour < 21:
|
|
85
|
+
time_blocks["Evening (5pm - 9pm)"] += log['amount']
|
|
86
|
+
else:
|
|
87
|
+
time_blocks["Night (9pm - 5am)"] += log['amount']
|
|
88
|
+
|
|
89
|
+
# Calculate percentage for each time block
|
|
90
|
+
total_time_blocked = sum(time_blocks.values())
|
|
91
|
+
time_block_pct = {}
|
|
92
|
+
for block, val in time_blocks.items():
|
|
93
|
+
time_block_pct[block] = (val / total_time_blocked * 100) if total_time_blocked > 0 else 0.0
|
|
94
|
+
|
|
95
|
+
# 4. Drink Gaps / Frequency Analysis
|
|
96
|
+
all_gaps_mins = []
|
|
97
|
+
|
|
98
|
+
for date_str, dts in daily_entries.items():
|
|
99
|
+
# Sort chronologically for the day
|
|
100
|
+
dts.sort()
|
|
101
|
+
if len(dts) < 2:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
for i in range(len(dts) - 1):
|
|
105
|
+
gap = dts[i+1] - dts[i]
|
|
106
|
+
gap_mins = gap.total_seconds() / 60.0
|
|
107
|
+
all_gaps_mins.append(gap_mins)
|
|
108
|
+
|
|
109
|
+
avg_gap_mins = statistics.mean(all_gaps_mins) if all_gaps_mins else 0
|
|
110
|
+
median_gap_mins = statistics.median(all_gaps_mins) if all_gaps_mins else 0
|
|
111
|
+
|
|
112
|
+
# 5. Consistency Index (last 14 days or fewer if newly joined)
|
|
113
|
+
# Target 80% completion of recommended intake
|
|
114
|
+
consistency_days = list(daily_totals.values())[-14:]
|
|
115
|
+
active_days_count = len(consistency_days)
|
|
116
|
+
met_80_percent_count = sum(1 for amount in consistency_days if amount >= (target * 0.8))
|
|
117
|
+
consistency_index = (met_80_percent_count / active_days_count * 100) if active_days_count > 0 else 0.0
|
|
118
|
+
|
|
119
|
+
# 6. Narrative Insights & Suggestions
|
|
120
|
+
suggestions = []
|
|
121
|
+
|
|
122
|
+
if avg_gap_mins > 0:
|
|
123
|
+
gap_h = int(avg_gap_mins // 60)
|
|
124
|
+
gap_m = int(avg_gap_mins % 60)
|
|
125
|
+
suggestions.append(f"β’ Your average drinking gap is {gap_h}h {gap_m}m.")
|
|
126
|
+
|
|
127
|
+
# Compare with reminder interval
|
|
128
|
+
reminder_interval = profile['reminder_interval']
|
|
129
|
+
if avg_gap_mins > reminder_interval + 15:
|
|
130
|
+
suggestions.append(f" - This is {int(avg_gap_mins - reminder_interval)} mins slower than your reminder setting. Consider checking notifications more closely.")
|
|
131
|
+
elif avg_gap_mins < reminder_interval:
|
|
132
|
+
suggestions.append(f" - Great job! You are drinking water more frequently than the alarm interval.")
|
|
133
|
+
else:
|
|
134
|
+
suggestions.append("β’ Drink multiple times a day to calculate gap frequency analysis.")
|
|
135
|
+
|
|
136
|
+
# Analyze best and worst days
|
|
137
|
+
valid_weekday_averages = {k: v for k, v in weekday_averages.items() if v > 0}
|
|
138
|
+
if valid_weekday_averages:
|
|
139
|
+
best_day = max(valid_weekday_averages, key=valid_weekday_averages.get)
|
|
140
|
+
worst_day = min(valid_weekday_averages, key=valid_weekday_averages.get)
|
|
141
|
+
|
|
142
|
+
if best_day != worst_day:
|
|
143
|
+
suggestions.append(f"β’ Hydration Champion Day: {best_day} (avg: {int(weekday_averages[best_day])} ml).")
|
|
144
|
+
suggestions.append(f"β’ Dehydration Danger Day: {worst_day} (avg: {int(weekday_averages[worst_day])} ml). Watch out on {worst_day}s!")
|
|
145
|
+
|
|
146
|
+
# Analyze time blocks
|
|
147
|
+
if time_block_pct:
|
|
148
|
+
best_block = max(time_block_pct, key=time_block_pct.get)
|
|
149
|
+
suggestions.append(f"β’ Peak drinking period: {best_block} ({time_block_pct[best_block]:.1f}% of total).")
|
|
150
|
+
|
|
151
|
+
# Check if night drinking is too high (may affect sleep)
|
|
152
|
+
if time_block_pct.get("Night (9pm - 5am)", 0.0) > 30.0:
|
|
153
|
+
suggestions.append(" - Warning: You drink more than 30% of your water at night. This can disrupt your sleep cycle. Try drinking more during the day.")
|
|
154
|
+
|
|
155
|
+
# Check if early morning is too low (important to kickstart metabolism)
|
|
156
|
+
if time_block_pct.get("Early Morning (5am - 9am)", 0.0) < 5.0:
|
|
157
|
+
suggestions.append(" - Suggestion: Start your day with a glass of water immediately after waking to wake up your organs.")
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
"target": target,
|
|
161
|
+
"total_consumed": total_consumed,
|
|
162
|
+
"total_entries": total_entries,
|
|
163
|
+
"unique_days": unique_days_logged,
|
|
164
|
+
"avg_daily": avg_daily_consumption,
|
|
165
|
+
"hit_rate": target_hit_rate,
|
|
166
|
+
"weekday_averages": weekday_averages,
|
|
167
|
+
"time_block_pct": time_block_pct,
|
|
168
|
+
"avg_gap_mins": avg_gap_mins,
|
|
169
|
+
"median_gap_mins": median_gap_mins,
|
|
170
|
+
"consistency_index": consistency_index,
|
|
171
|
+
"suggestions": suggestions
|
|
172
|
+
}
|