feat: add medicine dose graph plotting and toggle functionality with comprehensive tests
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled

This commit is contained in:
William Valentin
2025-07-30 13:18:25 -07:00
parent 0a8d27957f
commit d14d19e7d9
6 changed files with 591 additions and 15 deletions

View File

@@ -40,15 +40,17 @@ def sample_dataframe():
'sleep': [4, 3, 5],
'appetite': [3, 4, 2],
'bupropion': [1, 1, 0],
'bupropion_doses': ['', '', ''],
'bupropion_doses': ['2024-01-01 08:00:00:150mg', '2024-01-02 08:00:00:300mg', ''],
'hydroxyzine': [0, 1, 0],
'hydroxyzine_doses': ['', '', ''],
'hydroxyzine_doses': ['', '2024-01-02 20:00:00:25mg', ''],
'gabapentin': [2, 2, 1],
'gabapentin_doses': ['', '', ''],
'gabapentin_doses': ['2024-01-01 12:00:00:100mg|2024-01-01 20:00:00:100mg',
'2024-01-02 12:00:00:100mg|2024-01-02 20:00:00:100mg',
'2024-01-03 12:00:00:100mg'],
'propranolol': [1, 0, 1],
'propranolol_doses': ['', '', ''],
'propranolol_doses': ['2024-01-01 12:00:00:10mg', '', '2024-01-03 12:00:00:20mg'],
'quetiapine': [0, 1, 0],
'quetiapine_doses': ['', '', ''],
'quetiapine_doses': ['', '2024-01-02 22:00:00:50mg', ''],
'note': ['Test note 1', 'Test note 2', '']
})
@@ -72,3 +74,18 @@ def mock_env_vars(monkeypatch):
monkeypatch.setenv("LOG_LEVEL", "DEBUG")
monkeypatch.setenv("LOG_PATH", "/tmp/test_logs")
monkeypatch.setenv("LOG_CLEAR", "False")
@pytest.fixture
def sample_dose_data():
"""Sample dose data for testing dose calculation."""
return {
'standard_format': '2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg', # Should sum to 225
'with_bullets': '• • • • 2025-07-30 07:50:00:300', # Should be 300
'decimal_doses': '2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg', # Should sum to 20
'no_timestamp': '100mg|50mg', # Should sum to 150
'mixed_format': '• 2025-07-30 22:50:00:10|75mg', # Should sum to 85
'empty_string': '', # Should be 0
'nan_value': 'nan', # Should be 0
'no_units': '2025-07-28 18:59:45:10|2025-07-28 19:34:19:5', # Should sum to 15
}

View File

