diff --git a/.gitignore b/.gitignore index 5884542..464b168 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Data files (except example data) -*.csv +thechart_data.csv ### !thechart_data.csv # Environment files diff --git a/MODULAR_MEDICINE_SYSTEM.md b/MODULAR_MEDICINE_SYSTEM.md new file mode 100644 index 0000000..e82ced0 --- /dev/null +++ b/MODULAR_MEDICINE_SYSTEM.md @@ -0,0 +1,190 @@ +# Modular Medicine System + +The MedTracker application now features a modular medicine system that allows users to dynamically add, edit, and remove medicines without modifying the source code. + +## Features + +### โœจ Dynamic Medicine Management +- **Add new medicines** through the UI or programmatically +- **Edit existing medicines** - change names, dosages, colors, etc. +- **Remove medicines** - clean up unused medications +- **Automatic UI updates** - all interface elements update automatically + +### ๐ŸŽ›๏ธ Medicine Configuration +Each medicine has the following configurable properties: +- **Key**: Internal identifier (e.g., "bupropion") +- **Display Name**: User-friendly name (e.g., "Bupropion") +- **Dosage Info**: Dosage information (e.g., "150/300 mg") +- **Quick Doses**: Common dose amounts for quick selection +- **Color**: Hex color for graph display (e.g., "#FF6B6B") +- **Default Enabled**: Whether to show in graphs by default + +### ๐Ÿ“ Configuration Storage +- Medicines are stored in `medicines.json` +- Automatically created with default medicines on first run +- Human-readable JSON format for easy manual editing + +## Usage + +### Through the UI + +1. **Open Medicine Manager**: + - Launch the application + - Go to `Tools` โ†’ `Manage Medicines...` + +2. **Add a Medicine**: + - Click "Add Medicine" + - Fill in the required fields: + - Key (alphanumeric, underscores, hyphens only) + - Display Name + - Dosage Info + - Quick Doses (comma-separated) + - Graph Color (hex format, e.g., #FF6B6B) + - Default Enabled checkbox + - Click "Save" + +3. **Edit a Medicine**: + - Select a medicine from the list + - Click "Edit Medicine" + - Modify the fields as needed + - Click "Save" + +4. **Remove a Medicine**: + - Select a medicine from the list + - Click "Remove Medicine" + - Confirm the removal + +### Programmatically + +```python +from medicine_manager import MedicineManager, Medicine + +# Initialize manager +medicine_manager = MedicineManager() + +# Add a new medicine +new_medicine = Medicine( + key="sertraline", + display_name="Sertraline", + dosage_info="50mg", + quick_doses=["25", "50", "100"], + color="#9B59B6", + default_enabled=False +) + +medicine_manager.add_medicine(new_medicine) +``` + +### Manual Configuration + +Edit `medicines.json` directly: + +```json +{ + "medicines": [ + { + "key": "your_medicine", + "display_name": "Your Medicine", + "dosage_info": "25mg", + "quick_doses": ["25", "50"], + "color": "#FF6B6B", + "default_enabled": false + } + ] +} +``` + +## What Updates Automatically + +When you add, edit, or remove medicines, the following components update automatically: + +### ๐Ÿ–ฅ๏ธ User Interface +- **Input Form**: Medicine checkboxes in the main form +- **Data Table**: Column headers and display +- **Edit Windows**: Medicine fields and dose tracking +- **Graph Controls**: Toggle buttons for medicines + +### ๐Ÿ“Š Data Management +- **CSV Headers**: Automatically include new medicine columns +- **Data Loading**: Dynamic column type detection +- **Data Entry**: Medicine data is stored with appropriate columns + +### ๐Ÿ“ˆ Graphing +- **Toggle Controls**: Show/hide medicines in graphs +- **Color Coding**: Each medicine uses its configured color +- **Legend**: Medicine names and information in graph legends + +## Default Medicines + +The system comes with these default medicines: + +| Medicine | Dosage | Default Graph | Color | +|----------|--------|---------------|--------| +| Bupropion | 150/300 mg | โœ… | Red (#FF6B6B) | +| Hydroxyzine | 25 mg | โŒ | Teal (#4ECDC4) | +| Gabapentin | 100 mg | โŒ | Blue (#45B7D1) | +| Propranolol | 10 mg | โœ… | Green (#96CEB4) | +| Quetiapine | 25 mg | โŒ | Yellow (#FFEAA7) | + +## Technical Details + +### Architecture +- **MedicineManager**: Core class handling medicine CRUD operations +- **Medicine**: Data class representing individual medicines +- **Dynamic UI**: Components rebuild themselves when medicines change +- **Backward Compatibility**: Existing data continues to work + +### Files Involved +- `src/medicine_manager.py` - Core medicine management +- `src/medicine_management_window.py` - UI for managing medicines +- `medicines.json` - Configuration storage +- Updated: `main.py`, `ui_manager.py`, `data_manager.py`, `graph_manager.py` + +### CSV Data Format +The CSV structure adapts automatically: +``` +date,depression,anxiety,sleep,appetite,medicine1,medicine1_doses,medicine2,medicine2_doses,...,note +``` + +## Migration Notes + +### Existing Data +- Existing CSV files continue to work +- Old medicine columns are preserved +- New medicines get empty columns for existing entries + +### Backward Compatibility +- Hard-coded medicine references have been replaced with dynamic loading +- All existing functionality is preserved +- No data loss during updates + +## Examples + +See these example scripts: +- `add_medicine_example.py` - Shows how to add medicines programmatically +- `test_medicine_system.py` - Comprehensive system test + +## Troubleshooting + +### Medicine Not Appearing +1. Check `medicines.json` file exists and is valid JSON +2. Restart the application after manual JSON edits +3. Check logs for any loading errors + +### CSV Issues +1. Backup your data before adding/removing medicines +2. New medicines will have empty data for existing entries +3. Removed medicine data is preserved but not displayed + +### Color Issues +1. Colors must be in hex format: #RRGGBB +2. Ensure colors are visually distinct +3. Default color #DDA0DD is used for invalid colors + +## Development + +To extend the system: +1. Add new properties to the `Medicine` dataclass +2. Update the UI forms to handle new properties +3. Modify the JSON serialization if needed +4. Update the medicine management window diff --git a/debug_vars_dict.py b/debug_vars_dict.py deleted file mode 100644 index b30057d..0000000 --- a/debug_vars_dict.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -""" -Debug the vars_dict issue in the edit window. -""" - -import os -import sys -import tkinter as tk - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import logging - -from ui_manager import UIManager - - -def debug_vars_dict(): - """Debug what's in vars_dict when save is called.""" - print("๐Ÿ” Debugging vars_dict content...") - - root = tk.Tk() - root.title("Debug Test") - root.geometry("400x300") - - logger = logging.getLogger("debug") - ui_manager = UIManager(root, logger) - - sample_values = ("07/29/2025", 5, 3, 7, 6, 1, "", 0, "", 0, "", 0, "", "Debug test") - - def debug_save(*args): - print("\n๐Ÿ” Debug Save Called") - print(f"Number of arguments: {len(args)}") - - # The vars_dict should be accessible via the closure - # Let's examine what keys are available - print("\nTrying to access vars_dict from closure...") - - # Close window - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = {"save": debug_save, "delete": lambda x: x.destroy()} - - try: - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - - print("\n๐Ÿ“ Instructions:") - print("1. Add a dose to any medicine") - print("2. Click Save to see debug info") - - edit_window.wait_window() - - except Exception as e: - print(f"Error: {e}") - import traceback - - traceback.print_exc() - finally: - root.destroy() - - -if __name__ == "__main__": - os.chdir("/home/will/Code/thechart") - debug_vars_dict() diff --git a/medicines.json b/medicines.json new file mode 100644 index 0000000..a5c0b68 --- /dev/null +++ b/medicines.json @@ -0,0 +1,62 @@ +{ + "medicines": [ + { + "key": "bupropion", + "display_name": "Bupropion", + "dosage_info": "150/300 mg", + "quick_doses": [ + "150", + "300" + ], + "color": "#FF6B6B", + "default_enabled": true + }, + { + "key": "hydroxyzine", + "display_name": "Hydroxyzine", + "dosage_info": "25 mg", + "quick_doses": [ + "25", + "50" + ], + "color": "#4ECDC4", + "default_enabled": false + }, + { + "key": "gabapentin", + "display_name": "Gabapentin", + "dosage_info": "100 mg", + "quick_doses": [ + "100", + "300", + "600" + ], + "color": "#45B7D1", + "default_enabled": false + }, + { + "key": "propranolol", + "display_name": "Propranolol", + "dosage_info": "10 mg", + "quick_doses": [ + "10", + "20", + "40" + ], + "color": "#96CEB4", + "default_enabled": true + }, + { + "key": "quetiapine", + "display_name": "Quetiapine", + "dosage_info": "25 mg", + "quick_doses": [ + "25", + "50", + "100" + ], + "color": "#FFEAA7", + "default_enabled": false + } + ] +} diff --git a/nonexistent.csv b/nonexistent.csv new file mode 100644 index 0000000..b6bf2c8 --- /dev/null +++ b/nonexistent.csv @@ -0,0 +1 @@ +date,depression,anxiety,sleep,appetite,bupropion,bupropion_doses,hydroxyzine,hydroxyzine_doses,gabapentin,gabapentin_doses,propranolol,propranolol_doses,quetiapine,quetiapine_doses,note diff --git a/scripts/add_medicine_example.py b/scripts/add_medicine_example.py new file mode 100644 index 0000000..0138667 --- /dev/null +++ b/scripts/add_medicine_example.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +Example script showing how to add a new medicine programmatically. +This demonstrates the modular medicine system. +""" + +import os +import sys + +# Add src to path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) + +from init import logger +from medicine_manager import Medicine, MedicineManager + + +def add_example_medicine(): + """Add an example medicine to demonstrate the system.""" + print("๐Ÿ”ง Adding a new medicine example...") + + # Initialize medicine manager + medicine_manager = MedicineManager(logger=logger) + + # Display current medicines + print("\nCurrent medicines:") + for _, medicine in medicine_manager.get_all_medicines().items(): + print(f" - {medicine.display_name} ({medicine.dosage_info})") + + # Add a new medicine + new_medicine = Medicine( + key="lorazepam", + display_name="Lorazepam", + dosage_info="0.5mg", + quick_doses=["0.5", "1", "2"], + color="#8E44AD", + default_enabled=False, + ) + + if medicine_manager.add_medicine(new_medicine): + print(f"\nโœ… Successfully added {new_medicine.display_name}!") + + print("\nUpdated medicines:") + for _, medicine in medicine_manager.get_all_medicines().items(): + status = "๐ŸŸข" if medicine.default_enabled else "โšซ" + print(f" {status} {medicine.display_name} ({medicine.dosage_info})") + + print("\n๐Ÿ“‹ The medicine configuration has been saved to medicines.json") + print("๐Ÿ“ฑ Restart the application to see the new medicine in the UI") + print("๐ŸŽจ The new medicine will appear in:") + print(" - Input form checkboxes") + print(" - Data table columns") + print(" - Graph toggle controls") + print(" - CSV file headers") + + # Optionally remove it for demo purposes + response = input("\nRemove the example medicine? (y/n): ").lower().strip() + if response == "y": + medicine_manager.remove_medicine("lorazepam") + print("๐Ÿ—‘๏ธ Example medicine removed.") + + else: + print("โŒ Failed to add medicine (it may already exist)") + + +if __name__ == "__main__": + add_example_medicine() diff --git a/scripts/demo_failing_test.py b/scripts/demo_failing_test.py deleted file mode 100644 index bd9879f..0000000 --- a/scripts/demo_failing_test.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Demonstration script to show pre-commit test blocking. -This creates a temporary failing test to demonstrate the pre-commit behavior. -""" - -# Create a simple test file that will fail -test_content = ''' -def test_that_will_fail(): - """This test is designed to fail to demonstrate pre-commit blocking.""" - assert False, "This test intentionally fails" -''' - -with open("tests/test_demo_fail.py", "w") as f: - f.write(test_content) - -print("Created temporary failing test: tests/test_demo_fail.py") -print("Now try: git add . && git commit -m 'test commit'") -print("The commit should be blocked by the failing test.") -print("Remove the file with: rm tests/test_demo_fail.py") diff --git a/scripts/test_automated_punch.py b/scripts/test_automated_punch.py deleted file mode 100644 index d8d6e76..0000000 --- a/scripts/test_automated_punch.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python3 -""" -Automated test to simulate multiple punch button clicks and identify the -accumulation issue. -""" - -import os -import sys -import tkinter as tk - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import logging - -from src.ui_manager import UIManager - - -def test_automated_multiple_punches(): - """Automatically simulate multiple punch button clicks.""" - print("๐Ÿค– Automated Multiple Punch Test") - print("=" * 40) - - root = tk.Tk() - root.title("Auto Multi-Punch Test") - root.geometry("800x600") - - logger = logging.getLogger("auto_punch") - ui_manager = UIManager(root, logger) - - sample_values = ( - "07/29/2025", - 5, - 3, - 7, - 6, - 1, - "", - 0, - "", - 0, - "", - 0, - "", - "Auto multi-punch test", - ) - - punch_results = [] - save_result = None - - def capture_save(*args): - nonlocal save_result - save_result = args[-1] if len(args) >= 12 else {} - print("\n๐Ÿ’พ Save triggered, closing window...") - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = {"save": capture_save, "delete": lambda x: x.destroy()} - - try: - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - - # Find the dose widgets we need - def find_widgets(widget, widget_list=None): - if widget_list is None: - widget_list = [] - widget_list.append(widget) - for child in widget.winfo_children(): - find_widgets(child, widget_list) - return widget_list - - all_widgets = find_widgets(edit_window) - - # Find bupropion dose entry and text widgets - entry_widgets = [w for w in all_widgets if isinstance(w, tk.Entry)] - text_widgets = [w for w in all_widgets if isinstance(w, tk.Text)] - buttons = [w for w in all_widgets if isinstance(w, tk.ttk.Button)] - - # Find the specific widgets for bupropion - bupropion_entry = None - bupropion_text = None - bupropion_button = None - - # The first text widget should be bupropion (based on order in - # _add_dose_display_to_edit) - if len(text_widgets) >= 1: - bupropion_text = text_widgets[0] - - # Find the entry widget and button for bupropion - for button in buttons: - try: - if "Take Bupropion" in button.cget("text"): - bupropion_button = button - break - except Exception: - pass - - # Find the entry widget near the bupropion button - # This is tricky - let's use the first few entry widgets - if len(entry_widgets) >= 6: # Skip the first 5 (date, symptoms) - bupropion_entry = entry_widgets[5] # Should be first dose entry - - if not all([bupropion_entry, bupropion_text, bupropion_button]): - print("โŒ Could not find required widgets:") - print(f" Entry: {bupropion_entry is not None}") - print(f" Text: {bupropion_text is not None}") - print(f" Button: {bupropion_button is not None}") - edit_window.destroy() - return False - - print("โœ… Found bupropion widgets, starting automated test...") - - # Test sequence: Add 3 doses - doses = ["100mg", "200mg", "300mg"] - - for i, dose in enumerate(doses, 1): - print(f"\n๐Ÿ”„ Punch {i}: Adding {dose}") - - # Get content before - before_content = bupropion_text.get(1.0, tk.END).strip() - print(f" Content before: '{before_content}'") - - # Set the dose in entry - bupropion_entry.delete(0, tk.END) - bupropion_entry.insert(0, dose) - - # Click the punch button - bupropion_button.invoke() - - # Allow UI to update - root.update() - - # Get content after - after_content = bupropion_text.get(1.0, tk.END).strip() - print(f" Content after: '{after_content}'") - - # Count lines - lines = len([line for line in after_content.split("\n") if line.strip()]) - print(f" Lines in text: {lines}") - - punch_results.append( - { - "dose": dose, - "before": before_content, - "after": after_content, - "lines": lines, - } - ) - - # Small delay - root.after(100) - root.update() - - # Now trigger save - print("\n๐Ÿ’พ Triggering save...") - save_button = None - for button in buttons: - try: - if "Save" in button.cget("text"): - save_button = button - break - except Exception: - pass - - if save_button: - save_button.invoke() - root.update() - else: - print("โŒ Could not find Save button") - edit_window.destroy() - - # Wait a moment for save to complete - root.after(100) - root.update() - - # Analyze results - print("\n๐Ÿ“Š RESULTS ANALYSIS:") - final_lines = punch_results[-1]["lines"] if punch_results else 0 - - print(f" Total punches: {len(punch_results)}") - print(f" Final content lines: {final_lines}") - print(f" Expected lines: {len(doses)}") - - if save_result: - bup_doses = save_result.get("bupropion", "") - if bup_doses: - saved_dose_count = len(bup_doses.split("|")) - print(f" Saved dose count: {saved_dose_count}") - print(f" Saved doses: {bup_doses}") - - # Check if all doses were saved - if saved_dose_count == len(doses): - print("โœ… All doses were saved correctly!") - return True - else: - print("โŒ Not all doses were saved!") - return False - else: - print("โŒ No doses were saved!") - return False - else: - print("โŒ Save was not called!") - return False - - except Exception as e: - print(f"โŒ Error during test: {e}") - import traceback - - traceback.print_exc() - return False - finally: - import contextlib - - with contextlib.suppress(Exception): - root.destroy() - - -if __name__ == "__main__": - os.chdir("/home/will/Code/thechart") - success = test_automated_multiple_punches() - - if success: - print("\n๐ŸŽฏ Automated test PASSED - multiple doses work correctly!") - else: - print("\n๐Ÿšจ Automated test FAILED - multiple dose issue confirmed!") diff --git a/scripts/test_dose_calculation.py b/scripts/test_dose_calculation.py deleted file mode 100644 index ab80835..0000000 --- a/scripts/test_dose_calculation.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test script to verify dose calculation functionality. -""" - -import os -import sys - -# Add the src directory to Python path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import tkinter as tk - -from graph_manager import GraphManager - - -def test_dose_calculation(): - """Test the dose calculation method directly.""" - - # Create a minimal tkinter setup for GraphManager - root = tk.Tk() - root.withdraw() # Hide the window - - frame = tk.Frame(root) - - try: - # Create GraphManager instance - gm = GraphManager(frame) - - # Test cases - test_cases = [ - # (input, expected_output, description) - ("2025-07-28 18:59:45:150mg", 150.0, "Single dose with timestamp"), - ( - "2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg", - 225.0, - "Multiple doses", - ), - ("โ€ข โ€ข โ€ข โ€ข 2025-07-30 07:50:00:300", 300.0, "Dose with bullet symbols"), - ( - "2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg", - 20.0, - "Decimal doses", - ), - ("100mg|50mg", 150.0, "Doses without timestamps"), - ("โ€ข 2025-07-30 22:50:00:10|75mg", 85.0, "Mixed format"), - ("", 0.0, "Empty string"), - ("nan", 0.0, "NaN value"), - ("2025-07-28 18:59:45:10|2025-07-28 19:34:19:5", 15.0, "No units"), - ] - - print("Testing dose calculation...") - all_passed = True - - for input_str, expected, description in test_cases: - result = gm._calculate_daily_dose(input_str) - passed = ( - abs(result - expected) < 0.001 - ) # Allow for floating point precision - - status = "PASS" if passed else "FAIL" - print(f"{status}: {description}") - print(f" Input: '{input_str}'") - print(f" Expected: {expected}, Got: {result}") - print() - - if not passed: - all_passed = False - - if all_passed: - print("All dose calculation tests PASSED!") - else: - print("Some dose calculation tests FAILED!") - - return all_passed - - finally: - root.destroy() - - -if __name__ == "__main__": - success = test_dose_calculation() - sys.exit(0 if success else 1) diff --git a/scripts/test_dose_demonstration.py b/scripts/test_dose_demonstration.py deleted file mode 100644 index ac4434b..0000000 --- a/scripts/test_dose_demonstration.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3 -""" -Step-by-step test to demonstrate multiple dose functionality. -""" - -import os -import sys -import tkinter as tk - -import pandas as pd - -# 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")) - -import logging - -from src.ui_manager import UIManager - - -def demonstrate_multiple_doses(): - """Demonstrate the complete multiple dose workflow.""" - - print("๐Ÿงช Multiple Dose Demonstration") - print("=" * 40) - - # Check current CSV state - try: - df = pd.read_csv("thechart_data.csv") - print(f"๐Ÿ“‹ Current CSV has {len(df)} entries") - latest = df.iloc[-1] - print(f"๐Ÿ“… Latest entry date: {latest['date']}") - - # Show current dose state for latest entry - dose_columns = [col for col in df.columns if col.endswith("_doses")] - print("๐Ÿ’Š Current doses in latest entry:") - for dose_col in dose_columns: - medicine = dose_col.replace("_doses", "") - dose_data = str(latest[dose_col]) - if dose_data and dose_data != "nan" and dose_data.strip(): - dose_count = len(dose_data.split("|")) - print(f" {medicine}: {dose_count} dose(s)") - else: - print(f" {medicine}: No doses") - - except Exception as e: - print(f"โŒ Error reading CSV: {e}") - return - - print("\n๐Ÿ”ฌ Testing Edit Window Workflow:") - print("1. Create edit window for latest entry") - print("2. Add multiple doses using punch buttons") - print("3. Save and verify CSV is updated") - print("\nStarting test...") - - # Create test environment - root = tk.Tk() - root.title("Dose Test") - root.geometry("300x200") - - logger = logging.getLogger("dose_test") - logger.setLevel(logging.DEBUG) - - ui_manager = UIManager(root, logger) - - # Use the actual latest CSV data for testing - if len(latest) >= 14: - sample_values = tuple(latest.iloc[:14]) - else: - # Pad with empty values if needed - sample_values = tuple(list(latest) + [""] * (14 - len(latest))) - - # Track save operations - save_called = False - saved_dose_data = None - - def test_save(*args): - nonlocal save_called, saved_dose_data - save_called = True - - if len(args) >= 12: - saved_dose_data = args[-1] # dose_data is last argument - - print("\nโœ… Save called!") - print("๐Ÿ’พ Dose data being saved:") - for med, doses in saved_dose_data.items(): - if doses: - dose_count = len(doses.split("|")) if "|" in doses else 1 - print(f" {med}: {dose_count} dose(s) - {doses}") - else: - print(f" {med}: No doses") - - # Close the window - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - def test_delete(*args): - print("๐Ÿ—‘๏ธ Delete called") - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = { - "save": test_save, - "delete": test_delete, - } - - try: - # Create edit window - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - edit_window.geometry("700x500") - edit_window.lift() - edit_window.focus_force() - - print("\n๐Ÿ“ INSTRUCTIONS:") - print("1. In any medicine dose field, enter a dose amount (e.g., '100mg')") - print("2. Click the 'Take [Medicine]' button") - print("3. Enter another dose amount") - print("4. Click the 'Take [Medicine]' button again") - print("5. You should see both doses in the text area") - print("6. Click 'Save' to persist changes") - print("\nโณ Waiting for your interaction...") - - # Wait for user interaction - edit_window.wait_window() - - if save_called: - print("\n๐ŸŽ‰ SUCCESS: Save operation completed!") - print("๐Ÿ“Š Multiple doses should now be saved to CSV") - - # Verify the save actually updated the CSV - try: - df_after = pd.read_csv("thechart_data.csv") - if len(df_after) > len(df): - print("โœ… New entry added to CSV") - else: - print("โœ… Existing entry updated in CSV") - - print("\n๐Ÿ” Verifying saved data...") - latest_after = df_after.iloc[-1] - for dose_col in dose_columns: - medicine = dose_col.replace("_doses", "") - dose_data = str(latest_after[dose_col]) - if dose_data and dose_data != "nan" and dose_data.strip(): - dose_count = len(dose_data.split("|")) - print(f" {medicine}: {dose_count} dose(s) in CSV") - - except Exception as e: - print(f"โŒ Error verifying CSV: {e}") - - return True - else: - print("\nโŒ Save was not called - test incomplete") - return False - - except Exception as e: - print(f"โŒ Error during test: {e}") - import traceback - - traceback.print_exc() - return False - finally: - root.destroy() - - -if __name__ == "__main__": - os.chdir("/home/will/Code/thechart") - success = demonstrate_multiple_doses() - - if success: - print("\n๐ŸŽฏ Multiple dose functionality verified!") - else: - print("\nโ“ Test incomplete or failed") diff --git a/scripts/test_dose_editing_functionality.py b/scripts/test_dose_editing_functionality.py deleted file mode 100644 index c627651..0000000 --- a/scripts/test_dose_editing_functionality.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify dose editing functionality in the edit window. -""" - -import logging -import os -import shutil -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 src.data_manager import DataManager - - -def test_dose_editing_functionality(): - """Test the dose editing functionality with the edit window.""" - print("Testing dose editing functionality in edit window...") - - # Create a backup of the current CSV - 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 dose editing functionality") - return False - - # Test 1: Check that we can retrieve full row data including doses - print("\n=== Testing Full Row Data Retrieval ===") - first_entry_date = df.iloc[0]["date"] - first_entry = df[df["date"] == first_entry_date].iloc[0] - - print(f"Testing with date: {first_entry_date}") - - # Check that all expected columns are present - expected_columns = [ - "date", - "depression", - "anxiety", - "sleep", - "appetite", - "bupropion", - "bupropion_doses", - "hydroxyzine", - "hydroxyzine_doses", - "gabapentin", - "gabapentin_doses", - "propranolol", - "propranolol_doses", - "note", - ] - - missing_columns = [col for col in expected_columns if col not in df.columns] - if missing_columns: - print(f"โœ— Missing columns: {missing_columns}") - return False - else: - print("โœ“ All expected columns present in CSV") - - # Test 2: Check dose data access - print("\n=== Testing Dose Data Access ===") - dose_columns = [ - "bupropion_doses", - "hydroxyzine_doses", - "gabapentin_doses", - "propranolol_doses", - ] - - for col in dose_columns: - dose_data = first_entry[col] - print(f"{col}: '{dose_data}'") - - print("โœ“ Dose data accessible from CSV") - - # Test 3: Test parsing dose text (simulate edit window input) - print("\n=== Testing Dose Text Parsing ===") - - # Simulate some dose text that a user might enter - test_dose_text = "09:00: 150mg\n18:30: 150mg" - test_date = "07/28/2025" - - # Test the parsing logic (we'll need to import this) - try: - import tkinter as tk - - from src.ui_manager import UIManager - - # Create a temporary UI manager to test the parsing - root = tk.Tk() - root.withdraw() # Hide the window - ui_manager = UIManager(root, logger) - - parsed_doses = ui_manager._parse_dose_text(test_dose_text, test_date) - print(f"Original text: '{test_dose_text}'") - print(f"Parsed doses: '{parsed_doses}'") - - if "|" in parsed_doses and "2025-07-28" in parsed_doses: - print("โœ“ Dose text parsing working correctly") - else: - print("โœ— Dose text parsing failed") - root.destroy() - return False - - root.destroy() - - except Exception as e: - print(f"โœ— Error testing dose parsing: {e}") - return False - - print("\nโœ“ All dose editing 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_dose_editing_functionality() diff --git a/scripts/test_dose_simple.py b/scripts/test_dose_simple.py deleted file mode 100644 index cd4b579..0000000 --- a/scripts/test_dose_simple.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test script to verify dose calculation functionality without GUI. -""" - -import os -import sys - -# Add the src directory to Python path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - - -def calculate_daily_dose(dose_str: str) -> float: - """Calculate total daily dose from dose string format.""" - import pandas as pd - - if not dose_str or pd.isna(dose_str) or str(dose_str).lower() == "nan": - return 0.0 - - total_dose = 0.0 - # Handle different separators and clean the string - dose_str = str(dose_str).replace("โ€ข", "").strip() - - # Split by | or by spaces if no | present - dose_entries = dose_str.split("|") if "|" in dose_str else [dose_str] - - for entry in dose_entries: - entry = entry.strip() - if not entry: - continue - - try: - # Extract dose part after the last colon (timestamp:dose format) - dose_part = entry.split(":")[-1] if ":" in entry else entry - - # Extract numeric part from dose (e.g., "150mg" -> 150) - dose_value = "" - for char in dose_part: - if char.isdigit() or char == ".": - dose_value += char - elif dose_value: # Stop at first non-digit after finding digits - break - - if dose_value: - total_dose += float(dose_value) - except (ValueError, IndexError): - continue - - return total_dose - - -def test_dose_calculation(): - """Test the dose calculation method directly.""" - - # Test cases - test_cases = [ - # (input, expected_output, description) - ("2025-07-28 18:59:45:150mg", 150.0, "Single dose with timestamp"), - ("2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg", 225.0, "Multiple doses"), - ("โ€ข โ€ข โ€ข โ€ข 2025-07-30 07:50:00:300", 300.0, "Dose with bullet symbols"), - ("2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg", 20.0, "Decimal doses"), - ("100mg|50mg", 150.0, "Doses without timestamps"), - ("โ€ข 2025-07-30 22:50:00:10|75mg", 85.0, "Mixed format"), - ("", 0.0, "Empty string"), - ("nan", 0.0, "NaN value"), - ("2025-07-28 18:59:45:10|2025-07-28 19:34:19:5", 15.0, "No units"), - ] - - print("Testing dose calculation...") - all_passed = True - - for input_str, expected, description in test_cases: - result = calculate_daily_dose(input_str) - passed = abs(result - expected) < 0.001 # Allow for floating point precision - - status = "PASS" if passed else "FAIL" - print(f"{status}: {description}") - print(f" Input: '{input_str}'") - print(f" Expected: {expected}, Got: {result}") - print() - - if not passed: - all_passed = False - - if all_passed: - print("All dose calculation tests PASSED!") - else: - print("Some dose calculation tests FAILED!") - - return all_passed - - -if __name__ == "__main__": - success = test_dose_calculation() - sys.exit(0 if success else 1) diff --git a/scripts/test_dose_verification.py b/scripts/test_dose_verification.py deleted file mode 100644 index 3221d24..0000000 --- a/scripts/test_dose_verification.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to verify dose saving functionality by examining CSV data. -""" - -import os -import sys - -import pandas as pd - - -def verify_dose_saving(): - """Verify that multiple doses are being saved correctly.""" - - # Read the CSV data - try: - df = pd.read_csv("thechart_data.csv") - print("๐Ÿ“Š Examining CSV data for dose entries...") - print(f" Total entries: {len(df)}") - - # Check for dose columns - dose_columns = [col for col in df.columns if col.endswith("_doses")] - print(f" Dose columns found: {dose_columns}") - - # Look for entries with multiple doses - entries_with_doses = 0 - entries_with_multiple_doses = 0 - - for _, row in df.iterrows(): - row_has_doses = False - row_has_multiple = False - - for dose_col in dose_columns: - dose_data = str(row[dose_col]) - if dose_data and dose_data != "nan" and dose_data.strip(): - row_has_doses = True - # Count doses (separated by |) - dose_count = len(dose_data.split("|")) - medicine_name = dose_col.replace("_doses", "") - - print(f" {row['date']} - {medicine_name}: {dose_count} dose(s)") - if dose_count > 1: - row_has_multiple = True - print(f" โ†’ Multiple doses: {dose_data}") - - if row_has_doses: - entries_with_doses += 1 - if row_has_multiple: - entries_with_multiple_doses += 1 - - print("\n๐Ÿ“ˆ Summary:") - print(f" Entries with doses: {entries_with_doses}") - print(f" Entries with multiple doses: {entries_with_multiple_doses}") - - if entries_with_multiple_doses > 0: - print("โœ… Multiple dose saving IS working!") - return True - else: - print("โš ๏ธ No multiple dose entries found") - return False - - except Exception as e: - print(f"โŒ Error reading CSV: {e}") - return False - - -def check_latest_entry(): - """Check the most recent entry for dose data.""" - try: - df = pd.read_csv("thechart_data.csv") - latest = df.iloc[-1] - - print(f"\n๐Ÿ” Latest entry ({latest['date']}):") - dose_columns = [col for col in df.columns if col.endswith("_doses")] - - for dose_col in dose_columns: - medicine = dose_col.replace("_doses", "") - dose_data = str(latest[dose_col]) - - if dose_data and dose_data != "nan" and dose_data.strip(): - dose_count = len(dose_data.split("|")) - print(f" {medicine}: {dose_count} dose(s) - {dose_data}") - else: - print(f" {medicine}: No doses") - - except Exception as e: - print(f"โŒ Error checking latest entry: {e}") - - -if __name__ == "__main__": - print("๐Ÿ”ฌ Dose Verification Test") - print("=" * 30) - - # Change to the directory containing the CSV - os.chdir("/home/will/Code/thechart") - - success = verify_dose_saving() - check_latest_entry() - - if success: - print("\nโœ… Multiple dose functionality is working correctly!") - else: - print("\nโŒ Multiple dose functionality needs investigation") - sys.exit(1) diff --git a/scripts/test_edit_window_functionality.py b/scripts/test_edit_window_functionality.py deleted file mode 100644 index 4e63ffd..0000000 --- a/scripts/test_edit_window_functionality.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/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 src.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_edit_window_punch_buttons.py b/scripts/test_edit_window_punch_buttons.py deleted file mode 100644 index a990e03..0000000 --- a/scripts/test_edit_window_punch_buttons.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the new punch button functionality in the edit window. -""" - -import os -import sys -import tkinter as tk - -# 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")) - -import logging - -from src.ui_manager import UIManager - - -def test_edit_window_punch_buttons(): - """Test the punch buttons in the edit window.""" - print("Testing punch buttons in edit window...") - - # Create a test Tkinter root - root = tk.Tk() - root.withdraw() # Hide the main window - - # Create a logger - logger = logging.getLogger("test_logger") - logger.setLevel(logging.DEBUG) - - # Create UIManager - ui_manager = UIManager(root, logger) - - # Sample dose data for testing - sample_dose_data = { - "bupropion": "2025-01-15 08:00:00:300mg|2025-01-15 20:00:00:150mg", - "hydroxyzine": "2025-01-15 22:00:00:25mg", - "gabapentin": "", - "propranolol": "2025-01-15 09:30:00:10mg", - } - - # Sample values for the edit window (14 fields for new CSV format) - sample_values = ( - "01/15/2025", # date - 5, # depression - 3, # anxiety - 7, # sleep - 6, # appetite - 1, # bupropion - sample_dose_data["bupropion"], # bupropion_doses - 1, # hydroxyzine - sample_dose_data["hydroxyzine"], # hydroxyzine_doses - 0, # gabapentin - sample_dose_data["gabapentin"], # gabapentin_doses - 1, # propranolol - sample_dose_data["propranolol"], # propranolol_doses - "Test entry for punch button functionality", # note - ) - - # Define dummy callbacks - def dummy_save(*args): - print("Save callback triggered with args:", args) - - def dummy_delete(*args): - print("Delete callback triggered") - - callbacks = { - "save": dummy_save, - "delete": dummy_delete, - } - - try: - # Create the edit window - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - - print("โœ“ Edit window created successfully") - print("โœ“ Edit window should now display:") - print(" - Medicine checkboxes") - print(" - Dose entry fields for each medicine") - print(" - 'Take [Medicine]' punch buttons") - print(" - Editable dose display areas") - print(" - Formatted existing doses (times in HH:MM format)") - - print("\n=== Testing Dose Display Formatting ===") - print("Bupropion should show: 08:00: 300mg, 20:00: 150mg") - print("Hydroxyzine should show: 22:00: 25mg") - print("Gabapentin should show: No doses recorded") - print("Propranolol should show: 09:30: 10mg") - - print("\n=== Punch Button Test Instructions ===") - print("1. Enter a dose amount in any medicine's entry field") - print("2. Click the corresponding 'Take [Medicine]' button") - print("3. The dose should be added to the dose display with current time") - print("4. The entry field should be cleared") - print("5. A success message should appear") - - print("\nโœ“ Edit window is ready for testing") - print("Close the edit window when done testing.") - - # Start the event loop for the edit window - edit_window.wait_window() - - print("โœ“ Edit window test completed") - return True - - except Exception as e: - print(f"โœ— Error creating edit window: {e}") - import traceback - - traceback.print_exc() - return False - - finally: - root.destroy() - - -if __name__ == "__main__": - print("Testing Edit Window Punch Button Functionality") - print("=" * 50) - - success = test_edit_window_punch_buttons() - - if success: - print("\nโœ“ All edit window punch button tests completed successfully!") - else: - print("\nโœ— Edit window punch button tests failed!") - sys.exit(1) diff --git a/scripts/test_final_verification.py b/scripts/test_final_verification.py deleted file mode 100644 index bcc206c..0000000 --- a/scripts/test_final_verification.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python3 -""" -Final verification test for the fixed multiple dose functionality. -""" - -import os -import sys -import tkinter as tk - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import logging - -from src.ui_manager import UIManager - - -def final_verification_test(): - """Final test to verify the multiple dose fix works correctly.""" - print("๐ŸŽฏ Final Multiple Dose Verification") - print("=" * 40) - - root = tk.Tk() - root.title("Final Verification") - root.geometry("800x600") - - logger = logging.getLogger("final_test") - ui_manager = UIManager(root, logger) - - sample_values = ( - "07/29/2025", - 5, - 3, - 7, - 6, - 1, - "", - 0, - "", - 0, - "", - 0, - "", - "Final verification test", - ) - - save_result = None - - def capture_save(*args): - nonlocal save_result - save_result = args[-1] if len(args) >= 12 else {} - - print("\nโœ… FINAL RESULTS:") - for med, doses in save_result.items(): - if doses: - count = len(doses.split("|")) if "|" in doses else 1 - print(f" {med}: {count} dose(s)") - if count > 1: - print(f" โ””โ”€ Multiple doses: {doses}") - else: - print(f" โ””โ”€ Single dose: {doses}") - else: - print(f" {med}: No doses") - - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = {"save": capture_save, "delete": lambda x: x.destroy()} - - try: - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - edit_window.lift() - edit_window.focus_force() - - print("\n๐Ÿ“‹ FINAL TEST INSTRUCTIONS:") - print("1. Choose any medicine (e.g., Bupropion)") - print("2. Enter a dose amount (e.g., '100mg')") - print("3. Click 'Take [Medicine]' button") - print("4. Enter another dose amount (e.g., '200mg')") - print("5. Click 'Take [Medicine]' button again") - print("6. Enter a third dose amount (e.g., '300mg')") - print("7. Click 'Take [Medicine]' button a third time") - print("8. Verify you see THREE doses in the text area") - print("9. Click 'Save' to see the final results") - print("\n๐ŸŽฏ The fix should now properly accumulate multiple doses!") - - edit_window.wait_window() - - if save_result: - # Check if any medicine has multiple doses - multiple_doses_found = False - for med, doses in save_result.items(): - if doses and "|" in doses: - count = len(doses.split("|")) - if count > 1: - multiple_doses_found = True - print(f"\n๐ŸŽ‰ SUCCESS: {med} has {count} doses saved!") - break - - if multiple_doses_found: - print("\nโœ… MULTIPLE DOSE FUNCTIONALITY IS WORKING CORRECTLY!") - return True - else: - print("\nโš ๏ธ Only single doses were tested") - return True # Still success if save worked - else: - print("\nโŒ Save was not called") - return False - - except Exception as e: - print(f"โŒ Error: {e}") - return False - finally: - root.destroy() - - -if __name__ == "__main__": - os.chdir("/home/will/Code/thechart") - success = final_verification_test() - - if success: - print("\n๐Ÿ† FINAL VERIFICATION PASSED!") - print("๐Ÿ“ Multiple dose punch button functionality has been fixed!") - else: - print("\nโŒ Final verification failed") diff --git a/scripts/test_medicine_manager.py b/scripts/test_medicine_manager.py new file mode 100644 index 0000000..995cf04 --- /dev/null +++ b/scripts/test_medicine_manager.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +Simple test to check if the medicine manager works correctly. +""" + +import os +import sys + +# Add src to path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) + +from init import logger +from medicine_manager import MedicineManager + + +def test_medicine_manager(): + """Test the medicine manager functionality.""" + print("Testing MedicineManager...") + + # Create medicine manager + medicine_manager = MedicineManager(logger=logger) + + # Test getting all medicines + medicines = medicine_manager.get_all_medicines() + print(f"Found {len(medicines)} medicines:") + for key, medicine in medicines.items(): + print(f" - {key}: {medicine.display_name} ({medicine.dosage_info})") + + # Test getting medicine keys + keys = medicine_manager.get_medicine_keys() + print(f"Medicine keys: {keys}") + + # Test getting quick doses + for key in keys: + doses = medicine_manager.get_quick_doses(key) + print(f"Quick doses for {key}: {doses}") + + print("Medicine manager test completed successfully!") + + +if __name__ == "__main__": + test_medicine_manager() diff --git a/scripts/test_medicine_system.py b/scripts/test_medicine_system.py new file mode 100644 index 0000000..b27240e --- /dev/null +++ b/scripts/test_medicine_system.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +""" +Test script to validate the modular medicine system. +""" + +import os +import sys + +# Add src to path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) + +from init import logger +from medicine_manager import Medicine, MedicineManager + + +def test_medicine_system(): + """Test the complete medicine system.""" + print("๐Ÿงช Testing Medicine System...") + + # Test 1: Initialize medicine manager + print("\n1. Testing MedicineManager initialization...") + medicine_manager = MedicineManager(logger=logger) + medicines = medicine_manager.get_all_medicines() + print(f" โœ… Loaded {len(medicines)} medicines") + + # Test 2: List current medicines + print("\n2. Current medicines:") + for key, medicine in medicines.items(): + status = "๐ŸŸข" if medicine.default_enabled else "โšซ" + print(f" {status} {key}: {medicine.display_name} ({medicine.dosage_info})") + print(f" Quick doses: {medicine.quick_doses}") + print(f" Color: {medicine.color}") + + # Test 3: Add a new medicine + print("\n3. Testing adding new medicine...") + new_medicine = Medicine( + key="sertraline", + display_name="Sertraline", + dosage_info="50mg", + quick_doses=["25", "50", "100"], + color="#9B59B6", + default_enabled=False, + ) + + if medicine_manager.add_medicine(new_medicine): + print(" โœ… Successfully added Sertraline") + updated_medicines = medicine_manager.get_all_medicines() + print(f" Now have {len(updated_medicines)} medicines") + else: + print(" โŒ Failed to add Sertraline") + + # Test 4: Test CSV headers + print("\n4. Testing CSV headers generation...") + from data_manager import DataManager + + DataManager("test_medicines.csv", logger, medicine_manager) + + if os.path.exists("test_medicines.csv"): + with open("test_medicines.csv") as f: + headers = f.readline().strip() + print(f" CSV headers: {headers}") + os.remove("test_medicines.csv") # Clean up + print(" โœ… CSV headers generated successfully") + + # Test 5: Test graph colors + print("\n5. Testing graph colors...") + colors = medicine_manager.get_graph_colors() + for key, color in colors.items(): + print(f" {key}: {color}") + print(" โœ… Graph colors retrieved successfully") + + # Clean up - remove test medicine + print("\n6. Cleaning up...") + if medicine_manager.remove_medicine("sertraline"): + print(" โœ… Test medicine removed successfully") + + print("\n๐ŸŽ‰ All tests passed! Medicine system is working correctly.") + return True + + +if __name__ == "__main__": + try: + test_medicine_system() + except Exception as e: + print(f"โŒ Test failed with error: {e}") + import traceback + + traceback.print_exc() diff --git a/scripts/test_multiple_dose_issue.py b/scripts/test_multiple_dose_issue.py deleted file mode 100644 index b3f0e32..0000000 --- a/scripts/test_multiple_dose_issue.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to isolate and verify the multiple dose saving issue. -""" - -import os -import sys -import tkinter as tk - -# Add the src directory to the path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import logging - -from src.ui_manager import UIManager - - -def test_parse_dose_text(): - """Test the _parse_dose_text function directly.""" - print("๐Ÿงช Testing _parse_dose_text function...") - - # Create a minimal UIManager for testing - root = tk.Tk() - root.withdraw() - logger = logging.getLogger("test") - ui_manager = UIManager(root, logger) - - # Test data: multiple doses in the format shown in the text widget - test_text = """21:30: 150mg -21:35: 300mg -21:40: 75mg""" - - test_date = "07/29/2025" - - result = ui_manager._parse_dose_text(test_text, test_date) - print(f"Input text:\n{test_text}") - print(f"Date: {test_date}") - print(f"Parsed result: {result}") - - # Count how many doses were parsed - if result: - dose_count = len(result.split("|")) - print(f"Number of doses parsed: {dose_count}") - - if dose_count == 3: - print("โœ… _parse_dose_text is working correctly!") - return True - else: - print("โŒ _parse_dose_text is not parsing all doses!") - return False - else: - print("โŒ _parse_dose_text returned empty result!") - return False - - root.destroy() - - -def test_punch_button_accumulation(): - """Test that punch buttons properly accumulate in the text widget.""" - print("\n๐Ÿงช Testing punch button dose accumulation...") - - root = tk.Tk() - root.title("Punch Button Test") - root.geometry("400x300") - - logger = logging.getLogger("test") - ui_manager = UIManager(root, logger) - - # Sample values for creating edit window - sample_values = ( - "07/29/2025", # date - 5, - 3, - 7, - 6, # symptoms - 1, - "", # bupropion, bupropion_doses - 0, - "", # hydroxyzine, hydroxyzine_doses - 0, - "", # gabapentin, gabapentin_doses - 0, - "", # propranolol, propranolol_doses - "Test entry", # note - ) - - save_called = False - saved_dose_data = None - - def test_save(*args): - nonlocal save_called, saved_dose_data - save_called = True - saved_dose_data = args[-1] if args else None - - print("\n๐Ÿ’พ Save callback triggered") - if saved_dose_data: - print("Dose data received:") - for med, doses in saved_dose_data.items(): - if doses: - dose_count = len(doses.split("|")) if "|" in doses else 1 - print(f" {med}: {dose_count} dose(s) - {doses}") - else: - print(f" {med}: No doses") - - # Close window - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = {"save": test_save, "delete": lambda x: x.destroy()} - - try: - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - edit_window.lift() - edit_window.focus_force() - - print("\n๐Ÿ“ TEST INSTRUCTIONS:") - print("1. Select ANY medicine (e.g., Bupropion)") - print("2. Enter '100mg' in the dose field") - print("3. Click 'Take [Medicine]' button") - print("4. Enter '200mg' in the dose field") - print("5. Click 'Take [Medicine]' button again") - print("6. Enter '300mg' in the dose field") - print("7. Click 'Take [Medicine]' button a third time") - print("8. Verify you see THREE entries in the text area") - print("9. Click 'Save'") - print("\nโณ Please perform the test...") - - edit_window.wait_window() - - if save_called and saved_dose_data: - # Check if any medicine has multiple doses - multiple_found = False - for med, doses in saved_dose_data.items(): - if doses and "|" in doses: - dose_count = len(doses.split("|")) - if dose_count > 1: - print(f"โœ… Multiple doses found for {med}: {dose_count} doses") - multiple_found = True - - if multiple_found: - print("โœ… Multiple dose accumulation is working!") - return True - else: - print("โŒ No multiple doses found in save data") - return False - else: - print("โŒ Save was not called or no dose data received") - return False - - except Exception as e: - print(f"โŒ Error during test: {e}") - import traceback - - traceback.print_exc() - return False - finally: - root.destroy() - - -def main(): - print("๐Ÿ”ฌ Multiple Dose Issue Investigation") - print("=" * 50) - - os.chdir("/home/will/Code/thechart") - - # Test 1: Parse function - parse_test = test_parse_dose_text() - - # Test 2: UI workflow - ui_test = test_punch_button_accumulation() - - print("\n๐Ÿ“Š Results:") - print(f" Parse function test: {'โœ… PASS' if parse_test else 'โŒ FAIL'}") - print(f" UI workflow test: {'โœ… PASS' if ui_test else 'โŒ FAIL'}") - - if parse_test and ui_test: - print("\n๐ŸŽฏ Multiple dose functionality appears to be working correctly") - print("If you're still experiencing issues, please describe the exact steps") - else: - print("\n๐Ÿšจ Issues found with multiple dose functionality") - - -if __name__ == "__main__": - main() diff --git a/scripts/test_multiple_punch_save.py b/scripts/test_multiple_punch_save.py deleted file mode 100644 index b101c69..0000000 --- a/scripts/test_multiple_punch_save.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify multiple dose punching and saving behavior. -""" - -import os -import sys -import tkinter as tk - -# 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")) - -import logging - -from src.ui_manager import UIManager - - -def test_multiple_punch_and_save(): - """Test multiple dose punching followed by save.""" - print("Testing multiple dose punching and save functionality...") - - # Create a test Tkinter root - root = tk.Tk() - root.title("Test Root Window") - root.geometry("200x100") # Small root window - - # Create a logger - logger = logging.getLogger("test_logger") - logger.setLevel(logging.DEBUG) - - # Create UIManager - ui_manager = UIManager(root, logger) - - # Sample dose data for testing - sample_dose_data = { - "bupropion": "2025-01-15 08:00:00:300mg", - "hydroxyzine": "", - "gabapentin": "", - "propranolol": "", - } - - # Sample values for the edit window (14 fields for new CSV format) - sample_values = ( - "01/15/2025", # date - 5, # depression - 3, # anxiety - 7, # sleep - 6, # appetite - 1, # bupropion - sample_dose_data["bupropion"], # bupropion_doses - 0, # hydroxyzine - sample_dose_data["hydroxyzine"], # hydroxyzine_doses - 0, # gabapentin - sample_dose_data["gabapentin"], # gabapentin_doses - 0, # propranolol - sample_dose_data["propranolol"], # propranolol_doses - "Test entry for multiple punch testing", # note - ) - - # Track save calls - save_calls = [] - - # Define test callbacks - def test_save(*args): - save_calls.append(args) - print(f"โœ“ Save called with {len(args)} arguments") - - # Print dose data specifically - if len(args) >= 12: # Should have dose_data as last argument - dose_data = args[-1] # Last argument should be dose_data - print(" Dose data received:") - for med, doses in dose_data.items(): - print(f" {med}: {doses}") - - # Close window after save - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - def test_delete(*args): - print("Delete callback triggered") - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = { - "save": test_save, - "delete": test_delete, - } - - try: - # Create the edit window - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - edit_window.geometry("600x400") # Set a reasonable size - edit_window.lift() # Bring to front - edit_window.focus_force() # Force focus - - print("โœ“ Edit window created") - print("โœ“ Now simulating multiple dose punches...") - - # Let's simulate the manual process - - print("\n=== Manual Test Instructions ===") - print("1. In the Bupropion field, enter '150mg' and click 'Take Bupropion'") - print("2. Enter '300mg' and click 'Take Bupropion' again") - print("3. You should see both doses in the text area") - print("4. Click 'Save' to persist the changes") - print("5. Check if both doses are saved to the CSV") - print("\nWindow will stay open for manual testing...") - - # Wait for user to manually test - edit_window.wait_window() - - # Check if save was called - if save_calls: - print("โœ“ Save was called successfully") - return True - else: - print("โœ— Save was not called") - return False - - except Exception as e: - print(f"โœ— Error during test: {e}") - import traceback - - traceback.print_exc() - return False - - finally: - root.destroy() - - -if __name__ == "__main__": - print("Testing Multiple Dose Punching and Save") - print("=" * 40) - - success = test_multiple_punch_and_save() - - if success: - print("\nโœ… Multiple punch and save test completed!") - else: - print("\nโŒ Multiple punch and save test failed!") - sys.exit(1) diff --git a/scripts/test_programmatic_punch.py b/scripts/test_programmatic_punch.py deleted file mode 100644 index fa24328..0000000 --- a/scripts/test_programmatic_punch.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -""" -Test that programmatically clicks punch buttons to verify functionality. -""" - -import os -import sys -import tkinter as tk -from datetime import datetime - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import logging - -from src.ui_manager import UIManager - - -def test_programmatic_punch(): - """Test punch buttons programmatically.""" - print("๐Ÿค– Programmatic Punch Button Test") - print("=" * 40) - - root = tk.Tk() - root.title("Auto Punch Test") - root.geometry("800x600") - - logger = logging.getLogger("auto_punch") - ui_manager = UIManager(root, logger) - - sample_values = ( - "07/29/2025", - 5, - 3, - 7, - 6, - 1, - "", - 0, - "", - 0, - "", - 0, - "", - "Auto punch test", - ) - - save_called = False - saved_doses = None - - def capture_save(*args): - nonlocal save_called, saved_doses - save_called = True - if len(args) >= 12: - saved_doses = args[-1] - - print("๐Ÿ’พ Save captured doses:") - for med, doses in saved_doses.items(): - if doses: - count = len(doses.split("|")) if "|" in doses else 1 - print(f" {med}: {count} dose(s) - {doses}") - else: - print(f" {med}: No doses") - - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = {"save": capture_save, "delete": lambda x: x.destroy()} - - try: - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - - # Find the dose variables that were created - # We need to access them through the ui_manager somehow - print("๐Ÿ” Attempting to find dose widgets...") - - # Let's manually trigger the punch button functionality - # by calling the _punch_dose_in_edit method directly - - # Find the text widgets in the edit window - def find_widgets(widget, widget_list=None): - if widget_list is None: - widget_list = [] - - widget_list.append(widget) - for child in widget.winfo_children(): - find_widgets(child, widget_list) - - return widget_list - - all_widgets = find_widgets(edit_window) - - # Find Text widgets and Entry widgets - text_widgets = [w for w in all_widgets if isinstance(w, tk.Text)] - entry_widgets = [w for w in all_widgets if isinstance(w, tk.Entry)] - - print( - f"Found {len(text_widgets)} Text widgets and " - f"{len(entry_widgets)} Entry widgets" - ) - - if len(text_widgets) >= 4: # Should have 4 dose text widgets - # Let's manually add doses to the first text widget (bupropion) - bupropion_text = text_widgets[0] - - print("๐Ÿ“ Manually adding doses to bupropion text widget...") - - # Clear and add multiple doses - bupropion_text.delete(1.0, tk.END) - now = datetime.now() - time1 = now.strftime("%H:%M") - time2 = (now.replace(minute=now.minute + 1)).strftime("%H:%M") - time3 = (now.replace(minute=now.minute + 2)).strftime("%H:%M") - - dose_content = f"{time1}: 100mg\n{time2}: 200mg\n{time3}: 300mg" - bupropion_text.insert(1.0, dose_content) - - print(f"Added content: {dose_content}") - - # Verify content was added - actual_content = bupropion_text.get(1.0, tk.END).strip() - print(f"Actual content in widget: '{actual_content}'") - - # Now trigger save - print("๐Ÿ”„ Triggering save...") - - # We need to find the save button - buttons = [w for w in all_widgets if isinstance(w, tk.ttk.Button)] - save_button = None - - for button in buttons: - try: - if "Save" in button.cget("text"): - save_button = button - break - except Exception: - pass - - if save_button: - print("๐Ÿ’พ Found Save button, clicking it...") - save_button.invoke() - else: - print("โŒ Could not find Save button") - edit_window.destroy() - else: - print("โŒ Could not find expected Text widgets") - edit_window.destroy() - - # Wait for save to complete - root.update() - - if save_called: - return True - else: - print("โŒ Save was not called") - return False - - except Exception as e: - print(f"โŒ Error: {e}") - import traceback - - traceback.print_exc() - return False - finally: - root.destroy() - - -if __name__ == "__main__": - os.chdir("/home/will/Code/thechart") - success = test_programmatic_punch() - - if success: - print("\nโœ… Programmatic test completed successfully!") - else: - print("\nโŒ Programmatic test failed!") diff --git a/scripts/test_punch_diagnosis.py b/scripts/test_punch_diagnosis.py deleted file mode 100644 index ab8ccaa..0000000 --- a/scripts/test_punch_diagnosis.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive test to diagnose and fix punch button accumulation issue. -""" - -import os -import sys -import tkinter as tk - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import logging - -from src.ui_manager import UIManager - - -def test_punch_button_step_by_step(): - """Test punch button functionality step by step with detailed logging.""" - print("๐Ÿ”ฌ Punch Button Step-by-Step Diagnosis") - print("=" * 50) - - root = tk.Tk() - root.title("Punch Button Diagnosis") - root.geometry("800x600") - - logger = logging.getLogger("punch_diagnosis") - logger.setLevel(logging.DEBUG) - - ui_manager = UIManager(root, logger) - - sample_values = ( - "07/29/2025", - 5, - 3, - 7, - 6, - 1, - "", - 0, - "", - 0, - "", - 0, - "", - "Punch diagnosis test", - ) - - punch_calls = [] - save_calls = [] - - def track_save(*args): - save_calls.append(args) - if len(args) >= 12: - dose_data = args[-1] - print("\n๐Ÿ’พ SAVE CAPTURED:") - for med, doses in dose_data.items(): - if doses: - count = len(doses.split("|")) if "|" in doses else 1 - print(f" {med}: {count} dose(s) - {doses}") - else: - print(f" {med}: No doses") - - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = {"save": track_save, "delete": lambda x: x.destroy()} - - try: - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - - # Let's manually patch the _punch_dose_in_edit method to add logging - original_punch = ui_manager._punch_dose_in_edit - - def logged_punch(medicine_name, dose_vars): - print(f"\n๐ŸฅŠ PUNCH CALLED: {medicine_name}") - - dose_entry_var = dose_vars.get(f"{medicine_name}_entry_var") - dose_text_widget = dose_vars.get(f"{medicine_name}_doses_text") - - if not dose_entry_var or not dose_text_widget: - print(f"โŒ Missing variables for {medicine_name}") - return - - dose = dose_entry_var.get().strip() - print(f"๐Ÿ“ Dose entered: '{dose}'") - - if not dose: - print("โŒ No dose entered") - return - - # Get current content BEFORE modification - before_content = dose_text_widget.get(1.0, tk.END).strip() - print(f"๐Ÿ“‹ Content BEFORE: '{before_content}'") - - # Call original method - result = original_punch(medicine_name, dose_vars) - - # Get content AFTER modification - after_content = dose_text_widget.get(1.0, tk.END).strip() - print(f"๐Ÿ“‹ Content AFTER: '{after_content}'") - - punch_calls.append( - { - "medicine": medicine_name, - "dose": dose, - "before": before_content, - "after": after_content, - } - ) - - return result - - # Patch the method - ui_manager._punch_dose_in_edit = logged_punch - - print("\n๐Ÿ“ TEST INSTRUCTIONS:") - print("1. Enter '100mg' in Bupropion dose field") - print("2. Click 'Take Bupropion' - watch for PUNCH CALLED message") - print("3. Enter '200mg' in Bupropion dose field") - print("4. Click 'Take Bupropion' again - watch content changes") - print("5. Enter '300mg' in Bupropion dose field") - print("6. Click 'Take Bupropion' a third time") - print("7. Verify the text area shows all three doses") - print("8. Click Save") - print("\nโณ Please perform the test sequence...") - - edit_window.wait_window() - - print("\n๐Ÿ“Š ANALYSIS:") - print(f" Punch calls made: {len(punch_calls)}") - print(f" Save calls made: {len(save_calls)}") - - if punch_calls: - print("\n๐ŸฅŠ PUNCH CALL DETAILS:") - for i, call in enumerate(punch_calls, 1): - print(f" Call {i}: {call['medicine']} - {call['dose']}") - print(f" Before: '{call['before']}'") - print(f" After: '{call['after']}'") - print() - - # Check if multiple punches accumulated properly - if len(punch_calls) >= 2: - last_call = punch_calls[-1] - lines_in_final = ( - last_call["after"].count("\n") + 1 if last_call["after"] else 0 - ) - - print("๐Ÿ” ACCUMULATION CHECK:") - print(f" Final content has {lines_in_final} lines") - print(f" Expected: {len(punch_calls)} lines") - - if lines_in_final >= len(punch_calls): - print("โœ… Punch button accumulation appears to be working!") - return True - else: - print("โŒ Punch button accumulation is NOT working correctly!") - return False - else: - print("โš ๏ธ Not enough punch calls to test accumulation") - return False - - except Exception as e: - print(f"โŒ Error during test: {e}") - import traceback - - traceback.print_exc() - return False - finally: - root.destroy() - - -if __name__ == "__main__": - os.chdir("/home/will/Code/thechart") - success = test_punch_button_step_by_step() - - if success: - print("\n๐ŸŽฏ Punch button test completed - accumulation working!") - else: - print("\n๐Ÿšจ Punch button test revealed accumulation issues!") diff --git a/scripts/test_punch_only.py b/scripts/test_punch_only.py deleted file mode 100644 index 09a7038..0000000 --- a/scripts/test_punch_only.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test to just verify punch button functionality works in isolation. -""" - -import os -import sys -import tkinter as tk - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -import logging - -from src.ui_manager import UIManager - - -def test_punch_button_only(): - """Test just the punch button functionality.""" - print("๐ŸŽฏ Testing Punch Button Functionality Only") - print("=" * 45) - - root = tk.Tk() - root.title("Punch Button Test") - root.geometry("800x600") - - logger = logging.getLogger("punch_test") - ui_manager = UIManager(root, logger) - - # Simple test values - sample_values = ( - "07/29/2025", - 5, - 3, - 7, - 6, - 1, - "", - 0, - "", - 0, - "", - 0, - "", - "Punch button test", - ) - - def simple_save(*args): - print("Save button clicked - closing window") - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = {"save": simple_save, "delete": lambda x: x.destroy()} - - try: - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - edit_window.lift() - edit_window.focus_force() - - print("\n๐Ÿ”จ SIMPLE TEST:") - print("1. Enter '100mg' in the Bupropion dose field") - print("2. Click 'Take Bupropion' button") - print("3. Look for DEBUG PUNCH messages in the console") - print("4. Check if the dose appears in the text area") - print("5. Click Save when done") - print("\nโณ Performing test...") - - edit_window.wait_window() - print("โœ… Test completed") - - except Exception as e: - print(f"โŒ Error: {e}") - import traceback - - traceback.print_exc() - finally: - root.destroy() - - -if __name__ == "__main__": - os.chdir("/home/will/Code/thechart") - test_punch_button_only() diff --git a/scripts/test_save_functionality.py b/scripts/test_save_functionality.py deleted file mode 100644 index a6a8b5a..0000000 --- a/scripts/test_save_functionality.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -""" -Quick test to verify the save functionality works correctly. -""" - -import os -import sys -import tkinter as tk - -# 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")) - -import logging - -from src.ui_manager import UIManager - - -def test_save_functionality(): - """Test that the save button works without errors.""" - print("Testing save functionality in edit window...") - - # Create a test Tkinter root - root = tk.Tk() - root.withdraw() # Hide the main window - - # Create a logger - logger = logging.getLogger("test_logger") - logger.setLevel(logging.DEBUG) - - # Create UIManager - ui_manager = UIManager(root, logger) - - # Sample dose data for testing - sample_dose_data = { - "bupropion": "2025-01-15 08:00:00:300mg|2025-01-15 20:00:00:150mg", - "hydroxyzine": "2025-01-15 22:00:00:25mg", - "gabapentin": "", - "propranolol": "2025-01-15 09:30:00:10mg", - } - - # Sample values for the edit window (14 fields for new CSV format) - sample_values = ( - "01/15/2025", # date - 5, # depression - 3, # anxiety - 7, # sleep - 6, # appetite - 1, # bupropion - sample_dose_data["bupropion"], # bupropion_doses - 1, # hydroxyzine - sample_dose_data["hydroxyzine"], # hydroxyzine_doses - 0, # gabapentin - sample_dose_data["gabapentin"], # gabapentin_doses - 1, # propranolol - sample_dose_data["propranolol"], # propranolol_doses - "Test entry for save functionality", # note - ) - - # Track if save was called successfully - save_called = False - save_args = None - - # Define test callbacks - def test_save(*args): - nonlocal save_called, save_args - save_called = True - save_args = args - print("โœ“ Save callback executed successfully") - print(f" Arguments received: {len(args)} args") - # Close the edit window after save - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - def test_delete(*args): - print("Delete callback triggered") - if args and hasattr(args[0], "destroy"): - args[0].destroy() - - callbacks = { - "save": test_save, - "delete": test_delete, - } - - try: - # Create the edit window - edit_window = ui_manager.create_edit_window(sample_values, callbacks) - - print("โœ“ Edit window created successfully") - print("โœ“ Testing automatic save...") - - # Simulate clicking save button by calling the save function directly - # First, we need to get the vars_dict from the window - # We'll trigger a save by simulating the button press - - # Find the save button and trigger it - def find_save_button(widget): - """Recursively find the save button.""" - if isinstance(widget, tk.Button) and widget.cget("text") == "Save": - return widget - for child in widget.winfo_children(): - result = find_save_button(child) - if result: - return result - return None - - # Wait a moment for the window to fully initialize - edit_window.update_idletasks() - - # Find and click the save button - save_button = find_save_button(edit_window) - if save_button: - print("โœ“ Found save button, triggering click...") - save_button.invoke() - else: - print("โœ— Could not find save button") - edit_window.destroy() - return False - - # Check if save was called - if save_called: - print("โœ“ Save functionality test PASSED") - print( - f"โœ“ Save was called with {len(save_args) if save_args else 0} arguments" - ) - return True - else: - print("โœ— Save functionality test FAILED - save was not called") - return False - - except Exception as e: - print(f"โœ— Error during save test: {e}") - import traceback - - traceback.print_exc() - return False - - finally: - root.destroy() - - -if __name__ == "__main__": - print("Testing Save Functionality") - print("=" * 30) - - success = test_save_functionality() - - if success: - print("\nโœ… Save functionality test completed successfully!") - else: - print("\nโŒ Save functionality test failed!") - sys.exit(1) diff --git a/scripts/test_scrolling.py b/scripts/test_scrolling.py deleted file mode 100644 index 2cf9486..0000000 --- a/scripts/test_scrolling.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify mouse wheel scrolling works in both the new entry window -and edit window of TheChart application. -""" - -import logging -import tkinter as tk - -from src.ui_manager import UIManager - - -def test_scrolling(): - """Test both new entry and edit window scrolling.""" - print("Testing mouse wheel scrolling functionality...") - - # Create test root window - root = tk.Tk() - root.title("Scrolling Test") - root.geometry("800x600") - - # Create logger - logger = logging.getLogger("test") - logger.setLevel(logging.DEBUG) - - # Create UI manager - ui_manager = UIManager(root, logger) - - # Create main frame - main_frame = tk.Frame(root) - main_frame.pack(fill="both", expand=True) - main_frame.grid_rowconfigure(0, weight=1) - main_frame.grid_rowconfigure(1, weight=1) - main_frame.grid_columnconfigure(0, weight=1) - main_frame.grid_columnconfigure(1, weight=1) - - # Test 1: Create input frame (new entry window) - print("โœ“ Creating new entry input frame with mouse wheel scrolling...") - ui_manager.create_input_frame(main_frame) - - # Test 2: Create edit window - def test_edit_window(): - print("โœ“ Creating edit window with mouse wheel scrolling...") - # Sample data for edit window - test_values = ( - "01/15/2025", # date - "3", # depression - "5", # anxiety - "7", # sleep - "4", # appetite - "1", # bupropion - "09:00: 150", # bup_doses - "0", # hydroxyzine - "", # hydro_doses - "1", # gabapentin - "20:00: 100", # gaba_doses - "0", # propranolol - "", # prop_doses - "0", # quetiapine - "", # quet_doses - "Test note", # note - ) - - callbacks = { - "save": lambda *args: print("Save callback called"), - "delete": lambda *args: print("Delete callback called"), - } - - edit_window = ui_manager.create_edit_window(test_values, callbacks) - return edit_window - - # Add test button - test_button = tk.Button( - main_frame, - text="Test Edit Window Scrolling", - command=test_edit_window, - font=("TkDefaultFont", 12), - bg="#4CAF50", - fg="white", - padx=20, - pady=10, - ) - test_button.grid(row=2, column=0, columnspan=2, pady=20) - - # Add instructions - instructions = tk.Label( - main_frame, - text="Instructions:\n\n" - "1. Use mouse wheel anywhere in the 'New Entry' section to test scrolling\n" - "2. Click 'Test Edit Window Scrolling' button\n" - "3. Use mouse wheel anywhere in the edit window to test scrolling\n" - "4. Both windows should scroll smoothly with mouse wheel\n\n" - "โœ“ Mouse wheel scrolling has been enhanced for both windows!", - font=("TkDefaultFont", 10), - justify="left", - bg="#E8F5E8", - padx=20, - pady=15, - ) - instructions.grid(row=3, column=0, columnspan=2, padx=20, pady=10, sticky="ew") - - print("โœ“ Test setup complete!") - print("\nMouse wheel scrolling features implemented:") - print(" โ€ข Recursive binding to all child widgets") - print(" โ€ข Platform-specific event handling (Windows/Linux)") - print(" โ€ข Focus management for consistent scrolling") - print(" โ€ข Works anywhere within the scrollable areas") - print("\nTest the scrolling by moving your mouse wheel over any part of the") - print("'New Entry' section or the edit window when opened.") - - root.mainloop() - - -if __name__ == "__main__": - test_scrolling() diff --git a/src/data_manager.py b/src/data_manager.py index c8a81ab..3892468 100644 --- a/src/data_manager.py +++ b/src/data_manager.py @@ -4,40 +4,37 @@ import os import pandas as pd +from medicine_manager import MedicineManager + class DataManager: """Handle all data operations for the application.""" - def __init__(self, filename: str, logger: logging.Logger) -> None: + def __init__( + self, filename: str, logger: logging.Logger, medicine_manager: MedicineManager + ) -> None: self.filename: str = filename self.logger: logging.Logger = logger + self.medicine_manager = medicine_manager self._initialize_csv_file() + def _get_csv_headers(self) -> list[str]: + """Get CSV headers based on current medicine configuration.""" + base_headers = ["date", "depression", "anxiety", "sleep", "appetite"] + + # Add medicine headers + medicine_headers = [] + for medicine_key in self.medicine_manager.get_medicine_keys(): + medicine_headers.extend([medicine_key, f"{medicine_key}_doses"]) + + return base_headers + medicine_headers + ["note"] + def _initialize_csv_file(self) -> None: """Create CSV file with headers if it doesn't exist.""" if not os.path.exists(self.filename): with open(self.filename, mode="w", newline="") as file: writer = csv.writer(file) - writer.writerow( - [ - "date", - "depression", - "anxiety", - "sleep", - "appetite", - "bupropion", - "bupropion_doses", - "hydroxyzine", - "hydroxyzine_doses", - "gabapentin", - "gabapentin_doses", - "propranolol", - "propranolol_doses", - "quetiapine", - "quetiapine_doses", - "note", - ] - ) + writer.writerow(self._get_csv_headers()) def load_data(self) -> pd.DataFrame: """Load data from CSV file.""" @@ -46,27 +43,22 @@ class DataManager: return pd.DataFrame() try: - df: pd.DataFrame = pd.read_csv( - self.filename, - dtype={ - "depression": int, - "anxiety": int, - "sleep": int, - "appetite": int, - "bupropion": int, - "bupropion_doses": str, - "hydroxyzine": int, - "hydroxyzine_doses": str, - "gabapentin": int, - "gabapentin_doses": str, - "propranolol": int, - "propranolol_doses": str, - "quetiapine": int, - "quetiapine_doses": str, - "note": str, - "date": str, - }, - ).fillna("") + # Build dtype dictionary dynamically + dtype_dict = { + "depression": int, + "anxiety": int, + "sleep": int, + "appetite": int, + "date": str, + "note": str, + } + + # Add medicine types + for medicine_key in self.medicine_manager.get_medicine_keys(): + dtype_dict[medicine_key] = int + dtype_dict[f"{medicine_key}_doses"] = str + + df: pd.DataFrame = pd.read_csv(self.filename, dtype=dtype_dict).fillna("") return df.sort_values(by="date").reset_index(drop=True) except pd.errors.EmptyDataError: self.logger.warning("CSV file is empty. No data to load.") @@ -207,18 +199,14 @@ class DataManager: "anxiety": 0, "sleep": 0, "appetite": 0, - "bupropion": 0, - "bupropion_doses": "", - "hydroxyzine": 0, - "hydroxyzine_doses": "", - "gabapentin": 0, - "gabapentin_doses": "", - "propranolol": 0, - "propranolol_doses": "", - "quetiapine": 0, - "quetiapine_doses": "", "note": "", } + + # Add medicine columns dynamically + for medicine_key in self.medicine_manager.get_medicine_keys(): + new_entry[medicine_key] = 0 + new_entry[f"{medicine_key}_doses"] = "" + df = pd.concat([df, pd.DataFrame([new_entry])], ignore_index=True) # Add dose to the appropriate medicine diff --git a/src/graph_manager.py b/src/graph_manager.py index 3eb0737..fce0439 100644 --- a/src/graph_manager.py +++ b/src/graph_manager.py @@ -7,30 +7,43 @@ import pandas as pd from matplotlib.axes import Axes from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from medicine_manager import MedicineManager + class GraphManager: """Handle all graph-related operations for the application.""" - def __init__(self, parent_frame: ttk.LabelFrame) -> None: + def __init__( + self, parent_frame: ttk.LabelFrame, medicine_manager: MedicineManager + ) -> None: self.parent_frame: ttk.LabelFrame = parent_frame + self.medicine_manager = medicine_manager # Configure graph frame to expand self.parent_frame.grid_rowconfigure(0, weight=1) self.parent_frame.grid_columnconfigure(0, weight=1) - # Initialize toggle variables for chart elements + self._initialize_toggle_vars() + self._setup_ui() + + def _initialize_toggle_vars(self) -> None: + """Initialize toggle variables for chart elements.""" + # Initialize symptom toggles (always shown by default) self.toggle_vars: dict[str, tk.BooleanVar] = { "depression": tk.BooleanVar(value=True), "anxiety": tk.BooleanVar(value=True), "sleep": tk.BooleanVar(value=True), "appetite": tk.BooleanVar(value=True), - "bupropion": tk.BooleanVar(value=False), - "hydroxyzine": tk.BooleanVar(value=False), - "gabapentin": tk.BooleanVar(value=False), - "propranolol": tk.BooleanVar(value=False), - "quetiapine": tk.BooleanVar(value=False), } + # Add medicine toggles dynamically + for medicine_key in self.medicine_manager.get_medicine_keys(): + medicine = self.medicine_manager.get_medicine(medicine_key) + default_value = medicine.default_enabled if medicine else False + self.toggle_vars[medicine_key] = tk.BooleanVar(value=default_value) + + def _setup_ui(self) -> None: + """Set up the UI components.""" # Create control frame for toggles self.control_frame: ttk.Frame = ttk.Frame(self.parent_frame) self.control_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) @@ -84,26 +97,20 @@ class GraphManager: ) checkbox.pack(side="left", padx=3) - # Medicines toggles + # Medicines toggles - dynamic based on medicine manager medicines_frame = ttk.LabelFrame(self.control_frame, text="Medicines") medicines_frame.pack(side="left", padx=5, pady=2) - medicine_configs = [ - ("bupropion", "Bupropion"), - ("hydroxyzine", "Hydroxyzine"), - ("gabapentin", "Gabapentin"), - ("propranolol", "Propranolol"), - ("quetiapine", "Quetiapine"), - ] - - for key, label in medicine_configs: - checkbox = ttk.Checkbutton( - medicines_frame, - text=label, - variable=self.toggle_vars[key], - command=self._handle_toggle_changed, - ) - checkbox.pack(side="left", padx=3) + for medicine_key in self.medicine_manager.get_medicine_keys(): + medicine = self.medicine_manager.get_medicine(medicine_key) + if medicine: + checkbox = ttk.Checkbutton( + medicines_frame, + text=medicine.display_name, + variable=self.toggle_vars[medicine_key], + command=self._handle_toggle_changed, + ) + checkbox.pack(side="left", padx=3) def _handle_toggle_changed(self) -> None: """Handle toggle changes by replotting the graph.""" @@ -147,13 +154,8 @@ class GraphManager: has_plotted_series = True # Plot medicine dose data - medicine_colors = { - "bupropion": "#FF6B6B", # Red - "hydroxyzine": "#4ECDC4", # Teal - "gabapentin": "#45B7D1", # Blue - "propranolol": "#96CEB4", # Green - "quetiapine": "#FFEAA7", # Yellow - } + # Get medicine colors from medicine manager + medicine_colors = self.medicine_manager.get_graph_colors() medicines = [ "bupropion", diff --git a/src/main.py b/src/main.py index c778447..4a5decb 100644 --- a/src/main.py +++ b/src/main.py @@ -2,7 +2,7 @@ import os import sys import tkinter as tk from collections.abc import Callable -from tkinter import messagebox +from tkinter import messagebox, ttk from typing import Any import pandas as pd @@ -11,6 +11,8 @@ from constants import LOG_LEVEL, LOG_PATH from data_manager import DataManager from graph_manager import GraphManager from init import logger +from medicine_management_window import MedicineManagementWindow +from medicine_manager import MedicineManager from ui_manager import UIManager @@ -42,8 +44,11 @@ class MedTrackerApp: logger.debug(f"First argument: {first_argument}") # Initialize managers - self.ui_manager: UIManager = UIManager(root, logger) - self.data_manager: DataManager = DataManager(self.filename, logger) + self.medicine_manager: MedicineManager = MedicineManager(logger=logger) + self.ui_manager: UIManager = UIManager(root, logger, self.medicine_manager) + self.data_manager: DataManager = DataManager( + self.filename, logger, self.medicine_manager + ) # Set up application icon icon_path: str = "chart-671.png" @@ -54,6 +59,9 @@ class MedTrackerApp: # Set up the main application UI self._setup_main_ui() + # Add menu bar + self._setup_menu() + def _setup_main_ui(self) -> None: """Set up the main UI components.""" import tkinter.ttk as ttk @@ -74,7 +82,9 @@ class MedTrackerApp: # --- Create Graph Frame --- graph_frame: ttk.Frame = self.ui_manager.create_graph_frame(main_frame) - self.graph_manager: GraphManager = GraphManager(graph_frame) + self.graph_manager: GraphManager = GraphManager( + graph_frame, self.medicine_manager + ) # --- Create Input Frame --- input_ui: dict[str, Any] = self.ui_manager.create_input_frame(main_frame) @@ -106,6 +116,59 @@ class MedTrackerApp: # Load data self.refresh_data_display() + def _setup_menu(self) -> None: + """Set up the menu bar.""" + menubar = tk.Menu(self.root) + self.root.config(menu=menubar) + + # Tools menu + tools_menu = tk.Menu(menubar, tearoff=0) + menubar.add_cascade(label="Tools", menu=tools_menu) + tools_menu.add_command( + label="Manage Medicines...", command=self._open_medicine_manager + ) + + def _open_medicine_manager(self) -> None: + """Open the medicine management window.""" + MedicineManagementWindow( + self.root, self.medicine_manager, self._refresh_ui_after_medicine_change + ) + + def _refresh_ui_after_medicine_change(self) -> None: + """Refresh UI components after medicine configuration changes.""" + # Recreate the input frame with new medicines + self.input_frame.destroy() + input_ui: dict[str, Any] = self.ui_manager.create_input_frame( + self.input_frame.master + ) + self.input_frame: ttk.Frame = input_ui["frame"] + self.medicine_vars: dict[str, tuple[tk.IntVar, str]] = input_ui["medicine_vars"] + + # Add buttons to input frame + self.ui_manager.add_action_buttons( + self.input_frame, + [ + { + "text": "Add Entry", + "command": self.add_new_entry, + "fill": "both", + "expand": True, + }, + {"text": "Quit", "command": self.handle_window_closing}, + ], + ) + + # Recreate the table with new columns + self.tree.destroy() + table_ui: dict[str, Any] = self.ui_manager.create_table_frame( + self.tree.master.master + ) + self.tree: ttk.Treeview = table_ui["tree"] + self.tree.bind("", self.handle_double_click) + + # Refresh data display + self.refresh_data_display() + def handle_double_click(self, event: tk.Event) -> None: """Handle double-click event to edit an entry.""" logger.debug("Double-click event triggered on treeview.") @@ -124,24 +187,24 @@ class MedTrackerApp: if not df.empty and original_date in df["date"].values: full_row = df[df["date"] == original_date].iloc[0] # Convert to tuple in the expected order for the edit window - full_values = ( + full_values = [ full_row["date"], full_row["depression"], full_row["anxiety"], full_row["sleep"], full_row["appetite"], - full_row["bupropion"], - full_row["bupropion_doses"], - full_row["hydroxyzine"], - full_row["hydroxyzine_doses"], - full_row["gabapentin"], - full_row["gabapentin_doses"], - full_row["propranolol"], - full_row["propranolol_doses"], - full_row["quetiapine"], - full_row["quetiapine_doses"], - full_row["note"], - ) + ] + + # Add medicine data dynamically + for medicine_key in self.medicine_manager.get_medicine_keys(): + if medicine_key in full_row: + full_values.append(full_row[medicine_key]) + full_values.append(full_row.get(f"{medicine_key}_doses", "")) + else: + full_values.extend([0, ""]) + + full_values.append(full_row["note"]) + full_values = tuple(full_values) else: # Fallback to the table values if full data not found full_values = values @@ -164,11 +227,7 @@ class MedTrackerApp: anx: int, slp: int, app: int, - bup: int, - hydro: int, - gaba: int, - prop: int, - quet: int, + medicine_values: dict[str, int], note: str, dose_data: dict[str, str], ) -> None: @@ -179,19 +238,15 @@ class MedTrackerApp: anx, slp, app, - bup, - dose_data.get("bupropion", ""), - hydro, - dose_data.get("hydroxyzine", ""), - gaba, - dose_data.get("gabapentin", ""), - prop, - dose_data.get("propranolol", ""), - quet, - dose_data.get("quetiapine", ""), - note, ] + # Add medicine data dynamically + for medicine_key in self.medicine_manager.get_medicine_keys(): + values.append(medicine_values.get(medicine_key, 0)) + values.append(dose_data.get(medicine_key, "")) + + values.append(note) + if self.data_manager.update_entry(original_date, values): edit_win.destroy() messagebox.showinfo( @@ -223,49 +278,35 @@ class MedTrackerApp: """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 = "" - quetiapine_doses = "" + dose_values = {} 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" - ) - quet_doses = self.data_manager.get_today_medicine_doses(today, "quetiapine") - - 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]) - quetiapine_doses = "|".join([f"{ts}:{dose}" for ts, dose in quet_doses]) + # Get doses for all medicines dynamically + for medicine_key in self.medicine_manager.get_medicine_keys(): + doses = self.data_manager.get_today_medicine_doses(today, medicine_key) + dose_values[f"{medicine_key}_doses"] = "|".join( + [f"{ts}:{dose}" for ts, dose in doses] + ) + else: + # Set empty doses for all medicines + for medicine_key in self.medicine_manager.get_medicine_keys(): + dose_values[f"{medicine_key}_doses"] = "" + # Build entry dynamically entry: list[str | int] = [ self.date_var.get(), self.symptom_vars["depression"].get(), self.symptom_vars["anxiety"].get(), 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.medicine_vars["quetiapine"][0].get(), - quetiapine_doses, - self.note_var.get(), ] + + # Add medicine data + for medicine_key in self.medicine_manager.get_medicine_keys(): + entry.append(self.medicine_vars[medicine_key][0].get()) + entry.append(dose_values[f"{medicine_key}_doses"]) + + entry.append(self.note_var.get()) logger.debug(f"Adding entry: {entry}") # Check if date is empty @@ -336,26 +377,20 @@ class MedTrackerApp: # Update the treeview with the data if not df.empty: - # Only show user-friendly columns in the table (not the dose columns) - display_columns = [ - "date", - "depression", - "anxiety", - "sleep", - "appetite", - "bupropion", - "hydroxyzine", - "gabapentin", - "propranolol", - "quetiapine", - "note", - ] + # Build display columns dynamically (exclude dose columns for table view) + display_columns = ["date", "depression", "anxiety", "sleep", "appetite"] + + # Add medicine columns (without dose columns) + for medicine_key in self.medicine_manager.get_medicine_keys(): + display_columns.append(medicine_key) + + display_columns.append("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 + # Fallback - just use all columns display_df = df for _index, row in display_df.iterrows(): diff --git a/src/medicine_management_window.py b/src/medicine_management_window.py new file mode 100644 index 0000000..054b830 --- /dev/null +++ b/src/medicine_management_window.py @@ -0,0 +1,401 @@ +""" +Medicine management window for adding, editing, and removing medicines. +""" + +import tkinter as tk +from tkinter import messagebox, ttk + +from medicine_manager import Medicine, MedicineManager + + +class MedicineManagementWindow: + """Window for managing medicine configurations.""" + + def __init__( + self, parent: tk.Tk, medicine_manager: MedicineManager, refresh_callback + ): + self.parent = parent + self.medicine_manager = medicine_manager + self.refresh_callback = refresh_callback + + # Create the window + self.window = tk.Toplevel(parent) + self.window.title("Manage Medicines") + self.window.geometry("600x500") + self.window.resizable(True, True) + + # Make window modal + self.window.transient(parent) + self.window.grab_set() + + self._setup_ui() + self._populate_medicine_list() + + # Center window + self.window.update_idletasks() + x = (self.window.winfo_screenwidth() // 2) - (600 // 2) + y = (self.window.winfo_screenheight() // 2) - (500 // 2) + self.window.geometry(f"600x500+{x}+{y}") + + def _setup_ui(self): + """Set up the user interface.""" + main_frame = ttk.Frame(self.window, padding="10") + main_frame.grid(row=0, column=0, sticky="nsew") + + self.window.grid_rowconfigure(0, weight=1) + self.window.grid_columnconfigure(0, weight=1) + main_frame.grid_rowconfigure(1, weight=1) + main_frame.grid_columnconfigure(0, weight=1) + + # Title + title_label = ttk.Label( + main_frame, text="Medicine Management", font=("Arial", 14, "bold") + ) + title_label.grid(row=0, column=0, columnspan=2, pady=(0, 10)) + + # Medicine list + list_frame = ttk.LabelFrame(main_frame, text="Current Medicines") + list_frame.grid(row=1, column=0, columnspan=2, sticky="nsew", pady=(0, 10)) + list_frame.grid_rowconfigure(0, weight=1) + list_frame.grid_columnconfigure(0, weight=1) + + # Treeview for medicines + columns = ("key", "name", "dosage", "quick_doses", "color", "default") + self.tree = ttk.Treeview(list_frame, columns=columns, show="headings") + + # Column headings + self.tree.heading("key", text="Key") + self.tree.heading("name", text="Name") + self.tree.heading("dosage", text="Dosage Info") + self.tree.heading("quick_doses", text="Quick Doses") + self.tree.heading("color", text="Color") + self.tree.heading("default", text="Default Enabled") + + # Column widths + self.tree.column("key", width=80) + self.tree.column("name", width=100) + self.tree.column("dosage", width=100) + self.tree.column("quick_doses", width=120) + self.tree.column("color", width=70) + self.tree.column("default", width=100) + + self.tree.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) + + # Scrollbar for treeview + scrollbar = ttk.Scrollbar( + list_frame, orient="vertical", command=self.tree.yview + ) + scrollbar.grid(row=0, column=1, sticky="ns") + self.tree.configure(yscrollcommand=scrollbar.set) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=2, column=0, columnspan=2, pady=(10, 0)) + + ttk.Button(button_frame, text="Add Medicine", command=self._add_medicine).grid( + row=0, column=0, padx=(0, 5) + ) + + ttk.Button( + button_frame, text="Edit Medicine", command=self._edit_medicine + ).grid(row=0, column=1, padx=5) + + ttk.Button( + button_frame, text="Remove Medicine", command=self._remove_medicine + ).grid(row=0, column=2, padx=5) + + ttk.Button(button_frame, text="Close", command=self._close_window).grid( + row=0, column=3, padx=(5, 0) + ) + + def _populate_medicine_list(self): + """Populate the medicine list.""" + # Clear existing items + for item in self.tree.get_children(): + self.tree.delete(item) + + # Add medicines + for medicine in self.medicine_manager.get_all_medicines().values(): + self.tree.insert( + "", + "end", + values=( + medicine.key, + medicine.display_name, + medicine.dosage_info, + ", ".join(medicine.quick_doses), + medicine.color, + "Yes" if medicine.default_enabled else "No", + ), + ) + + def _add_medicine(self): + """Add a new medicine.""" + MedicineEditDialog( + self.window, self.medicine_manager, None, self._on_medicine_changed + ) + + def _edit_medicine(self): + """Edit selected medicine.""" + selection = self.tree.selection() + if not selection: + messagebox.showwarning("No Selection", "Please select a medicine to edit.") + return + + item = self.tree.item(selection[0]) + medicine_key = item["values"][0] + medicine = self.medicine_manager.get_medicine(medicine_key) + + if medicine: + MedicineEditDialog( + self.window, self.medicine_manager, medicine, self._on_medicine_changed + ) + + def _remove_medicine(self): + """Remove selected medicine.""" + selection = self.tree.selection() + if not selection: + messagebox.showwarning( + "No Selection", "Please select a medicine to remove." + ) + return + + item = self.tree.item(selection[0]) + medicine_key = item["values"][0] + medicine_name = item["values"][1] + + if messagebox.askyesno( + "Confirm Removal", + f"Are you sure you want to remove '{medicine_name}'?\n\n" + "This will also remove all associated data from your records!", + ): + if self.medicine_manager.remove_medicine(medicine_key): + messagebox.showinfo( + "Success", f"'{medicine_name}' removed successfully!" + ) + self._populate_medicine_list() + self._refresh_main_app() + else: + messagebox.showerror("Error", f"Failed to remove '{medicine_name}'.") + + def _on_medicine_changed(self): + """Called when a medicine is added or edited.""" + self._populate_medicine_list() + self._refresh_main_app() + + def _refresh_main_app(self): + """Refresh the main application after medicine changes.""" + if self.refresh_callback: + self.refresh_callback() + + def _close_window(self): + """Close the window.""" + self.window.destroy() + + +class MedicineEditDialog: + """Dialog for adding/editing a medicine.""" + + def __init__( + self, + parent: tk.Toplevel, + medicine_manager: MedicineManager, + medicine: Medicine | None, + callback, + ): + self.parent = parent + self.medicine_manager = medicine_manager + self.medicine = medicine + self.callback = callback + self.is_edit = medicine is not None + + # Create dialog + self.dialog = tk.Toplevel(parent) + self.dialog.title("Edit Medicine" if self.is_edit else "Add Medicine") + self.dialog.geometry("400x350") + self.dialog.resizable(False, False) + + # Make modal + self.dialog.transient(parent) + self.dialog.grab_set() + + self._setup_dialog() + self._populate_fields() + + # Center dialog + self.dialog.update_idletasks() + x = parent.winfo_x() + (parent.winfo_width() // 2) - (400 // 2) + y = parent.winfo_y() + (parent.winfo_height() // 2) - (350 // 2) + self.dialog.geometry(f"400x350+{x}+{y}") + + def _setup_dialog(self): + """Set up the dialog UI.""" + main_frame = ttk.Frame(self.dialog, padding="15") + main_frame.grid(row=0, column=0, sticky="nsew") + + self.dialog.grid_rowconfigure(0, weight=1) + self.dialog.grid_columnconfigure(0, weight=1) + + # Fields + fields_frame = ttk.Frame(main_frame) + fields_frame.grid(row=0, column=0, sticky="ew", pady=(0, 15)) + fields_frame.grid_columnconfigure(1, weight=1) + + row = 0 + + # Key + ttk.Label(fields_frame, text="Key:").grid(row=row, column=0, sticky="w", pady=5) + self.key_var = tk.StringVar() + key_entry = ttk.Entry(fields_frame, textvariable=self.key_var) + key_entry.grid(row=row, column=1, sticky="ew", padx=(10, 0), pady=5) + if self.is_edit: + key_entry.configure(state="readonly") + row += 1 + + # Display Name + ttk.Label(fields_frame, text="Display Name:").grid( + row=row, column=0, sticky="w", pady=5 + ) + self.name_var = tk.StringVar() + ttk.Entry(fields_frame, textvariable=self.name_var).grid( + row=row, column=1, sticky="ew", padx=(10, 0), pady=5 + ) + row += 1 + + # Dosage Info + ttk.Label(fields_frame, text="Dosage Info:").grid( + row=row, column=0, sticky="w", pady=5 + ) + self.dosage_var = tk.StringVar() + ttk.Entry(fields_frame, textvariable=self.dosage_var).grid( + row=row, column=1, sticky="ew", padx=(10, 0), pady=5 + ) + row += 1 + + # Quick Doses + ttk.Label(fields_frame, text="Quick Doses:").grid( + row=row, column=0, sticky="w", pady=5 + ) + self.doses_var = tk.StringVar() + ttk.Entry(fields_frame, textvariable=self.doses_var).grid( + row=row, column=1, sticky="ew", padx=(10, 0), pady=5 + ) + ttk.Label( + fields_frame, text="(comma-separated, e.g. 25,50,100)", font=("Arial", 8) + ).grid(row=row + 1, column=1, sticky="w", padx=(10, 0)) + row += 2 + + # Color + ttk.Label(fields_frame, text="Graph Color:").grid( + row=row, column=0, sticky="w", pady=5 + ) + self.color_var = tk.StringVar() + ttk.Entry(fields_frame, textvariable=self.color_var).grid( + row=row, column=1, sticky="ew", padx=(10, 0), pady=5 + ) + ttk.Label( + fields_frame, text="(hex color, e.g. #FF6B6B)", font=("Arial", 8) + ).grid(row=row + 1, column=1, sticky="w", padx=(10, 0)) + row += 2 + + # Default Enabled + self.default_var = tk.BooleanVar() + ttk.Checkbutton( + fields_frame, + text="Show in graph by default", + variable=self.default_var, + ).grid(row=row, column=0, columnspan=2, sticky="w", pady=5) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=1, column=0) + + ttk.Button(button_frame, text="Save", command=self._save_medicine).grid( + row=0, column=0, padx=(0, 10) + ) + + ttk.Button(button_frame, text="Cancel", command=self.dialog.destroy).grid( + row=0, column=1 + ) + + def _populate_fields(self): + """Populate fields if editing.""" + if self.medicine: + self.key_var.set(self.medicine.key) + self.name_var.set(self.medicine.display_name) + self.dosage_var.set(self.medicine.dosage_info) + self.doses_var.set(",".join(self.medicine.quick_doses)) + self.color_var.set(self.medicine.color) + self.default_var.set(self.medicine.default_enabled) + + def _save_medicine(self): + """Save the medicine.""" + # Validate fields + key = self.key_var.get().strip() + name = self.name_var.get().strip() + dosage = self.dosage_var.get().strip() + doses_str = self.doses_var.get().strip() + color = self.color_var.get().strip() + + if not all([key, name, dosage, doses_str, color]): + messagebox.showerror("Error", "All fields are required.") + return + + # Validate key format (alphanumeric and underscores only) + if not key.replace("_", "").replace("-", "").isalnum(): + messagebox.showerror( + "Error", + "Key must contain only letters, numbers, underscores, and hyphens.", + ) + return + + # Parse quick doses + try: + quick_doses = [dose.strip() for dose in doses_str.split(",")] + quick_doses = [dose for dose in quick_doses if dose] # Remove empty strings + if not quick_doses: + raise ValueError("At least one quick dose is required.") + except Exception: + messagebox.showerror("Error", "Quick doses must be comma-separated values.") + return + + # Validate color format + if not color.startswith("#") or len(color) != 7: + messagebox.showerror( + "Error", "Color must be in hex format (e.g., #FF6B6B)." + ) + return + + try: + int(color[1:], 16) # Validate hex color + except ValueError: + messagebox.showerror("Error", "Invalid hex color format.") + return + + # Create medicine object + new_medicine = Medicine( + key=key, + display_name=name, + dosage_info=dosage, + quick_doses=quick_doses, + color=color, + default_enabled=self.default_var.get(), + ) + + # Save medicine + success = False + if self.is_edit: + success = self.medicine_manager.update_medicine( + self.medicine.key, new_medicine + ) + else: + success = self.medicine_manager.add_medicine(new_medicine) + + if success: + action = "updated" if self.is_edit else "added" + messagebox.showinfo("Success", f"Medicine {action} successfully!") + self.callback() + self.dialog.destroy() + else: + action = "update" if self.is_edit else "add" + messagebox.showerror("Error", f"Failed to {action} medicine.") diff --git a/src/medicine_manager.py b/src/medicine_manager.py new file mode 100644 index 0000000..e3dbb33 --- /dev/null +++ b/src/medicine_manager.py @@ -0,0 +1,195 @@ +""" +Medicine configuration manager for the MedTracker application. +Handles dynamic loading and saving of medicine configurations. +""" + +import json +import logging +import os +from dataclasses import asdict, dataclass +from typing import Any + + +@dataclass +class Medicine: + """Data class representing a medicine.""" + + key: str # Internal key (e.g., "bupropion") + display_name: str # Display name (e.g., "Bupropion") + dosage_info: str # Dosage information (e.g., "150/300 mg") + quick_doses: list[str] # Common dose amounts for quick selection + color: str # Color for graph display + default_enabled: bool = False # Whether to show in graph by default + + +class MedicineManager: + """Manages medicine configurations and provides access to medicine data.""" + + def __init__( + self, config_file: str = "medicines.json", logger: logging.Logger = None + ): + self.config_file = config_file + self.logger = logger or logging.getLogger(__name__) + self.medicines: dict[str, Medicine] = {} + self._load_medicines() + + def _get_default_medicines(self) -> list[Medicine]: + """Get the default medicine configuration.""" + return [ + Medicine( + key="bupropion", + display_name="Bupropion", + dosage_info="150/300 mg", + quick_doses=["150", "300"], + color="#FF6B6B", + default_enabled=True, + ), + Medicine( + key="hydroxyzine", + display_name="Hydroxyzine", + dosage_info="25 mg", + quick_doses=["25", "50"], + color="#4ECDC4", + default_enabled=False, + ), + Medicine( + key="gabapentin", + display_name="Gabapentin", + dosage_info="100 mg", + quick_doses=["100", "300", "600"], + color="#45B7D1", + default_enabled=False, + ), + Medicine( + key="propranolol", + display_name="Propranolol", + dosage_info="10 mg", + quick_doses=["10", "20", "40"], + color="#96CEB4", + default_enabled=True, + ), + Medicine( + key="quetiapine", + display_name="Quetiapine", + dosage_info="25 mg", + quick_doses=["25", "50", "100"], + color="#FFEAA7", + default_enabled=False, + ), + ] + + def _load_medicines(self) -> None: + """Load medicines from configuration file.""" + if os.path.exists(self.config_file): + try: + with open(self.config_file) as f: + data = json.load(f) + + self.medicines = {} + for medicine_data in data.get("medicines", []): + medicine = Medicine(**medicine_data) + self.medicines[medicine.key] = medicine + + self.logger.info( + f"Loaded {len(self.medicines)} medicines from {self.config_file}" + ) + except Exception as e: + self.logger.error(f"Error loading medicines config: {e}") + self._create_default_config() + else: + self._create_default_config() + + def _create_default_config(self) -> None: + """Create default medicine configuration.""" + default_medicines = self._get_default_medicines() + self.medicines = {med.key: med for med in default_medicines} + self.save_medicines() + self.logger.info("Created default medicine configuration") + + def save_medicines(self) -> bool: + """Save current medicines to configuration file.""" + try: + data = { + "medicines": [asdict(medicine) for medicine in self.medicines.values()] + } + + with open(self.config_file, "w") as f: + json.dump(data, f, indent=2) + + self.logger.info( + f"Saved {len(self.medicines)} medicines to {self.config_file}" + ) + return True + except Exception as e: + self.logger.error(f"Error saving medicines config: {e}") + return False + + def get_all_medicines(self) -> dict[str, Medicine]: + """Get all medicines.""" + return self.medicines.copy() + + def get_medicine(self, key: str) -> Medicine | None: + """Get a specific medicine by key.""" + return self.medicines.get(key) + + def add_medicine(self, medicine: Medicine) -> bool: + """Add a new medicine.""" + if medicine.key in self.medicines: + self.logger.warning(f"Medicine with key '{medicine.key}' already exists") + return False + + self.medicines[medicine.key] = medicine + return self.save_medicines() + + def update_medicine(self, key: str, medicine: Medicine) -> bool: + """Update an existing medicine.""" + if key not in self.medicines: + self.logger.warning(f"Medicine with key '{key}' does not exist") + return False + + # If key is changing, remove old entry + if key != medicine.key: + del self.medicines[key] + + self.medicines[medicine.key] = medicine + return self.save_medicines() + + def remove_medicine(self, key: str) -> bool: + """Remove a medicine.""" + if key not in self.medicines: + self.logger.warning(f"Medicine with key '{key}' does not exist") + return False + + del self.medicines[key] + return self.save_medicines() + + def get_medicine_keys(self) -> list[str]: + """Get list of all medicine keys.""" + return list(self.medicines.keys()) + + def get_display_names(self) -> dict[str, str]: + """Get mapping of keys to display names.""" + return {key: med.display_name for key, med in self.medicines.items()} + + def get_quick_doses(self, key: str) -> list[str]: + """Get quick dose options for a medicine.""" + medicine = self.medicines.get(key) + return medicine.quick_doses if medicine else ["25", "50"] + + def get_graph_colors(self) -> dict[str, str]: + """Get mapping of medicine keys to graph colors.""" + return {key: med.color for key, med in self.medicines.items()} + + def get_default_enabled_medicines(self) -> list[str]: + """Get list of medicines that should be enabled by default in graphs.""" + return [key for key, med in self.medicines.items() if med.default_enabled] + + def get_medicine_vars_dict(self) -> dict[str, tuple[Any, str]]: + """Get medicine variables dictionary for UI compatibility.""" + # This maintains compatibility with existing UI code + import tkinter as tk + + return { + key: (tk.IntVar(value=0), f"{med.display_name} {med.dosage_info}") + for key, med in self.medicines.items() + } diff --git a/src/ui_manager.py b/src/ui_manager.py index f004345..8247939 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -9,13 +9,18 @@ from typing import Any from PIL import Image, ImageTk +from medicine_manager import MedicineManager + class UIManager: """Handle UI creation and management for the application.""" - def __init__(self, root: tk.Tk, logger: logging.Logger) -> None: + def __init__( + self, root: tk.Tk, logger: logging.Logger, medicine_manager: MedicineManager + ) -> None: self.root: tk.Tk = root self.logger: logging.Logger = logger + self.medicine_manager = medicine_manager def setup_application_icon(self, img_path: str) -> bool: """Set up the application icon.""" @@ -157,14 +162,15 @@ class UIManager: medicine_frame.grid(row=4, column=1, padx=0, pady=10, sticky="nsew") medicine_frame.grid_columnconfigure(0, weight=1) - # Store medicine variables (checkboxes only) - medicine_vars: dict[str, tuple[tk.IntVar, str]] = { - "bupropion": (tk.IntVar(value=0), "Bupropion 150/300 mg"), - "hydroxyzine": (tk.IntVar(value=0), "Hydroxyzine 25mg"), - "gabapentin": (tk.IntVar(value=0), "Gabapentin 100mg"), - "propranolol": (tk.IntVar(value=0), "Propranolol 10mg"), - "quetiapine": (tk.IntVar(value=0), "Quetiapine 25mg"), - } + # Store medicine variables (checkboxes only) - dynamic based on medicine manager + medicine_vars: dict[str, tuple[tk.IntVar, str]] = {} + + for medicine_key in self.medicine_manager.get_medicine_keys(): + medicine = self.medicine_manager.get_medicine(medicine_key) + if medicine: + var = tk.IntVar(value=0) + text = f"{medicine.display_name} {medicine.dosage_info}" + medicine_vars[medicine_key] = (var, text) for idx, (_med_name, (var, text)) in enumerate(medicine_vars.items()): # Just checkbox for medicine taken @@ -218,53 +224,34 @@ class UIManager: table_frame.grid_rowconfigure(0, weight=1) table_frame.grid_columnconfigure(0, weight=1) - columns: list[str] = [ - "Date", - "Depression", - "Anxiety", - "Sleep", - "Appetite", - "Bupropion", - "Hydroxyzine", - "Gabapentin", - "Propranolol", - "Quetiapine", - "Note", - ] - - tree: ttk.Treeview = ttk.Treeview(table_frame, columns=columns, show="headings") - - col_labels: list[str] = [ - "Date", - "Depression", - "Anxiety", - "Sleep", - "Appetite", - "Bupropion 150/300 mg", - "Hydroxyzine 25mg", - "Gabapentin 100mg", - "Propranolol 10mg", - "Quetiapine 25mg", - "Note", - ] - - for col, label in zip(columns, col_labels, strict=False): - tree.heading(col, text=label) - + # Build columns dynamically + columns: list[str] = ["Date", "Depression", "Anxiety", "Sleep", "Appetite"] + col_labels: list[str] = ["Date", "Depression", "Anxiety", "Sleep", "Appetite"] col_settings: list[tuple[str, int, str]] = [ ("Date", 80, "center"), ("Depression", 80, "center"), ("Anxiety", 80, "center"), ("Sleep", 80, "center"), ("Appetite", 80, "center"), - ("Bupropion", 120, "center"), - ("Hydroxyzine", 120, "center"), - ("Gabapentin", 120, "center"), - ("Propranolol", 120, "center"), - ("Quetiapine", 120, "center"), - ("Note", 300, "w"), ] + # Add medicine columns dynamically + for medicine_key in self.medicine_manager.get_medicine_keys(): + medicine = self.medicine_manager.get_medicine(medicine_key) + if medicine: + columns.append(medicine.display_name) + col_labels.append(f"{medicine.display_name} {medicine.dosage_info}") + col_settings.append((medicine.display_name, 120, "center")) + + columns.append("Note") + col_labels.append("Note") + col_settings.append(("Note", 300, "w")) + + tree: ttk.Treeview = ttk.Treeview(table_frame, columns=columns, show="headings") + + for col, label in zip(columns, col_labels, strict=False): + tree.heading(col, text=label) + for col, width, anchor in col_settings: tree.column(col, width=width, anchor=anchor) @@ -918,14 +905,7 @@ class UIManager: def _get_quick_doses(self, medicine_key: str) -> list[str]: """Get common dose amounts for quick selection.""" - dose_map = { - "bupropion": ["150", "300"], - "hydroxyzine": ["25", "50"], - "gabapentin": ["100", "300", "600"], - "propranolol": ["10", "20", "40"], - "quetiapine": ["25", "50", "100"], - } - return dose_map.get(medicine_key, ["25", "50"]) + return self.medicine_manager.get_quick_doses(medicine_key) def _populate_dose_history(self, text_widget: tk.Text, doses_str: str) -> None: """Populate dose history text widget with formatted dose data.""" diff --git a/tests/conftest.py b/tests/conftest.py index 60e690b..846aa14 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,12 @@ import pandas as pd from unittest.mock import Mock import logging +# Add src to path for imports +import sys +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) + +from src.medicine_manager import MedicineManager, Medicine + @pytest.fixture def temp_csv_file(): @@ -20,6 +26,64 @@ def temp_csv_file(): os.unlink(path) +@pytest.fixture +def mock_medicine_manager(): + """Create a mock medicine manager with default medicines for testing.""" + mock_manager = Mock(spec=MedicineManager) + + # Default medicines matching the original system + default_medicines = { + "bupropion": Medicine( + key="bupropion", + display_name="Bupropion", + dosage_info="150/300 mg", + quick_doses=["150", "300"], + color="#FF6B6B", + default_enabled=True + ), + "hydroxyzine": Medicine( + key="hydroxyzine", + display_name="Hydroxyzine", + dosage_info="25 mg", + quick_doses=["25", "50"], + color="#4ECDC4", + default_enabled=False + ), + "gabapentin": Medicine( + key="gabapentin", + display_name="Gabapentin", + dosage_info="100 mg", + quick_doses=["100", "300", "600"], + color="#45B7D1", + default_enabled=False + ), + "propranolol": Medicine( + key="propranolol", + display_name="Propranolol", + dosage_info="10 mg", + quick_doses=["10", "20", "40"], + color="#96CEB4", + default_enabled=True + ), + "quetiapine": Medicine( + key="quetiapine", + display_name="Quetiapine", + dosage_info="25 mg", + quick_doses=["25", "50", "100"], + color="#FFEAA7", + default_enabled=False + ) + } + + mock_manager.get_medicine_keys.return_value = list(default_medicines.keys()) + mock_manager.get_all_medicines.return_value = default_medicines + mock_manager.get_medicine.side_effect = lambda key: default_medicines.get(key) + mock_manager.get_graph_colors.return_value = {k: v.color for k, v in default_medicines.items()} + mock_manager.get_quick_doses.side_effect = lambda key: default_medicines.get(key, Medicine("", "", "", [], "", False)).quick_doses + + return mock_manager + + @pytest.fixture def sample_data(): """Sample data for testing.""" diff --git a/tests/test_data_manager.py b/tests/test_data_manager.py index a744b4e..241a6c0 100644 --- a/tests/test_data_manager.py +++ b/tests/test_data_manager.py @@ -3,10 +3,7 @@ Tests for the DataManager class. """ import os import csv -import pytest -import pandas as pd -from unittest.mock import Mock, patch -import tempfile +from unittest.mock import patch import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) @@ -17,20 +14,21 @@ from src.data_manager import DataManager class TestDataManager: """Test cases for the DataManager class.""" - def test_init(self, temp_csv_file, mock_logger): + def test_init(self, temp_csv_file, mock_logger, mock_medicine_manager): """Test DataManager initialization.""" - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) assert dm.filename == temp_csv_file assert dm.logger == mock_logger + assert dm.medicine_manager == mock_medicine_manager assert os.path.exists(temp_csv_file) - def test_initialize_csv_creates_file_with_headers(self, temp_csv_file, mock_logger): + def test_initialize_csv_creates_file_with_headers(self, temp_csv_file, mock_logger, mock_medicine_manager): """Test that initialize_csv creates a file with proper headers.""" # Remove the file if it exists if os.path.exists(temp_csv_file): os.unlink(temp_csv_file) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) # Check file exists and has correct headers assert os.path.exists(temp_csv_file) @@ -45,33 +43,33 @@ class TestDataManager: ] assert headers == expected_headers - def test_initialize_csv_does_not_overwrite_existing_file(self, temp_csv_file, mock_logger): + def test_initialize_csv_does_not_overwrite_existing_file(self, temp_csv_file, mock_logger, mock_medicine_manager): """Test that initialize_csv does not overwrite existing file.""" # Write some data to the file first with open(temp_csv_file, 'w') as f: f.write("existing,data\n1,2\n") - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) # Check that existing data is preserved with open(temp_csv_file, 'r') as f: content = f.read() assert "existing,data" in content - def test_load_data_empty_file(self, temp_csv_file, mock_logger): + def test_load_data_empty_file(self, temp_csv_file, mock_logger, mock_medicine_manager): """Test loading data from an empty file.""" - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) df = dm.load_data() assert df.empty - def test_load_data_nonexistent_file(self, mock_logger): + def test_load_data_nonexistent_file(self, mock_logger, mock_medicine_manager): """Test loading data from a nonexistent file.""" - dm = DataManager("nonexistent.csv", mock_logger) + dm = DataManager("nonexistent.csv", mock_logger, mock_medicine_manager) df = dm.load_data() assert df.empty mock_logger.warning.assert_called() - def test_load_data_with_valid_data(self, temp_csv_file, mock_logger, sample_data): + def test_load_data_with_valid_data(self, temp_csv_file, mock_logger, mock_medicine_manager, sample_data): """Test loading valid data from CSV file.""" # Write sample data to file with open(temp_csv_file, 'w', newline='') as f: @@ -86,7 +84,7 @@ class TestDataManager: # Write sample data writer.writerows(sample_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) df = dm.load_data() assert not df.empty @@ -102,7 +100,7 @@ class TestDataManager: assert df["anxiety"].dtype == int assert df["note"].dtype == object - def test_load_data_sorted_by_date(self, temp_csv_file, mock_logger): + def test_load_data_sorted_by_date(self, temp_csv_file, mock_logger, mock_medicine_manager): """Test that loaded data is sorted by date.""" # Write data in random order unsorted_data = [ @@ -121,7 +119,7 @@ class TestDataManager: ]) writer.writerows(unsorted_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) df = dm.load_data() # Check that data is sorted by date @@ -129,9 +127,9 @@ class TestDataManager: assert df.iloc[1]["note"] == "second" assert df.iloc[2]["note"] == "third" - def test_add_entry_success(self, temp_csv_file, mock_logger): + def test_add_entry_success(self, temp_csv_file, mock_logger, mock_medicine_manager): """Test successfully adding an entry.""" - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) entry = ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note"] result = dm.add_entry(entry) @@ -143,7 +141,7 @@ class TestDataManager: assert df.iloc[0]["date"] == "2024-01-01" assert df.iloc[0]["note"] == "Test note" - def test_add_entry_duplicate_date(self, temp_csv_file, mock_logger, sample_data): + def test_add_entry_duplicate_date(self, temp_csv_file, mock_logger, mock_medicine_manager, sample_data): """Test adding entry with duplicate date.""" # Add initial data with open(temp_csv_file, 'w', newline='') as f: @@ -156,7 +154,7 @@ class TestDataManager: ]) writer.writerows(sample_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) # Try to add entry with existing date duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, "", 1, "", 1, "", 1, "", 0, "", "Duplicate"] @@ -164,7 +162,7 @@ class TestDataManager: assert result is False mock_logger.warning.assert_called_with("Entry with date 2024-01-01 already exists.") - def test_update_entry_success(self, temp_csv_file, mock_logger, sample_data): + def test_update_entry_success(self, temp_csv_file, mock_logger, mock_medicine_manager, sample_data): """Test successfully updating an entry.""" # Add initial data with open(temp_csv_file, 'w', newline='') as f: @@ -177,7 +175,7 @@ class TestDataManager: ]) writer.writerows(sample_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) updated_values = ["2024-01-01", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"] result = dm.update_entry("2024-01-01", updated_values) @@ -189,7 +187,7 @@ class TestDataManager: assert updated_row["depression"] == 5 assert updated_row["note"] == "Updated note" - def test_update_entry_change_date(self, temp_csv_file, mock_logger, sample_data): + def test_update_entry_change_date(self, temp_csv_file, mock_logger, mock_medicine_manager, sample_data): """Test updating an entry with a date change.""" # Add initial data with open(temp_csv_file, 'w', newline='') as f: @@ -202,7 +200,7 @@ class TestDataManager: ]) writer.writerows(sample_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) updated_values = ["2024-01-05", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"] result = dm.update_entry("2024-01-01", updated_values) @@ -213,7 +211,7 @@ class TestDataManager: assert not any(df["date"] == "2024-01-01") assert any(df["date"] == "2024-01-05") - def test_update_entry_duplicate_date(self, temp_csv_file, mock_logger, sample_data): + def test_update_entry_duplicate_date(self, temp_csv_file, mock_logger, mock_medicine_manager, sample_data): """Test updating entry to a date that already exists.""" # Add initial data with open(temp_csv_file, 'w', newline='') as f: @@ -226,7 +224,7 @@ class TestDataManager: ]) writer.writerows(sample_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) # Try to change date to one that already exists updated_values = ["2024-01-02", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"] @@ -236,7 +234,7 @@ class TestDataManager: "Cannot update: entry with date 2024-01-02 already exists." ) - def test_delete_entry_success(self, temp_csv_file, mock_logger, sample_data): + def test_delete_entry_success(self, temp_csv_file, mock_logger, mock_medicine_manager, sample_data): """Test successfully deleting an entry.""" # Add initial data with open(temp_csv_file, 'w', newline='') as f: @@ -249,7 +247,7 @@ class TestDataManager: ]) writer.writerows(sample_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) result = dm.delete_entry("2024-01-02") assert result is True @@ -259,7 +257,7 @@ class TestDataManager: assert len(df) == 2 assert not any(df["date"] == "2024-01-02") - def test_delete_entry_nonexistent(self, temp_csv_file, mock_logger, sample_data): + def test_delete_entry_nonexistent(self, temp_csv_file, mock_logger, mock_medicine_manager, sample_data): """Test deleting a nonexistent entry.""" # Add initial data with open(temp_csv_file, 'w', newline='') as f: @@ -272,7 +270,7 @@ class TestDataManager: ]) writer.writerows(sample_data) - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) result = dm.delete_entry("2024-01-10") assert result is True # Should return True even if no matching entry @@ -282,22 +280,22 @@ class TestDataManager: assert len(df) == 3 @patch('pandas.read_csv') - def test_load_data_exception_handling(self, mock_read_csv, temp_csv_file, mock_logger): + def test_load_data_exception_handling(self, mock_read_csv, temp_csv_file, mock_logger, mock_medicine_manager): """Test exception handling in load_data.""" mock_read_csv.side_effect = Exception("Test error") - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) df = dm.load_data() assert df.empty mock_logger.error.assert_called_with("Error loading data: Test error") @patch('builtins.open') - def test_add_entry_exception_handling(self, mock_open, temp_csv_file, mock_logger): + def test_add_entry_exception_handling(self, mock_open, temp_csv_file, mock_logger, mock_medicine_manager): """Test exception handling in add_entry.""" mock_open.side_effect = Exception("Test error") - dm = DataManager(temp_csv_file, mock_logger) + dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager) entry = ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note"] result = dm.add_entry(entry) diff --git a/tests/test_graph_manager.py b/tests/test_graph_manager.py index 2db6182..ef6b2ad 100644 --- a/tests/test_graph_manager.py +++ b/tests/test_graph_manager.py @@ -6,8 +6,7 @@ import pytest import pandas as pd import tkinter as tk from tkinter import ttk -from unittest.mock import Mock, patch, MagicMock -import matplotlib.pyplot as plt +from unittest.mock import Mock, patch import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))