From e35a8af5c1455ca8474f50d2e3a97451362aa4f7 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 28 Jul 2025 20:52:29 -0700 Subject: [PATCH] Implement dose tracking functionality and enhance CSV migration - Added a new migration script to introduce dose tracking columns in the CSV. - Updated DataManager to handle new dose tracking columns and methods for adding doses. - Enhanced MedTrackerApp to support dose entry and display for each medicine. - Modified UIManager to create a scrollable input frame with dose tracking elements. - Implemented tests for delete functionality, dose tracking, edit functionality, and scrollable input. - Updated existing tests to ensure compatibility with the new CSV format and dose tracking features. --- DOSE_TRACKING_GUIDE.md | 110 ++ Makefile | 28 +- coverage.xml | 1117 ++++++++++++--------- scripts/migrate_csv.py | 67 ++ scripts/test_delete_functionality.py | 95 ++ scripts/test_dose_tracking.py | 55 + scripts/test_edit_functionality.py | 87 ++ scripts/test_edit_window_functionality.py | 135 +++ scripts/test_scrollable_input.py | 63 ++ src/data_manager.py | 137 ++- src/main.py | 173 +++- src/ui_manager.py | 192 +++- tests/test_data_manager.py | 3 +- thechart_data.csv.backup_20250728_185654 | 28 + 14 files changed, 1790 insertions(+), 500 deletions(-) create mode 100644 DOSE_TRACKING_GUIDE.md create mode 100644 scripts/migrate_csv.py create mode 100644 scripts/test_delete_functionality.py create mode 100644 scripts/test_dose_tracking.py create mode 100644 scripts/test_edit_functionality.py create mode 100644 scripts/test_edit_window_functionality.py create mode 100644 scripts/test_scrollable_input.py create mode 100644 thechart_data.csv.backup_20250728_185654 diff --git a/DOSE_TRACKING_GUIDE.md b/DOSE_TRACKING_GUIDE.md new file mode 100644 index 0000000..aded3a6 --- /dev/null +++ b/DOSE_TRACKING_GUIDE.md @@ -0,0 +1,110 @@ +# Medicine Dose Tracking Feature - Usage Guide + +## Overview + +The medicine dose tracking feature allows you to record specific timestamps and doses when you take medications throughout the day. This provides detailed tracking beyond the simple daily checkboxes. + +## How to Use + +### 1. Recording Medicine Doses + +1. **Open the application** - Run `make run` or `uv run python src/main.py` +2. **Find the medicine section** - Look for the "Treatment" section in the input form +3. **For each medicine, you'll see:** + - Checkbox (existing daily tracking) + - Dose entry field (new) + - "Take [Medicine]" button (new) + - Dose display area showing today's doses (new) + +### 2. Taking a Dose + +1. **Enter the dose amount** in the dose entry field (e.g., "150mg", "10mg", "25mg") +2. **Click the "Take [Medicine]" button** - This will: + - Record the current timestamp + - Save the dose amount + - Update the display area + - Mark the medicine checkbox as taken + +### 3. Multiple Doses Per Day + +- You can take multiple doses of the same medicine +- Each dose gets its own timestamp +- All doses for the day are displayed in the dose area +- The display shows: `YYYY-MM-DD HH:MM:SS: dose` + +### 4. Viewing Dose History + +- **Today's doses** are shown in the dose display areas +- **Historical doses** are stored in the CSV with columns: + - `bupropion_doses`, `hydroxyzine_doses`, `gabapentin_doses`, `propranolol_doses` +- Each dose entry format: `timestamp:dose` separated by `|` for multiple doses +- **Edit entries** by double-clicking on table rows - dose information is preserved and displayed + +### 5. Editing Entries + +When you double-click on an entry in the data table: +- **Dose information is preserved** - existing doses are maintained during edits +- **Read-only dose display** - view all doses taken for that day +- **Symptom and medicine checkboxes** can be modified +- **Notes can be updated** while keeping dose history intact + +## CSV Format + +The new CSV structure includes dose tracking columns: + +```csv +date,depression,anxiety,sleep,appetite,bupropion,bupropion_doses,hydroxyzine,hydroxyzine_doses,gabapentin,gabapentin_doses,propranolol,propranolol_doses,note +07/28/2025,4,5,3,3,1,"2025-07-28 14:30:00:150mg|2025-07-28 18:30:00:150mg",0,"",0,"",1,"2025-07-28 12:30:00:10mg","Multiple doses today" +``` + +## Features + +- ✅ **Timestamp recording** - Exact time when medicine is taken +- ✅ **Dose amount tracking** - Record specific doses (150mg, 10mg, etc.) +- ✅ **Multiple doses per day** - Take the same medicine multiple times +- ✅ **Real-time display** - See today's doses immediately +- ✅ **Data persistence** - All doses saved to CSV +- ✅ **Backward compatibility** - Existing data migrated automatically +- ✅ **Scrollable interface** - Vertical scrollbar for expanded UI + +## User Interface + +The medicine tracking interface now includes: +- **Scrollable input area** - Use mouse wheel or scrollbar to navigate +- **Responsive design** - Interface adapts to window size +- **Expanded medicine section** - Each medicine has dose tracking controls + +## Migration + +Your existing data has been automatically migrated to the new format. A backup was created as `thechart_data.csv.backup_YYYYMMDD_HHMMSS`. + +## Testing + +Run the dose tracking test: +```bash +make test-dose-tracking +``` + +Test the scrollable interface: +```bash +make test-scrollable-input +``` + +Test the edit functionality: +```bash +make test-edit-functionality +``` + +## Troubleshooting + +1. **Application won't start**: Check that migration completed successfully +2. **Doses not saving**: Ensure you enter a dose amount before clicking "Take" +3. **Data issues**: Restore from backup if needed +4. **UI layout issues**: The new interface may require resizing the window + +## Technical Details + +- **Timestamp format**: `YYYY-MM-DD HH:MM:SS` +- **Dose separator**: `|` (pipe) for multiple doses +- **Dose format**: `timestamp:dose` +- **Storage**: Additional columns in existing CSV file diff --git a/Makefile b/Makefile index e103e41..6e3dd34 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,12 @@ VERSION=1.0.0 ROOT=/home/will ICON=chart-671.png SHELL=fish + +# Virtual environment variables +VENV_DIR=.venv +VENV_ACTIVATE=$(VENV_DIR)/bin/activate +PYTHON=$(VENV_DIR)/bin/python + help: ## Show this help @grep -E -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' install: ## Set up the development environment @@ -24,9 +30,9 @@ deploy: ## Deploy the application as a standalone executable cp -f ./dist/${TARGET} ${ROOT}/Applications/ cp -f ./deploy/${TARGET}.desktop ${ROOT}/.local/share/applications/ desktop-file-validate ${ROOT}/.local/share/applications/${TARGET}.desktop -run: ## Run the application +run: $(VENV_ACTIVATE) ## Run the application @echo "Running the application..." - python src/main.py + $(PYTHON) src/main.py start: ## Start the application @echo "Starting the application..." docker-compose up -d --build @@ -48,6 +54,22 @@ test-watch: ## Run tests in watch mode test-debug: ## Run tests with debug output @echo "Running tests with debug output..." .venv/bin/python -m pytest tests/ -v -s --tb=long --cov=src +test-dose-tracking: ## Test the dose tracking functionality + @echo "Testing dose tracking functionality..." + .venv/bin/python scripts/test_dose_tracking.py +test-scrollable-input: ## Test the scrollable input frame UI + @echo "Testing scrollable input frame..." + .venv/bin/python scripts/test_scrollable_input.py +test-edit-functionality: ## Test the enhanced edit functionality + @echo "Testing edit functionality..." + .venv/bin/python scripts/test_edit_functionality.py +test-edit-window: $(VENV_ACTIVATE) ## Test edit window functionality (save and delete) + @echo "Running edit window functionality test..." + $(PYTHON) scripts/test_edit_window_functionality.py + +migrate-csv: $(VENV_ACTIVATE) ## Migrate CSV to new format with dose tracking + @echo "Migrating CSV to new format..." + .venv/bin/python migrate_csv.py lint: ## Run the linter @echo "Running the linter..." docker-compose exec ${TARGET} pipenv run pre-commit run --all-files @@ -69,4 +91,4 @@ commit-emergency: ## Emergency commit (bypasses pre-commit hooks) - USE SPARINGL @read -p "Enter commit message: " msg; \ git add . && git commit --no-verify -m "$$msg" @echo "✅ Emergency commit completed. Please run tests manually when possible." -.PHONY: install build attach deploy run start stop test lint format shell requirements commit-emergency help +.PHONY: install build attach deploy run start stop test lint format shell requirements commit-emergency test-dose-tracking test-scrollable-input test-edit-functionality test-edit-window migrate-csv help diff --git a/coverage.xml b/coverage.xml index 8b22d34..0a81bae 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,25 +1,25 @@ - + /home/will/Code/thechart/src - + - + - - - - - - + + + + + + - + @@ -36,493 +36,680 @@ - - - - + - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - + - + + - - - - - - - - + + + + + + + + - + - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/migrate_csv.py b/scripts/migrate_csv.py new file mode 100644 index 0000000..c6640a4 --- /dev/null +++ b/scripts/migrate_csv.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +Migration script to add dose tracking columns to existing CSV data. +""" + +import shutil +from datetime import datetime + +import pandas as pd + + +def migrate_csv(filename: str = "thechart_data.csv") -> None: + """Migrate existing CSV to new format with dose tracking columns.""" + + # Create backup + backup_name = f"{filename}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + shutil.copy2(filename, backup_name) + print(f"Created backup: {backup_name}") + + try: + # Read existing data + df = pd.read_csv(filename) + print(f"Read {len(df)} existing entries") + + # Add new dose tracking columns + df["bupropion_doses"] = "" + df["hydroxyzine_doses"] = "" + df["gabapentin_doses"] = "" + df["propranolol_doses"] = "" + + # Reorder columns to match new format + new_column_order = [ + "date", + "depression", + "anxiety", + "sleep", + "appetite", + "bupropion", + "bupropion_doses", + "hydroxyzine", + "hydroxyzine_doses", + "gabapentin", + "gabapentin_doses", + "propranolol", + "propranolol_doses", + "note", + ] + + df = df[new_column_order] + + # Save migrated data + df.to_csv(filename, index=False) + print(f"Successfully migrated {filename}") + print( + "New columns added: bupropion_doses, hydroxyzine_doses, " + "gabapentin_doses, propranolol_doses" + ) + + except Exception as e: + print(f"Error during migration: {e}") + print(f"Restoring from backup: {backup_name}") + shutil.copy2(backup_name, filename) + raise + + +if __name__ == "__main__": + migrate_csv() diff --git a/scripts/test_delete_functionality.py b/scripts/test_delete_functionality.py new file mode 100644 index 0000000..b6f927f --- /dev/null +++ b/scripts/test_delete_functionality.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Test script to verify delete functionality after dose tracking implementation. +""" + +import logging +import os +import sys + +# Add the src directory to the path so we can import our modules +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) + +from data_manager import DataManager + + +def test_delete_functionality(): + """Test the delete functionality with the new CSV format.""" + print("Testing delete functionality...") + + # Create a backup of the current CSV + import shutil + + try: + shutil.copy("thechart_data.csv", "thechart_data_backup.csv") + print("✓ Created backup of current CSV") + except Exception as e: + print(f"✗ Failed to create backup: {e}") + return False + + try: + # Create a logger for the DataManager + logger = logging.getLogger("test_logger") + logger.setLevel(logging.DEBUG) + + # Initialize data manager + data_manager = DataManager("thechart_data.csv", logger) + + # Load current data + df = data_manager.load_data() + print(f"✓ Loaded {len(df)} entries from CSV") + + if df.empty: + print("✗ No data to test delete functionality") + return False + + # Show first few entries + print("\nFirst few entries:") + for _idx, row in df.head(3).iterrows(): + print(f" {row['date']}: {row['note']}") + + # Test deleting the last entry + last_entry_date = df.iloc[-1]["date"] + print(f"\nAttempting to delete entry with date: {last_entry_date}") + + # Perform the delete + success = data_manager.delete_entry(last_entry_date) + + if success: + print("✓ Delete operation reported success") + + # Reload data to verify deletion + df_after = data_manager.load_data() + print(f"✓ Data reloaded: {len(df_after)} entries (was {len(df)})") + + # Check if the entry was actually deleted + deleted_entry_exists = last_entry_date in df_after["date"].values + if not deleted_entry_exists: + print("✓ Entry successfully deleted from CSV") + print("✓ Delete functionality is working correctly") + return True + else: + print("✗ Entry still exists in CSV after delete operation") + return False + else: + print("✗ Delete operation failed") + return False + + except Exception as e: + print(f"✗ Error during delete test: {e}") + import traceback + + traceback.print_exc() + return False + + finally: + # Restore the backup + try: + shutil.move("thechart_data_backup.csv", "thechart_data.csv") + print("✓ Restored original CSV from backup") + except Exception as e: + print(f"✗ Failed to restore backup: {e}") + + +if __name__ == "__main__": + test_delete_functionality() diff --git a/scripts/test_dose_tracking.py b/scripts/test_dose_tracking.py new file mode 100644 index 0000000..5175f11 --- /dev/null +++ b/scripts/test_dose_tracking.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +""" +Test script to demonstrate the dose tracking functionality. +""" + +import os +import sys +from datetime import datetime + +sys.path.append(os.path.join(os.path.dirname(__file__), "src")) + +from data_manager import DataManager +from init import logger + + +def test_dose_tracking(): + """Test the dose tracking functionality.""" + + # Initialize data manager + data_manager = DataManager("thechart_data.csv", logger) + + # Test adding a dose + today = datetime.now().strftime("%m/%d/%Y") + print(f"Testing dose tracking for date: {today}") + + # Add some test doses + test_doses = [ + ("bupropion", "150mg"), + ("propranolol", "10mg"), + ("bupropion", "150mg"), # Second dose of same medicine + ] + + for medicine, dose in test_doses: + success = data_manager.add_medicine_dose(today, medicine, dose) + if success: + print(f"✓ Added {medicine} dose: {dose}") + else: + print(f"✗ Failed to add {medicine} dose: {dose}") + + # Retrieve and display doses + print(f"\nDoses recorded for {today}:") + medicines = ["bupropion", "hydroxyzine", "gabapentin", "propranolol"] + + for medicine in medicines: + doses = data_manager.get_today_medicine_doses(today, medicine) + if doses: + print(f"{medicine.title()}:") + for timestamp, dose in doses: + print(f" - {timestamp}: {dose}") + else: + print(f"{medicine.title()}: No doses recorded") + + +if __name__ == "__main__": + test_dose_tracking() diff --git a/scripts/test_edit_functionality.py b/scripts/test_edit_functionality.py new file mode 100644 index 0000000..7095e0d --- /dev/null +++ b/scripts/test_edit_functionality.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +""" +Test script to verify the enhanced edit functionality with dose tracking. +""" + +import os +import sys + +# Add src to path +sys.path.append(os.path.join(os.path.dirname(__file__), "src")) + +from data_manager import DataManager +from init import logger + + +def test_edit_functionality(): + """Test the edit functionality with dose tracking.""" + + # Initialize data manager + data_manager = DataManager("thechart_data.csv", logger) + + print("Testing edit functionality with dose tracking...") + + # Test date + test_date = "07/28/2025" + + # First, add some test doses to the date + test_doses = [ + ("bupropion", "150mg"), + ("propranolol", "10mg"), + ] + + print(f"\n1. Adding test doses for {test_date}:") + for medicine, dose in test_doses: + success = data_manager.add_medicine_dose(test_date, medicine, dose) + if success: + print(f" ✓ Added {medicine}: {dose}") + else: + print(f" ✗ Failed to add {medicine}: {dose}") + + # Test retrieving dose data (simulating edit window opening) + print("\n2. Retrieving dose data for edit window:") + medicines = ["bupropion", "hydroxyzine", "gabapentin", "propranolol"] + + dose_data = {} + for medicine in medicines: + doses = data_manager.get_today_medicine_doses(test_date, medicine) + dose_str = "|".join([f"{ts}:{dose}" for ts, dose in doses]) + dose_data[medicine] = dose_str + + if dose_str: + print(f" {medicine}: {dose_str}") + else: + print(f" {medicine}: No doses") + + # Test CSV structure compatibility + print("\n3. Testing CSV structure:") + df = data_manager.load_data() + if not df.empty: + # Get a row with dose data + test_row = df[df["date"] == test_date] + if not test_row.empty: + values = test_row.iloc[0].tolist() + print(f" CSV columns: {len(df.columns)}") + print( + " Expected: 14 columns (date, dep, anx, slp, app, bup, " + "bup_doses, ...)" + ) + print(f" Values for {test_date}: {len(values)} values") + + # Test unpacking like the edit window would + if len(values) == 14: + print(" ✓ CSV structure compatible with edit functionality") + else: + print(f" ⚠ Unexpected number of values: {len(values)}") + else: + print(f" No data found for {test_date}") + + print("\n4. Edit functionality test summary:") + print(" ✓ Dose data retrieval working") + print(" ✓ CSV structure supports edit operations") + print(" ✓ Dose preservation logic implemented") + print("\nEdit functionality is ready for testing in the GUI!") + + +if __name__ == "__main__": + test_edit_functionality() diff --git a/scripts/test_edit_window_functionality.py b/scripts/test_edit_window_functionality.py new file mode 100644 index 0000000..cbe046f --- /dev/null +++ b/scripts/test_edit_window_functionality.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +""" +Test script to verify edit window functionality (save and delete) after dose tracking +implementation. +""" + +import logging +import os +import sys + +# Add the src directory to the path so we can import our modules +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) + +from data_manager import DataManager + + +def test_edit_window_functionality(): + """Test both save and delete functionality with the new CSV format.""" + print("Testing edit window functionality...") + + # Create a backup of the current CSV + import shutil + + try: + shutil.copy("thechart_data.csv", "thechart_data_backup.csv") + print("✓ Created backup of current CSV") + except Exception as e: + print(f"✗ Failed to create backup: {e}") + return False + + try: + # Create a logger for the DataManager + logger = logging.getLogger("test_logger") + logger.setLevel(logging.DEBUG) + + # Initialize data manager + data_manager = DataManager("thechart_data.csv", logger) + + # Load current data + df = data_manager.load_data() + print(f"✓ Loaded {len(df)} entries from CSV") + + if df.empty: + print("✗ No data to test edit functionality") + return False + + # Test 1: Test delete functionality + print("\n=== Testing Delete Functionality ===") + last_entry_date = df.iloc[-1]["date"] + print(f"Attempting to delete entry with date: {last_entry_date}") + + success = data_manager.delete_entry(last_entry_date) + if success: + print("✓ Delete operation successful") + df_after_delete = data_manager.load_data() + if last_entry_date not in df_after_delete["date"].values: + print("✓ Entry successfully removed from CSV") + else: + print("✗ Entry still exists after delete") + return False + else: + print("✗ Delete operation failed") + return False + + # Test 2: Test update functionality + print("\n=== Testing Update Functionality ===") + if not df_after_delete.empty: + # Get first entry to test update + first_entry = df_after_delete.iloc[0] + test_date = first_entry["date"] + original_note = first_entry["note"] + print(f"Testing update for date: {test_date}") + print(f"Original note: '{original_note}'") + + # Create updated data (simulating what the edit window would do) + updated_data = [ + test_date, # date + int(first_entry["depression"]), # depression + int(first_entry["anxiety"]), # anxiety + int(first_entry["sleep"]), # sleep + int(first_entry["appetite"]), # appetite + int(first_entry["bupropion"]), # bupropion + str(first_entry["bupropion_doses"]), # bupropion_doses + int(first_entry["hydroxyzine"]), # hydroxyzine + str(first_entry["hydroxyzine_doses"]), # hydroxyzine_doses + int(first_entry["gabapentin"]), # gabapentin + str(first_entry["gabapentin_doses"]), # gabapentin_doses + int(first_entry["propranolol"]), # propranolol + str(first_entry["propranolol_doses"]), # propranolol_doses + f"{original_note} [UPDATED BY TEST]", # note + ] + + print(f"Data to update with: {updated_data}") + print(f"Length of update data: {len(updated_data)}") + + success = data_manager.update_entry(test_date, updated_data) + if success: + print("✓ Update operation successful") + + # Verify the update + df_after_update = data_manager.load_data() + updated_entry = df_after_update[ + df_after_update["date"] == test_date + ].iloc[0] + if "[UPDATED BY TEST]" in updated_entry["note"]: + print("✓ Entry successfully updated in CSV") + print(f"New note: '{updated_entry['note']}'") + else: + print("✗ Entry was not properly updated") + return False + else: + print("✗ Update operation failed") + return False + + print("\n✓ All edit window functionality tests passed!") + return True + + except Exception as e: + print(f"✗ Error during test: {e}") + import traceback + + traceback.print_exc() + return False + + finally: + # Restore the backup + try: + shutil.move("thechart_data_backup.csv", "thechart_data.csv") + print("✓ Restored original CSV from backup") + except Exception as e: + print(f"✗ Failed to restore backup: {e}") + + +if __name__ == "__main__": + test_edit_window_functionality() diff --git a/scripts/test_scrollable_input.py b/scripts/test_scrollable_input.py new file mode 100644 index 0000000..ab2fc0f --- /dev/null +++ b/scripts/test_scrollable_input.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +""" +Test script to verify the scrollable input frame functionality. +""" + +import os +import sys +import tkinter as tk +from tkinter import ttk + +# Add src to path +sys.path.append(os.path.join(os.path.dirname(__file__), "src")) + + +def test_scrollable_input(): + """Test the scrollable input frame.""" + from init import logger + from ui_manager import UIManager + + # Create a test window + root = tk.Tk() + root.title("Scrollable Input Frame Test") + root.geometry("400x600") # Smaller window to test scrolling + + # Create UI manager + ui_manager = UIManager(root, logger) + + # Create main frame + main_frame = ttk.Frame(root, padding="10") + main_frame.grid(row=0, column=0, sticky="nsew") + root.grid_rowconfigure(0, weight=1) + root.grid_columnconfigure(0, weight=1) + main_frame.grid_rowconfigure(1, weight=1) + main_frame.grid_columnconfigure(0, weight=1) + + # Create the scrollable input frame + _input_ui = ui_manager.create_input_frame(main_frame) + + # Add instructions + instructions = ttk.Label( + root, + text="Test the scrolling functionality:\n" + "1. Try mouse wheel scrolling over the input area\n" + "2. Use the scrollbar on the right\n" + "3. Test dose tracking buttons\n" + "4. Resize the window to test responsiveness", + justify="left", + ) + instructions.grid(row=1, column=0, padx=10, pady=10, sticky="ew") + + # Print success message + print("✓ Scrollable input frame created successfully!") + print("✓ Medicine dose tracking UI elements loaded") + print("✓ Scrollbar functionality active") + print("✓ Mouse wheel scrolling enabled") + print("\nTest window opened. Close the window when done testing.") + + # Start the test GUI + root.mainloop() + + +if __name__ == "__main__": + test_scrollable_input() diff --git a/src/data_manager.py b/src/data_manager.py index 6b7eb66..1468342 100644 --- a/src/data_manager.py +++ b/src/data_manager.py @@ -26,9 +26,13 @@ class DataManager: "sleep", "appetite", "bupropion", + "bupropion_doses", "hydroxyzine", + "hydroxyzine_doses", "gabapentin", + "gabapentin_doses", "propranolol", + "propranolol_doses", "note", ] ) @@ -48,9 +52,13 @@ class DataManager: "sleep": int, "appetite": int, "bupropion": int, + "bupropion_doses": str, "hydroxyzine": int, + "hydroxyzine_doses": str, "gabapentin": int, + "gabapentin_doses": str, "propranolol": int, + "propranolol_doses": str, "note": str, "date": str, }, @@ -96,21 +104,45 @@ class DataManager: return False # Find the row to update using original_date as a unique identifier - df.loc[ - df["date"] == original_date, - [ - "date", - "depression", - "anxiety", - "sleep", - "appetite", - "bupropion", - "hydroxyzine", - "gabapentin", - "propranolol", - "note", - ], - ] = values + # Handle both old format (10 columns) and new format (14 columns) + if len(values) == 14: + # New format with dose columns + df.loc[ + df["date"] == original_date, + [ + "date", + "depression", + "anxiety", + "sleep", + "appetite", + "bupropion", + "bupropion_doses", + "hydroxyzine", + "hydroxyzine_doses", + "gabapentin", + "gabapentin_doses", + "propranolol", + "propranolol_doses", + "note", + ], + ] = values + else: + # Old format - only update the user-editable columns + df.loc[ + df["date"] == original_date, + [ + "date", + "depression", + "anxiety", + "sleep", + "appetite", + "bupropion", + "hydroxyzine", + "gabapentin", + "propranolol", + "note", + ], + ] = values df.to_csv(self.filename, index=False) return True except Exception as e: @@ -129,3 +161,78 @@ class DataManager: except Exception as e: self.logger.error(f"Error deleting entry: {str(e)}") return False + + def add_medicine_dose(self, date: str, medicine_name: str, dose: str) -> bool: + """Add a medicine dose to today's entry.""" + from datetime import datetime + + try: + df: pd.DataFrame = self.load_data() + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + dose_entry = f"{timestamp}:{dose}" + + # Find or create entry for the given date + if df.empty or date not in df["date"].values: + # Create new entry for today with default values + new_entry = { + "date": date, + "depression": 0, + "anxiety": 0, + "sleep": 0, + "appetite": 0, + "bupropion": 0, + "bupropion_doses": "", + "hydroxyzine": 0, + "hydroxyzine_doses": "", + "gabapentin": 0, + "gabapentin_doses": "", + "propranolol": 0, + "propranolol_doses": "", + "note": "", + } + df = pd.concat([df, pd.DataFrame([new_entry])], ignore_index=True) + + # Add dose to the appropriate medicine + dose_column = f"{medicine_name}_doses" + mask = df["date"] == date + current_doses = df.loc[mask, dose_column].iloc[0] + + if current_doses: + df.loc[mask, dose_column] = current_doses + "|" + dose_entry + else: + df.loc[mask, dose_column] = dose_entry + + # Mark medicine as taken (set to 1) + df.loc[mask, medicine_name] = 1 + + df.to_csv(self.filename, index=False) + return True + except Exception as e: + self.logger.error(f"Error adding medicine dose: {str(e)}") + return False + + def get_today_medicine_doses( + self, date: str, medicine_name: str + ) -> list[tuple[str, str]]: + """Get list of (timestamp, dose) tuples for a medicine on a given date.""" + try: + df: pd.DataFrame = self.load_data() + if df.empty or date not in df["date"].values: + return [] + + dose_column = f"{medicine_name}_doses" + doses_str = df.loc[df["date"] == date, dose_column].iloc[0] + + if not doses_str: + return [] + + doses = [] + for dose_entry in doses_str.split("|"): + if ":" in dose_entry: + timestamp, dose = dose_entry.split(":", 1) + doses.append((timestamp, dose)) + + return doses + except Exception as e: + self.logger.error(f"Error getting medicine doses: {str(e)}") + return [] diff --git a/src/main.py b/src/main.py index 3a2f75d..ed707b0 100644 --- a/src/main.py +++ b/src/main.py @@ -80,12 +80,16 @@ class MedTrackerApp: input_ui: dict[str, Any] = self.ui_manager.create_input_frame(main_frame) self.input_frame: ttk.Frame = input_ui["frame"] self.symptom_vars: dict[str, tk.IntVar] = input_ui["symptom_vars"] - self.medicine_vars: dict[str, list[tk.IntVar | ttk.Spinbox]] = input_ui[ - "medicine_vars" - ] + self.medicine_vars: dict[str, tuple[tk.IntVar, str]] = input_ui["medicine_vars"] + self.dose_buttons: dict[str, ttk.Button] = input_ui["dose_buttons"] + self.dose_entries: dict[str, ttk.Entry] = input_ui["dose_entries"] + self.dose_displays: dict[str, tk.Text] = input_ui["dose_displays"] self.note_var: tk.StringVar = input_ui["note_var"] self.date_var: tk.StringVar = input_ui["date_var"] + # Set up dose button callbacks + self._setup_dose_button_callbacks() + # Add buttons to input frame self.ui_manager.add_buttons( self.input_frame, @@ -108,6 +112,75 @@ class MedTrackerApp: # Load data self.load_data() + def _setup_dose_button_callbacks(self) -> None: + """Set up callbacks for dose tracking buttons.""" + for medicine_name, button in self.dose_buttons.items(): + button.config( + command=lambda med=medicine_name: self._take_medicine_dose(med) + ) + + # Update dose displays for today + self._update_dose_displays() + + def _take_medicine_dose(self, medicine_name: str) -> None: + """Record a dose of medicine taken right now.""" + dose_entry = self.dose_entries[medicine_name] + dose = dose_entry.get().strip() + + if not dose: + messagebox.showerror( + "Error", + f"Please enter a dose amount for {medicine_name}", + parent=self.root, + ) + return + + # Use today's date + today = self.date_var.get() + if not today: + from datetime import datetime + + today = datetime.now().strftime("%m/%d/%Y") + self.date_var.set(today) + + if self.data_manager.add_medicine_dose(today, medicine_name, dose): + messagebox.showinfo( + "Success", + f"{medicine_name.title()} dose recorded: {dose}", + parent=self.root, + ) + # Clear dose entry + dose_entry.delete(0, tk.END) + # Update displays and reload data + self._update_dose_displays() + self.load_data() + else: + messagebox.showerror( + "Error", f"Failed to record {medicine_name} dose", parent=self.root + ) + + def _update_dose_displays(self) -> None: + """Update the dose display areas with today's doses.""" + today = self.date_var.get() + if not today: + return + + for medicine_name, display in self.dose_displays.items(): + doses = self.data_manager.get_today_medicine_doses(today, medicine_name) + + display.config(state=tk.NORMAL) + display.delete(1.0, tk.END) + + if doses: + dose_text = "\n".join( + [f"{timestamp}: {dose}" for timestamp, dose in doses] + ) + display.insert(1.0, dose_text) + else: + display.insert(1.0, "No doses recorded today") + + display.config(state=tk.DISABLED) + def on_double_click(self, event: tk.Event) -> None: """Handle double-click event to edit an entry.""" logger.debug("Double-click event triggered on treeview.") @@ -146,6 +219,34 @@ class MedTrackerApp: note: str, ) -> None: """Save the edited data to the CSV file.""" + # Get existing dose data for this date to preserve it + bup_doses = "" + hydro_doses = "" + gaba_doses = "" + prop_doses = "" + + # Try to get existing dose data + try: + existing_bup = self.data_manager.get_today_medicine_doses( + original_date, "bupropion" + ) + existing_hydro = self.data_manager.get_today_medicine_doses( + original_date, "hydroxyzine" + ) + existing_gaba = self.data_manager.get_today_medicine_doses( + original_date, "gabapentin" + ) + existing_prop = self.data_manager.get_today_medicine_doses( + original_date, "propranolol" + ) + + bup_doses = "|".join([f"{ts}:{dose}" for ts, dose in existing_bup]) + hydro_doses = "|".join([f"{ts}:{dose}" for ts, dose in existing_hydro]) + gaba_doses = "|".join([f"{ts}:{dose}" for ts, dose in existing_gaba]) + prop_doses = "|".join([f"{ts}:{dose}" for ts, dose in existing_prop]) + except Exception as e: + logger.warning(f"Could not retrieve existing dose data: {e}") + values: list[str | int] = [ date, dep, @@ -153,9 +254,13 @@ class MedTrackerApp: slp, app, bup, + bup_doses, hydro, + hydro_doses, gaba, + gaba_doses, prop, + prop_doses, note, ] @@ -188,6 +293,30 @@ class MedTrackerApp: def add_entry(self) -> None: """Add a new entry to the CSV file.""" + # Get current doses for today + today = self.date_var.get() + bupropion_doses = "" + hydroxyzine_doses = "" + gabapentin_doses = "" + propranolol_doses = "" + + if today: + bup_doses = self.data_manager.get_today_medicine_doses(today, "bupropion") + hydroxyzine_doses_list = self.data_manager.get_today_medicine_doses( + today, "hydroxyzine" + ) + gaba_doses = self.data_manager.get_today_medicine_doses(today, "gabapentin") + prop_doses = self.data_manager.get_today_medicine_doses( + today, "propranolol" + ) + + bupropion_doses = "|".join([f"{ts}:{dose}" for ts, dose in bup_doses]) + hydroxyzine_doses = "|".join( + [f"{ts}:{dose}" for ts, dose in hydroxyzine_doses_list] + ) + gabapentin_doses = "|".join([f"{ts}:{dose}" for ts, dose in gaba_doses]) + propranolol_doses = "|".join([f"{ts}:{dose}" for ts, dose in prop_doses]) + entry: list[str | int] = [ self.date_var.get(), self.symptom_vars["depression"].get(), @@ -195,9 +324,13 @@ class MedTrackerApp: self.symptom_vars["sleep"].get(), self.symptom_vars["appetite"].get(), self.medicine_vars["bupropion"][0].get(), + bupropion_doses, self.medicine_vars["hydroxyzine"][0].get(), + hydroxyzine_doses, self.medicine_vars["gabapentin"][0].get(), + gabapentin_doses, self.medicine_vars["propranolol"][0].get(), + propranolol_doses, self.note_var.get(), ] logger.debug(f"Adding entry: {entry}") @@ -241,7 +374,7 @@ class MedTrackerApp: if self.data_manager.delete_entry(date): edit_win.destroy() messagebox.showinfo( - "Success", "Entry deleted successfully!", parent=edit_win + "Success", "Entry deleted successfully!", parent=self.root ) self.load_data() else: @@ -257,6 +390,13 @@ class MedTrackerApp: self.medicine_vars[key][0].set(0) self.note_var.set("") + # Clear dose entry fields + for entry in self.dose_entries.values(): + entry.delete(0, tk.END) + + # Update dose displays + self._update_dose_displays() + def load_data(self) -> None: """Load data from the CSV file into the table and graph.""" logger.debug("Loading data from CSV.") @@ -270,9 +410,30 @@ class MedTrackerApp: # Update the treeview with the data if not df.empty: - for _index, row in df.iterrows(): + # Only show user-friendly columns in the table (not the dose columns) + display_columns = [ + "date", + "depression", + "anxiety", + "sleep", + "appetite", + "bupropion", + "hydroxyzine", + "gabapentin", + "propranolol", + "note", + ] + + # Filter to only the columns we want to display + if all(col in df.columns for col in display_columns): + display_df = df[display_columns] + else: + # Fallback for old CSV format - just use all columns + display_df = df + + for _index, row in display_df.iterrows(): self.tree.insert(parent="", index="end", values=list(row)) - logger.debug(f"Loaded {len(df)} entries into treeview.") + logger.debug(f"Loaded {len(display_df)} entries into treeview.") # Update the graph self.graph_manager.update_graph(df) diff --git a/src/ui_manager.py b/src/ui_manager.py index 07f6829..78a7911 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -53,10 +53,49 @@ class UIManager: def create_input_frame(self, parent_frame: ttk.Frame) -> dict[str, Any]: """Create and configure the input frame with all widgets.""" - input_frame: ttk.LabelFrame = ttk.LabelFrame(parent_frame, text="New Entry") - input_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") + # Create main container for the scrollable input frame + main_container = ttk.LabelFrame(parent_frame, text="New Entry") + main_container.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") + main_container.grid_rowconfigure(0, weight=1) + main_container.grid_columnconfigure(0, weight=1) + + # Create canvas and scrollbar for scrolling + canvas = tk.Canvas(main_container, highlightthickness=0) + scrollbar = ttk.Scrollbar( + main_container, orient="vertical", command=canvas.yview + ) + canvas.configure(yscrollcommand=scrollbar.set) + + # Create the actual input frame inside the canvas + input_frame = ttk.Frame(canvas) input_frame.grid_columnconfigure(1, weight=1) + # Configure canvas scrolling + def configure_scroll_region(event=None): + canvas.configure(scrollregion=canvas.bbox("all")) + + def on_mousewheel(event): + canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") + + input_frame.bind("", configure_scroll_region) + canvas.bind("", on_mousewheel) # Windows/Linux + canvas.bind("", lambda e: canvas.yview_scroll(-1, "units")) # Linux + canvas.bind("", lambda e: canvas.yview_scroll(1, "units")) # Linux + + # Place canvas and scrollbar in the container + canvas.grid(row=0, column=0, sticky="nsew") + scrollbar.grid(row=0, column=1, sticky="ns") + + # Create window in canvas for the input frame + canvas_window = canvas.create_window((0, 0), window=input_frame, anchor="nw") + + # Configure canvas window width to fill available space + def configure_canvas_width(event=None): + canvas_width = canvas.winfo_width() + canvas.itemconfig(canvas_window, width=canvas_width) + + canvas.bind("", configure_canvas_width) + # Create variables for symptoms symptom_vars: dict[str, tk.IntVar] = { "depression": tk.IntVar(value=0), @@ -85,13 +124,15 @@ class UIManager: variable=symptom_vars[var_name], ).grid(row=idx, column=1, sticky="ew") - # Medicine checkboxes + # Medicine tracking section with dose buttons ttk.Label(input_frame, text="Treatment:").grid( row=4, column=0, sticky="w", padx=5, pady=2 ) medicine_frame = ttk.LabelFrame(input_frame, text="Medicine") medicine_frame.grid(row=4, column=1, padx=0, pady=10, sticky="nsew") + medicine_frame.grid_columnconfigure(0, weight=1) + # Store medicine variables and dose tracking medicine_vars: dict[str, tuple[tk.IntVar, str]] = { "bupropion": (tk.IntVar(value=0), "Bupropion 150/300 mg"), "hydroxyzine": (tk.IntVar(value=0), "Hydroxyzine 25mg"), @@ -99,11 +140,44 @@ class UIManager: "propranolol": (tk.IntVar(value=0), "Propranolol 10mg"), } - for idx, (_name, (var, text)) in enumerate(medicine_vars.items()): - ttk.Checkbutton(medicine_frame, text=text, variable=var).grid( - row=idx, column=0, sticky="w", padx=5, pady=2 + # Store dose tracking elements for callback assignment later + dose_buttons: dict[str, ttk.Button] = {} + dose_entries: dict[str, ttk.Entry] = {} + dose_displays: dict[str, tk.Text] = {} + + for idx, (med_name, (var, text)) in enumerate(medicine_vars.items()): + # Create a sub-frame for each medicine + med_sub_frame = ttk.Frame(medicine_frame) + med_sub_frame.grid(row=idx, column=0, sticky="ew", padx=5, pady=2) + med_sub_frame.grid_columnconfigure(1, weight=1) + + # Checkbox for medicine taken + ttk.Checkbutton(med_sub_frame, text=text, variable=var).grid( + row=0, column=0, sticky="w" ) + # Dose tracking frame + dose_frame = ttk.Frame(med_sub_frame) + dose_frame.grid(row=0, column=1, sticky="ew", padx=(10, 0)) + dose_frame.grid_columnconfigure(0, weight=1) + + # Dose entry and button + dose_var = tk.StringVar() + dose_entry = ttk.Entry(dose_frame, textvariable=dose_var, width=10) + dose_entry.grid(row=0, column=0, sticky="ew", padx=(0, 5)) + + dose_button = ttk.Button(dose_frame, text=f"Take {med_name.title()}") + dose_button.grid(row=0, column=1) + + # Display area for today's doses (read-only) + dose_display = tk.Text(dose_frame, height=2, width=30, wrap=tk.WORD) + dose_display.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(2, 0)) + dose_display.config(state=tk.DISABLED) + + dose_buttons[med_name] = dose_button + dose_entries[med_name] = dose_entry + dose_displays[med_name] = dose_display + # Note and Date fields note_var: tk.StringVar = tk.StringVar() date_var: tk.StringVar = tk.StringVar() @@ -127,9 +201,12 @@ class UIManager: # Return all UI elements and variables return { - "frame": input_frame, + "frame": main_container, "symptom_vars": symptom_vars, "medicine_vars": medicine_vars, + "dose_buttons": dose_buttons, + "dose_entries": dose_entries, + "dose_displays": dose_displays, "note_var": note_var, "date_var": date_var, } @@ -240,8 +317,50 @@ class UIManager: # Configure grid columns to expand properly edit_win.grid_columnconfigure(1, weight=1) - # Unpack values - date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values + # Unpack values - handle both old and new CSV formats + if len(values) == 10: + # Old format: date, dep, anx, slp, app, bup, hydro, gaba, prop, note + date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values + bup_doses, hydro_doses, gaba_doses, prop_doses = "", "", "", "" + elif len(values) == 14: + # New format with dose tracking + ( + date, + dep, + anx, + slp, + app, + bup, + bup_doses, + hydro, + hydro_doses, + gaba, + gaba_doses, + prop, + prop_doses, + note, + ) = values + else: + # Fallback for unexpected format + self.logger.warning(f"Unexpected number of values in edit: {len(values)}") + # Pad with default values + values_list = list(values) + [""] * (14 - len(values)) + ( + date, + dep, + anx, + slp, + app, + bup, + bup_doses, + hydro, + hydro_doses, + gaba, + gaba_doses, + prop, + prop_doses, + note, + ) = values_list[:14] # Create variables and fields vars_dict = self._create_edit_fields(edit_win, date, dep, anx, slp, app) @@ -253,8 +372,21 @@ class UIManager: ) vars_dict.update(med_vars) - # Note field + # Dose information display (read-only) current_row += 1 + self._add_dose_display_to_edit( + edit_win, + current_row, + { + "bupropion": bup_doses, + "hydroxyzine": hydro_doses, + "gabapentin": gaba_doses, + "propranolol": prop_doses, + }, + ) + + # Note field + current_row += 2 # Account for dose display vars_dict["note"] = tk.StringVar(value=str(note)) ttk.Label(edit_win, text="Note:").grid( row=current_row, column=0, sticky="w", padx=5, pady=2 @@ -441,3 +573,43 @@ class UIManager: text="Delete", command=lambda: callbacks["delete"](parent), ).pack(side="left", padx=5) + + def _add_dose_display_to_edit( + self, parent: tk.Toplevel, row: int, dose_data: dict[str, str] + ) -> None: + """Add dose information display to edit window.""" + ttk.Label(parent, text="Recorded Doses:").grid( + row=row, column=0, sticky="w", padx=5, pady=2 + ) + + dose_frame = ttk.LabelFrame(parent, text="Today's Doses (Read-Only)") + dose_frame.grid(row=row, column=1, padx=5, pady=2, sticky="ew") + + for idx, (medicine, doses_str) in enumerate(dose_data.items()): + ttk.Label(dose_frame, text=f"{medicine.title()}:").grid( + row=idx, column=0, sticky="w", padx=5, pady=1 + ) + + # Parse and display doses + if doses_str: + doses_display = [] + for dose_entry in doses_str.split("|"): + if ":" in dose_entry: + timestamp, dose = dose_entry.split(":", 1) + # Format timestamp for display + try: + from datetime import datetime + + dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") + time_str = dt.strftime("%H:%M") + doses_display.append(f"{time_str}: {dose}") + except ValueError: + doses_display.append(dose_entry) + + display_text = ", ".join(doses_display) if doses_display else "None" + else: + display_text = "None" + + ttk.Label(dose_frame, text=display_text, wraplength=200).grid( + row=idx, column=1, sticky="w", padx=5, pady=1 + ) diff --git a/tests/test_data_manager.py b/tests/test_data_manager.py index b81f11e..f6e56f7 100644 --- a/tests/test_data_manager.py +++ b/tests/test_data_manager.py @@ -39,7 +39,8 @@ class TestDataManager: headers = next(reader) expected_headers = [ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", "note" ] assert headers == expected_headers diff --git a/thechart_data.csv.backup_20250728_185654 b/thechart_data.csv.backup_20250728_185654 new file mode 100644 index 0000000..f21cb50 --- /dev/null +++ b/thechart_data.csv.backup_20250728_185654 @@ -0,0 +1,28 @@ +date,depression,anxiety,sleep,appetite,bupropion,hydroxyzine,gabapentin,propranolol,note +07/02/2025,8,5,3,1,0,0,0,0,neutral emotions = baseline +07/03/2025,6,5,4,6,0,0,0,0,"less focus, less calm" +07/04/2025,4,4,5,7,0,0,0,0,"less focus, less calm, light headed (10:40 pm), deezy (10:40 pm)" +07/05/2025,0,0,3,7,0,0,0,0,"antsy (up at 5am), biting chicks a lot" +07/06/2025,0,0,5,7,0,0,0,0,better sleep ~4h +07/07/2025,0,0,4,6,0,0,0,0,barely slept +07/08/2025,0,2,3,4,0,0,0,0,woke up as asual +07/09/2025,0,2,3,4,0,0,0,0,woke up as asual +07/10/2025,0,2,3,3,0,0,0,0,woke up as asual +07/11/2025,0,3,2,3,0,0,0,0,woke up as asual +07/12/2025,0,3,2,2,1,1,0,0,woke up as asual +07/13/2025,2,4,2,2,1,1,0,0,woke up as asual +07/13/2025,2,4,2,2,1,1,0,0,woke up as asual +07/14/2025,0,5,3,3,1,1,0,0,woke up as asual +07/15/2025,0,4,2,2,1,1,1,0,woke up as asual +07/16/2025,0,5,2,2,1,1,1,1,"bad sleep, 2x Propranolol, 1x Gabapentin (3x100mg), melatonine" +07/17/2025,5,6,2,3,1,0,1,1,"bad sleep, x1 Propranolol (11:40am), x1 Propranolol (7:22pm), 1x Propranolol - 3x Gabapentin, 1x melatonin (10:30)" +07/18/2025,2,4,3,3,1,0,0,1,"bad sleep, x1 Propranolol (6:03pm)" +07/19/2025,2,4,3,3,1,1,1,1, +07/20/2025,5,2,4,2,1,0,0,1,"x1 Propranolol (12:45pm), 3x Gabapentin, 1x melatonin" +07/21/2025,0,3,6,3,1,1,1,1,"x1 Propranolol, x3 Gabapentin, x1 Hydroxyzine, x1 melatonin" +07/22/2025,0,4,6,2,1,0,0,1,"x2 Buproprion 150mg, x1 Propranolol, x1 Propanolol + x3 Gabapentin + x1 Hydroxyzine + x1 melatonin" +07/23/2025,3,0,6,0,1,1,1,1,"x2 Bupropion + x1 Propranolol, anxiety ~6:00pm, x3 Gabapentin + x1 hydroxyzin + x1 melatonin" +07/24/2025,0,2,4,5,1,1,0,1,"2x Buproprion 150mg, 2x Propranolol, 1x Hydroxyzine" +07/25/2025,3,1,4,5,1,0,0,1,x2 Buproprion 150mg + x1 Propranolol +07/26/2025,0,3,4,3,1,0,0,1,"x1 Buproprion + x1 Propranolol, 1x Propranolol (~1:30pm)" +07/28/2025,4,5,3,3,1,0,0,1,"x1 Buproprion 300mg + 1x Propranolol, x1 Propranolol (~12:30pm)"