@@ -38,14 +38,32 @@ class TestGraphManager:
assert gm.parent_frame == parent_frame
assert isinstance(gm.toggle_vars, dict)
# Check symptom toggles
assert "depression" in gm.toggle_vars
assert "anxiety" in gm.toggle_vars
assert "sleep" in gm.toggle_vars
assert "appetite" in gm.toggle_vars
# Check that all toggles are initially True
for var in gm.toggle_vars.values():
assert var.get() is True
# Check medicine toggles
assert "bupropion" in gm.toggle_vars
assert "hydroxyzine" in gm.toggle_vars
assert "gabapentin" in gm.toggle_vars
assert "propranolol" in gm.toggle_vars
assert "quetiapine" in gm.toggle_vars
# Check that symptom toggles are initially True
for symptom in ["depression", "anxiety", "sleep", "appetite"]:
assert gm.toggle_vars[symptom].get() is True
# Check that some medicine toggles are True by default
assert gm.toggle_vars["bupropion"].get() is True
assert gm.toggle_vars["propranolol"].get() is True
# Check that some medicine toggles are False by default
assert gm.toggle_vars["hydroxyzine"].get() is False
assert gm.toggle_vars["gabapentin"].get() is False
assert gm.toggle_vars["quetiapine"].get() is False
def test_toggle_controls_creation(self, parent_frame):
"""Test that toggle controls are created properly."""
@@ -55,8 +73,9 @@ class TestGraphManager:
assert hasattr(gm, 'control_frame')
assert isinstance(gm.control_frame, ttk.Frame)
# Check that toggle variables exist
expected_toggles = ["depression", "anxiety", "sleep", "appetite"]
# Check that all toggle variables exist
expected_toggles = ["depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "quetiapine"]
for toggle in expected_toggles:
assert toggle in gm.toggle_vars
assert isinstance(gm.toggle_vars[toggle], tk.BooleanVar)
@@ -265,3 +284,183 @@ class TestGraphManager:
# Verify the graph was updated in each case
assert mock_ax.clear.call_count >= 2
assert mock_canvas.draw.call_count >= 2
def test_calculate_daily_dose_empty_input(self, parent_frame):
"""Test dose calculation with empty/invalid input."""
gm = GraphManager(parent_frame)
# Test empty string
assert gm._calculate_daily_dose("") == 0.0
# Test NaN values
assert gm._calculate_daily_dose("nan") == 0.0
assert gm._calculate_daily_dose("NaN") == 0.0
# Test None (will be converted to string)
assert gm._calculate_daily_dose(None) == 0.0
def test_calculate_daily_dose_standard_format(self, parent_frame):
"""Test dose calculation with standard timestamp:dose format."""
gm = GraphManager(parent_frame)
# Single dose
dose_str = "2025-07-28 18:59:45:150mg"
assert gm._calculate_daily_dose(dose_str) == 150.0
# Multiple doses
dose_str = "2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg"
assert gm._calculate_daily_dose(dose_str) == 225.0
# Doses without units
dose_str = "2025-07-28 18:59:45:10|2025-07-28 19:34:19:5"
assert gm._calculate_daily_dose(dose_str) == 15.0
def test_calculate_daily_dose_with_symbols(self, parent_frame):
"""Test dose calculation with bullet symbols."""
gm = GraphManager(parent_frame)
# With bullet symbols
dose_str = "• • • • 2025-07-30 07:50:00:300"
assert gm._calculate_daily_dose(dose_str) == 300.0
# Multiple bullets
dose_str = "• 2025-07-30 22:50:00:10|• 2025-07-30 23:50:00:5"
assert gm._calculate_daily_dose(dose_str) == 15.0
def test_calculate_daily_dose_no_timestamp(self, parent_frame):
"""Test dose calculation without timestamp."""
gm = GraphManager(parent_frame)
# Just dose value
dose_str = "150mg"
assert gm._calculate_daily_dose(dose_str) == 150.0
# Multiple values without timestamp
dose_str = "100|50"
assert gm._calculate_daily_dose(dose_str) == 150.0
def test_calculate_daily_dose_decimal_values(self, parent_frame):
"""Test dose calculation with decimal values."""
gm = GraphManager(parent_frame)
# Decimal dose
dose_str = "2025-07-28 18:59:45:12.5mg"
assert gm._calculate_daily_dose(dose_str) == 12.5
# Multiple decimal doses
dose_str = "2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg"
assert gm._calculate_daily_dose(dose_str) == 20.0
def test_medicine_dose_plotting(self, parent_frame):
"""Test that medicine doses are plotted correctly."""
# Create a DataFrame with dose data
df_with_doses = pd.DataFrame({
'date': ['2024-01-01', '2024-01-02', '2024-01-03'],
'depression': [3, 2, 4],
'anxiety': [2, 3, 1],
'sleep': [4, 3, 5],
'appetite': [3, 4, 2],
'bupropion': [1, 1, 0],
'bupropion_doses': ['2024-01-01 08:00:00:150mg', '2024-01-02 08:00:00:300mg', ''],
'hydroxyzine': [0, 1, 0],
'hydroxyzine_doses': ['', '2024-01-02 20:00:00:25mg', ''],
'gabapentin': [0, 0, 0],
'gabapentin_doses': ['', '', ''],
'propranolol': [1, 0, 1],
'propranolol_doses': ['2024-01-01 12:00:00:10mg', '', '2024-01-03 12:00:00:20mg'],
'quetiapine': [0, 0, 0],
'quetiapine_doses': ['', '', ''],
})
with patch('matplotlib.pyplot.subplots') as mock_subplots:
mock_fig = Mock()
mock_ax = Mock()
mock_subplots.return_value = (mock_fig, mock_ax)
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
mock_canvas = Mock()
mock_canvas_class.return_value = mock_canvas
gm = GraphManager(parent_frame)
gm.update_graph(df_with_doses)
# Verify that bar plots were called (for medicines with doses)
mock_ax.bar.assert_called()
# Verify canvas was redrawn
mock_canvas.draw.assert_called()
def test_medicine_toggle_functionality(self, parent_frame):
"""Test that medicine toggles affect dose display."""
df_with_doses = pd.DataFrame({
'date': ['2024-01-01'],
'depression': [3],
'anxiety': [2],
'sleep': [4],
'appetite': [3],
'bupropion': [1],
'bupropion_doses': ['2024-01-01 08:00:00:150mg'],
'hydroxyzine': [0],
'hydroxyzine_doses': [''],
'gabapentin': [0],
'gabapentin_doses': [''],
'propranolol': [1],
'propranolol_doses': ['2024-01-01 12:00:00:10mg'],
'quetiapine': [0],
'quetiapine_doses': [''],
})
with patch('matplotlib.pyplot.subplots') as mock_subplots:
mock_fig = Mock()
mock_ax = Mock()
mock_subplots.return_value = (mock_fig, mock_ax)
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
mock_canvas = Mock()
mock_canvas_class.return_value = mock_canvas
gm = GraphManager(parent_frame)
# Turn off bupropion toggle
gm.toggle_vars["bupropion"].set(False)
gm.update_graph(df_with_doses)
# Turn on hydroxyzine toggle (though it has no doses)
gm.toggle_vars["hydroxyzine"].set(True)
gm.update_graph(df_with_doses)
# Verify the graph was updated
assert mock_ax.clear.call_count >= 2
assert mock_canvas.draw.call_count >= 2
def test_dose_calculation_comprehensive(self, parent_frame, sample_dose_data):
"""Test dose calculation with comprehensive test cases."""
gm = GraphManager(parent_frame)
# Test all sample dose data cases
assert gm._calculate_daily_dose(sample_dose_data['standard_format']) == 225.0
assert gm._calculate_daily_dose(sample_dose_data['with_bullets']) == 300.0
assert gm._calculate_daily_dose(sample_dose_data['decimal_doses']) == 20.0
assert gm._calculate_daily_dose(sample_dose_data['no_timestamp']) == 150.0
assert gm._calculate_daily_dose(sample_dose_data['mixed_format']) == 85.0
assert gm._calculate_daily_dose(sample_dose_data['empty_string']) == 0.0
assert gm._calculate_daily_dose(sample_dose_data['nan_value']) == 0.0
assert gm._calculate_daily_dose(sample_dose_data['no_units']) == 15.0
def test_dose_calculation_edge_cases(self, parent_frame):
"""Test dose calculation with edge cases."""
gm = GraphManager(parent_frame)
# Test with malformed data
assert gm._calculate_daily_dose("malformed:data") == 0.0
assert gm._calculate_daily_dose("::::") == 0.0
assert gm._calculate_daily_dose("2025-07-28:") == 0.0
assert gm._calculate_daily_dose("2025-07-28::mg") == 0.0
# Test with partial data
assert gm._calculate_daily_dose("2025-07-28 18:59:45:150") == 150.0 # no units
assert gm._calculate_daily_dose("150mg") == 150.0 # no timestamp
# Test with spaces and special characters
assert gm._calculate_daily_dose(" 2025-07-28 18:59:45:150mg ") == 150.0
assert gm._calculate_daily_dose("••• 2025-07-28 18:59:45:150mg •••") == 150.0