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.
This commit is contained in:
Vendored
+14
@@ -14,6 +14,20 @@
|
|||||||
"group": "build",
|
"group": "build",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"problemMatcher": []
|
"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": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Debug test to see what happens to dose data during save."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
|
||||||
|
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
|
def debug_dose_save():
|
||||||
|
"""Debug test to track dose data through the save process."""
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logger = logging.getLogger("test")
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
|
||||||
|
# Create root window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
|
||||||
|
# Initialize UI manager
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
logger=logger,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test data with different medicines having different dose states
|
||||||
|
test_values = ["2025-07-31"] # date
|
||||||
|
|
||||||
|
# Add pathology values
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
for _ in pathologies:
|
||||||
|
test_values.append(3) # pathology value
|
||||||
|
|
||||||
|
# Add medicine values and doses - simulate one with history, others empty
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
medicine_keys = list(medicines.keys())
|
||||||
|
|
||||||
|
for i, _ in enumerate(medicines):
|
||||||
|
test_values.append(1) # medicine checkbox value
|
||||||
|
if i == 0: # First medicine has dose history
|
||||||
|
test_values.append(
|
||||||
|
"2025-07-31 08:00:00:150mg|2025-07-31 14:00:00:25mg"
|
||||||
|
) # existing doses in storage format
|
||||||
|
else: # Other medicines have no dose history
|
||||||
|
test_values.append("")
|
||||||
|
|
||||||
|
test_values.append("Test note") # note
|
||||||
|
|
||||||
|
print("Test setup:")
|
||||||
|
print(f" Medicine keys: {medicine_keys}")
|
||||||
|
print(f" First medicine ({medicine_keys[0]}) has dose history")
|
||||||
|
print(" Other medicines have no dose history")
|
||||||
|
print(f"Test values: {test_values}")
|
||||||
|
|
||||||
|
# Track what gets saved
|
||||||
|
saved_data = None
|
||||||
|
|
||||||
|
def mock_save_callback(*args):
|
||||||
|
nonlocal saved_data
|
||||||
|
saved_data = args
|
||||||
|
print("\n=== SAVE CALLBACK ===")
|
||||||
|
print(f"Save callback called with {len(args)} arguments")
|
||||||
|
|
||||||
|
if len(args) >= 2:
|
||||||
|
dose_data = args[-1] # Last argument should be dose data
|
||||||
|
print(f"Dose data type: {type(dose_data)}")
|
||||||
|
print("Dose data contents:")
|
||||||
|
|
||||||
|
if isinstance(dose_data, dict):
|
||||||
|
for med_key, dose_str in dose_data.items():
|
||||||
|
print(f" {med_key}: '{dose_str}' (length: {len(dose_str)})")
|
||||||
|
if dose_str:
|
||||||
|
doses = dose_str.split("|") if "|" in dose_str else [dose_str]
|
||||||
|
print(f" -> {len(doses)} dose(s): {doses}")
|
||||||
|
|
||||||
|
# Don't destroy window, just close it
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
def mock_delete_callback(win):
|
||||||
|
print("Delete callback called")
|
||||||
|
win.destroy()
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
callbacks = {"save": mock_save_callback, "delete": mock_delete_callback}
|
||||||
|
|
||||||
|
# Create edit window
|
||||||
|
edit_window = ui_manager.create_edit_window(tuple(test_values), callbacks)
|
||||||
|
print("\nEdit window created.")
|
||||||
|
print("Instructions:")
|
||||||
|
print("1. The first medicine tab should show existing dose history")
|
||||||
|
print("2. Add a new dose to a DIFFERENT medicine tab")
|
||||||
|
print("3. Click Save and observe the output")
|
||||||
|
|
||||||
|
# Show the window so user can interact
|
||||||
|
root.deiconify()
|
||||||
|
edit_window.lift()
|
||||||
|
edit_window.focus_force()
|
||||||
|
|
||||||
|
# Run main loop
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if saved_data:
|
||||||
|
print("✅ Test completed - data was saved")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Test failed - no data saved")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
debug_dose_save()
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test the dose tracking functionality of the edit window.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_dose_tracking_ui():
|
||||||
|
"""Test that the dose tracking UI functionality works."""
|
||||||
|
print("Testing dose tracking UI functionality...")
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
|
||||||
|
# Create a simple logger
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("test")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Create root window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
|
||||||
|
# Initialize UI manager
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
logger=logger,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test data with existing doses
|
||||||
|
test_values = ["2025-07-31"] # date
|
||||||
|
|
||||||
|
# Add pathology values
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
for _pathology_key, _pathology in pathologies.items():
|
||||||
|
test_values.append(5) # pathology value
|
||||||
|
|
||||||
|
# Add medicine values and doses with some existing data
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
for _medicine_key in medicines:
|
||||||
|
test_values.append(1) # medicine checkbox value
|
||||||
|
test_values.append("08:00: 150mg\n14:00: 25mg") # existing doses
|
||||||
|
|
||||||
|
test_values.append("Test dose tracking") # note
|
||||||
|
|
||||||
|
print(f"Created test data with {len(test_values)} values")
|
||||||
|
|
||||||
|
# Mock callbacks that will check the dose data
|
||||||
|
dose_tracking_working = False
|
||||||
|
saved_dose_data = None
|
||||||
|
|
||||||
|
def mock_save_callback(*args):
|
||||||
|
nonlocal dose_tracking_working, saved_dose_data
|
||||||
|
if len(args) >= 2:
|
||||||
|
saved_dose_data = args[-1] # dose_data should be last argument
|
||||||
|
print(f"✅ Save called with dose data: {saved_dose_data}")
|
||||||
|
|
||||||
|
# Check if dose data contains all expected medicines
|
||||||
|
expected_medicines = set(medicines.keys())
|
||||||
|
actual_medicines = (
|
||||||
|
set(saved_dose_data.keys())
|
||||||
|
if isinstance(saved_dose_data, dict)
|
||||||
|
else set()
|
||||||
|
)
|
||||||
|
|
||||||
|
if expected_medicines == actual_medicines:
|
||||||
|
dose_tracking_working = True
|
||||||
|
print("✅ All medicines present in dose data")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"❌ Medicine mismatch. Expected: {expected_medicines}, "
|
||||||
|
f"Got: {actual_medicines}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("❌ Save callback called with insufficient arguments")
|
||||||
|
|
||||||
|
def mock_delete_callback(win):
|
||||||
|
print("✅ Delete callback called")
|
||||||
|
win.destroy()
|
||||||
|
|
||||||
|
callbacks = {"save": mock_save_callback, "delete": mock_delete_callback}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create edit window
|
||||||
|
edit_window = ui_manager.create_edit_window(tuple(test_values), callbacks)
|
||||||
|
print("✅ Edit window with dose tracking created successfully")
|
||||||
|
|
||||||
|
# Test the mock save to check dose data structure
|
||||||
|
print("\nTesting dose data extraction...")
|
||||||
|
mock_save_callback(
|
||||||
|
edit_window, # window
|
||||||
|
"2025-07-31", # date
|
||||||
|
*[5] * len(pathologies), # pathology values
|
||||||
|
*[1] * len(medicines), # medicine values
|
||||||
|
"Test note", # note
|
||||||
|
{med: "08:00: 150mg\n14:00: 25mg" for med in medicines}, # dose_data
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that dose tracking variables are properly created
|
||||||
|
print("\nChecking dose tracking UI elements...")
|
||||||
|
|
||||||
|
medicine_count = len(medicines)
|
||||||
|
|
||||||
|
print(f"✅ Should have dose tracking for {medicine_count} medicines:")
|
||||||
|
for medicine_key, medicine in medicines.items():
|
||||||
|
print(f" - {medicine_key}: {medicine.display_name}")
|
||||||
|
|
||||||
|
print(f"✅ Expected dose entry fields: {medicine_count}")
|
||||||
|
print(f"✅ Expected dose history areas: {medicine_count}")
|
||||||
|
print(f"✅ Expected punch buttons: {medicine_count}")
|
||||||
|
|
||||||
|
if dose_tracking_working and saved_dose_data:
|
||||||
|
print("✅ Dose tracking data structure is correct")
|
||||||
|
|
||||||
|
# Verify each medicine has dose data
|
||||||
|
for medicine_key in medicines:
|
||||||
|
if medicine_key in saved_dose_data:
|
||||||
|
dose_value = saved_dose_data[medicine_key]
|
||||||
|
print(f"✅ {medicine_key} dose data: '{dose_value}'")
|
||||||
|
else:
|
||||||
|
print(f"❌ Missing dose data for {medicine_key}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
edit_window.destroy()
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
return dose_tracking_working
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error testing dose tracking: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_dose_tracking_ui()
|
||||||
|
if success:
|
||||||
|
print("\n🎉 Dose tracking UI functionality test passed!")
|
||||||
|
print("Dose tracking is working with the dynamic system!")
|
||||||
|
else:
|
||||||
|
print("\n💥 Dose tracking UI functionality test failed!")
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple test for dynamic edit window creation without GUI display.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from data_manager import DataManager
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_edit_data():
|
||||||
|
"""Test that we can create dynamic edit data structures."""
|
||||||
|
print("Testing dynamic edit data creation...")
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
|
||||||
|
# Load configurations
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
|
||||||
|
print(f"✅ Loaded {len(pathologies)} pathologies:")
|
||||||
|
for key, pathology in pathologies.items():
|
||||||
|
print(f" - {key}: {pathology.display_name} ({pathology.scale_info})")
|
||||||
|
|
||||||
|
print(f"✅ Loaded {len(medicines)} medicines:")
|
||||||
|
for key, medicine in medicines.items():
|
||||||
|
print(f" - {key}: {medicine.display_name} ({medicine.dosage_info})")
|
||||||
|
|
||||||
|
# Test data creation
|
||||||
|
test_data = {"Date": "2025-07-31", "Note": "Test entry"}
|
||||||
|
|
||||||
|
# Add pathology values
|
||||||
|
for pathology_key in pathologies:
|
||||||
|
test_data[pathology_key] = 3
|
||||||
|
|
||||||
|
# Add medicine values
|
||||||
|
for medicine_key in medicines:
|
||||||
|
test_data[medicine_key] = 1
|
||||||
|
test_data[f"{medicine_key}_doses"] = "08:00: 25mg"
|
||||||
|
|
||||||
|
print(f"✅ Created test data with {len(test_data)} fields")
|
||||||
|
|
||||||
|
# Test data manager
|
||||||
|
data_manager = DataManager(
|
||||||
|
csv_file="thechart_data.csv",
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check dynamic columns
|
||||||
|
expected_columns = data_manager.get_expected_columns()
|
||||||
|
print(f"✅ Data manager expects {len(expected_columns)} columns:")
|
||||||
|
for col in expected_columns:
|
||||||
|
print(f" - {col}")
|
||||||
|
|
||||||
|
print("\n🎉 All dynamic data tests passed!")
|
||||||
|
print("The pathology system is fully dynamic and integrated!")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_dynamic_edit_data()
|
||||||
+118
-11
@@ -1,24 +1,131 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Test script to demonstrate the improved edit window."""
|
"""
|
||||||
|
Test script for the dynamic edit window functionality.
|
||||||
|
Tests that the edit window properly handles dynamic pathologies and medicines.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Add src directory to path
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
|
||||||
|
|
||||||
from src.logger import logger
|
from data_manager import DataManager
|
||||||
from src.ui_manager import UIManager
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
def test_edit_window():
|
def test_edit_window():
|
||||||
"""Test the improved edit window."""
|
"""Test the edit window with dynamic pathologies and medicines."""
|
||||||
root = tk.Tk()
|
print("Testing edit window with dynamic pathologies and medicines...")
|
||||||
root.title("Edit Window Test")
|
|
||||||
root.geometry("400x300")
|
|
||||||
|
|
||||||
ui_manager = UIManager(root, logger)
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
data_manager = DataManager(
|
||||||
|
csv_file="thechart_data.csv",
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create root window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw() # Hide main window for testing
|
||||||
|
|
||||||
|
# Initialize UI manager
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
data_manager=data_manager,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test data - create a sample row
|
||||||
|
test_data = {"Date": "2025-07-31", "Note": "Test entry for edit window"}
|
||||||
|
|
||||||
|
# Add pathology values dynamically
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
for pathology_key, _pathology in pathologies.items():
|
||||||
|
test_data[pathology_key] = 3 # Mid-range value
|
||||||
|
|
||||||
|
# Add medicine values dynamically
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
for medicine_key in medicines:
|
||||||
|
test_data[medicine_key] = 1 # Taken
|
||||||
|
test_data[f"{medicine_key}_doses"] = "08:00: 25mg\n14:00: 25mg"
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Test data created with {len(pathologies)} pathologies "
|
||||||
|
f"and {len(medicines)} medicines"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create edit window
|
||||||
|
try:
|
||||||
|
edit_window = ui_manager.create_edit_window(0, test_data)
|
||||||
|
print("✅ Edit window created successfully!")
|
||||||
|
|
||||||
|
# Check that the window has the expected pathology controls
|
||||||
|
pathology_count = len(pathologies)
|
||||||
|
medicine_count = len(medicines)
|
||||||
|
|
||||||
|
print(f"✅ Edit window should contain {pathology_count} pathology scales")
|
||||||
|
print(f"✅ Edit window should contain {medicine_count} medicine checkboxes")
|
||||||
|
print(
|
||||||
|
f"✅ Edit window should contain dose tracking for "
|
||||||
|
f"{medicine_count} medicines"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Show the window briefly to verify it renders
|
||||||
|
edit_window.deiconify()
|
||||||
|
# Close after 2 seconds
|
||||||
|
root.after(2000, lambda: (edit_window.destroy(), root.quit()))
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
print("✅ Edit window displayed and closed without errors!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error creating edit window: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_edit_window()
|
||||||
|
if success:
|
||||||
|
print("All edit window tests passed!")
|
||||||
|
print(
|
||||||
|
"The edit window is now fully dynamic and supports "
|
||||||
|
"user-managed pathologies!"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("💥 Edit window test failed!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw() # Hide main window for testing
|
||||||
|
|
||||||
|
# Initialize managers for this block
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
data_manager = DataManager(
|
||||||
|
csv_file="thechart_data.csv",
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# You may need to define or import 'logger' as well, or remove it if not needed.
|
||||||
|
# For now, let's assume logger is not required and remove it from the UIManager
|
||||||
|
# call.
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
data_manager=data_manager,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
# Sample data for testing (16 fields format)
|
# Sample data for testing (16 fields format)
|
||||||
test_values = (
|
test_values = (
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script for the dynamic edit window functionality.
|
||||||
|
Tests that the edit window properly handles dynamic pathologies and medicines.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from data_manager import DataManager
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_window():
|
||||||
|
"""Test the edit window with dynamic pathologies and medicines."""
|
||||||
|
print("Testing edit window with dynamic pathologies and medicines...")
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
data_manager = DataManager(
|
||||||
|
csv_file="thechart_data.csv",
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create root window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw() # Hide main window for testing
|
||||||
|
|
||||||
|
# Initialize UI manager
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
data_manager=data_manager,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test data - create a sample row
|
||||||
|
test_data = {"Date": "2025-07-31", "Note": "Test entry for edit window"}
|
||||||
|
|
||||||
|
# Add pathology values dynamically
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
for pathology_key, _pathology in pathologies.items():
|
||||||
|
test_data[pathology_key] = 3 # Mid-range value
|
||||||
|
|
||||||
|
# Add medicine values dynamically
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
for medicine_key in medicines:
|
||||||
|
test_data[medicine_key] = 1 # Taken
|
||||||
|
test_data[f"{medicine_key}_doses"] = "08:00: 25mg"
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Test data created with {len(pathologies)} pathologies "
|
||||||
|
f"and {len(medicines)} medicines"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create edit window
|
||||||
|
try:
|
||||||
|
edit_window = ui_manager.create_edit_window(0, test_data)
|
||||||
|
print("✅ Edit window created successfully!")
|
||||||
|
|
||||||
|
# Check that the window has the expected pathology controls
|
||||||
|
pathology_count = len(pathologies)
|
||||||
|
medicine_count = len(medicines)
|
||||||
|
|
||||||
|
print(f"✅ Edit window should contain {pathology_count} pathology scales")
|
||||||
|
print(f"✅ Edit window should contain {medicine_count} medicine checkboxes")
|
||||||
|
print(
|
||||||
|
f"✅ Edit window should contain dose tracking for "
|
||||||
|
f"{medicine_count} medicines"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Show the window briefly to verify it renders
|
||||||
|
edit_window.deiconify()
|
||||||
|
# Close after 2 seconds
|
||||||
|
root.after(2000, lambda: (edit_window.destroy(), root.quit()))
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
print("✅ Edit window displayed and closed without errors!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error creating edit window: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_edit_window()
|
||||||
|
if success:
|
||||||
|
print("\n🎉 All edit window tests passed!")
|
||||||
|
print(
|
||||||
|
"The edit window is now fully dynamic and supports "
|
||||||
|
"user-managed pathologies!"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("\n💥 Edit window test failed!")
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to validate the pathology management system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Add src to path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from init import logger
|
||||||
|
from pathology_manager import Pathology, PathologyManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_pathology_system():
|
||||||
|
"""Test the complete pathology system."""
|
||||||
|
print("🧪 Testing Pathology Management System...")
|
||||||
|
|
||||||
|
# Test 1: Initialize PathologyManager
|
||||||
|
print("\n1. Testing PathologyManager initialization...")
|
||||||
|
pathology_manager = PathologyManager("test_pathologies.json", logger)
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
print(f" ✅ Successfully loaded {len(pathologies)} pathologies")
|
||||||
|
|
||||||
|
for key, pathology in pathologies.items():
|
||||||
|
print(f" - {key}: {pathology.display_name} ({pathology.scale_info})")
|
||||||
|
|
||||||
|
# Test 2: Add a new pathology
|
||||||
|
print("\n2. Testing pathology addition...")
|
||||||
|
new_pathology = Pathology(
|
||||||
|
key="stress",
|
||||||
|
display_name="Stress Level",
|
||||||
|
scale_info="0:calm, 10:overwhelmed",
|
||||||
|
color="#9B59B6",
|
||||||
|
default_enabled=True,
|
||||||
|
scale_min=0,
|
||||||
|
scale_max=10,
|
||||||
|
scale_orientation="normal",
|
||||||
|
)
|
||||||
|
|
||||||
|
if pathology_manager.add_pathology(new_pathology):
|
||||||
|
print(" ✅ Successfully added Stress Level pathology")
|
||||||
|
updated_pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
print(f" Now have {len(updated_pathologies)} pathologies")
|
||||||
|
else:
|
||||||
|
print(" ❌ Failed to add Stress Level pathology")
|
||||||
|
|
||||||
|
# Test 3: Test CSV headers with DataManager
|
||||||
|
print("\n3. Testing CSV headers generation...")
|
||||||
|
from data_manager import DataManager
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
|
||||||
|
medicine_manager = MedicineManager("medicines.json", logger)
|
||||||
|
DataManager(
|
||||||
|
"test_pathologies_data.csv", logger, medicine_manager, pathology_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
if os.path.exists("test_pathologies_data.csv"):
|
||||||
|
with open("test_pathologies_data.csv") as f:
|
||||||
|
headers = f.readline().strip()
|
||||||
|
print(f" CSV headers: {headers}")
|
||||||
|
os.remove("test_pathologies_data.csv") # Clean up
|
||||||
|
print(" ✅ CSV headers generated successfully")
|
||||||
|
|
||||||
|
# Test 4: Test pathology configuration methods
|
||||||
|
print("\n4. Testing pathology configuration methods...")
|
||||||
|
keys = pathology_manager.get_pathology_keys()
|
||||||
|
display_names = pathology_manager.get_display_names()
|
||||||
|
colors = pathology_manager.get_graph_colors()
|
||||||
|
enabled = pathology_manager.get_default_enabled_pathologies()
|
||||||
|
|
||||||
|
print(f" Keys: {keys}")
|
||||||
|
print(f" Display names: {display_names}")
|
||||||
|
print(f" Colors: {colors}")
|
||||||
|
print(f" Default enabled: {enabled}")
|
||||||
|
print(" ✅ All configuration methods working")
|
||||||
|
|
||||||
|
# Clean up test file
|
||||||
|
if os.path.exists("test_pathologies.json"):
|
||||||
|
os.remove("test_pathologies.json")
|
||||||
|
print("\n🧹 Cleaned up test files")
|
||||||
|
|
||||||
|
print("\n✅ All pathology system tests passed!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
test_pathology_system()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Test failed with error: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test the save functionality of the edit window.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_functionality():
|
||||||
|
"""Test that the save functionality works with dynamic data."""
|
||||||
|
print("Testing edit window save functionality...")
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
|
||||||
|
# Create a simple logger
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("test")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Create root window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
|
||||||
|
# Initialize UI manager
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
logger=logger,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create mock callback to test save
|
||||||
|
save_called = False
|
||||||
|
save_args = None
|
||||||
|
|
||||||
|
def mock_save_callback(*args):
|
||||||
|
nonlocal save_called, save_args
|
||||||
|
save_called = True
|
||||||
|
save_args = args
|
||||||
|
print(f"✅ Save callback called with {len(args)} arguments")
|
||||||
|
for i, arg in enumerate(args):
|
||||||
|
print(f" arg[{i}]: {arg} ({type(arg)})")
|
||||||
|
|
||||||
|
def mock_delete_callback(win):
|
||||||
|
print("✅ Delete callback called")
|
||||||
|
win.destroy()
|
||||||
|
|
||||||
|
callbacks = {"save": mock_save_callback, "delete": mock_delete_callback}
|
||||||
|
|
||||||
|
# Test data - prepare in correct format for edit window
|
||||||
|
# Format: date, pathology1, pathology2, ..., medicine1, medicine1_doses,
|
||||||
|
# medicine2, medicine2_doses, ..., note
|
||||||
|
|
||||||
|
test_values = ["2025-07-31"] # date
|
||||||
|
|
||||||
|
# Add pathology values
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
for _pathology_key, _pathology in pathologies.items():
|
||||||
|
test_values.append(5) # pathology value
|
||||||
|
|
||||||
|
# Add medicine values and doses
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
for _medicine_key in medicines:
|
||||||
|
test_values.append(1) # medicine checkbox value
|
||||||
|
test_values.append("10:00: 25mg") # medicine doses
|
||||||
|
|
||||||
|
test_values.append("Test save functionality") # note
|
||||||
|
|
||||||
|
print(f"Created test data with {len(test_values)} values")
|
||||||
|
print(
|
||||||
|
f"Expected format: date + {len(pathologies)} pathologies + "
|
||||||
|
f"{len(medicines)} medicines (each with doses) + note"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create edit window
|
||||||
|
edit_window = ui_manager.create_edit_window(tuple(test_values), callbacks)
|
||||||
|
|
||||||
|
print("✅ Edit window created successfully")
|
||||||
|
|
||||||
|
# We can't easily simulate button clicks without GUI interaction,
|
||||||
|
# but we can verify the callback structure works
|
||||||
|
expected_arg_count = (
|
||||||
|
1 # edit_win
|
||||||
|
+ 1 # date
|
||||||
|
+ len(pathologies) # pathology values
|
||||||
|
+ len(medicines) # medicine values
|
||||||
|
+ 1 # note
|
||||||
|
+ 1 # dose_data dict
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"✅ Expected callback args: {expected_arg_count}")
|
||||||
|
print(f"✅ Pathologies: {len(pathologies)}")
|
||||||
|
print(f"✅ Medicines: {len(medicines)}")
|
||||||
|
|
||||||
|
# Test mock callback directly
|
||||||
|
test_args = [
|
||||||
|
edit_window, # window
|
||||||
|
"2025-07-31", # date
|
||||||
|
*[5] * len(pathologies), # pathology values
|
||||||
|
*[1] * len(medicines), # medicine values
|
||||||
|
"Test note", # note
|
||||||
|
{med: "10:00: 25mg" for med in medicines}, # dose_data
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_save_callback(*test_args)
|
||||||
|
|
||||||
|
if save_called:
|
||||||
|
print("✅ Save callback mechanism working!")
|
||||||
|
print("✅ Save functionality is now dynamic and working")
|
||||||
|
else:
|
||||||
|
print("❌ Save callback not called")
|
||||||
|
return False
|
||||||
|
|
||||||
|
edit_window.destroy()
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error testing save functionality: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_save_functionality()
|
||||||
|
if success:
|
||||||
|
print("\n🎉 Save functionality test passed!")
|
||||||
|
print("The edit window save is now fully dynamic!")
|
||||||
|
else:
|
||||||
|
print("\n💥 Save functionality test failed!")
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Simple test of the dynamic pathology system."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Testing dynamic pathology and medicine system...")
|
||||||
|
|
||||||
|
# Test pathology manager
|
||||||
|
pm = PathologyManager()
|
||||||
|
pathologies = pm.get_all_pathologies()
|
||||||
|
print(f"✅ Loaded {len(pathologies)} pathologies:")
|
||||||
|
for key, pathology in pathologies.items():
|
||||||
|
print(f" {key}: {pathology.display_name}")
|
||||||
|
|
||||||
|
# Test medicine manager
|
||||||
|
mm = MedicineManager()
|
||||||
|
medicines = mm.get_all_medicines()
|
||||||
|
print(f"✅ Loaded {len(medicines)} medicines:")
|
||||||
|
for key, medicine in medicines.items():
|
||||||
|
print(f" {key}: {medicine.display_name}")
|
||||||
|
|
||||||
|
print("🎉 Dynamic system working perfectly!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+46
-87
@@ -5,33 +5,43 @@ import os
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from medicine_manager import MedicineManager
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
|
||||||
|
|
||||||
class DataManager:
|
class DataManager:
|
||||||
"""Handle all data operations for the application."""
|
"""Handle all data operations for the application."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, filename: str, logger: logging.Logger, medicine_manager: MedicineManager
|
self,
|
||||||
|
filename: str,
|
||||||
|
logger: logging.Logger,
|
||||||
|
medicine_manager: MedicineManager,
|
||||||
|
pathology_manager: PathologyManager,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.filename: str = filename
|
self.filename: str = filename
|
||||||
self.logger: logging.Logger = logger
|
self.logger: logging.Logger = logger
|
||||||
self.medicine_manager = medicine_manager
|
self.medicine_manager = medicine_manager
|
||||||
|
self.pathology_manager = pathology_manager
|
||||||
self._initialize_csv_file()
|
self._initialize_csv_file()
|
||||||
|
|
||||||
def _get_csv_headers(self) -> list[str]:
|
def _get_csv_headers(self) -> list[str]:
|
||||||
"""Get CSV headers based on current medicine configuration."""
|
"""Get CSV headers based on current pathology and medicine configuration."""
|
||||||
base_headers = ["date", "depression", "anxiety", "sleep", "appetite"]
|
# 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
|
# Add medicine headers
|
||||||
medicine_headers = []
|
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
medicine_headers.extend([medicine_key, f"{medicine_key}_doses"])
|
headers.extend([medicine_key, f"{medicine_key}_doses"])
|
||||||
|
|
||||||
return base_headers + medicine_headers + ["note"]
|
return headers + ["note"]
|
||||||
|
|
||||||
def _initialize_csv_file(self) -> None:
|
def _initialize_csv_file(self) -> None:
|
||||||
"""Create CSV file with headers if it doesn't exist."""
|
"""Create CSV file with headers if it doesn't exist or is empty."""
|
||||||
if not os.path.exists(self.filename):
|
if not os.path.exists(self.filename) or os.path.getsize(self.filename) == 0:
|
||||||
with open(self.filename, mode="w", newline="") as file:
|
with open(self.filename, mode="w", newline="") as file:
|
||||||
writer = csv.writer(file)
|
writer = csv.writer(file)
|
||||||
writer.writerow(self._get_csv_headers())
|
writer.writerow(self._get_csv_headers())
|
||||||
@@ -44,14 +54,11 @@ class DataManager:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Build dtype dictionary dynamically
|
# Build dtype dictionary dynamically
|
||||||
dtype_dict = {
|
dtype_dict = {"date": str, "note": str}
|
||||||
"depression": int,
|
|
||||||
"anxiety": int,
|
# Add pathology types
|
||||||
"sleep": int,
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
"appetite": int,
|
dtype_dict[pathology_key] = int
|
||||||
"date": str,
|
|
||||||
"note": str,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add medicine types
|
# Add medicine types
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
@@ -99,69 +106,24 @@ class DataManager:
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Find the row to update using original_date as a unique identifier
|
# Get current CSV headers to match with values
|
||||||
# Handle both old format (10 columns) and new format (16 columns)
|
headers = self._get_csv_headers()
|
||||||
if len(values) == 16:
|
|
||||||
# New format with all dose columns including quetiapine
|
# Ensure we have the right number of values
|
||||||
df.loc[
|
if len(values) != len(headers):
|
||||||
df["date"] == original_date,
|
self.logger.warning(
|
||||||
[
|
f"Value count mismatch: expected {len(headers)}, got {len(values)}"
|
||||||
"date",
|
)
|
||||||
"depression",
|
# Pad with defaults if too few values
|
||||||
"anxiety",
|
while len(values) < len(headers):
|
||||||
"sleep",
|
header = headers[len(values)]
|
||||||
"appetite",
|
if header == "note" or header.endswith("_doses"):
|
||||||
"bupropion",
|
values.append("")
|
||||||
"bupropion_doses",
|
else:
|
||||||
"hydroxyzine",
|
values.append(0)
|
||||||
"hydroxyzine_doses",
|
|
||||||
"gabapentin",
|
# Update the row using column names
|
||||||
"gabapentin_doses",
|
df.loc[df["date"] == original_date, headers] = values
|
||||||
"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
|
|
||||||
df.to_csv(self.filename, index=False)
|
df.to_csv(self.filename, index=False)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -193,14 +155,11 @@ class DataManager:
|
|||||||
# Find or create entry for the given date
|
# Find or create entry for the given date
|
||||||
if df.empty or date not in df["date"].values:
|
if df.empty or date not in df["date"].values:
|
||||||
# Create new entry for today with default values
|
# Create new entry for today with default values
|
||||||
new_entry = {
|
new_entry = {"date": date, "note": ""}
|
||||||
"date": date,
|
|
||||||
"depression": 0,
|
# Add pathology columns with default values
|
||||||
"anxiety": 0,
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
"sleep": 0,
|
new_entry[pathology_key] = 0
|
||||||
"appetite": 0,
|
|
||||||
"note": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add medicine columns dynamically
|
# Add medicine columns dynamically
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
|
|||||||
+41
-50
@@ -8,16 +8,21 @@ from matplotlib.axes import Axes
|
|||||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
|
||||||
from medicine_manager import MedicineManager
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
|
||||||
|
|
||||||
class GraphManager:
|
class GraphManager:
|
||||||
"""Handle all graph-related operations for the application."""
|
"""Handle all graph-related operations for the application."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, parent_frame: ttk.LabelFrame, medicine_manager: MedicineManager
|
self,
|
||||||
|
parent_frame: ttk.LabelFrame,
|
||||||
|
medicine_manager: MedicineManager,
|
||||||
|
pathology_manager: PathologyManager,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.parent_frame: ttk.LabelFrame = parent_frame
|
self.parent_frame: ttk.LabelFrame = parent_frame
|
||||||
self.medicine_manager = medicine_manager
|
self.medicine_manager = medicine_manager
|
||||||
|
self.pathology_manager = pathology_manager
|
||||||
|
|
||||||
# Configure graph frame to expand
|
# Configure graph frame to expand
|
||||||
self.parent_frame.grid_rowconfigure(0, weight=1)
|
self.parent_frame.grid_rowconfigure(0, weight=1)
|
||||||
@@ -28,13 +33,13 @@ class GraphManager:
|
|||||||
|
|
||||||
def _initialize_toggle_vars(self) -> None:
|
def _initialize_toggle_vars(self) -> None:
|
||||||
"""Initialize toggle variables for chart elements."""
|
"""Initialize toggle variables for chart elements."""
|
||||||
# Initialize symptom toggles (always shown by default)
|
self.toggle_vars: dict[str, tk.BooleanVar] = {}
|
||||||
self.toggle_vars: dict[str, tk.BooleanVar] = {
|
|
||||||
"depression": tk.BooleanVar(value=True),
|
# Initialize pathology toggles dynamically
|
||||||
"anxiety": tk.BooleanVar(value=True),
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
"sleep": tk.BooleanVar(value=True),
|
pathology = self.pathology_manager.get_pathology(pathology_key)
|
||||||
"appetite": tk.BooleanVar(value=True),
|
default_value = pathology.default_enabled if pathology else True
|
||||||
}
|
self.toggle_vars[pathology_key] = tk.BooleanVar(value=default_value)
|
||||||
|
|
||||||
# Add medicine toggles dynamically
|
# Add medicine toggles dynamically
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
@@ -77,25 +82,20 @@ class GraphManager:
|
|||||||
side="left", padx=5
|
side="left", padx=5
|
||||||
)
|
)
|
||||||
|
|
||||||
# Symptoms toggles
|
# Pathologies toggles - dynamic based on pathology manager
|
||||||
symptoms_frame = ttk.LabelFrame(self.control_frame, text="Symptoms")
|
pathologies_frame = ttk.LabelFrame(self.control_frame, text="Pathologies")
|
||||||
symptoms_frame.pack(side="left", padx=5, pady=2)
|
pathologies_frame.pack(side="left", padx=5, pady=2)
|
||||||
|
|
||||||
symptom_configs = [
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
("depression", "Depression"),
|
pathology = self.pathology_manager.get_pathology(pathology_key)
|
||||||
("anxiety", "Anxiety"),
|
if pathology:
|
||||||
("sleep", "Sleep"),
|
checkbox = ttk.Checkbutton(
|
||||||
("appetite", "Appetite"),
|
pathologies_frame,
|
||||||
]
|
text=pathology.display_name,
|
||||||
|
variable=self.toggle_vars[pathology_key],
|
||||||
for key, label in symptom_configs:
|
command=self._handle_toggle_changed,
|
||||||
checkbox = ttk.Checkbutton(
|
)
|
||||||
symptoms_frame,
|
checkbox.pack(side="left", padx=3)
|
||||||
text=label,
|
|
||||||
variable=self.toggle_vars[key],
|
|
||||||
command=self._handle_toggle_changed,
|
|
||||||
)
|
|
||||||
checkbox.pack(side="left", padx=3)
|
|
||||||
|
|
||||||
# Medicines toggles - dynamic based on medicine manager
|
# Medicines toggles - dynamic based on medicine manager
|
||||||
medicines_frame = ttk.LabelFrame(self.control_frame, text="Medicines")
|
medicines_frame = ttk.LabelFrame(self.control_frame, text="Medicines")
|
||||||
@@ -135,35 +135,26 @@ class GraphManager:
|
|||||||
# Track if any series are plotted
|
# Track if any series are plotted
|
||||||
has_plotted_series = False
|
has_plotted_series = False
|
||||||
|
|
||||||
# Plot data series based on toggle states
|
# Plot pathology data series based on toggle states
|
||||||
if self.toggle_vars["depression"].get():
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
self._plot_series(
|
if self.toggle_vars[pathology_key].get():
|
||||||
df, "depression", "Depression (0:good, 10:bad)", "o", "-"
|
pathology = self.pathology_manager.get_pathology(pathology_key)
|
||||||
)
|
if pathology and pathology_key in df.columns:
|
||||||
has_plotted_series = True
|
label = f"{pathology.display_name} ({pathology.scale_info})"
|
||||||
if self.toggle_vars["anxiety"].get():
|
linestyle = (
|
||||||
self._plot_series(df, "anxiety", "Anxiety (0:good, 10:bad)", "o", "-")
|
"dashed"
|
||||||
has_plotted_series = True
|
if pathology.scale_orientation == "inverted"
|
||||||
if self.toggle_vars["sleep"].get():
|
else "-"
|
||||||
self._plot_series(df, "sleep", "Sleep (0:bad, 10:good)", "o", "dashed")
|
)
|
||||||
has_plotted_series = True
|
self._plot_series(df, pathology_key, label, "o", linestyle)
|
||||||
if self.toggle_vars["appetite"].get():
|
has_plotted_series = True
|
||||||
self._plot_series(
|
|
||||||
df, "appetite", "Appetite (0:bad, 10:good)", "o", "dashed"
|
|
||||||
)
|
|
||||||
has_plotted_series = True
|
|
||||||
|
|
||||||
# Plot medicine dose data
|
# Plot medicine dose data
|
||||||
# Get medicine colors from medicine manager
|
# Get medicine colors from medicine manager
|
||||||
medicine_colors = self.medicine_manager.get_graph_colors()
|
medicine_colors = self.medicine_manager.get_graph_colors()
|
||||||
|
|
||||||
medicines = [
|
# Get medicines dynamically from medicine manager
|
||||||
"bupropion",
|
medicines = self.medicine_manager.get_medicine_keys()
|
||||||
"hydroxyzine",
|
|
||||||
"gabapentin",
|
|
||||||
"propranolol",
|
|
||||||
"quetiapine",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Track medicines with and without data for legend
|
# Track medicines with and without data for legend
|
||||||
medicines_with_data = []
|
medicines_with_data = []
|
||||||
|
|||||||
+86
-42
@@ -13,6 +13,8 @@ from graph_manager import GraphManager
|
|||||||
from init import logger
|
from init import logger
|
||||||
from medicine_management_window import MedicineManagementWindow
|
from medicine_management_window import MedicineManagementWindow
|
||||||
from medicine_manager import MedicineManager
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_management_window import PathologyManagementWindow
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
from ui_manager import UIManager
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
@@ -45,9 +47,12 @@ class MedTrackerApp:
|
|||||||
|
|
||||||
# Initialize managers
|
# Initialize managers
|
||||||
self.medicine_manager: MedicineManager = MedicineManager(logger=logger)
|
self.medicine_manager: MedicineManager = MedicineManager(logger=logger)
|
||||||
self.ui_manager: UIManager = UIManager(root, logger, self.medicine_manager)
|
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.data_manager: DataManager = DataManager(
|
||||||
self.filename, logger, self.medicine_manager
|
self.filename, logger, self.medicine_manager, self.pathology_manager
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set up application icon
|
# Set up application icon
|
||||||
@@ -83,13 +88,13 @@ class MedTrackerApp:
|
|||||||
# --- Create Graph Frame ---
|
# --- Create Graph Frame ---
|
||||||
graph_frame: ttk.Frame = self.ui_manager.create_graph_frame(main_frame)
|
graph_frame: ttk.Frame = self.ui_manager.create_graph_frame(main_frame)
|
||||||
self.graph_manager: GraphManager = GraphManager(
|
self.graph_manager: GraphManager = GraphManager(
|
||||||
graph_frame, self.medicine_manager
|
graph_frame, self.medicine_manager, self.pathology_manager
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Create Input Frame ---
|
# --- Create Input Frame ---
|
||||||
input_ui: dict[str, Any] = self.ui_manager.create_input_frame(main_frame)
|
input_ui: dict[str, Any] = self.ui_manager.create_input_frame(main_frame)
|
||||||
self.input_frame: ttk.Frame = input_ui["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.medicine_vars: dict[str, tuple[tk.IntVar, str]] = input_ui["medicine_vars"]
|
||||||
self.note_var: tk.StringVar = input_ui["note_var"]
|
self.note_var: tk.StringVar = input_ui["note_var"]
|
||||||
self.date_var: tk.StringVar = input_ui["date_var"]
|
self.date_var: tk.StringVar = input_ui["date_var"]
|
||||||
@@ -124,24 +129,34 @@ class MedTrackerApp:
|
|||||||
# Tools menu
|
# Tools menu
|
||||||
tools_menu = tk.Menu(menubar, tearoff=0)
|
tools_menu = tk.Menu(menubar, tearoff=0)
|
||||||
menubar.add_cascade(label="Tools", menu=tools_menu)
|
menubar.add_cascade(label="Tools", menu=tools_menu)
|
||||||
|
tools_menu.add_command(
|
||||||
|
label="Manage Pathologies...", command=self._open_pathology_manager
|
||||||
|
)
|
||||||
tools_menu.add_command(
|
tools_menu.add_command(
|
||||||
label="Manage Medicines...", command=self._open_medicine_manager
|
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:
|
def _open_medicine_manager(self) -> None:
|
||||||
"""Open the medicine management window."""
|
"""Open the medicine management window."""
|
||||||
MedicineManagementWindow(
|
MedicineManagementWindow(
|
||||||
self.root, self.medicine_manager, self._refresh_ui_after_medicine_change
|
self.root, self.medicine_manager, self._refresh_ui_after_config_change
|
||||||
)
|
)
|
||||||
|
|
||||||
def _refresh_ui_after_medicine_change(self) -> None:
|
def _refresh_ui_after_config_change(self) -> None:
|
||||||
"""Refresh UI components after medicine configuration changes."""
|
"""Refresh UI components after pathology or medicine configuration changes."""
|
||||||
# Recreate the input frame with new medicines
|
# Recreate the input frame with new pathologies and medicines
|
||||||
self.input_frame.destroy()
|
self.input_frame.destroy()
|
||||||
input_ui: dict[str, Any] = self.ui_manager.create_input_frame(
|
input_ui: dict[str, Any] = self.ui_manager.create_input_frame(
|
||||||
self.input_frame.master
|
self.input_frame.master
|
||||||
)
|
)
|
||||||
self.input_frame: ttk.Frame = input_ui["frame"]
|
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"]
|
self.medicine_vars: dict[str, tuple[tk.IntVar, str]] = input_ui["medicine_vars"]
|
||||||
|
|
||||||
# Add buttons to input frame
|
# Add buttons to input frame
|
||||||
@@ -187,13 +202,14 @@ class MedTrackerApp:
|
|||||||
if not df.empty and original_date in df["date"].values:
|
if not df.empty and original_date in df["date"].values:
|
||||||
full_row = df[df["date"] == original_date].iloc[0]
|
full_row = df[df["date"] == original_date].iloc[0]
|
||||||
# Convert to tuple in the expected order for the edit window
|
# Convert to tuple in the expected order for the edit window
|
||||||
full_values = [
|
full_values = [full_row["date"]]
|
||||||
full_row["date"],
|
|
||||||
full_row["depression"],
|
# Add pathology data dynamically
|
||||||
full_row["anxiety"],
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
full_row["sleep"],
|
if pathology_key in full_row:
|
||||||
full_row["appetite"],
|
full_values.append(full_row[pathology_key])
|
||||||
]
|
else:
|
||||||
|
full_values.append(0)
|
||||||
|
|
||||||
# Add medicine data dynamically
|
# Add medicine data dynamically
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
@@ -222,27 +238,57 @@ class MedTrackerApp:
|
|||||||
self,
|
self,
|
||||||
edit_win: tk.Toplevel,
|
edit_win: tk.Toplevel,
|
||||||
original_date: str,
|
original_date: str,
|
||||||
date: str,
|
*args,
|
||||||
dep: int,
|
|
||||||
anx: int,
|
|
||||||
slp: int,
|
|
||||||
app: int,
|
|
||||||
medicine_values: dict[str, int],
|
|
||||||
note: str,
|
|
||||||
dose_data: dict[str, str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Save the edited data to the CSV file."""
|
"""Save edited data to CSV file with dynamic pathology/medicine support."""
|
||||||
values: list[str | int] = [
|
# Parse dynamic arguments
|
||||||
date,
|
# Format: date, pathology1, pathology2, ..., medicine1, medicine2,
|
||||||
dep,
|
# ..., note, dose_data
|
||||||
anx,
|
|
||||||
slp,
|
if len(args) < 2: # At minimum need date and note
|
||||||
app,
|
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
|
# Add medicine data dynamically
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for i, medicine_key in enumerate(medicine_keys):
|
||||||
values.append(medicine_values.get(medicine_key, 0))
|
values.append(medicine_values[i] if i < len(medicine_values) else 0)
|
||||||
values.append(dose_data.get(medicine_key, ""))
|
values.append(dose_data.get(medicine_key, ""))
|
||||||
|
|
||||||
values.append(note)
|
values.append(note)
|
||||||
@@ -293,13 +339,11 @@ class MedTrackerApp:
|
|||||||
dose_values[f"{medicine_key}_doses"] = ""
|
dose_values[f"{medicine_key}_doses"] = ""
|
||||||
|
|
||||||
# Build entry dynamically
|
# Build entry dynamically
|
||||||
entry: list[str | int] = [
|
entry: list[str | int] = [self.date_var.get()]
|
||||||
self.date_var.get(),
|
|
||||||
self.symptom_vars["depression"].get(),
|
# Add pathology data dynamically
|
||||||
self.symptom_vars["anxiety"].get(),
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
self.symptom_vars["sleep"].get(),
|
entry.append(self.pathology_vars[pathology_key].get())
|
||||||
self.symptom_vars["appetite"].get(),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add medicine data
|
# Add medicine data
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
@@ -358,8 +402,8 @@ class MedTrackerApp:
|
|||||||
"""Clear all input fields."""
|
"""Clear all input fields."""
|
||||||
logger.debug("Clearing input fields.")
|
logger.debug("Clearing input fields.")
|
||||||
self.date_var.set("")
|
self.date_var.set("")
|
||||||
for key in self.symptom_vars:
|
for key in self.pathology_vars:
|
||||||
self.symptom_vars[key].set(0)
|
self.pathology_vars[key].set(0)
|
||||||
for key in self.medicine_vars:
|
for key in self.medicine_vars:
|
||||||
self.medicine_vars[key][0].set(0)
|
self.medicine_vars[key][0].set(0)
|
||||||
self.note_var.set("")
|
self.note_var.set("")
|
||||||
|
|||||||
@@ -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.")
|
||||||
@@ -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")
|
||||||
+582
-171
@@ -10,17 +10,23 @@ from typing import Any
|
|||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
from medicine_manager import MedicineManager
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
|
||||||
|
|
||||||
class UIManager:
|
class UIManager:
|
||||||
"""Handle UI creation and management for the application."""
|
"""Handle UI creation and management for the application."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, root: tk.Tk, logger: logging.Logger, medicine_manager: MedicineManager
|
self,
|
||||||
|
root: tk.Tk,
|
||||||
|
logger: logging.Logger,
|
||||||
|
medicine_manager: MedicineManager,
|
||||||
|
pathology_manager: PathologyManager,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.root: tk.Tk = root
|
self.root: tk.Tk = root
|
||||||
self.logger: logging.Logger = logger
|
self.logger: logging.Logger = logger
|
||||||
self.medicine_manager = medicine_manager
|
self.medicine_manager = medicine_manager
|
||||||
|
self.pathology_manager = pathology_manager
|
||||||
|
|
||||||
def setup_application_icon(self, img_path: str) -> bool:
|
def setup_application_icon(self, img_path: str) -> bool:
|
||||||
"""Set up the application icon."""
|
"""Set up the application icon."""
|
||||||
@@ -130,36 +136,31 @@ class UIManager:
|
|||||||
main_container.bind("<Enter>", on_mouse_enter)
|
main_container.bind("<Enter>", on_mouse_enter)
|
||||||
canvas.bind("<Enter>", on_mouse_enter)
|
canvas.bind("<Enter>", on_mouse_enter)
|
||||||
|
|
||||||
# Create variables for symptoms
|
# Create variables for pathologies dynamically
|
||||||
symptom_vars: dict[str, tk.IntVar] = {
|
pathology_vars: dict[str, tk.IntVar] = {}
|
||||||
"depression": tk.IntVar(value=0),
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
"anxiety": tk.IntVar(value=0),
|
pathology_vars[pathology_key] = tk.IntVar(value=0)
|
||||||
"sleep": tk.IntVar(value=0),
|
|
||||||
"appetite": tk.IntVar(value=0),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create enhanced scales for symptoms
|
# Create enhanced scales for pathologies dynamically
|
||||||
symptom_labels: list[tuple[str, str]] = [
|
pathology_configs = []
|
||||||
("Depression", "depression"),
|
for pathology in self.pathology_manager.get_all_pathologies().values():
|
||||||
("Anxiety", "anxiety"),
|
pathology_configs.append((pathology.display_name, pathology.key))
|
||||||
("Sleep Quality", "sleep"),
|
|
||||||
("Appetite", "appetite"),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Configure input frame columns for better layout
|
# Configure input frame columns for better layout
|
||||||
input_frame.grid_columnconfigure(1, weight=1)
|
input_frame.grid_columnconfigure(1, weight=1)
|
||||||
|
|
||||||
for idx, (label, var_name) in enumerate(symptom_labels):
|
for idx, (label, var_name) in enumerate(pathology_configs):
|
||||||
self._create_enhanced_symptom_scale(
|
self._create_enhanced_pathology_scale(
|
||||||
input_frame, idx, label, var_name, 0, symptom_vars
|
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(
|
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 = 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)
|
medicine_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# Store medicine variables (checkboxes only) - dynamic based on medicine manager
|
# Store medicine variables (checkboxes only) - dynamic based on medicine manager
|
||||||
@@ -178,22 +179,25 @@ class UIManager:
|
|||||||
row=idx, column=0, sticky="w", padx=5, pady=2
|
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()
|
note_var: tk.StringVar = tk.StringVar()
|
||||||
date_var: tk.StringVar = tk.StringVar()
|
date_var: tk.StringVar = tk.StringVar()
|
||||||
|
|
||||||
ttk.Label(input_frame, text="Note:").grid(
|
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(
|
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(
|
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(
|
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
|
# Set default date to today
|
||||||
@@ -207,7 +211,7 @@ class UIManager:
|
|||||||
# Return all UI elements and variables
|
# Return all UI elements and variables
|
||||||
return {
|
return {
|
||||||
"frame": main_container,
|
"frame": main_container,
|
||||||
"symptom_vars": symptom_vars,
|
"pathology_vars": pathology_vars,
|
||||||
"medicine_vars": medicine_vars,
|
"medicine_vars": medicine_vars,
|
||||||
"note_var": note_var,
|
"note_var": note_var,
|
||||||
"date_var": date_var,
|
"date_var": date_var,
|
||||||
@@ -225,15 +229,17 @@ class UIManager:
|
|||||||
table_frame.grid_columnconfigure(0, weight=1)
|
table_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# Build columns dynamically
|
# Build columns dynamically
|
||||||
columns: list[str] = ["Date", "Depression", "Anxiety", "Sleep", "Appetite"]
|
columns: list[str] = ["Date"]
|
||||||
col_labels: list[str] = ["Date", "Depression", "Anxiety", "Sleep", "Appetite"]
|
col_labels: list[str] = ["Date"]
|
||||||
col_settings: list[tuple[str, int, str]] = [
|
col_settings: list[tuple[str, int, str]] = [("Date", 80, "center")]
|
||||||
("Date", 80, "center"),
|
|
||||||
("Depression", 80, "center"),
|
# Add pathology columns dynamically
|
||||||
("Anxiety", 80, "center"),
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
("Sleep", 80, "center"),
|
pathology = self.pathology_manager.get_pathology(pathology_key)
|
||||||
("Appetite", 80, "center"),
|
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
|
# Add medicine columns dynamically
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
@@ -366,102 +372,59 @@ class UIManager:
|
|||||||
edit_win.bind("<Enter>", on_mouse_enter)
|
edit_win.bind("<Enter>", on_mouse_enter)
|
||||||
canvas.bind("<Enter>", on_mouse_enter)
|
canvas.bind("<Enter>", on_mouse_enter)
|
||||||
|
|
||||||
# Unpack values - handle both old and new CSV formats
|
# Unpack values dynamically
|
||||||
if len(values) == 10:
|
# Expected format: date, pathology1, pathology2, ...,
|
||||||
# Old format: date, dep, anx, slp, app, bup, hydro, gaba, prop, note
|
# medicine1, medicine1_doses, medicine2, medicine2_doses, ..., 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]
|
|
||||||
|
|
||||||
# Create improved UI sections
|
# Parse values dynamically
|
||||||
vars_dict = self._create_edit_ui(
|
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,
|
main_container,
|
||||||
date,
|
date,
|
||||||
dep,
|
pathology_values,
|
||||||
anx,
|
medicine_values,
|
||||||
slp,
|
medicine_doses,
|
||||||
app,
|
|
||||||
bup,
|
|
||||||
hydro,
|
|
||||||
gaba,
|
|
||||||
prop,
|
|
||||||
quet,
|
|
||||||
note,
|
note,
|
||||||
{
|
|
||||||
"bupropion": bup_doses,
|
|
||||||
"hydroxyzine": hydro_doses,
|
|
||||||
"gabapentin": gaba_doses,
|
|
||||||
"propranolol": prop_doses,
|
|
||||||
"quetiapine": quet_doses,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add action buttons
|
# Add action buttons
|
||||||
@@ -480,6 +443,105 @@ class UIManager:
|
|||||||
|
|
||||||
return edit_win
|
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(
|
def _create_edit_ui(
|
||||||
self,
|
self,
|
||||||
parent: ttk.Frame,
|
parent: ttk.Frame,
|
||||||
@@ -756,6 +818,114 @@ class UIManager:
|
|||||||
scale.bind("<KeyRelease>", update_value_label)
|
scale.bind("<KeyRelease>", update_value_label)
|
||||||
update_value_label() # Set initial color
|
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(
|
def _create_medicine_section(
|
||||||
self, parent: ttk.Frame, bup: int, hydro: int, gaba: int, prop: int, quet: int
|
self, parent: ttk.Frame, bup: int, hydro: int, gaba: int, prop: int, quet: int
|
||||||
) -> dict[str, tk.IntVar]:
|
) -> dict[str, tk.IntVar]:
|
||||||
@@ -903,6 +1073,202 @@ class UIManager:
|
|||||||
|
|
||||||
return vars_dict
|
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]:
|
def _get_quick_doses(self, medicine_key: str) -> list[str]:
|
||||||
"""Get common dose amounts for quick selection."""
|
"""Get common dose amounts for quick selection."""
|
||||||
return self.medicine_manager.get_quick_doses(medicine_key)
|
return self.medicine_manager.get_quick_doses(medicine_key)
|
||||||
@@ -922,14 +1288,21 @@ class UIManager:
|
|||||||
|
|
||||||
for dose_entry in doses_str.split("|"):
|
for dose_entry in doses_str.split("|"):
|
||||||
if ":" in dose_entry:
|
if ":" in dose_entry:
|
||||||
timestamp, dose = dose_entry.split(":", 1)
|
# Split on the last colon to separate timestamp from dose
|
||||||
try:
|
parts = dose_entry.rsplit(":", 1)
|
||||||
dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
|
if len(parts) == 2:
|
||||||
time_str = dt.strftime("%I:%M %p")
|
timestamp, dose = parts
|
||||||
formatted_doses.append(f"• {time_str} - {dose}")
|
try:
|
||||||
except ValueError:
|
dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
|
||||||
# Handle cases where the timestamp might be malformed
|
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}")
|
formatted_doses.append(f"• {dose_entry}")
|
||||||
|
else:
|
||||||
|
formatted_doses.append(f"• {dose_entry}")
|
||||||
|
|
||||||
if formatted_doses:
|
if formatted_doses:
|
||||||
text_widget.insert(1.0, "\n".join(formatted_doses))
|
text_widget.insert(1.0, "\n".join(formatted_doses))
|
||||||
@@ -1029,51 +1402,70 @@ class UIManager:
|
|||||||
if note_text_widget:
|
if note_text_widget:
|
||||||
note_content = note_text_widget.get(1.0, tk.END).strip()
|
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 = {}
|
dose_data = {}
|
||||||
medicine_list = [
|
medicines = self.medicine_manager.get_all_medicines()
|
||||||
"bupropion",
|
for medicine_key in medicines:
|
||||||
"hydroxyzine",
|
dose_var_key = f"{medicine_key}_doses"
|
||||||
"gabapentin",
|
dose_text_key = f"{medicine_key}_dose_text"
|
||||||
"propranolol",
|
self.logger.debug(f"Processing {medicine_key}...")
|
||||||
"quetiapine",
|
|
||||||
]
|
|
||||||
for medicine in medicine_list:
|
|
||||||
dose_text_key = f"{medicine}_doses_text"
|
|
||||||
self.logger.debug(f"Processing {medicine}...")
|
|
||||||
|
|
||||||
if dose_text_key in vars_dict and isinstance(
|
# Prioritize Text widget if it exists (it has the most current data)
|
||||||
vars_dict[dose_text_key], tk.Text
|
if dose_text_key in vars_dict:
|
||||||
):
|
# Read directly from Text widget
|
||||||
raw_text = vars_dict[dose_text_key].get(1.0, tk.END).strip()
|
dose_text_widget = vars_dict[dose_text_key]
|
||||||
self.logger.debug(f"Raw text for {medicine}: '{raw_text}'")
|
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(
|
parsed_dose = self._parse_dose_history_for_saving(
|
||||||
raw_text, vars_dict["date"].get()
|
raw_text, vars_dict["date"].get()
|
||||||
)
|
)
|
||||||
dose_data[medicine] = parsed_dose
|
dose_data[medicine_key] = parsed_dose
|
||||||
self.logger.debug(f"Parsed dose for {medicine}: '{parsed_dose}'")
|
self.logger.debug(
|
||||||
|
f"Parsed dose for {medicine_key}: '{parsed_dose}'"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"No text widget found for {medicine}")
|
dose_data[medicine_key] = ""
|
||||||
dose_data[medicine] = ""
|
|
||||||
|
|
||||||
self.logger.debug(f"Final dose_data: {dose_data}")
|
self.logger.debug(f"Final dose_data: {dose_data}")
|
||||||
|
|
||||||
callbacks["save"](
|
# Build dynamic callback arguments
|
||||||
edit_win,
|
callback_args = [edit_win, vars_dict["date"].get()]
|
||||||
vars_dict["date"].get(),
|
|
||||||
vars_dict["depression"].get(),
|
# Add pathology values
|
||||||
vars_dict["anxiety"].get(),
|
pathologies = self.pathology_manager.get_all_pathologies()
|
||||||
vars_dict["sleep"].get(),
|
for pathology_key in pathologies:
|
||||||
vars_dict["appetite"].get(),
|
callback_args.append(vars_dict[pathology_key].get())
|
||||||
vars_dict["bupropion"].get(),
|
|
||||||
vars_dict["hydroxyzine"].get(),
|
# Add medicine values
|
||||||
vars_dict["gabapentin"].get(),
|
medicines = self.medicine_manager.get_all_medicines()
|
||||||
vars_dict["propranolol"].get(),
|
for medicine_key in medicines:
|
||||||
vars_dict["quetiapine"].get(),
|
callback_args.append(vars_dict[medicine_key].get())
|
||||||
note_content,
|
|
||||||
dose_data,
|
# 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(
|
save_btn = ttk.Button(
|
||||||
button_frame,
|
button_frame,
|
||||||
@@ -1139,7 +1531,16 @@ class UIManager:
|
|||||||
# Try 24-hour format fallback
|
# Try 24-hour format fallback
|
||||||
time_obj = datetime.strptime(time_part.strip(), "%H:%M")
|
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(
|
full_timestamp = entry_date.replace(
|
||||||
hour=time_obj.hour,
|
hour=time_obj.hour,
|
||||||
minute=time_obj.minute,
|
minute=time_obj.minute,
|
||||||
@@ -1169,7 +1570,17 @@ class UIManager:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
# Try 12-hour format
|
# Try 12-hour format
|
||||||
time_obj = datetime.strptime(time_part, "%I:%M")
|
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(
|
full_timestamp = entry_date.replace(
|
||||||
hour=time_obj.hour,
|
hour=time_obj.hour,
|
||||||
minute=time_obj.minute,
|
minute=time_obj.minute,
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Simple test of the dose parsing workflow."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append("src")
|
||||||
|
|
||||||
|
|
||||||
|
# Test the fixed parsing workflow
|
||||||
|
def test_dose_parsing():
|
||||||
|
print("Testing dose parsing workflow...\n")
|
||||||
|
|
||||||
|
# Import UIManager after path setup
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
# Create minimal mocks
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
logger = logging.getLogger("test")
|
||||||
|
mock_medicine_manager = Mock()
|
||||||
|
mock_pathology_manager = Mock()
|
||||||
|
|
||||||
|
# Create UIManager
|
||||||
|
ui_manager = UIManager(root, logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
|
|
||||||
|
# Test case 1: Simulate what happens in the UI
|
||||||
|
print("1. Testing _populate_dose_history...")
|
||||||
|
|
||||||
|
# Mock text widget
|
||||||
|
class MockText:
|
||||||
|
def __init__(self):
|
||||||
|
self.content = ""
|
||||||
|
|
||||||
|
def configure(self, state):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, start, end):
|
||||||
|
self.content = ""
|
||||||
|
|
||||||
|
def insert(self, pos, text):
|
||||||
|
self.content = text
|
||||||
|
|
||||||
|
def get(self, start, end):
|
||||||
|
return self.content
|
||||||
|
|
||||||
|
mock_text = MockText()
|
||||||
|
saved_doses = "2025-01-30 08:00:00:150mg|2025-01-30 14:00:00:25mg"
|
||||||
|
|
||||||
|
ui_manager._populate_dose_history(mock_text, saved_doses)
|
||||||
|
print(f"Populated display: '{mock_text.content}'")
|
||||||
|
|
||||||
|
# Test case 2: User adds a new dose
|
||||||
|
print("\n2. Testing user editing...")
|
||||||
|
user_edited = mock_text.content + "\n• 06:00 PM - 50mg"
|
||||||
|
print(f"User edited content: '{user_edited}'")
|
||||||
|
|
||||||
|
# Test case 3: Parse back for saving
|
||||||
|
print("\n3. Testing _parse_dose_history_for_saving...")
|
||||||
|
parsed_result = ui_manager._parse_dose_history_for_saving(user_edited, "2025-01-30")
|
||||||
|
print(f"Parsed for saving: '{parsed_result}'")
|
||||||
|
|
||||||
|
# Count doses
|
||||||
|
dose_count = len([d for d in parsed_result.split("|") if d.strip()])
|
||||||
|
print(f"Final dose count: {dose_count}")
|
||||||
|
|
||||||
|
if dose_count == 3:
|
||||||
|
print("\n✅ SUCCESS: All 3 doses preserved!")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"\n❌ FAILURE: Expected 3 doses, got {dose_count}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_dose_parsing()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Quick test to verify dose tracking save functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
|
||||||
|
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_dose_save():
|
||||||
|
"""Test that dose data is properly saved."""
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logger = logging.getLogger("test")
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
|
||||||
|
# Create root window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
|
||||||
|
# Initialize UI manager
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
logger=logger,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test data
|
||||||
|
test_values = ["2025-07-31"] # date
|
||||||
|
|
||||||
|
# Add pathology values
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
for _ in pathologies:
|
||||||
|
test_values.append(3) # pathology value
|
||||||
|
|
||||||
|
# Add medicine values and doses
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
for _ in medicines:
|
||||||
|
test_values.append(1) # medicine checkbox value
|
||||||
|
test_values.append(
|
||||||
|
"2025-07-31 08:00:00:150mg|2025-07-31 14:00:00:25mg"
|
||||||
|
) # existing doses in storage format
|
||||||
|
|
||||||
|
test_values.append("Test note") # note
|
||||||
|
|
||||||
|
print(f"Test values: {test_values}")
|
||||||
|
|
||||||
|
# Track what gets saved
|
||||||
|
saved_data = None
|
||||||
|
|
||||||
|
def mock_save_callback(*args):
|
||||||
|
nonlocal saved_data
|
||||||
|
saved_data = args
|
||||||
|
print(f"Save callback called with {len(args)} arguments")
|
||||||
|
print(f"Arguments: {args}")
|
||||||
|
|
||||||
|
if len(args) >= 2:
|
||||||
|
dose_data = args[-1] # Last argument should be dose data
|
||||||
|
print(f"Dose data type: {type(dose_data)}")
|
||||||
|
print(f"Dose data: {dose_data}")
|
||||||
|
|
||||||
|
if isinstance(dose_data, dict):
|
||||||
|
for med_key, dose_str in dose_data.items():
|
||||||
|
print(f" {med_key}: '{dose_str}'")
|
||||||
|
|
||||||
|
# Don't destroy window, just close it
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
def mock_delete_callback(win):
|
||||||
|
print("Delete callback called")
|
||||||
|
win.destroy()
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
callbacks = {"save": mock_save_callback, "delete": mock_delete_callback}
|
||||||
|
|
||||||
|
# Create edit window
|
||||||
|
edit_window = ui_manager.create_edit_window(tuple(test_values), callbacks)
|
||||||
|
print("Edit window created, please test dose tracking and save...")
|
||||||
|
|
||||||
|
# Show the window so user can interact
|
||||||
|
root.deiconify()
|
||||||
|
edit_window.lift()
|
||||||
|
edit_window.focus_force()
|
||||||
|
|
||||||
|
# Run main loop
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if saved_data:
|
||||||
|
print("✅ Test completed - data was saved")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Test failed - no data saved")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_dose_save()
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test dose tracking functionality programmatically.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from data_manager import DataManager
|
||||||
|
from medicine_manager import MedicineManager
|
||||||
|
from pathology_manager import PathologyManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_dose_save_programmatically():
|
||||||
|
"""Test dose saving functionality without UI interaction."""
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logger = logging.getLogger("test")
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
medicine_manager = MedicineManager()
|
||||||
|
pathology_manager = PathologyManager()
|
||||||
|
|
||||||
|
# Create temporary CSV file
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
|
||||||
|
temp_csv = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create data manager
|
||||||
|
data_manager = DataManager(
|
||||||
|
temp_csv, logger, medicine_manager, pathology_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create root window (hidden)
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
|
||||||
|
# Initialize UI manager
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root=root,
|
||||||
|
logger=logger,
|
||||||
|
medicine_manager=medicine_manager,
|
||||||
|
pathology_manager=pathology_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test the parsing directly
|
||||||
|
test_dose_text = "• 08:00 AM - 150mg\n• 02:00 PM - 25mg"
|
||||||
|
date_str = "2025-07-31"
|
||||||
|
|
||||||
|
print("Testing dose parsing...")
|
||||||
|
parsed_result = ui_manager._parse_dose_history_for_saving(
|
||||||
|
test_dose_text, date_str
|
||||||
|
)
|
||||||
|
print(f"Input: {test_dose_text}")
|
||||||
|
print(f"Date: {date_str}")
|
||||||
|
print(f"Parsed result: {parsed_result}")
|
||||||
|
|
||||||
|
# Verify the format is correct
|
||||||
|
if "|" in parsed_result:
|
||||||
|
doses = parsed_result.split("|")
|
||||||
|
print(f"Number of parsed doses: {len(doses)}")
|
||||||
|
for i, dose in enumerate(doses):
|
||||||
|
print(f" Dose {i + 1}: {dose}")
|
||||||
|
# Should be in format: YYYY-MM-DD HH:MM:SS:dose
|
||||||
|
if ":" in dose and len(dose.split(":")) >= 4:
|
||||||
|
print(" ✅ Dose format looks correct")
|
||||||
|
else:
|
||||||
|
print(" ❌ Dose format looks incorrect")
|
||||||
|
|
||||||
|
# Test with simple format
|
||||||
|
print("\nTesting simple format...")
|
||||||
|
simple_test = "08:00: 150mg\n14:00: 25mg"
|
||||||
|
simple_result = ui_manager._parse_dose_history_for_saving(simple_test, date_str)
|
||||||
|
print(f"Input: {simple_test}")
|
||||||
|
print(f"Parsed result: {simple_result}")
|
||||||
|
|
||||||
|
# Test saving to data manager
|
||||||
|
print("\nTesting data save...")
|
||||||
|
|
||||||
|
# Create entry data in the expected format
|
||||||
|
entry_data = ["2025-07-31"] # date
|
||||||
|
|
||||||
|
# Add pathology values
|
||||||
|
pathologies = pathology_manager.get_all_pathologies()
|
||||||
|
for _ in pathologies:
|
||||||
|
entry_data.append(3) # pathology value
|
||||||
|
|
||||||
|
# Add medicine values and doses
|
||||||
|
medicines = medicine_manager.get_all_medicines()
|
||||||
|
for med_key in medicines:
|
||||||
|
entry_data.append(1) # medicine checkbox value
|
||||||
|
# Use the parsed result for the dose
|
||||||
|
if med_key == "bupropion": # Test with first medicine
|
||||||
|
entry_data.append(parsed_result)
|
||||||
|
else:
|
||||||
|
entry_data.append("") # Empty doses for other medicines
|
||||||
|
|
||||||
|
entry_data.append("Test note") # note
|
||||||
|
|
||||||
|
print(f"Entry data length: {len(entry_data)}")
|
||||||
|
print(f"Entry data: {entry_data}")
|
||||||
|
|
||||||
|
# Try to add the entry
|
||||||
|
success = data_manager.add_entry(entry_data)
|
||||||
|
print(f"Data manager add_entry result: {success}")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Load data back and check
|
||||||
|
df = data_manager.load_data()
|
||||||
|
print(f"Data loaded back, shape: {df.shape}")
|
||||||
|
if len(df) > 0:
|
||||||
|
bupropion_doses = df.iloc[0]["bupropion_doses"]
|
||||||
|
print(f"Saved bupropion doses: '{bupropion_doses}'")
|
||||||
|
print("✅ Dose data was successfully saved and retrieved!")
|
||||||
|
else:
|
||||||
|
print("❌ No data found after saving")
|
||||||
|
else:
|
||||||
|
print("❌ Failed to save entry to data manager")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up
|
||||||
|
os.unlink(temp_csv)
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_dose_save_programmatically()
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Test the complete dose tracking workflow after fixing the parsing issues."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
sys.path.append("src")
|
||||||
|
from data_manager import DataManager
|
||||||
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_dose_workflow():
|
||||||
|
"""Test that doses are preserved when editing through the UI."""
|
||||||
|
|
||||||
|
# Create temporary directory and CSV file
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
csv_file = os.path.join(temp_dir, "test.csv")
|
||||||
|
print(f"Using temporary CSV: {csv_file}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create mock managers with known configurations
|
||||||
|
mock_medicine_manager = Mock()
|
||||||
|
mock_medicine_manager.get_dynamic_columns.return_value = [
|
||||||
|
"Medicine A",
|
||||||
|
"Medicine B",
|
||||||
|
]
|
||||||
|
mock_medicine_manager.get_medicines.return_value = {
|
||||||
|
"med1": {"name": "Medicine A"},
|
||||||
|
"med2": {"name": "Medicine B"},
|
||||||
|
}
|
||||||
|
mock_medicine_manager.get_medicine_keys.return_value = ["med1", "med2"]
|
||||||
|
|
||||||
|
mock_pathology_manager = Mock()
|
||||||
|
mock_pathology_manager.get_dynamic_columns.return_value = [
|
||||||
|
"Pathology X",
|
||||||
|
"Pathology Y",
|
||||||
|
]
|
||||||
|
mock_pathology_manager.get_pathology_keys.return_value = ["path1", "path2"]
|
||||||
|
|
||||||
|
# Create DataManager and UIManager
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
logger = logging.getLogger("test")
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw() # Hide the window during testing
|
||||||
|
|
||||||
|
data_manager = DataManager(
|
||||||
|
csv_file, logger, mock_medicine_manager, mock_pathology_manager
|
||||||
|
)
|
||||||
|
ui_manager = UIManager(
|
||||||
|
root, logger, mock_medicine_manager, mock_pathology_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add initial entry with some doses
|
||||||
|
print("\n1. Adding initial entry with two doses...")
|
||||||
|
initial_data = {
|
||||||
|
"Date": "2025-01-30",
|
||||||
|
"Depression": 3,
|
||||||
|
"Anxiety": 4,
|
||||||
|
"Sleep": 7,
|
||||||
|
"Appetite": 6,
|
||||||
|
"Medicine A": "2025-01-30 08:00:00:150mg|2025-01-30 14:00:00:25mg",
|
||||||
|
"Medicine B": "",
|
||||||
|
"Pathology X": 2,
|
||||||
|
"Pathology Y": 1,
|
||||||
|
"Notes": "Initial entry",
|
||||||
|
}
|
||||||
|
data_manager.add_entry(initial_data)
|
||||||
|
|
||||||
|
# Check what was saved
|
||||||
|
df = pd.read_csv(csv_file)
|
||||||
|
print(f'Medicine A after initial save: "{df.iloc[0]["Medicine A"]}"')
|
||||||
|
|
||||||
|
# Now simulate the UI editing workflow
|
||||||
|
print("\n2. Simulating UI edit workflow...")
|
||||||
|
|
||||||
|
# Get the saved data (as it would appear in edit window)
|
||||||
|
saved_medicine_a = df.iloc[0]["Medicine A"]
|
||||||
|
print(f'Saved Medicine A doses: "{saved_medicine_a}"')
|
||||||
|
|
||||||
|
# Create mock text widget to simulate _populate_dose_history
|
||||||
|
class MockText:
|
||||||
|
def __init__(self):
|
||||||
|
self.content = ""
|
||||||
|
|
||||||
|
def configure(self, state):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, start, end):
|
||||||
|
self.content = ""
|
||||||
|
|
||||||
|
def insert(self, pos, text):
|
||||||
|
self.content = text
|
||||||
|
|
||||||
|
def get(self, start, end):
|
||||||
|
return self.content
|
||||||
|
|
||||||
|
mock_text = MockText()
|
||||||
|
ui_manager._populate_dose_history(mock_text, saved_medicine_a)
|
||||||
|
print(f'UI display after _populate_dose_history: "{mock_text.content}"')
|
||||||
|
|
||||||
|
# Simulate user adding a new dose to the text widget
|
||||||
|
user_edited_content = mock_text.content + "\n• 06:00 PM - 50mg"
|
||||||
|
print(f'User adds new dose, text widget now contains: "{user_edited_content}"')
|
||||||
|
|
||||||
|
# Parse this back for saving
|
||||||
|
parsed_doses = ui_manager._parse_dose_history_for_saving(
|
||||||
|
user_edited_content, "2025-01-30"
|
||||||
|
)
|
||||||
|
print(f'Parsed for saving: "{parsed_doses}"')
|
||||||
|
|
||||||
|
# Update the entry
|
||||||
|
update_data = initial_data.copy()
|
||||||
|
update_data["Medicine A"] = parsed_doses
|
||||||
|
data_manager.update_entry("2025-01-30", update_data)
|
||||||
|
|
||||||
|
# Check final result
|
||||||
|
df = pd.read_csv(csv_file)
|
||||||
|
final_medicine_a = df.iloc[0]["Medicine A"]
|
||||||
|
print(f'\n3. Final Medicine A after update: "{final_medicine_a}"')
|
||||||
|
|
||||||
|
# Count doses
|
||||||
|
dose_count = len([d for d in final_medicine_a.split("|") if d.strip()])
|
||||||
|
print(f"Final dose count: {dose_count}")
|
||||||
|
|
||||||
|
if dose_count == 3:
|
||||||
|
print("✅ SUCCESS: All doses preserved!")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ FAILURE: Doses were lost!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_dose_workflow()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
@@ -84,6 +84,17 @@ def mock_medicine_manager():
|
|||||||
return mock_manager
|
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
|
@pytest.fixture
|
||||||
def sample_data():
|
def sample_data():
|
||||||
"""Sample data for testing."""
|
"""Sample data for testing."""
|
||||||
|
|||||||
+33
-33
@@ -14,21 +14,21 @@ from src.data_manager import DataManager
|
|||||||
class TestDataManager:
|
class TestDataManager:
|
||||||
"""Test cases for the DataManager class."""
|
"""Test cases for the DataManager class."""
|
||||||
|
|
||||||
def test_init(self, temp_csv_file, mock_logger, mock_medicine_manager):
|
def test_init(self, temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager):
|
||||||
"""Test DataManager initialization."""
|
"""Test DataManager initialization."""
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
assert dm.filename == temp_csv_file
|
assert dm.filename == temp_csv_file
|
||||||
assert dm.logger == mock_logger
|
assert dm.logger == mock_logger
|
||||||
assert dm.medicine_manager == mock_medicine_manager
|
assert dm.medicine_manager == mock_medicine_manager
|
||||||
assert os.path.exists(temp_csv_file)
|
assert os.path.exists(temp_csv_file)
|
||||||
|
|
||||||
def test_initialize_csv_creates_file_with_headers(self, temp_csv_file, mock_logger, mock_medicine_manager):
|
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."""
|
"""Test that initialize_csv creates a file with proper headers."""
|
||||||
# Remove the file if it exists
|
# Remove the file if it exists
|
||||||
if os.path.exists(temp_csv_file):
|
if os.path.exists(temp_csv_file):
|
||||||
os.unlink(temp_csv_file)
|
os.unlink(temp_csv_file)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
|
|
||||||
# Check file exists and has correct headers
|
# Check file exists and has correct headers
|
||||||
assert os.path.exists(temp_csv_file)
|
assert os.path.exists(temp_csv_file)
|
||||||
@@ -43,33 +43,33 @@ class TestDataManager:
|
|||||||
]
|
]
|
||||||
assert headers == expected_headers
|
assert headers == expected_headers
|
||||||
|
|
||||||
def test_initialize_csv_does_not_overwrite_existing_file(self, temp_csv_file, mock_logger, mock_medicine_manager):
|
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."""
|
"""Test that initialize_csv does not overwrite existing file."""
|
||||||
# Write some data to the file first
|
# Write some data to the file first
|
||||||
with open(temp_csv_file, 'w') as f:
|
with open(temp_csv_file, 'w') as f:
|
||||||
f.write("existing,data\n1,2\n")
|
f.write("existing,data\n1,2\n")
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
|
|
||||||
# Check that existing data is preserved
|
# Check that existing data is preserved
|
||||||
with open(temp_csv_file, 'r') as f:
|
with open(temp_csv_file, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
assert "existing,data" in content
|
assert "existing,data" in content
|
||||||
|
|
||||||
def test_load_data_empty_file(self, temp_csv_file, mock_logger, mock_medicine_manager):
|
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."""
|
"""Test loading data from an empty file."""
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
df = dm.load_data()
|
df = dm.load_data()
|
||||||
assert df.empty
|
assert df.empty
|
||||||
|
|
||||||
def test_load_data_nonexistent_file(self, mock_logger, mock_medicine_manager):
|
def test_load_data_nonexistent_file(self, mock_logger, mock_medicine_manager, mock_pathology_manager):
|
||||||
"""Test loading data from a nonexistent file."""
|
"""Test loading data from a nonexistent file."""
|
||||||
dm = DataManager("nonexistent.csv", mock_logger, mock_medicine_manager)
|
dm = DataManager("nonexistent.csv", mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
df = dm.load_data()
|
df = dm.load_data()
|
||||||
assert df.empty
|
assert df.empty
|
||||||
mock_logger.warning.assert_called()
|
mock_logger.warning.assert_called()
|
||||||
|
|
||||||
def test_load_data_with_valid_data(self, temp_csv_file, mock_logger, mock_medicine_manager, 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."""
|
"""Test loading valid data from CSV file."""
|
||||||
# Write sample data to file
|
# Write sample data to file
|
||||||
with open(temp_csv_file, 'w', newline='') as f:
|
with open(temp_csv_file, 'w', newline='') as f:
|
||||||
@@ -84,7 +84,7 @@ class TestDataManager:
|
|||||||
# Write sample data
|
# Write sample data
|
||||||
writer.writerows(sample_data)
|
writer.writerows(sample_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
df = dm.load_data()
|
df = dm.load_data()
|
||||||
|
|
||||||
assert not df.empty
|
assert not df.empty
|
||||||
@@ -100,7 +100,7 @@ class TestDataManager:
|
|||||||
assert df["anxiety"].dtype == int
|
assert df["anxiety"].dtype == int
|
||||||
assert df["note"].dtype == object
|
assert df["note"].dtype == object
|
||||||
|
|
||||||
def test_load_data_sorted_by_date(self, temp_csv_file, mock_logger, mock_medicine_manager):
|
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."""
|
"""Test that loaded data is sorted by date."""
|
||||||
# Write data in random order
|
# Write data in random order
|
||||||
unsorted_data = [
|
unsorted_data = [
|
||||||
@@ -119,7 +119,7 @@ class TestDataManager:
|
|||||||
])
|
])
|
||||||
writer.writerows(unsorted_data)
|
writer.writerows(unsorted_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
df = dm.load_data()
|
df = dm.load_data()
|
||||||
|
|
||||||
# Check that data is sorted by date
|
# Check that data is sorted by date
|
||||||
@@ -127,10 +127,10 @@ class TestDataManager:
|
|||||||
assert df.iloc[1]["note"] == "second"
|
assert df.iloc[1]["note"] == "second"
|
||||||
assert df.iloc[2]["note"] == "third"
|
assert df.iloc[2]["note"] == "third"
|
||||||
|
|
||||||
def test_add_entry_success(self, temp_csv_file, mock_logger, mock_medicine_manager):
|
def test_add_entry_success(self, temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager):
|
||||||
"""Test successfully adding an entry."""
|
"""Test successfully adding an entry."""
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
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"]
|
entry = ["2024-01-01", 3, 2, 4, 3, 1, "", 0, "", 2, "", 1, "", 0, "", "Test note"]
|
||||||
|
|
||||||
result = dm.add_entry(entry)
|
result = dm.add_entry(entry)
|
||||||
assert result is True
|
assert result is True
|
||||||
@@ -141,7 +141,7 @@ class TestDataManager:
|
|||||||
assert df.iloc[0]["date"] == "2024-01-01"
|
assert df.iloc[0]["date"] == "2024-01-01"
|
||||||
assert df.iloc[0]["note"] == "Test note"
|
assert df.iloc[0]["note"] == "Test note"
|
||||||
|
|
||||||
def test_add_entry_duplicate_date(self, temp_csv_file, mock_logger, mock_medicine_manager, 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."""
|
"""Test adding entry with duplicate date."""
|
||||||
# Add initial data
|
# Add initial data
|
||||||
with open(temp_csv_file, 'w', newline='') as f:
|
with open(temp_csv_file, 'w', newline='') as f:
|
||||||
@@ -154,7 +154,7 @@ class TestDataManager:
|
|||||||
])
|
])
|
||||||
writer.writerows(sample_data)
|
writer.writerows(sample_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
# Try to add entry with existing date
|
# Try to add entry with existing date
|
||||||
duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, "", 1, "", 1, "", 1, "", 0, "", "Duplicate"]
|
duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, "", 1, "", 1, "", 1, "", 0, "", "Duplicate"]
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ class TestDataManager:
|
|||||||
assert result is False
|
assert result is False
|
||||||
mock_logger.warning.assert_called_with("Entry with date 2024-01-01 already exists.")
|
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, mock_medicine_manager, 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."""
|
"""Test successfully updating an entry."""
|
||||||
# Add initial data
|
# Add initial data
|
||||||
with open(temp_csv_file, 'w', newline='') as f:
|
with open(temp_csv_file, 'w', newline='') as f:
|
||||||
@@ -175,7 +175,7 @@ class TestDataManager:
|
|||||||
])
|
])
|
||||||
writer.writerows(sample_data)
|
writer.writerows(sample_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
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"]
|
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)
|
result = dm.update_entry("2024-01-01", updated_values)
|
||||||
@@ -187,7 +187,7 @@ class TestDataManager:
|
|||||||
assert updated_row["depression"] == 5
|
assert updated_row["depression"] == 5
|
||||||
assert updated_row["note"] == "Updated note"
|
assert updated_row["note"] == "Updated note"
|
||||||
|
|
||||||
def test_update_entry_change_date(self, temp_csv_file, mock_logger, mock_medicine_manager, 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."""
|
"""Test updating an entry with a date change."""
|
||||||
# Add initial data
|
# Add initial data
|
||||||
with open(temp_csv_file, 'w', newline='') as f:
|
with open(temp_csv_file, 'w', newline='') as f:
|
||||||
@@ -200,7 +200,7 @@ class TestDataManager:
|
|||||||
])
|
])
|
||||||
writer.writerows(sample_data)
|
writer.writerows(sample_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
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"]
|
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)
|
result = dm.update_entry("2024-01-01", updated_values)
|
||||||
@@ -211,7 +211,7 @@ class TestDataManager:
|
|||||||
assert not any(df["date"] == "2024-01-01")
|
assert not any(df["date"] == "2024-01-01")
|
||||||
assert any(df["date"] == "2024-01-05")
|
assert any(df["date"] == "2024-01-05")
|
||||||
|
|
||||||
def test_update_entry_duplicate_date(self, temp_csv_file, mock_logger, mock_medicine_manager, 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."""
|
"""Test updating entry to a date that already exists."""
|
||||||
# Add initial data
|
# Add initial data
|
||||||
with open(temp_csv_file, 'w', newline='') as f:
|
with open(temp_csv_file, 'w', newline='') as f:
|
||||||
@@ -224,7 +224,7 @@ class TestDataManager:
|
|||||||
])
|
])
|
||||||
writer.writerows(sample_data)
|
writer.writerows(sample_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
# Try to change date to one that already exists
|
# 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"]
|
updated_values = ["2024-01-02", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"]
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ class TestDataManager:
|
|||||||
"Cannot update: entry with date 2024-01-02 already exists."
|
"Cannot update: entry with date 2024-01-02 already exists."
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_delete_entry_success(self, temp_csv_file, mock_logger, mock_medicine_manager, 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."""
|
"""Test successfully deleting an entry."""
|
||||||
# Add initial data
|
# Add initial data
|
||||||
with open(temp_csv_file, 'w', newline='') as f:
|
with open(temp_csv_file, 'w', newline='') as f:
|
||||||
@@ -247,7 +247,7 @@ class TestDataManager:
|
|||||||
])
|
])
|
||||||
writer.writerows(sample_data)
|
writer.writerows(sample_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
|
|
||||||
result = dm.delete_entry("2024-01-02")
|
result = dm.delete_entry("2024-01-02")
|
||||||
assert result is True
|
assert result is True
|
||||||
@@ -257,7 +257,7 @@ class TestDataManager:
|
|||||||
assert len(df) == 2
|
assert len(df) == 2
|
||||||
assert not any(df["date"] == "2024-01-02")
|
assert not any(df["date"] == "2024-01-02")
|
||||||
|
|
||||||
def test_delete_entry_nonexistent(self, temp_csv_file, mock_logger, mock_medicine_manager, 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."""
|
"""Test deleting a nonexistent entry."""
|
||||||
# Add initial data
|
# Add initial data
|
||||||
with open(temp_csv_file, 'w', newline='') as f:
|
with open(temp_csv_file, 'w', newline='') as f:
|
||||||
@@ -270,7 +270,7 @@ class TestDataManager:
|
|||||||
])
|
])
|
||||||
writer.writerows(sample_data)
|
writer.writerows(sample_data)
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
|
|
||||||
result = dm.delete_entry("2024-01-10")
|
result = dm.delete_entry("2024-01-10")
|
||||||
assert result is True # Should return True even if no matching entry
|
assert result is True # Should return True even if no matching entry
|
||||||
@@ -280,22 +280,22 @@ class TestDataManager:
|
|||||||
assert len(df) == 3
|
assert len(df) == 3
|
||||||
|
|
||||||
@patch('pandas.read_csv')
|
@patch('pandas.read_csv')
|
||||||
def test_load_data_exception_handling(self, mock_read_csv, temp_csv_file, mock_logger, mock_medicine_manager):
|
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."""
|
"""Test exception handling in load_data."""
|
||||||
mock_read_csv.side_effect = Exception("Test error")
|
mock_read_csv.side_effect = Exception("Test error")
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager, mock_pathology_manager)
|
||||||
df = dm.load_data()
|
df = dm.load_data()
|
||||||
|
|
||||||
assert df.empty
|
assert df.empty
|
||||||
mock_logger.error.assert_called_with("Error loading data: Test error")
|
mock_logger.error.assert_called_with("Error loading data: Test error")
|
||||||
|
|
||||||
@patch('builtins.open')
|
@patch('builtins.open')
|
||||||
def test_add_entry_exception_handling(self, mock_open, temp_csv_file, mock_logger, mock_medicine_manager):
|
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."""
|
"""Test exception handling in add_entry."""
|
||||||
mock_open.side_effect = Exception("Test error")
|
mock_open.side_effect = Exception("Test error")
|
||||||
|
|
||||||
dm = DataManager(temp_csv_file, mock_logger, mock_medicine_manager)
|
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"]
|
entry = ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note"]
|
||||||
|
|
||||||
result = dm.add_entry(entry)
|
result = dm.add_entry(entry)
|
||||||
|
|||||||
Reference in New Issue
Block a user