Add medicine management functionality with UI and data handling
- Implemented MedicineManagementWindow for adding, editing, and removing medicines. - Created MedicineManager to handle medicine configurations, including loading and saving to JSON. - Updated UIManager to dynamically generate medicine-related UI components based on the MedicineManager. - Enhanced test suite with mock objects for MedicineManager to ensure proper functionality in DataManager tests. - Added validation for medicine input fields in the UI. - Introduced default medicine configurations for initial setup.
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Data files (except example data)
|
||||
*.csv
|
||||
thechart_data.csv
|
||||
### !thechart_data.csv
|
||||
|
||||
# Environment files
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
date,depression,anxiety,sleep,appetite,bupropion,bupropion_doses,hydroxyzine,hydroxyzine_doses,gabapentin,gabapentin_doses,propranolol,propranolol_doses,quetiapine,quetiapine_doses,note
|
||||
|
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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!")
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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!")
|
||||
@@ -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!")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
+40
-52
@@ -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
|
||||
|
||||
+33
-31
@@ -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",
|
||||
|
||||
+116
-81
@@ -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("<Double-1>", 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():
|
||||
|
||||
@@ -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.")
|
||||
@@ -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()
|
||||
}
|
||||
+36
-56
@@ -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."""
|
||||
|
||||
@@ -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."""
|
||||
|
||||
+34
-36
@@ -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)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
Reference in New Issue
Block a user