8 Commits

Author SHA1 Message Date
William Valentin e5e654a0b3 fix: Correct shell activation command in Makefile for proper environment setup
Build and Push Docker Image / build-and-push (push) Has been cancelled
2025-07-31 11:20:18 -07:00
William Valentin 00443a540f chore: Remove obsolete test scripts and unused methods from the data manager
- Deleted test scripts for dose tracking, UI functionality, dynamic data, edit functionality, and final workflow.
- Removed the `add_medicine_dose` method from the DataManager class as it is no longer needed.
2025-07-31 11:11:21 -07:00
William Valentin 59251ced31 chore: moved tests scripts 2025-07-31 10:18:09 -07:00
William Valentin 9471b91f4c feat: Update default_enabled states for bupropion and propranolol to false 2025-07-31 10:06:25 -07:00
William Valentin c755f0affc Add comprehensive tests for dose tracking functionality
- Implemented `test_dose_parsing_simple.py` to validate the dose parsing workflow.
- Created `test_dose_save.py` to verify the saving functionality of dose tracking.
- Added `test_dose_save_simple.py` for programmatic testing of dose saving without UI interaction.
- Developed `test_final_workflow.py` to test the complete dose tracking workflow, ensuring doses are preserved during edits.
- Enhanced `conftest.py` with a mock pathology manager for testing.
- Updated `test_data_manager.py` to include pathology manager in DataManager tests and ensure compatibility with new features.
2025-07-31 09:50:45 -07:00
William Valentin b8600ae57a feat: Remove unused imports from test files for cleaner code 2025-07-30 16:02:26 -07:00
William Valentin d7d4b332d4 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.
2025-07-30 16:01:02 -07:00
William Valentin ea30cb88c9 feat: Update default toggle states for bupropion and propranolol to false 2025-07-30 14:46:25 -07:00
52 changed files with 2579 additions and 3684 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Data files (except example data)
*.csv
thechart_data.csv
### !thechart_data.csv
# Environment files
+14
View File
@@ -14,6 +14,20 @@
"group": "build",
"isBackground": false,
"problemMatcher": []
},
{
"label": "Test Dose Tracking UI",
"type": "shell",
"command": "/home/will/Code/thechart/.venv/bin/python",
"args": [
"scripts/test_dose_tracking_ui.py"
],
"options": {
"cwd": "/home/will/Code/thechart"
},
"group": "test",
"isBackground": false,
"problemMatcher": []
}
]
}
+190
View File
@@ -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 -1
View File
@@ -147,7 +147,7 @@ attach: ## Open a shell in the container
docker-compose exec -it ${TARGET} /bin/bash
shell: ## Open a shell in the local environment
@echo "Opening a shell in the local environment..."
source .venv/bin/activate.${SHELL} && /bin/${SHELL}
source .venv/bin/activate.${SHELL}; /bin/${SHELL}
requirements: ## Export the requirements to a file
@echo "Exporting requirements to requirements.txt..."
poetry export --without-hashes -f requirements.txt -o requirements.txt
-64
View File
@@ -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()
+62
View File
@@ -0,0 +1,62 @@
{
"medicines": [
{
"key": "bupropion",
"display_name": "Bupropion",
"dosage_info": "150/300 mg",
"quick_doses": [
"150",
"300"
],
"color": "#FF6B6B",
"default_enabled": false
},
{
"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": false
},
{
"key": "quetiapine",
"display_name": "Quetiapine",
"dosage_info": "25 mg",
"quick_doses": [
"25",
"50",
"100"
],
"color": "#FFEAA7",
"default_enabled": false
}
]
}
+1
View File
@@ -0,0 +1 @@
date,depression,anxiety,sleep,appetite,bupropion,bupropion_doses,hydroxyzine,hydroxyzine_doses,gabapentin,gabapentin_doses,propranolol,propranolol_doses,quetiapine,quetiapine_doses,note
1 date depression anxiety sleep appetite bupropion bupropion_doses hydroxyzine hydroxyzine_doses gabapentin gabapentin_doses propranolol propranolol_doses quetiapine quetiapine_doses note
+44
View File
@@ -0,0 +1,44 @@
{
"pathologies": [
{
"key": "depression",
"display_name": "Depression",
"scale_info": "0:good, 10:bad",
"color": "#FF6B6B",
"default_enabled": true,
"scale_min": 0,
"scale_max": 10,
"scale_orientation": "normal"
},
{
"key": "anxiety",
"display_name": "Anxiety",
"scale_info": "0:good, 10:bad",
"color": "#FFA726",
"default_enabled": true,
"scale_min": 0,
"scale_max": 10,
"scale_orientation": "normal"
},
{
"key": "sleep",
"display_name": "Sleep Quality",
"scale_info": "0:bad, 10:good",
"color": "#66BB6A",
"default_enabled": true,
"scale_min": 0,
"scale_max": 10,
"scale_orientation": "inverted"
},
{
"key": "appetite",
"display_name": "Appetite",
"scale_info": "0:bad, 10:good",
"color": "#42A5F5",
"default_enabled": true,
"scale_min": 0,
"scale_max": 10,
"scale_orientation": "inverted"
}
]
}
-19
View File
@@ -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")
-67
View File
@@ -1,67 +0,0 @@
#!/usr/bin/env python3
"""
Migration script to add dose tracking columns to existing CSV data.
"""
import shutil
from datetime import datetime
import pandas as pd
def migrate_csv(filename: str = "thechart_data.csv") -> None:
"""Migrate existing CSV to new format with dose tracking columns."""
# Create backup
backup_name = f"{filename}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(filename, backup_name)
print(f"Created backup: {backup_name}")
try:
# Read existing data
df = pd.read_csv(filename)
print(f"Read {len(df)} existing entries")
# Add new dose tracking columns
df["bupropion_doses"] = ""
df["hydroxyzine_doses"] = ""
df["gabapentin_doses"] = ""
df["propranolol_doses"] = ""
# Reorder columns to match new format
new_column_order = [
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"bupropion_doses",
"hydroxyzine",
"hydroxyzine_doses",
"gabapentin",
"gabapentin_doses",
"propranolol",
"propranolol_doses",
"note",
]
df = df[new_column_order]
# Save migrated data
df.to_csv(filename, index=False)
print(f"Successfully migrated {filename}")
print(
"New columns added: bupropion_doses, hydroxyzine_doses, "
"gabapentin_doses, propranolol_doses"
)
except Exception as e:
print(f"Error during migration: {e}")
print(f"Restoring from backup: {backup_name}")
shutil.copy2(backup_name, filename)
raise
if __name__ == "__main__":
migrate_csv()
-61
View File
@@ -1,61 +0,0 @@
#!/usr/bin/env python3
"""
Migration script to add quetiapine columns to existing CSV data.
This script will backup the existing CSV and add the new columns.
"""
import os
import shutil
from datetime import datetime
import pandas as pd
def migrate_csv_add_quetiapine(csv_file: str = "thechart_data.csv"):
"""Add quetiapine and quetiapine_doses columns to existing CSV."""
if not os.path.exists(csv_file):
print(f"CSV file {csv_file} not found. No migration needed.")
return
# Create backup
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_file = f"{csv_file}.backup_quetiapine_{timestamp}"
shutil.copy2(csv_file, backup_file)
print(f"Backup created: {backup_file}")
# Load existing data
try:
df = pd.read_csv(csv_file)
print(f"Loaded {len(df)} rows from {csv_file}")
# Check if quetiapine columns already exist
if "quetiapine" in df.columns:
print("Quetiapine columns already exist. No migration needed.")
return
# Add new columns
# Insert quetiapine columns before the note column
note_col_index = (
df.columns.get_loc("note") if "note" in df.columns else len(df.columns)
)
# Insert quetiapine column
df.insert(note_col_index, "quetiapine", 0)
df.insert(note_col_index + 1, "quetiapine_doses", "")
# Save updated CSV
df.to_csv(csv_file, index=False)
print(f"Successfully added quetiapine columns to {csv_file}")
print(f"New column order: {list(df.columns)}")
except Exception as e:
print(f"Error during migration: {e}")
# Restore backup on error
if os.path.exists(backup_file):
shutil.copy2(backup_file, csv_file)
print("Restored backup due to error")
if __name__ == "__main__":
migrate_csv_add_quetiapine()
-51
View File
@@ -1,51 +0,0 @@
#!/usr/bin/env python3
"""
Quick test runner for TheChart application.
This script provides a simple way to run the test suite.
"""
import os
import subprocess
import sys
def main():
"""Run the test suite."""
print("🧪 Running TheChart Test Suite")
print("=" * 50)
# Change to project directory
os.chdir(os.path.dirname(os.path.abspath(__file__)))
# Run tests with coverage
cmd = [
"uv",
"run",
"pytest",
"tests/",
"--cov=src",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"-v",
]
try:
result = subprocess.run(cmd, check=False)
if result.returncode == 0:
print("\n✅ All tests passed!")
else:
print(f"\n❌ Some tests failed (exit code: {result.returncode})")
print("\n📊 Coverage report generated in htmlcov/index.html")
return result.returncode
except KeyboardInterrupt:
print("\n⚠️ Tests interrupted by user")
return 1
except Exception as e:
print(f"\n💥 Error running tests: {e}")
return 1
if __name__ == "__main__":
sys.exit(main())
-224
View File
@@ -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!")
-91
View File
@@ -1,91 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify date uniqueness functionality in TheChart app.
"""
import logging
import os
import sys
# Add the src directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from src.data_manager import DataManager
# Set up simple logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("test")
def test_date_uniqueness():
"""Test the date uniqueness validation."""
print("Testing date uniqueness functionality...")
# Create a test data manager with a test file
test_filename = "test_data.csv"
dm = DataManager(test_filename, logger)
# Test 1: Add first entry (should succeed)
print("\n1. Adding first entry...")
entry1 = ["2025-07-28", 5, 5, 5, 5, 0, 0, 0, 0, "First entry"]
result1 = dm.add_entry(entry1)
print(f"Result: {result1} (Expected: True)")
# Test 2: Try to add duplicate date (should fail)
print("\n2. Trying to add duplicate date...")
entry2 = ["2025-07-28", 3, 3, 3, 3, 1, 1, 1, 1, "Duplicate entry"]
result2 = dm.add_entry(entry2)
print(f"Result: {result2} (Expected: False)")
# Test 3: Add different date (should succeed)
print("\n3. Adding different date...")
entry3 = ["2025-07-29", 4, 4, 4, 4, 0, 0, 0, 0, "Second entry"]
result3 = dm.add_entry(entry3)
print(f"Result: {result3} (Expected: True)")
# Test 4: Update entry with same date (should succeed)
print("\n4. Updating entry with same date...")
updated_entry = ["2025-07-28", 6, 6, 6, 6, 1, 1, 1, 1, "Updated entry"]
result4 = dm.update_entry("2025-07-28", updated_entry)
print(f"Result: {result4} (Expected: True)")
# Test 5: Try to update entry to existing date (should fail)
print("\n5. Trying to update entry to existing date...")
conflicting_entry = ["2025-07-29", 7, 7, 7, 7, 1, 1, 1, 1, "Conflicting entry"]
result5 = dm.update_entry("2025-07-28", conflicting_entry)
print(f"Result: {result5} (Expected: False)")
# Test 6: Update entry to new date (should succeed)
print("\n6. Updating entry to new date...")
new_date_entry = ["2025-07-30", 8, 8, 8, 8, 1, 1, 1, 1, "New date entry"]
result6 = dm.update_entry("2025-07-28", new_date_entry)
print(f"Result: {result6} (Expected: True)")
# Cleanup
if os.path.exists(test_filename):
os.remove(test_filename)
# Summary
expected_results = [True, False, True, True, False, True]
actual_results = [result1, result2, result3, result4, result5, result6]
print("\n" + "=" * 50)
print("TEST SUMMARY:")
print("=" * 50)
all_passed = True
for i, (expected, actual) in enumerate(
zip(expected_results, actual_results, strict=True), 1
):
status = "PASS" if expected == actual else "FAIL"
if expected != actual:
all_passed = False
print(f"Test {i}: {status} (Expected: {expected}, Got: {actual})")
overall_result = "ALL TESTS PASSED" if all_passed else "SOME TESTS FAILED"
print(f"\nOverall result: {overall_result}")
return all_passed
if __name__ == "__main__":
test_date_uniqueness()
-95
View File
@@ -1,95 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify delete functionality after dose tracking implementation.
"""
import logging
import os
import sys
# Add the src directory to the path so we can import our modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from src.data_manager import DataManager
def test_delete_functionality():
"""Test the delete functionality with the new CSV format."""
print("Testing delete functionality...")
# Create a backup of the current CSV
import shutil
try:
shutil.copy("thechart_data.csv", "thechart_data_backup.csv")
print("✓ Created backup of current CSV")
except Exception as e:
print(f"✗ Failed to create backup: {e}")
return False
try:
# Create a logger for the DataManager
logger = logging.getLogger("test_logger")
logger.setLevel(logging.DEBUG)
# Initialize data manager
data_manager = DataManager("thechart_data.csv", logger)
# Load current data
df = data_manager.load_data()
print(f"✓ Loaded {len(df)} entries from CSV")
if df.empty:
print("✗ No data to test delete functionality")
return False
# Show first few entries
print("\nFirst few entries:")
for _idx, row in df.head(3).iterrows():
print(f" {row['date']}: {row['note']}")
# Test deleting the last entry
last_entry_date = df.iloc[-1]["date"]
print(f"\nAttempting to delete entry with date: {last_entry_date}")
# Perform the delete
success = data_manager.delete_entry(last_entry_date)
if success:
print("✓ Delete operation reported success")
# Reload data to verify deletion
df_after = data_manager.load_data()
print(f"✓ Data reloaded: {len(df_after)} entries (was {len(df)})")
# Check if the entry was actually deleted
deleted_entry_exists = last_entry_date in df_after["date"].values
if not deleted_entry_exists:
print("✓ Entry successfully deleted from CSV")
print("✓ Delete functionality is working correctly")
return True
else:
print("✗ Entry still exists in CSV after delete operation")
return False
else:
print("✗ Delete operation failed")
return False
except Exception as e:
print(f"✗ Error during delete test: {e}")
import traceback
traceback.print_exc()
return False
finally:
# Restore the backup
try:
shutil.move("thechart_data_backup.csv", "thechart_data.csv")
print("✓ Restored original CSV from backup")
except Exception as e:
print(f"✗ Failed to restore backup: {e}")
if __name__ == "__main__":
test_delete_functionality()
-114
View File
@@ -1,114 +0,0 @@
"""
Direct test of dose calculation functionality.
"""
import pandas as pd
import pytest
def calculate_daily_dose(dose_str: str) -> float:
"""Calculate total daily dose from dose string format - copied from GraphManager."""
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
class TestDoseCalculation:
"""Test dose calculation functionality."""
def test_standard_format(self):
"""Test dose calculation with standard timestamp:dose format."""
# Single dose
dose_str = "2025-07-28 18:59:45:150mg"
assert calculate_daily_dose(dose_str) == 150.0
# Multiple doses
dose_str = "2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg"
assert calculate_daily_dose(dose_str) == 225.0
def test_with_symbols(self):
"""Test dose calculation with bullet symbols."""
# With bullet symbols
dose_str = "• • • • 2025-07-30 07:50:00:300"
assert calculate_daily_dose(dose_str) == 300.0
def test_decimal_values(self):
"""Test dose calculation with decimal values."""
# Decimal dose
dose_str = "2025-07-28 18:59:45:12.5mg"
assert calculate_daily_dose(dose_str) == 12.5
# Multiple decimal doses
dose_str = "2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg"
assert calculate_daily_dose(dose_str) == 20.0
def test_no_timestamp_format(self):
"""Test dose calculation without timestamps."""
# Simple dose without timestamp
dose_str = "100mg|50mg"
assert calculate_daily_dose(dose_str) == 150.0
def test_mixed_format(self):
"""Test dose calculation with mixed formats."""
# Mixed format
dose_str = "• 2025-07-30 22:50:00:10|75mg"
assert calculate_daily_dose(dose_str) == 85.0
def test_edge_cases(self):
"""Test dose calculation with edge cases."""
# Empty string
assert calculate_daily_dose("") == 0.0
# NaN value
assert calculate_daily_dose("nan") == 0.0
# No units
dose_str = "2025-07-28 18:59:45:10|2025-07-28 19:34:19:5"
assert calculate_daily_dose(dose_str) == 15.0
def test_malformed_data(self):
"""Test dose calculation with malformed data."""
# Malformed data
assert calculate_daily_dose("malformed:data") == 0.0
assert calculate_daily_dose("::::") == 0.0
assert calculate_daily_dose("2025-07-28:") == 0.0
assert calculate_daily_dose("2025-07-28::mg") == 0.0
def test_partial_data(self):
"""Test dose calculation with partial data."""
# No units but valid dose
assert calculate_daily_dose("2025-07-28 18:59:45:150") == 150.0
if __name__ == "__main__":
pytest.main([__file__, "-v"])
-83
View File
@@ -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)
-171
View File
@@ -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")
-147
View File
@@ -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()
-95
View File
@@ -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)
-55
View File
@@ -1,55 +0,0 @@
#!/usr/bin/env python3
"""
Test script to demonstrate the dose tracking functionality.
"""
import os
import sys
from datetime import datetime
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
from src.data_manager import DataManager
from src.init import logger
def test_dose_tracking():
"""Test the dose tracking functionality."""
# Initialize data manager
data_manager = DataManager("thechart_data.csv", logger)
# Test adding a dose
today = datetime.now().strftime("%m/%d/%Y")
print(f"Testing dose tracking for date: {today}")
# Add some test doses
test_doses = [
("bupropion", "150mg"),
("propranolol", "10mg"),
("bupropion", "150mg"), # Second dose of same medicine
]
for medicine, dose in test_doses:
success = data_manager.add_medicine_dose(today, medicine, dose)
if success:
print(f"✓ Added {medicine} dose: {dose}")
else:
print(f"✗ Failed to add {medicine} dose: {dose}")
# Retrieve and display doses
print(f"\nDoses recorded for {today}:")
medicines = ["bupropion", "hydroxyzine", "gabapentin", "propranolol"]
for medicine in medicines:
doses = data_manager.get_today_medicine_doses(today, medicine)
if doses:
print(f"{medicine.title()}:")
for timestamp, dose in doses:
print(f" - {timestamp}: {dose}")
else:
print(f"{medicine.title()}: No doses recorded")
if __name__ == "__main__":
test_dose_tracking()
-104
View File
@@ -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)
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify the enhanced edit functionality with dose tracking.
"""
import os
import sys
# Add src to path
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
from src.data_manager import DataManager
from src.init import logger
def test_edit_functionality():
"""Test the edit functionality with dose tracking."""
# Initialize data manager
data_manager = DataManager("thechart_data.csv", logger)
print("Testing edit functionality with dose tracking...")
# Test date
test_date = "07/28/2025"
# First, add some test doses to the date
test_doses = [
("bupropion", "150mg"),
("propranolol", "10mg"),
]
print(f"\n1. Adding test doses for {test_date}:")
for medicine, dose in test_doses:
success = data_manager.add_medicine_dose(test_date, medicine, dose)
if success:
print(f" ✓ Added {medicine}: {dose}")
else:
print(f" ✗ Failed to add {medicine}: {dose}")
# Test retrieving dose data (simulating edit window opening)
print("\n2. Retrieving dose data for edit window:")
medicines = ["bupropion", "hydroxyzine", "gabapentin", "propranolol"]
dose_data = {}
for medicine in medicines:
doses = data_manager.get_today_medicine_doses(test_date, medicine)
dose_str = "|".join([f"{ts}:{dose}" for ts, dose in doses])
dose_data[medicine] = dose_str
if dose_str:
print(f" {medicine}: {dose_str}")
else:
print(f" {medicine}: No doses")
# Test CSV structure compatibility
print("\n3. Testing CSV structure:")
df = data_manager.load_data()
if not df.empty:
# Get a row with dose data
test_row = df[df["date"] == test_date]
if not test_row.empty:
values = test_row.iloc[0].tolist()
print(f" CSV columns: {len(df.columns)}")
print(
" Expected: 14 columns (date, dep, anx, slp, app, bup, "
"bup_doses, ...)"
)
print(f" Values for {test_date}: {len(values)} values")
# Test unpacking like the edit window would
if len(values) == 14:
print(" ✓ CSV structure compatible with edit functionality")
else:
print(f" ⚠ Unexpected number of values: {len(values)}")
else:
print(f" No data found for {test_date}")
print("\n4. Edit functionality test summary:")
print(" ✓ Dose data retrieval working")
print(" ✓ CSV structure supports edit operations")
print(" ✓ Dose preservation logic implemented")
print("\nEdit functionality is ready for testing in the GUI!")
if __name__ == "__main__":
test_edit_functionality()
-68
View File
@@ -1,68 +0,0 @@
#!/usr/bin/env python3
"""Test script to demonstrate the improved edit window."""
import sys
import tkinter as tk
from pathlib import Path
# Add src directory to path
sys.path.insert(0, str(Path(__file__).parent / "src"))
from src.logger import logger
from src.ui_manager import UIManager
def test_edit_window():
"""Test the improved edit window."""
root = tk.Tk()
root.title("Edit Window Test")
root.geometry("400x300")
ui_manager = UIManager(root, logger)
# Sample data for testing (16 fields format)
test_values = (
"12/25/2024", # date
7, # depression
5, # anxiety
6, # sleep
4, # appetite
1, # bupropion
"09:00:00:150|18:00:00:150", # bupropion_doses
1, # hydroxyzine
"21:30:00:25", # hydroxyzine_doses
0, # gabapentin
"", # gabapentin_doses
1, # propranolol
"07:00:00:10|14:00:00:10", # propranolol_doses
0, # quetiapine
"", # quetiapine_doses
# Had a good day overall, feeling better with new medication routine
"Had a good day overall, feeling better with the new medication routine.",
)
# Mock callbacks
def save_callback(win, *args):
print("Save called with args:", args)
win.destroy()
def delete_callback(win):
print("Delete called")
win.destroy()
callbacks = {"save": save_callback, "delete": delete_callback}
# Create the improved edit window
edit_win = ui_manager.create_edit_window(test_values, callbacks)
# Center the edit window
edit_win.update_idletasks()
x = (edit_win.winfo_screenwidth() // 2) - (edit_win.winfo_width() // 2)
y = (edit_win.winfo_screenheight() // 2) - (edit_win.winfo_height() // 2)
edit_win.geometry(f"+{x}+{y}")
root.mainloop()
if __name__ == "__main__":
test_edit_window()
-135
View File
@@ -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()
-126
View File
@@ -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)
-124
View File
@@ -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")
-184
View File
@@ -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()
-141
View File
@@ -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)
-174
View File
@@ -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!")
-179
View File
@@ -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!")
-81
View File
@@ -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()
-151
View File
@@ -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)
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify the scrollable input frame functionality.
"""
import os
import sys
import tkinter as tk
from tkinter import ttk
# Add src to path
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
def test_scrollable_input():
"""Test the scrollable input frame."""
from src.init import logger
from src.ui_manager import UIManager
# Create a test window
root = tk.Tk()
root.title("Scrollable Input Frame Test")
root.geometry("400x600") # Smaller window to test scrolling
# Create UI manager
ui_manager = UIManager(root, logger)
# Create main frame
main_frame = ttk.Frame(root, padding="10")
main_frame.grid(row=0, column=0, sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
main_frame.grid_rowconfigure(1, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
# Create the scrollable input frame
_input_ui = ui_manager.create_input_frame(main_frame)
# Add instructions
instructions = ttk.Label(
root,
text="Test the scrolling functionality:\n"
"1. Try mouse wheel scrolling over the input area\n"
"2. Use the scrollbar on the right\n"
"3. Test dose tracking buttons\n"
"4. Resize the window to test responsiveness",
justify="left",
)
instructions.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
# Print success message
print("✓ Scrollable input frame created successfully!")
print("✓ Medicine dose tracking UI elements loaded")
print("✓ Scrollbar functionality active")
print("✓ Mouse wheel scrolling enabled")
print("\nTest window opened. Close the window when done testing.")
# Start the test GUI
root.mainloop()
if __name__ == "__main__":
test_scrollable_input()
-115
View File
@@ -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()
+61 -158
View File
@@ -4,40 +4,47 @@ import os
import pandas as pd
from medicine_manager import MedicineManager
from pathology_manager import PathologyManager
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,
pathology_manager: PathologyManager,
) -> None:
self.filename: str = filename
self.logger: logging.Logger = logger
self.medicine_manager = medicine_manager
self.pathology_manager = pathology_manager
self._initialize_csv_file()
def _get_csv_headers(self) -> list[str]:
"""Get CSV headers based on current pathology and medicine configuration."""
# Start with date
headers = ["date"]
# Add pathology headers
for pathology_key in self.pathology_manager.get_pathology_keys():
headers.append(pathology_key)
# Add medicine headers
for medicine_key in self.medicine_manager.get_medicine_keys():
headers.extend([medicine_key, f"{medicine_key}_doses"])
return 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):
"""Create CSV file with headers if it doesn't exist or is empty."""
if not os.path.exists(self.filename) or os.path.getsize(self.filename) == 0:
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 +53,19 @@ 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 = {"date": str, "note": str}
# Add pathology types
for pathology_key in self.pathology_manager.get_pathology_keys():
dtype_dict[pathology_key] = int
# 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.")
@@ -107,69 +106,24 @@ class DataManager:
)
return False
# Find the row to update using original_date as a unique identifier
# Handle both old format (10 columns) and new format (16 columns)
if len(values) == 16:
# New format with all dose columns including quetiapine
df.loc[
df["date"] == original_date,
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"bupropion_doses",
"hydroxyzine",
"hydroxyzine_doses",
"gabapentin",
"gabapentin_doses",
"propranolol",
"propranolol_doses",
"quetiapine",
"quetiapine_doses",
"note",
],
] = values
elif len(values) == 14:
# Format without quetiapine
df.loc[
df["date"] == original_date,
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"bupropion_doses",
"hydroxyzine",
"hydroxyzine_doses",
"gabapentin",
"gabapentin_doses",
"propranolol",
"propranolol_doses",
"note",
],
] = values
else:
# Old format - only update the user-editable columns
df.loc[
df["date"] == original_date,
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"note",
],
] = values
# Get current CSV headers to match with values
headers = self._get_csv_headers()
# Ensure we have the right number of values
if len(values) != len(headers):
self.logger.warning(
f"Value count mismatch: expected {len(headers)}, got {len(values)}"
)
# Pad with defaults if too few values
while len(values) < len(headers):
header = headers[len(values)]
if header == "note" or header.endswith("_doses"):
values.append("")
else:
values.append(0)
# Update the row using column names
df.loc[df["date"] == original_date, headers] = values
df.to_csv(self.filename, index=False)
return True
except Exception as e:
@@ -189,57 +143,6 @@ class DataManager:
self.logger.error(f"Error deleting entry: {str(e)}")
return False
def add_medicine_dose(self, date: str, medicine_name: str, dose: str) -> bool:
"""Add a medicine dose to today's entry."""
from datetime import datetime
try:
df: pd.DataFrame = self.load_data()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
dose_entry = f"{timestamp}:{dose}"
# Find or create entry for the given date
if df.empty or date not in df["date"].values:
# Create new entry for today with default values
new_entry = {
"date": date,
"depression": 0,
"anxiety": 0,
"sleep": 0,
"appetite": 0,
"bupropion": 0,
"bupropion_doses": "",
"hydroxyzine": 0,
"hydroxyzine_doses": "",
"gabapentin": 0,
"gabapentin_doses": "",
"propranolol": 0,
"propranolol_doses": "",
"quetiapine": 0,
"quetiapine_doses": "",
"note": "",
}
df = pd.concat([df, pd.DataFrame([new_entry])], ignore_index=True)
# Add dose to the appropriate medicine
dose_column = f"{medicine_name}_doses"
mask = df["date"] == date
current_doses = df.loc[mask, dose_column].iloc[0]
if current_doses:
df.loc[mask, dose_column] = current_doses + "|" + dose_entry
else:
df.loc[mask, dose_column] = dose_entry
# Mark medicine as taken (set to 1)
df.loc[mask, medicine_name] = 1
df.to_csv(self.filename, index=False)
return True
except Exception as e:
self.logger.error(f"Error adding medicine dose: {str(e)}")
return False
def get_today_medicine_doses(
self, date: str, medicine_name: str
) -> list[tuple[str, str]]:
+72 -79
View File
@@ -7,30 +7,48 @@ import pandas as pd
from matplotlib.axes import Axes
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from medicine_manager import MedicineManager
from pathology_manager import PathologyManager
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,
pathology_manager: PathologyManager,
) -> None:
self.parent_frame: ttk.LabelFrame = parent_frame
self.medicine_manager = medicine_manager
self.pathology_manager = pathology_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.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=True), # Show by default (most used)
"hydroxyzine": tk.BooleanVar(value=False),
"gabapentin": tk.BooleanVar(value=False),
"propranolol": tk.BooleanVar(value=True), # Show by default (commonly used)
"quetiapine": tk.BooleanVar(value=False),
}
self._initialize_toggle_vars()
self._setup_ui()
def _initialize_toggle_vars(self) -> None:
"""Initialize toggle variables for chart elements."""
self.toggle_vars: dict[str, tk.BooleanVar] = {}
# Initialize pathology toggles dynamically
for pathology_key in self.pathology_manager.get_pathology_keys():
pathology = self.pathology_manager.get_pathology(pathology_key)
default_value = pathology.default_enabled if pathology else True
self.toggle_vars[pathology_key] = tk.BooleanVar(value=default_value)
# 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)
@@ -64,46 +82,35 @@ class GraphManager:
side="left", padx=5
)
# Symptoms toggles
symptoms_frame = ttk.LabelFrame(self.control_frame, text="Symptoms")
symptoms_frame.pack(side="left", padx=5, pady=2)
# Pathologies toggles - dynamic based on pathology manager
pathologies_frame = ttk.LabelFrame(self.control_frame, text="Pathologies")
pathologies_frame.pack(side="left", padx=5, pady=2)
symptom_configs = [
("depression", "Depression"),
("anxiety", "Anxiety"),
("sleep", "Sleep"),
("appetite", "Appetite"),
]
for pathology_key in self.pathology_manager.get_pathology_keys():
pathology = self.pathology_manager.get_pathology(pathology_key)
if pathology:
checkbox = ttk.Checkbutton(
pathologies_frame,
text=pathology.display_name,
variable=self.toggle_vars[pathology_key],
command=self._handle_toggle_changed,
)
checkbox.pack(side="left", padx=3)
for key, label in symptom_configs:
checkbox = ttk.Checkbutton(
symptoms_frame,
text=label,
variable=self.toggle_vars[key],
command=self._handle_toggle_changed,
)
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."""
@@ -128,40 +135,26 @@ class GraphManager:
# Track if any series are plotted
has_plotted_series = False
# Plot data series based on toggle states
if self.toggle_vars["depression"].get():
self._plot_series(
df, "depression", "Depression (0:good, 10:bad)", "o", "-"
)
has_plotted_series = True
if self.toggle_vars["anxiety"].get():
self._plot_series(df, "anxiety", "Anxiety (0:good, 10:bad)", "o", "-")
has_plotted_series = True
if self.toggle_vars["sleep"].get():
self._plot_series(df, "sleep", "Sleep (0:bad, 10:good)", "o", "dashed")
has_plotted_series = True
if self.toggle_vars["appetite"].get():
self._plot_series(
df, "appetite", "Appetite (0:bad, 10:good)", "o", "dashed"
)
has_plotted_series = True
# Plot pathology data series based on toggle states
for pathology_key in self.pathology_manager.get_pathology_keys():
if self.toggle_vars[pathology_key].get():
pathology = self.pathology_manager.get_pathology(pathology_key)
if pathology and pathology_key in df.columns:
label = f"{pathology.display_name} ({pathology.scale_info})"
linestyle = (
"dashed"
if pathology.scale_orientation == "inverted"
else "-"
)
self._plot_series(df, pathology_key, label, "o", linestyle)
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",
"hydroxyzine",
"gabapentin",
"propranolol",
"quetiapine",
]
# Get medicines dynamically from medicine manager
medicines = self.medicine_manager.get_medicine_keys()
# Track medicines with and without data for legend
medicines_with_data = []
+189 -110
View File
@@ -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,10 @@ 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 pathology_management_window import PathologyManagementWindow
from pathology_manager import PathologyManager
from ui_manager import UIManager
@@ -42,8 +46,14 @@ 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.pathology_manager: PathologyManager = PathologyManager(logger=logger)
self.ui_manager: UIManager = UIManager(
root, logger, self.medicine_manager, self.pathology_manager
)
self.data_manager: DataManager = DataManager(
self.filename, logger, self.medicine_manager, self.pathology_manager
)
# Set up application icon
icon_path: str = "chart-671.png"
@@ -54,6 +64,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,12 +87,14 @@ 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, self.pathology_manager
)
# --- Create Input Frame ---
input_ui: dict[str, Any] = self.ui_manager.create_input_frame(main_frame)
self.input_frame: ttk.Frame = input_ui["frame"]
self.symptom_vars: dict[str, tk.IntVar] = input_ui["symptom_vars"]
self.pathology_vars: dict[str, tk.IntVar] = input_ui["pathology_vars"]
self.medicine_vars: dict[str, tuple[tk.IntVar, str]] = input_ui["medicine_vars"]
self.note_var: tk.StringVar = input_ui["note_var"]
self.date_var: tk.StringVar = input_ui["date_var"]
@@ -106,6 +121,69 @@ 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 Pathologies...", command=self._open_pathology_manager
)
tools_menu.add_command(
label="Manage Medicines...", command=self._open_medicine_manager
)
def _open_pathology_manager(self) -> None:
"""Open the pathology management window."""
PathologyManagementWindow(
self.root, self.pathology_manager, self._refresh_ui_after_config_change
)
def _open_medicine_manager(self) -> None:
"""Open the medicine management window."""
MedicineManagementWindow(
self.root, self.medicine_manager, self._refresh_ui_after_config_change
)
def _refresh_ui_after_config_change(self) -> None:
"""Refresh UI components after pathology or medicine configuration changes."""
# Recreate the input frame with new pathologies and 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.pathology_vars: dict[str, tk.IntVar] = input_ui["pathology_vars"]
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 +202,25 @@ 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_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"],
)
full_values = [full_row["date"]]
# Add pathology data dynamically
for pathology_key in self.pathology_manager.get_pathology_keys():
if pathology_key in full_row:
full_values.append(full_row[pathology_key])
else:
full_values.append(0)
# 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
@@ -159,38 +238,60 @@ class MedTrackerApp:
self,
edit_win: tk.Toplevel,
original_date: str,
date: str,
dep: int,
anx: int,
slp: int,
app: int,
bup: int,
hydro: int,
gaba: int,
prop: int,
quet: int,
note: str,
dose_data: dict[str, str],
*args,
) -> None:
"""Save the edited data to the CSV file."""
values: list[str | int] = [
date,
dep,
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,
]
"""Save edited data to CSV file with dynamic pathology/medicine support."""
# Parse dynamic arguments
# Format: date, pathology1, pathology2, ..., medicine1, medicine2,
# ..., note, dose_data
if len(args) < 2: # At minimum need date and note
messagebox.showerror("Error", "Invalid save data format", parent=edit_win)
return
# Extract arguments
date = args[0]
# Get pathology count to extract values
pathology_keys = self.pathology_manager.get_pathology_keys()
medicine_keys = self.medicine_manager.get_medicine_keys()
# Expected format: date, pathology_values..., medicine_values...,
# note, dose_data
expected_pathology_count = len(pathology_keys)
expected_medicine_count = len(medicine_keys)
# Extract pathology values
pathology_values = []
for i in range(expected_pathology_count):
if i + 1 < len(args):
pathology_values.append(args[i + 1])
else:
pathology_values.append(0)
# Extract medicine values
medicine_values = []
medicine_start_idx = 1 + expected_pathology_count
for i in range(expected_medicine_count):
if medicine_start_idx + i < len(args):
medicine_values.append(args[medicine_start_idx + i])
else:
medicine_values.append(0)
# Extract note and dose data (last two arguments)
note = args[-2] if len(args) >= 2 else ""
dose_data = args[-1] if len(args) >= 1 else {}
# Build the values list for data manager
values = [date]
values.extend(pathology_values)
# Add medicine data dynamically
for i, medicine_key in enumerate(medicine_keys):
values.append(medicine_values[i] if i < len(medicine_values) else 0)
values.append(dose_data.get(medicine_key, ""))
values.append(note)
if self.data_manager.update_entry(original_date, values):
edit_win.destroy()
@@ -223,49 +324,33 @@ 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")
# 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"] = ""
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])
# Build entry dynamically
entry: list[str | int] = [self.date_var.get()]
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 pathology data dynamically
for pathology_key in self.pathology_manager.get_pathology_keys():
entry.append(self.pathology_vars[pathology_key].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
@@ -317,8 +402,8 @@ class MedTrackerApp:
"""Clear all input fields."""
logger.debug("Clearing input fields.")
self.date_var.set("")
for key in self.symptom_vars:
self.symptom_vars[key].set(0)
for key in self.pathology_vars:
self.pathology_vars[key].set(0)
for key in self.medicine_vars:
self.medicine_vars[key][0].set(0)
self.note_var.set("")
@@ -336,26 +421,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():
+401
View File
@@ -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.")
+195
View File
@@ -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()
}
+425
View File
@@ -0,0 +1,425 @@
"""
Pathology management window for adding, editing, and removing pathologies.
"""
import tkinter as tk
from tkinter import messagebox, ttk
from pathology_manager import Pathology, PathologyManager
class PathologyManagementWindow:
"""Window for managing pathology configurations."""
def __init__(
self, parent: tk.Tk, pathology_manager: PathologyManager, refresh_callback
):
self.parent = parent
self.pathology_manager = pathology_manager
self.refresh_callback = refresh_callback
# Create the window
self.window = tk.Toplevel(parent)
self.window.title("Manage Pathologies")
self.window.geometry("800x500")
self.window.resizable(True, True)
# Make window modal
self.window.transient(parent)
self.window.grab_set()
self._setup_ui()
self._populate_pathology_list()
# Center window
self.window.update_idletasks()
x = (self.window.winfo_screenwidth() // 2) - (800 // 2)
y = (self.window.winfo_screenheight() // 2) - (500 // 2)
self.window.geometry(f"800x500+{x}+{y}")
def _setup_ui(self):
"""Set up the UI components."""
# Main frame
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)
# Pathology list
list_frame = ttk.LabelFrame(main_frame, text="Pathologies", padding="5")
list_frame.grid(row=0, column=0, sticky="nsew", pady=(0, 10))
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
# Treeview for pathology list
columns = (
"Key",
"Display Name",
"Scale Info",
"Color",
"Default Enabled",
"Scale Range",
)
self.tree = ttk.Treeview(list_frame, columns=columns, show="headings")
# Configure columns
self.tree.heading("Key", text="Key")
self.tree.heading("Display Name", text="Display Name")
self.tree.heading("Scale Info", text="Scale Info")
self.tree.heading("Color", text="Color")
self.tree.heading("Default Enabled", text="Default Enabled")
self.tree.heading("Scale Range", text="Scale Range")
self.tree.column("Key", width=120)
self.tree.column("Display Name", width=150)
self.tree.column("Scale Info", width=150)
self.tree.column("Color", width=80)
self.tree.column("Default Enabled", width=100)
self.tree.column("Scale Range", width=100)
# Scrollbar for treeview
scrollbar = ttk.Scrollbar(
list_frame, orient="vertical", command=self.tree.yview
)
self.tree.configure(yscrollcommand=scrollbar.set)
self.tree.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
list_frame.grid_rowconfigure(0, weight=1)
list_frame.grid_columnconfigure(0, weight=1)
# Buttons frame
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=1, column=0, sticky="ew")
ttk.Button(
button_frame, text="Add Pathology", command=self._add_pathology
).pack(side="left", padx=(0, 5))
ttk.Button(
button_frame, text="Edit Pathology", command=self._edit_pathology
).pack(side="left", padx=(0, 5))
ttk.Button(
button_frame, text="Remove Pathology", command=self._remove_pathology
).pack(side="left", padx=(0, 5))
ttk.Button(button_frame, text="Close", command=self.window.destroy).pack(
side="right"
)
def _populate_pathology_list(self):
"""Populate the pathology list."""
# Clear existing items
for item in self.tree.get_children():
self.tree.delete(item)
# Add pathologies
for pathology in self.pathology_manager.get_all_pathologies().values():
scale_range = f"{pathology.scale_min}-{pathology.scale_max}"
self.tree.insert(
"",
"end",
values=(
pathology.key,
pathology.display_name,
pathology.scale_info,
pathology.color,
"Yes" if pathology.default_enabled else "No",
scale_range,
),
)
def _add_pathology(self):
"""Add a new pathology."""
PathologyEditDialog(
self.window, self.pathology_manager, None, self._on_pathology_changed
)
def _edit_pathology(self):
"""Edit selected pathology."""
selection = self.tree.selection()
if not selection:
messagebox.showwarning("No Selection", "Please select a pathology to edit.")
return
item = self.tree.item(selection[0])
pathology_key = item["values"][0]
pathology = self.pathology_manager.get_pathology(pathology_key)
if pathology:
PathologyEditDialog(
self.window,
self.pathology_manager,
pathology,
self._on_pathology_changed,
)
def _remove_pathology(self):
"""Remove selected pathology."""
selection = self.tree.selection()
if not selection:
messagebox.showwarning(
"No Selection", "Please select a pathology to remove."
)
return
item = self.tree.item(selection[0])
pathology_key = item["values"][0]
pathology_name = item["values"][1]
if messagebox.askyesno(
"Confirm Removal",
f"Are you sure you want to remove '{pathology_name}'?\n\n"
"This will also remove all associated data from your records!",
):
if self.pathology_manager.remove_pathology(pathology_key):
messagebox.showinfo(
"Success", f"'{pathology_name}' removed successfully!"
)
self._populate_pathology_list()
self._refresh_main_app()
else:
messagebox.showerror("Error", f"Failed to remove '{pathology_name}'.")
def _on_pathology_changed(self):
"""Handle pathology changes."""
self._populate_pathology_list()
self._refresh_main_app()
def _refresh_main_app(self):
"""Refresh the main application."""
if self.refresh_callback:
self.refresh_callback()
class PathologyEditDialog:
"""Dialog for adding/editing a pathology."""
def __init__(
self,
parent: tk.Toplevel,
pathology_manager: PathologyManager,
pathology: Pathology | None,
callback,
):
self.parent = parent
self.pathology_manager = pathology_manager
self.pathology = pathology
self.callback = callback
self.is_edit = pathology is not None
# Create dialog
self.dialog = tk.Toplevel(parent)
self.dialog.title("Edit Pathology" if self.is_edit else "Add Pathology")
self.dialog.geometry("450x400")
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) - (450 // 2)
y = parent.winfo_y() + (parent.winfo_height() // 2) - (400 // 2)
self.dialog.geometry(f"450x400+{x}+{y}")
def _setup_dialog(self):
"""Set up the dialog UI."""
# Main frame
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)
# Form fields
self.key_var = tk.StringVar()
self.name_var = tk.StringVar()
self.scale_info_var = tk.StringVar()
self.color_var = tk.StringVar()
self.default_var = tk.BooleanVar()
self.scale_min_var = tk.IntVar(value=0)
self.scale_max_var = tk.IntVar(value=10)
self.orientation_var = tk.StringVar(value="normal")
# Key field
ttk.Label(main_frame, text="Key:").grid(
row=0, column=0, sticky="w", pady=(0, 5)
)
key_entry = ttk.Entry(main_frame, textvariable=self.key_var, width=40)
key_entry.grid(row=0, column=1, sticky="ew", pady=(0, 5))
ttk.Label(main_frame, text="(alphanumeric, underscores, hyphens only)").grid(
row=0, column=2, sticky="w", padx=(5, 0), pady=(0, 5)
)
# Display name field
ttk.Label(main_frame, text="Display Name:").grid(
row=1, column=0, sticky="w", pady=(0, 5)
)
ttk.Entry(main_frame, textvariable=self.name_var, width=40).grid(
row=1, column=1, sticky="ew", pady=(0, 5)
)
# Scale info field
ttk.Label(main_frame, text="Scale Info:").grid(
row=2, column=0, sticky="w", pady=(0, 5)
)
ttk.Entry(main_frame, textvariable=self.scale_info_var, width=40).grid(
row=2, column=1, sticky="ew", pady=(0, 5)
)
ttk.Label(main_frame, text='(e.g., "0:good, 10:bad")').grid(
row=2, column=2, sticky="w", padx=(5, 0), pady=(0, 5)
)
# Scale range
scale_frame = ttk.Frame(main_frame)
scale_frame.grid(row=3, column=1, sticky="ew", pady=(0, 5))
ttk.Label(main_frame, text="Scale Range:").grid(
row=3, column=0, sticky="w", pady=(0, 5)
)
ttk.Label(scale_frame, text="Min:").grid(row=0, column=0, sticky="w")
ttk.Entry(scale_frame, textvariable=self.scale_min_var, width=5).grid(
row=0, column=1, padx=(5, 10)
)
ttk.Label(scale_frame, text="Max:").grid(row=0, column=2, sticky="w")
ttk.Entry(scale_frame, textvariable=self.scale_max_var, width=5).grid(
row=0, column=3, padx=5
)
# Scale orientation
ttk.Label(main_frame, text="Scale Orientation:").grid(
row=4, column=0, sticky="w", pady=(0, 5)
)
orientation_frame = ttk.Frame(main_frame)
orientation_frame.grid(row=4, column=1, sticky="ew", pady=(0, 5))
ttk.Radiobutton(
orientation_frame,
text="Normal (0=good)",
variable=self.orientation_var,
value="normal",
).grid(row=0, column=0, sticky="w")
ttk.Radiobutton(
orientation_frame,
text="Inverted (0=bad)",
variable=self.orientation_var,
value="inverted",
).grid(row=0, column=1, sticky="w", padx=(20, 0))
# Color field
ttk.Label(main_frame, text="Color:").grid(
row=5, column=0, sticky="w", pady=(0, 5)
)
ttk.Entry(main_frame, textvariable=self.color_var, width=40).grid(
row=5, column=1, sticky="ew", pady=(0, 5)
)
ttk.Label(main_frame, text="(hex format, e.g., #FF6B6B)").grid(
row=5, column=2, sticky="w", padx=(5, 0), pady=(0, 5)
)
# Default enabled checkbox
ttk.Checkbutton(
main_frame, text="Show in graph by default", variable=self.default_var
).grid(row=6, column=1, sticky="w", pady=(10, 15))
# Buttons
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=7, column=0, columnspan=3, sticky="ew", pady=(10, 0))
ttk.Button(button_frame, text="Save", command=self._save_pathology).pack(
side="right", padx=(5, 0)
)
ttk.Button(button_frame, text="Cancel", command=self.dialog.destroy).pack(
side="right"
)
# Configure column weights
main_frame.grid_columnconfigure(1, weight=1)
# Focus on first field
key_entry.focus()
def _populate_fields(self):
"""Populate fields if editing."""
if self.pathology:
self.key_var.set(self.pathology.key)
self.name_var.set(self.pathology.display_name)
self.scale_info_var.set(self.pathology.scale_info)
self.color_var.set(self.pathology.color)
self.default_var.set(self.pathology.default_enabled)
self.scale_min_var.set(self.pathology.scale_min)
self.scale_max_var.set(self.pathology.scale_max)
self.orientation_var.set(self.pathology.scale_orientation)
def _save_pathology(self):
"""Save the pathology."""
# Validate fields
key = self.key_var.get().strip()
name = self.name_var.get().strip()
scale_info = self.scale_info_var.get().strip()
color = self.color_var.get().strip()
scale_min = self.scale_min_var.get()
scale_max = self.scale_max_var.get()
if not all([key, name, scale_info, 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
# Validate scale range
if scale_min >= scale_max:
messagebox.showerror("Error", "Scale minimum must be less than maximum.")
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 pathology object
new_pathology = Pathology(
key=key,
display_name=name,
scale_info=scale_info,
color=color,
default_enabled=self.default_var.get(),
scale_min=scale_min,
scale_max=scale_max,
scale_orientation=self.orientation_var.get(),
)
# Save pathology
success = False
if self.is_edit:
success = self.pathology_manager.update_pathology(
self.pathology.key, new_pathology
)
else:
success = self.pathology_manager.add_pathology(new_pathology)
if success:
action = "updated" if self.is_edit else "added"
messagebox.showinfo("Success", f"Pathology {action} successfully!")
self.callback()
self.dialog.destroy()
else:
action = "update" if self.is_edit else "add"
messagebox.showerror("Error", f"Failed to {action} pathology.")
+199
View File
@@ -0,0 +1,199 @@
"""
Pathology configuration manager for the MedTracker application.
Handles dynamic loading and saving of pathology/symptom configurations.
"""
import json
import logging
import os
from dataclasses import asdict, dataclass
from typing import Any
@dataclass
class Pathology:
"""Data class representing a pathology/symptom."""
key: str # Internal key (e.g., "depression")
display_name: str # Display name (e.g., "Depression")
scale_info: str # Scale information (e.g., "0:good, 10:bad")
color: str # Color for graph display
default_enabled: bool = True # Whether to show in graph by default
scale_min: int = 0 # Minimum scale value
scale_max: int = 10 # Maximum scale value
scale_orientation: str = "normal" # "normal" (0=good) or "inverted" (0=bad)
class PathologyManager:
"""Manages pathology configurations and provides access to pathology data."""
def __init__(
self, config_file: str = "pathologies.json", logger: logging.Logger = None
):
self.config_file = config_file
self.logger = logger or logging.getLogger(__name__)
self.pathologies: dict[str, Pathology] = {}
self._load_pathologies()
def _get_default_pathologies(self) -> list[Pathology]:
"""Get the default pathology configuration."""
return [
Pathology(
key="depression",
display_name="Depression",
scale_info="0:good, 10:bad",
color="#FF6B6B",
default_enabled=True,
scale_orientation="normal",
),
Pathology(
key="anxiety",
display_name="Anxiety",
scale_info="0:good, 10:bad",
color="#FFA726",
default_enabled=True,
scale_orientation="normal",
),
Pathology(
key="sleep",
display_name="Sleep Quality",
scale_info="0:bad, 10:good",
color="#66BB6A",
default_enabled=True,
scale_orientation="inverted",
),
Pathology(
key="appetite",
display_name="Appetite",
scale_info="0:bad, 10:good",
color="#42A5F5",
default_enabled=True,
scale_orientation="inverted",
),
]
def _load_pathologies(self) -> None:
"""Load pathologies from configuration file."""
if os.path.exists(self.config_file):
try:
with open(self.config_file) as f:
data = json.load(f)
self.pathologies = {}
for pathology_data in data.get("pathologies", []):
pathology = Pathology(**pathology_data)
self.pathologies[pathology.key] = pathology
self.logger.info(
f"Loaded {len(self.pathologies)} pathologies from "
f"{self.config_file}"
)
except Exception as e:
self.logger.error(f"Error loading pathologies config: {e}")
self._create_default_config()
else:
self._create_default_config()
def _create_default_config(self) -> None:
"""Create default pathology configuration."""
default_pathologies = self._get_default_pathologies()
self.pathologies = {path.key: path for path in default_pathologies}
self.save_pathologies()
self.logger.info("Created default pathology configuration")
def save_pathologies(self) -> bool:
"""Save current pathologies to configuration file."""
try:
data = {
"pathologies": [
asdict(pathology) for pathology in self.pathologies.values()
]
}
with open(self.config_file, "w") as f:
json.dump(data, f, indent=2)
self.logger.info(
f"Saved {len(self.pathologies)} pathologies to {self.config_file}"
)
return True
except Exception as e:
self.logger.error(f"Error saving pathologies config: {e}")
return False
def get_all_pathologies(self) -> dict[str, Pathology]:
"""Get all pathologies."""
return self.pathologies.copy()
def get_pathology(self, key: str) -> Pathology | None:
"""Get a specific pathology by key."""
return self.pathologies.get(key)
def add_pathology(self, pathology: Pathology) -> bool:
"""Add a new pathology."""
if pathology.key in self.pathologies:
self.logger.warning(f"Pathology with key '{pathology.key}' already exists")
return False
self.pathologies[pathology.key] = pathology
return self.save_pathologies()
def update_pathology(self, key: str, pathology: Pathology) -> bool:
"""Update an existing pathology."""
if key not in self.pathologies:
self.logger.warning(f"Pathology with key '{key}' does not exist")
return False
# If key is changing, remove old entry
if key != pathology.key:
del self.pathologies[key]
self.pathologies[pathology.key] = pathology
return self.save_pathologies()
def remove_pathology(self, key: str) -> bool:
"""Remove a pathology."""
if key not in self.pathologies:
self.logger.warning(f"Pathology with key '{key}' does not exist")
return False
del self.pathologies[key]
return self.save_pathologies()
def get_pathology_keys(self) -> list[str]:
"""Get list of all pathology keys."""
return list(self.pathologies.keys())
def get_display_names(self) -> dict[str, str]:
"""Get mapping of keys to display names."""
return {key: path.display_name for key, path in self.pathologies.items()}
def get_graph_colors(self) -> dict[str, str]:
"""Get mapping of pathology keys to graph colors."""
return {key: path.color for key, path in self.pathologies.items()}
def get_default_enabled_pathologies(self) -> list[str]:
"""Get list of pathologies that should be enabled by default in graphs."""
return [key for key, path in self.pathologies.items() if path.default_enabled]
def get_pathology_vars_dict(self) -> dict[str, tuple[Any, str]]:
"""Get pathology variables dictionary for UI compatibility."""
# This maintains compatibility with existing UI code
import tkinter as tk
return {
key: (tk.IntVar(value=0), path.display_name)
for key, path in self.pathologies.items()
}
def get_scale_info(self, key: str) -> tuple[int, int, str, str]:
"""Get scale information for a pathology."""
pathology = self.get_pathology(key)
if pathology:
return (
pathology.scale_min,
pathology.scale_max,
pathology.scale_info,
pathology.scale_orientation,
)
return (0, 10, "0-10", "normal")
+610 -219
View File
@@ -9,13 +9,24 @@ from typing import Any
from PIL import Image, ImageTk
from medicine_manager import MedicineManager
from pathology_manager import PathologyManager
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,
pathology_manager: PathologyManager,
) -> None:
self.root: tk.Tk = root
self.logger: logging.Logger = logger
self.medicine_manager = medicine_manager
self.pathology_manager = pathology_manager
def setup_application_icon(self, img_path: str) -> bool:
"""Set up the application icon."""
@@ -125,46 +136,42 @@ class UIManager:
main_container.bind("<Enter>", on_mouse_enter)
canvas.bind("<Enter>", on_mouse_enter)
# Create variables for symptoms
symptom_vars: dict[str, tk.IntVar] = {
"depression": tk.IntVar(value=0),
"anxiety": tk.IntVar(value=0),
"sleep": tk.IntVar(value=0),
"appetite": tk.IntVar(value=0),
}
# Create variables for pathologies dynamically
pathology_vars: dict[str, tk.IntVar] = {}
for pathology_key in self.pathology_manager.get_pathology_keys():
pathology_vars[pathology_key] = tk.IntVar(value=0)
# Create enhanced scales for symptoms
symptom_labels: list[tuple[str, str]] = [
("Depression", "depression"),
("Anxiety", "anxiety"),
("Sleep Quality", "sleep"),
("Appetite", "appetite"),
]
# Create enhanced scales for pathologies dynamically
pathology_configs = []
for pathology in self.pathology_manager.get_all_pathologies().values():
pathology_configs.append((pathology.display_name, pathology.key))
# Configure input frame columns for better layout
input_frame.grid_columnconfigure(1, weight=1)
for idx, (label, var_name) in enumerate(symptom_labels):
self._create_enhanced_symptom_scale(
input_frame, idx, label, var_name, 0, symptom_vars
for idx, (label, var_name) in enumerate(pathology_configs):
self._create_enhanced_pathology_scale(
input_frame, idx, label, var_name, 0, pathology_vars
)
# Medicine tracking section (simplified)
# Medicine tracking section (simplified) - adjust row number dynamically
medicine_row = len(pathology_configs)
ttk.Label(input_frame, text="Treatment:").grid(
row=4, column=0, sticky="w", padx=5, pady=2
row=medicine_row, column=0, sticky="w", padx=5, pady=2
)
medicine_frame = ttk.LabelFrame(input_frame, text="Medicine")
medicine_frame.grid(row=4, column=1, padx=0, pady=10, sticky="nsew")
medicine_frame.grid(row=medicine_row, 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
@@ -172,22 +179,25 @@ class UIManager:
row=idx, column=0, sticky="w", padx=5, pady=2
)
# Note and Date fields
# Note and Date fields - adjust row numbers
note_row = medicine_row + 1
date_row = medicine_row + 2
note_var: tk.StringVar = tk.StringVar()
date_var: tk.StringVar = tk.StringVar()
ttk.Label(input_frame, text="Note:").grid(
row=5, column=0, sticky="w", padx=5, pady=2
row=note_row, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(input_frame, textvariable=note_var).grid(
row=5, column=1, sticky="ew", padx=5, pady=2
row=note_row, column=1, sticky="ew", padx=5, pady=2
)
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
row=6, column=0, sticky="w", padx=5, pady=2
row=date_row, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(input_frame, textvariable=date_var, justify="center").grid(
row=6, column=1, sticky="ew", padx=5, pady=2
row=date_row, column=1, sticky="ew", padx=5, pady=2
)
# Set default date to today
@@ -201,7 +211,7 @@ class UIManager:
# Return all UI elements and variables
return {
"frame": main_container,
"symptom_vars": symptom_vars,
"pathology_vars": pathology_vars,
"medicine_vars": medicine_vars,
"note_var": note_var,
"date_var": date_var,
@@ -218,53 +228,36 @@ 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",
]
# Build columns dynamically
columns: list[str] = ["Date"]
col_labels: list[str] = ["Date"]
col_settings: list[tuple[str, int, str]] = [("Date", 80, "center")]
# Add pathology columns dynamically
for pathology_key in self.pathology_manager.get_pathology_keys():
pathology = self.pathology_manager.get_pathology(pathology_key)
if pathology:
columns.append(pathology.display_name)
col_labels.append(pathology.display_name)
col_settings.append((pathology.display_name, 80, "center"))
# 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")
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)
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"),
]
for col, width, anchor in col_settings:
tree.column(col, width=width, anchor=anchor)
@@ -379,102 +372,59 @@ class UIManager:
edit_win.bind("<Enter>", on_mouse_enter)
canvas.bind("<Enter>", on_mouse_enter)
# Unpack values - handle both old and new CSV formats
if len(values) == 10:
# Old format: date, dep, anx, slp, app, bup, hydro, gaba, prop, note
date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values
bup_doses, hydro_doses, gaba_doses, prop_doses, quet_doses = (
"",
"",
"",
"",
"",
)
quet = 0
elif len(values) == 14:
# Old new format with dose tracking (without quetiapine)
(
date,
dep,
anx,
slp,
app,
bup,
bup_doses,
hydro,
hydro_doses,
gaba,
gaba_doses,
prop,
prop_doses,
note,
) = values
quet, quet_doses = 0, ""
elif len(values) == 16:
# New format with quetiapine and dose tracking
(
date,
dep,
anx,
slp,
app,
bup,
bup_doses,
hydro,
hydro_doses,
gaba,
gaba_doses,
prop,
prop_doses,
quet,
quet_doses,
note,
) = values
else:
# Fallback for unexpected format
self.logger.warning(f"Unexpected number of values in edit: {len(values)}")
# Pad with default values
values_list = list(values) + [""] * (16 - len(values))
(
date,
dep,
anx,
slp,
app,
bup,
bup_doses,
hydro,
hydro_doses,
gaba,
gaba_doses,
prop,
prop_doses,
quet,
quet_doses,
note,
) = values_list[:16]
# Unpack values dynamically
# Expected format: date, pathology1, pathology2, ...,
# medicine1, medicine1_doses, medicine2, medicine2_doses, ..., note
# Create improved UI sections
vars_dict = self._create_edit_ui(
# Parse values dynamically
values_list = list(values)
# Extract date
date = values_list[0] if len(values_list) > 0 else ""
# Extract pathology values
pathology_values = {}
pathology_keys = self.pathology_manager.get_pathology_keys()
for i, pathology_key in enumerate(pathology_keys):
if i + 1 < len(values_list):
pathology_values[pathology_key] = values_list[i + 1]
else:
pathology_values[pathology_key] = 0
# Extract medicine values and doses
medicine_values = {}
medicine_doses = {}
medicine_keys = self.medicine_manager.get_medicine_keys()
# Start index after date and pathologies
medicine_start_idx = 1 + len(pathology_keys)
for i, medicine_key in enumerate(medicine_keys):
# Each medicine has 2 values: checkbox value and doses string
checkbox_idx = medicine_start_idx + (i * 2)
doses_idx = medicine_start_idx + (i * 2) + 1
if checkbox_idx < len(values_list):
medicine_values[medicine_key] = values_list[checkbox_idx]
else:
medicine_values[medicine_key] = 0
if doses_idx < len(values_list):
medicine_doses[medicine_key] = values_list[doses_idx]
else:
medicine_doses[medicine_key] = ""
# Extract note (should be the last value)
note = values_list[-1] if len(values_list) > 0 else ""
# Create improved UI sections dynamically
vars_dict = self._create_edit_ui_dynamic(
main_container,
date,
dep,
anx,
slp,
app,
bup,
hydro,
gaba,
prop,
quet,
pathology_values,
medicine_values,
medicine_doses,
note,
{
"bupropion": bup_doses,
"hydroxyzine": hydro_doses,
"gabapentin": gaba_doses,
"propranolol": prop_doses,
"quetiapine": quet_doses,
},
)
# Add action buttons
@@ -493,6 +443,105 @@ class UIManager:
return edit_win
def _create_edit_ui_dynamic(
self,
parent: ttk.Frame,
date: str,
pathology_values: dict[str, int],
medicine_values: dict[str, int],
medicine_doses: dict[str, str],
note: str,
) -> dict[str, Any]:
"""Create UI layout for edit window with dynamic pathologies and medicines."""
vars_dict = {}
row = 0
# Header with entry date
header_frame = ttk.Frame(parent)
header_frame.grid(row=row, column=0, sticky="ew", pady=(0, 20))
header_frame.grid_columnconfigure(1, weight=1)
ttk.Label(
header_frame, text="Editing Entry for:", font=("TkDefaultFont", 12, "bold")
).grid(row=0, column=0, sticky="w")
vars_dict["date"] = tk.StringVar(value=str(date))
date_entry = ttk.Entry(
header_frame,
textvariable=vars_dict["date"],
font=("TkDefaultFont", 12),
width=15,
)
date_entry.grid(row=0, column=1, sticky="w", padx=(10, 0))
row += 1
# Pathologies section
pathologies_frame = ttk.LabelFrame(
parent, text="Daily Pathologies", padding="15"
)
pathologies_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
pathologies_frame.grid_columnconfigure(1, weight=1)
# Create pathology scales dynamically
for i, (pathology_key, value) in enumerate(pathology_values.items()):
pathology = self.pathology_manager.get_pathology(pathology_key)
if pathology:
label = f"{pathology.display_name} ({pathology.scale_info})"
self._create_symptom_scale(
pathologies_frame, i, label, pathology_key, value, vars_dict
)
row += 1
# Medications section
meds_frame = ttk.LabelFrame(parent, text="Medications Taken", padding="15")
meds_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
meds_frame.grid_columnconfigure(0, weight=1)
# Create medicine checkboxes dynamically
med_vars = self._create_medicine_section_dynamic(meds_frame, medicine_values)
vars_dict.update(med_vars)
row += 1
# Dose tracking section
dose_frame = ttk.LabelFrame(parent, text="Dose Tracking", padding="15")
dose_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
dose_frame.grid_columnconfigure(0, weight=1)
dose_vars = self._create_dose_tracking_dynamic(dose_frame, medicine_doses)
vars_dict.update(dose_vars)
row += 1
# Notes section
notes_frame = ttk.LabelFrame(parent, text="Notes", padding="15")
notes_frame.grid(row=row, column=0, sticky="ew", pady=(0, 20))
notes_frame.grid_columnconfigure(0, weight=1)
vars_dict["note"] = tk.StringVar(value=str(note))
note_text = tk.Text(
notes_frame,
height=4,
width=50,
wrap=tk.WORD,
font=("TkDefaultFont", 10),
relief="solid",
borderwidth=1,
)
note_text.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
note_text.insert("1.0", str(note))
# Bind text widget to string var for easy access
def update_note(*args):
vars_dict["note"].set(note_text.get("1.0", tk.END).strip())
note_text.bind("<KeyRelease>", update_note)
note_text.bind("<FocusOut>", update_note)
return vars_dict
def _create_edit_ui(
self,
parent: ttk.Frame,
@@ -769,6 +818,114 @@ class UIManager:
scale.bind("<KeyRelease>", update_value_label)
update_value_label() # Set initial color
def _create_enhanced_pathology_scale(
self,
parent: ttk.Frame,
row: int,
label: str,
key: str,
value: int,
vars_dict: dict[str, tk.IntVar],
) -> None:
"""Create enhanced pathology scale for new entry form."""
# Ensure value is properly converted
try:
value = int(float(value)) if value not in ["", None] else 0
except (ValueError, TypeError):
value = 0
# Get pathology configuration
pathology = self.pathology_manager.get_pathology(key)
if not pathology:
# Fallback for missing pathology
pathology_info = f"{label} (0-10):"
scale_min, scale_max = 0, 10
scale_orientation = "normal"
else:
pathology_info = f"{pathology.display_name} ({pathology.scale_info}):"
scale_min, scale_max = pathology.scale_min, pathology.scale_max
scale_orientation = pathology.scale_orientation
# Label
label_widget = ttk.Label(
parent, text=pathology_info, font=("TkDefaultFont", 10, "bold")
)
label_widget.grid(row=row, column=0, sticky="w", padx=5, pady=8)
# Scale container
scale_container = ttk.Frame(parent)
scale_container.grid(row=row, column=1, sticky="ew", padx=(20, 5), pady=8)
scale_container.grid_columnconfigure(0, weight=1)
# Scale with value labels
scale_frame = ttk.Frame(scale_container)
scale_frame.grid(row=0, column=0, sticky="ew")
scale_frame.grid_columnconfigure(1, weight=1)
# Current value display
value_label = ttk.Label(
scale_frame,
text=str(value),
font=("TkDefaultFont", 12, "bold"),
foreground="#2E86AB",
width=3,
)
value_label.grid(row=0, column=0, padx=(0, 10))
# Scale widget
scale = ttk.Scale(
scale_frame,
from_=scale_min,
to=scale_max,
variable=vars_dict[key],
orient=tk.HORIZONTAL,
length=250,
)
scale.grid(row=0, column=1, sticky="ew")
# Scale labels
labels_frame = ttk.Frame(scale_container)
labels_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))
ttk.Label(labels_frame, text=str(scale_min), font=("TkDefaultFont", 8)).grid(
row=0, column=0, sticky="w"
)
labels_frame.grid_columnconfigure(1, weight=1)
mid_value = (scale_min + scale_max) // 2
ttk.Label(labels_frame, text=str(mid_value), font=("TkDefaultFont", 8)).grid(
row=0, column=1
)
ttk.Label(labels_frame, text=str(scale_max), font=("TkDefaultFont", 8)).grid(
row=0, column=2, sticky="e"
)
# Update label when scale changes
def update_value_label_pathology(event=None):
current_val = vars_dict[key].get()
value_label.configure(text=str(current_val))
# Change color based on value and orientation
if scale_orientation == "inverted":
# For inverted scales (like sleep, appetite), higher is better
if current_val >= scale_max * 0.7:
value_label.configure(foreground="#28A745") # Green for good
elif current_val >= scale_max * 0.4:
value_label.configure(foreground="#FFC107") # Yellow for medium
else:
value_label.configure(foreground="#DC3545") # Red for bad
else:
# For normal scales (like depression, anxiety), lower is better
if current_val <= scale_max * 0.3:
value_label.configure(foreground="#28A745") # Green for good
elif current_val <= scale_max * 0.6:
value_label.configure(foreground="#FFC107") # Yellow for medium
else:
value_label.configure(foreground="#DC3545") # Red for bad
scale.bind("<Motion>", update_value_label_pathology)
scale.bind("<ButtonRelease-1>", update_value_label_pathology)
scale.bind("<KeyRelease>", update_value_label_pathology)
update_value_label_pathology() # Set initial color
def _create_medicine_section(
self, parent: ttk.Frame, bup: int, hydro: int, gaba: int, prop: int, quet: int
) -> dict[str, tk.IntVar]:
@@ -916,16 +1073,205 @@ class UIManager:
return vars_dict
def _create_medicine_section_dynamic(
self, parent: ttk.Frame, medicine_values: dict[str, int]
) -> dict[str, tk.IntVar]:
"""Create medicine checkboxes dynamically."""
vars_dict = {}
# Create a grid layout for medicines
medicine_items = []
for medicine_key, value in medicine_values.items():
medicine = self.medicine_manager.get_medicine(medicine_key)
if medicine:
medicine_items.append(
(
medicine_key,
value,
medicine.display_name,
medicine.dosage_info,
medicine.color,
)
)
# Create medicine cards in a 2-column layout
for i, (key, value, name, dose, _color) in enumerate(medicine_items):
row = i // 2
col = i % 2
# Medicine card frame
med_card = ttk.Frame(parent, relief="solid", borderwidth=1)
med_card.grid(row=row, column=col, sticky="ew", padx=5, pady=5)
parent.grid_columnconfigure(col, weight=1)
vars_dict[key] = tk.IntVar(value=int(value))
# Checkbox with medicine name
check_frame = ttk.Frame(med_card)
check_frame.pack(fill="x", padx=10, pady=8)
checkbox = ttk.Checkbutton(
check_frame,
text=f"{name} ({dose})",
variable=vars_dict[key],
style="Medicine.TCheckbutton",
)
checkbox.pack(anchor="w")
return vars_dict
def _create_dose_tracking_dynamic(
self, parent: ttk.Frame, medicine_doses: dict[str, str]
) -> dict[str, Any]:
"""Create dose tracking interface dynamically."""
vars_dict = {}
# Create notebook for organized dose tracking
notebook = ttk.Notebook(parent)
notebook.pack(fill="both", expand=True)
for medicine_key, dose_str in medicine_doses.items():
medicine = self.medicine_manager.get_medicine(medicine_key)
if not medicine:
continue
# Create tab for each medicine
tab_frame = ttk.Frame(notebook)
notebook.add(tab_frame, text=medicine.display_name)
# Configure tab layout
tab_frame.grid_columnconfigure(0, weight=1)
# Quick dose entry section
entry_frame = ttk.LabelFrame(tab_frame, text="Add New Dose", padding="10")
entry_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5)
entry_frame.grid_columnconfigure(0, weight=1)
# Dose entry
dose_entry_var = tk.StringVar()
vars_dict[f"{medicine_key}_dose_entry"] = dose_entry_var
dose_entry = ttk.Entry(entry_frame, textvariable=dose_entry_var, width=12)
dose_entry.grid(row=0, column=0, padx=5, pady=5, sticky="w")
# Quick dose buttons
quick_frame = ttk.Frame(entry_frame)
quick_frame.grid(row=0, column=1, padx=10, pady=5, sticky="w")
# Create the dose StringVar that will be used for saving
dose_string_var = tk.StringVar(value=str(dose_str))
vars_dict[f"{medicine_key}_doses"] = dose_string_var
# Punch button - updated to use the StringVar properly
def create_punch_callback(med_key, entry_var, dose_var):
def punch_dose():
dose = entry_var.get().strip()
if dose:
from datetime import datetime
timestamp = datetime.now().strftime("%H:%M")
new_dose = f"{timestamp}: {dose}"
current_doses = dose_var.get()
if current_doses and current_doses.strip():
dose_var.set(current_doses + f"\n{new_dose}")
else:
dose_var.set(new_dose)
entry_var.set("")
return punch_dose
punch_btn = ttk.Button(
quick_frame,
text=f"Take {medicine.display_name}",
command=create_punch_callback(
medicine_key, dose_entry_var, dose_string_var
),
width=15,
)
punch_btn.grid(row=0, column=0, padx=5)
# Quick dose buttons
quick_doses = self.medicine_manager.get_quick_doses(medicine_key)
for i, dose in enumerate(quick_doses[:3]): # Limit to 3 quick doses
def create_quick_callback(d, entry_var=dose_entry_var):
return lambda: entry_var.set(d)
btn = ttk.Button(
quick_frame,
text=f"{dose}mg",
command=create_quick_callback(dose),
width=8,
)
btn.grid(row=0, column=i + 1, padx=2)
# Dose history section
history_frame = ttk.LabelFrame(
tab_frame, text="Dose History (HH:MM: dose)", padding="10"
)
history_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=5)
history_frame.grid_columnconfigure(0, weight=1)
# Dose display text area
dose_text = tk.Text(
history_frame,
height=3,
width=40,
wrap=tk.WORD,
font=("Consolas", 9),
relief="solid",
borderwidth=1,
)
dose_text.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
# Populate with existing doses using the proper formatting method
self._populate_dose_history(dose_text, dose_str)
# Bind text widget to update string var - fixed closure issue
def create_update_callback(text_widget, dose_var):
def update_doses(*args):
content = text_widget.get("1.0", tk.END).strip()
dose_var.set(content)
return update_doses
update_callback = create_update_callback(dose_text, dose_string_var)
dose_text.bind("<KeyRelease>", update_callback)
dose_text.bind("<FocusOut>", update_callback)
# Also update text widget when StringVar changes (for punch button)
def create_var_to_text_callback(text_widget, string_var):
def update_text_from_var(*args):
current_text = text_widget.get("1.0", tk.END).strip()
var_content = string_var.get()
if current_text != var_content:
text_widget.delete("1.0", tk.END)
text_widget.insert("1.0", var_content)
return update_text_from_var
var_to_text_callback = create_var_to_text_callback(
dose_text, dose_string_var
)
dose_string_var.trace("w", var_to_text_callback)
# Scrollbar for dose text
dose_scroll = ttk.Scrollbar(
history_frame, orient="vertical", command=dose_text.yview
)
dose_scroll.grid(row=0, column=1, sticky="ns")
dose_text.configure(yscrollcommand=dose_scroll.set)
# Store reference to text widget for save function
vars_dict[f"{medicine_key}_dose_text"] = dose_text
return vars_dict
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."""
@@ -942,14 +1288,21 @@ class UIManager:
for dose_entry in doses_str.split("|"):
if ":" in dose_entry:
timestamp, dose = dose_entry.split(":", 1)
try:
dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
time_str = dt.strftime("%I:%M %p")
formatted_doses.append(f"{time_str} - {dose}")
except ValueError:
# Handle cases where the timestamp might be malformed
# Split on the last colon to separate timestamp from dose
parts = dose_entry.rsplit(":", 1)
if len(parts) == 2:
timestamp, dose = parts
try:
dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
time_str = dt.strftime("%I:%M %p")
formatted_doses.append(f"{time_str} - {dose}")
except ValueError:
# Handle cases where the timestamp might be malformed
formatted_doses.append(f"{dose_entry}")
else:
formatted_doses.append(f"{dose_entry}")
else:
formatted_doses.append(f"{dose_entry}")
if formatted_doses:
text_widget.insert(1.0, "\n".join(formatted_doses))
@@ -1049,51 +1402,70 @@ class UIManager:
if note_text_widget:
note_content = note_text_widget.get(1.0, tk.END).strip()
# Extract dose data from the editable text widgets
# Extract dose data dynamically from all medicines
dose_data = {}
medicine_list = [
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"quetiapine",
]
for medicine in medicine_list:
dose_text_key = f"{medicine}_doses_text"
self.logger.debug(f"Processing {medicine}...")
medicines = self.medicine_manager.get_all_medicines()
for medicine_key in medicines:
dose_var_key = f"{medicine_key}_doses"
dose_text_key = f"{medicine_key}_dose_text"
self.logger.debug(f"Processing {medicine_key}...")
if dose_text_key in vars_dict and isinstance(
vars_dict[dose_text_key], tk.Text
):
raw_text = vars_dict[dose_text_key].get(1.0, tk.END).strip()
self.logger.debug(f"Raw text for {medicine}: '{raw_text}'")
# Prioritize Text widget if it exists (it has the most current data)
if dose_text_key in vars_dict:
# Read directly from Text widget
dose_text_widget = vars_dict[dose_text_key]
raw_text = dose_text_widget.get(1.0, tk.END).strip()
self.logger.debug(
f"Raw text from Text widget for {medicine_key}: '{raw_text}'"
)
elif dose_var_key in vars_dict:
# Fall back to StringVar
if isinstance(vars_dict[dose_var_key], tk.StringVar):
raw_text = vars_dict[dose_var_key].get().strip()
elif isinstance(vars_dict[dose_var_key], tk.Text):
raw_text = vars_dict[dose_var_key].get(1.0, tk.END).strip()
else:
raw_text = str(vars_dict[dose_var_key]).strip()
self.logger.debug(
f"Raw text from StringVar for {medicine_key}: '{raw_text}'"
)
else:
raw_text = ""
self.logger.debug(f"No dose data found for {medicine_key}")
if raw_text:
parsed_dose = self._parse_dose_history_for_saving(
raw_text, vars_dict["date"].get()
)
dose_data[medicine] = parsed_dose
self.logger.debug(f"Parsed dose for {medicine}: '{parsed_dose}'")
dose_data[medicine_key] = parsed_dose
self.logger.debug(
f"Parsed dose for {medicine_key}: '{parsed_dose}'"
)
else:
self.logger.debug(f"No text widget found for {medicine}")
dose_data[medicine] = ""
dose_data[medicine_key] = ""
self.logger.debug(f"Final dose_data: {dose_data}")
callbacks["save"](
edit_win,
vars_dict["date"].get(),
vars_dict["depression"].get(),
vars_dict["anxiety"].get(),
vars_dict["sleep"].get(),
vars_dict["appetite"].get(),
vars_dict["bupropion"].get(),
vars_dict["hydroxyzine"].get(),
vars_dict["gabapentin"].get(),
vars_dict["propranolol"].get(),
vars_dict["quetiapine"].get(),
note_content,
dose_data,
# Build dynamic callback arguments
callback_args = [edit_win, vars_dict["date"].get()]
# Add pathology values
pathologies = self.pathology_manager.get_all_pathologies()
for pathology_key in pathologies:
callback_args.append(vars_dict[pathology_key].get())
# Add medicine values
medicines = self.medicine_manager.get_all_medicines()
for medicine_key in medicines:
callback_args.append(vars_dict[medicine_key].get())
# Add note and dose data
callback_args.extend([note_content, dose_data])
self.logger.debug(
f"Calling save callback with {len(callback_args)} arguments"
)
callbacks["save"](*callback_args)
save_btn = ttk.Button(
button_frame,
@@ -1159,7 +1531,16 @@ class UIManager:
# Try 24-hour format fallback
time_obj = datetime.strptime(time_part.strip(), "%H:%M")
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
# Try different date formats
try:
entry_date = datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
try:
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
except ValueError:
# If both fail, try ISO format
entry_date = datetime.fromisoformat(date_str)
full_timestamp = entry_date.replace(
hour=time_obj.hour,
minute=time_obj.minute,
@@ -1189,7 +1570,17 @@ class UIManager:
except ValueError:
# Try 12-hour format
time_obj = datetime.strptime(time_part, "%I:%M")
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
# Try different date formats
try:
entry_date = datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
try:
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
except ValueError:
# If both fail, try ISO format
entry_date = datetime.fromisoformat(date_str)
full_timestamp = entry_date.replace(
hour=time_obj.hour,
minute=time_obj.minute,
+75
View File
@@ -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,75 @@ 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 mock_pathology_manager():
"""Create a mock pathology manager with default pathologies for testing."""
mock_manager = Mock()
# Default pathologies matching the original system
mock_manager.get_pathology_keys.return_value = ["depression", "anxiety", "sleep", "appetite"]
return mock_manager
@pytest.fixture
def sample_data():
"""Sample data for testing."""
-1
View File
@@ -2,7 +2,6 @@
Tests for constants module.
"""
import os
import pytest
from unittest.mock import patch
import sys
+35 -37
View File
@@ -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, mock_pathology_manager):
"""Test DataManager initialization."""
dm = DataManager(temp_csv_file, mock_logger)
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_manager):
"""Test loading data from a nonexistent file."""
dm = DataManager("nonexistent.csv", mock_logger)
dm = DataManager("nonexistent.csv", mock_logger, mock_medicine_manager, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_manager)
df = dm.load_data()
# Check that data is sorted by date
@@ -129,10 +127,10 @@ 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, mock_pathology_manager):
"""Test successfully adding an entry."""
dm = DataManager(temp_csv_file, mock_logger)
entry = ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note"]
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
entry = ["2024-01-01", 3, 2, 4, 3, 1, "", 0, "", 2, "", 1, "", 0, "", "Test note"]
result = dm.add_entry(entry)
assert result is True
@@ -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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_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, mock_pathology_manager)
entry = ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note"]
result = dm.add_entry(entry)
-1
View File
@@ -1,5 +1,4 @@
import pytest
from datetime import datetime
import tkinter as tk
from src.ui_manager import UIManager
+1 -2
View File
@@ -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'))
-1
View File
@@ -2,7 +2,6 @@
Tests for init module.
"""
import os
import tempfile
import pytest
from unittest.mock import patch, Mock
+1 -2
View File
@@ -3,9 +3,8 @@ Tests for logger module.
"""
import os
import logging
import tempfile
import pytest
from unittest.mock import patch, Mock
from unittest.mock import patch
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
+1 -1
View File
@@ -4,7 +4,7 @@ Tests for the main application and MedTrackerApp class.
import os
import pytest
import tkinter as tk
from unittest.mock import Mock, patch, MagicMock
from unittest.mock import Mock, patch
import pandas as pd
import sys
+1 -2
View File
@@ -5,8 +5,7 @@ import os
import pytest
import tkinter as tk
from tkinter import ttk
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime
from unittest.mock import Mock, patch
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))