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
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user