From 2b037a83e89384504586d1932ac813029184ce55 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Tue, 29 Jul 2025 13:22:35 -0700 Subject: [PATCH] Feat: Add quetiapine support to medication tracking - Implement migration script to add quetiapine and quetiapine_doses columns to existing CSV data. - Update DataManager to include quetiapine and quetiapine_doses in data handling. - Modify MedTrackerApp to manage quetiapine entries and doses. - Enhance UIManager to include quetiapine in the user interface for medication selection and display. - Update tests to cover new quetiapine functionality, including sample data and DataManager tests. --- coverage.xml | 578 +++++++++++++++++----------------- scripts/migrate_quetiapine.py | 61 ++++ src/data_manager.py | 4 + src/main.py | 11 + src/ui_manager.py | 65 +++- tests/conftest.py | 12 +- tests/test_data_manager.py | 53 +++- 7 files changed, 467 insertions(+), 317 deletions(-) create mode 100644 scripts/migrate_quetiapine.py diff --git a/coverage.xml b/coverage.xml index 0279c9e..92e6234 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,12 +1,12 @@ - + /home/will/Code/thechart/src - + @@ -27,7 +27,7 @@ - + @@ -44,96 +44,96 @@ - - + - - - - - + + - + + + - - + - + + - + + - - - - - - + + + + + - - + + - - - - - + + + + + - - + - + + - - - + + + - - - - - + + + + - - - - - - + + + + + + - + - + - + - + + + + @@ -270,7 +270,7 @@ - + @@ -346,87 +346,90 @@ - - - - - - - - + + + + + - + - - - - + + + + - + + - - - + - + + - + + - + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + + + - - - - - - - + + + + + + + + + + + - + @@ -500,248 +503,251 @@ - - - + + - - - - - - - - - - + + + + + + + + + + - - + + - - + - - - - + + + + - + - + + - - - - + + + + - - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + - - - + - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - + + + + + - - - - - + + - - + + + + - - - + + + - - - + + + + - + + + - - + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - + - + + - - - - - - - - - - - - - - + + + + + + + + + - - - + + + + + - - + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + - - + + + - - - - - - + + + + + + - - - - + + - - - - - + + + - + - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/migrate_quetiapine.py b/scripts/migrate_quetiapine.py new file mode 100644 index 0000000..3d9d2c8 --- /dev/null +++ b/scripts/migrate_quetiapine.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Migration script to add quetiapine columns to existing CSV data. +This script will backup the existing CSV and add the new columns. +""" + +import os +import shutil +from datetime import datetime + +import pandas as pd + + +def migrate_csv_add_quetiapine(csv_file: str = "thechart_data.csv"): + """Add quetiapine and quetiapine_doses columns to existing CSV.""" + + if not os.path.exists(csv_file): + print(f"CSV file {csv_file} not found. No migration needed.") + return + + # Create backup + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = f"{csv_file}.backup_quetiapine_{timestamp}" + shutil.copy2(csv_file, backup_file) + print(f"Backup created: {backup_file}") + + # Load existing data + try: + df = pd.read_csv(csv_file) + print(f"Loaded {len(df)} rows from {csv_file}") + + # Check if quetiapine columns already exist + if "quetiapine" in df.columns: + print("Quetiapine columns already exist. No migration needed.") + return + + # Add new columns + # Insert quetiapine columns before the note column + note_col_index = ( + df.columns.get_loc("note") if "note" in df.columns else len(df.columns) + ) + + # Insert quetiapine column + df.insert(note_col_index, "quetiapine", 0) + df.insert(note_col_index + 1, "quetiapine_doses", "") + + # Save updated CSV + df.to_csv(csv_file, index=False) + print(f"Successfully added quetiapine columns to {csv_file}") + print(f"New column order: {list(df.columns)}") + + except Exception as e: + print(f"Error during migration: {e}") + # Restore backup on error + if os.path.exists(backup_file): + shutil.copy2(backup_file, csv_file) + print("Restored backup due to error") + + +if __name__ == "__main__": + migrate_csv_add_quetiapine() diff --git a/src/data_manager.py b/src/data_manager.py index 1468342..6e970e8 100644 --- a/src/data_manager.py +++ b/src/data_manager.py @@ -33,6 +33,8 @@ class DataManager: "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", + "quetiapine_doses", "note", ] ) @@ -59,6 +61,8 @@ class DataManager: "gabapentin_doses": str, "propranolol": int, "propranolol_doses": str, + "quetiapine": int, + "quetiapine_doses": str, "note": str, "date": str, }, diff --git a/src/main.py b/src/main.py index 2c7f6bd..15bffd1 100644 --- a/src/main.py +++ b/src/main.py @@ -138,6 +138,8 @@ class MedTrackerApp: full_row["gabapentin_doses"], full_row["propranolol"], full_row["propranolol_doses"], + full_row.get("quetiapine", 0), + full_row.get("quetiapine_doses", ""), full_row["note"], ) else: @@ -166,6 +168,7 @@ class MedTrackerApp: hydro: int, gaba: int, prop: int, + quet: int, note: str, dose_data: dict[str, str], ) -> None: @@ -184,6 +187,8 @@ class MedTrackerApp: dose_data.get("gabapentin", ""), prop, dose_data.get("propranolol", ""), + quet, + dose_data.get("quetiapine", ""), note, ] @@ -222,6 +227,7 @@ class MedTrackerApp: hydroxyzine_doses = "" gabapentin_doses = "" propranolol_doses = "" + quetiapine_doses = "" if today: bup_doses = self.data_manager.get_today_medicine_doses(today, "bupropion") @@ -232,6 +238,7 @@ class MedTrackerApp: prop_doses = self.data_manager.get_today_medicine_doses( today, "propranolol" ) + quet_doses = self.data_manager.get_today_medicine_doses(today, "quetiapine") bupropion_doses = "|".join([f"{ts}:{dose}" for ts, dose in bup_doses]) hydroxyzine_doses = "|".join( @@ -239,6 +246,7 @@ class MedTrackerApp: ) gabapentin_doses = "|".join([f"{ts}:{dose}" for ts, dose in gaba_doses]) propranolol_doses = "|".join([f"{ts}:{dose}" for ts, dose in prop_doses]) + quetiapine_doses = "|".join([f"{ts}:{dose}" for ts, dose in quet_doses]) entry: list[str | int] = [ self.date_var.get(), @@ -254,6 +262,8 @@ class MedTrackerApp: gabapentin_doses, self.medicine_vars["propranolol"][0].get(), propranolol_doses, + self.medicine_vars["quetiapine"][0].get(), + quetiapine_doses, self.note_var.get(), ] logger.debug(f"Adding entry: {entry}") @@ -337,6 +347,7 @@ class MedTrackerApp: "hydroxyzine", "gabapentin", "propranolol", + "quetiapine", "note", ] diff --git a/src/ui_manager.py b/src/ui_manager.py index cdeb9a4..36f5ab3 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -138,6 +138,7 @@ class UIManager: "hydroxyzine": (tk.IntVar(value=0), "Hydroxyzine 25mg"), "gabapentin": (tk.IntVar(value=0), "Gabapentin 100mg"), "propranolol": (tk.IntVar(value=0), "Propranolol 10mg"), + "quetiapine": (tk.IntVar(value=0), "Quetiapine 25mg"), } for idx, (_med_name, (var, text)) in enumerate(medicine_vars.items()): @@ -197,6 +198,7 @@ class UIManager: "Hydroxyzine", "Gabapentin", "Propranolol", + "Quetiapine", "Note", ] @@ -212,6 +214,7 @@ class UIManager: "Hydroxyzine 25mg", "Gabapentin 100mg", "Propranolol 10mg", + "Quetiapine 25mg", "Note", ] @@ -228,6 +231,7 @@ class UIManager: ("Hydroxyzine", 120, "center"), ("Gabapentin", 120, "center"), ("Propranolol", 120, "center"), + ("Quetiapine", 120, "center"), ("Note", 300, "w"), ] @@ -286,9 +290,16 @@ class UIManager: if len(values) == 10: # Old format: date, dep, anx, slp, app, bup, hydro, gaba, prop, note date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values - bup_doses, hydro_doses, gaba_doses, prop_doses = "", "", "", "" + bup_doses, hydro_doses, gaba_doses, prop_doses, quet_doses = ( + "", + "", + "", + "", + "", + ) + quet = 0 elif len(values) == 14: - # New format with dose tracking + # Old new format with dose tracking (without quetiapine) ( date, dep, @@ -305,11 +316,9 @@ class UIManager: prop_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) + [""] * (14 - len(values)) + quet, quet_doses = 0, "" + elif len(values) == 16: + # New format with quetiapine and dose tracking ( date, dep, @@ -324,8 +333,33 @@ class UIManager: gaba_doses, prop, prop_doses, + quet, + quet_doses, note, - ) = values_list[:14] + ) = 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 variables and fields vars_dict = self._create_edit_fields(edit_win, date, dep, anx, slp, app) @@ -333,7 +367,7 @@ class UIManager: # Medicine checkboxes current_row = 6 # After the 5 fields (date, dep, anx, slp, app) med_vars = self._create_medicine_checkboxes( - edit_win, current_row, bup, hydro, gaba, prop + edit_win, current_row, bup, hydro, gaba, prop, quet ) vars_dict.update(med_vars) @@ -347,6 +381,7 @@ class UIManager: "hydroxyzine": hydro_doses, "gabapentin": gaba_doses, "propranolol": prop_doses, + "quetiapine": quet_doses, }, ) vars_dict.update(dose_vars) @@ -474,6 +509,7 @@ class UIManager: hydro: int, gaba: int, prop: int, + quet: int, ) -> dict[str, tk.IntVar]: """Create medicine checkboxes in the edit window.""" ttk.Label(parent, text="Treatment:").grid( @@ -487,6 +523,7 @@ class UIManager: "hydroxyzine": (hydro, "Hydroxyzine 25mg"), "gabapentin": (gaba, "Gabapentin 100mg"), "propranolol": (prop, "Propranolol 10mg"), + "quetiapine": (quet, "Quetiapine 25mg"), } vars_dict: dict[str, tk.IntVar] = {} @@ -514,7 +551,13 @@ class UIManager: # Extract dose data from the text widgets dose_data = {} - for medicine in ["bupropion", "hydroxyzine", "gabapentin", "propranolol"]: + for medicine in [ + "bupropion", + "hydroxyzine", + "gabapentin", + "propranolol", + "quetiapine", + ]: dose_text_key = f"{medicine}_doses_text" if dose_text_key in vars_dict and isinstance( @@ -526,7 +569,6 @@ class UIManager: ) else: dose_data[medicine] = "" - dose_data[medicine] = "" callbacks["save"]( parent, @@ -539,6 +581,7 @@ class UIManager: vars_dict["hydroxyzine"].get(), vars_dict["gabapentin"].get(), vars_dict["propranolol"].get(), + vars_dict["quetiapine"].get(), vars_dict["note"].get(), dose_data, ) diff --git a/tests/conftest.py b/tests/conftest.py index 0fbd38d..b0865f3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,9 +24,9 @@ def temp_csv_file(): def sample_data(): """Sample data for testing.""" return [ - ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note 1"], - ["2024-01-02", 2, 3, 3, 4, 1, 1, 2, 0, "Test note 2"], - ["2024-01-03", 4, 1, 5, 2, 0, 0, 1, 1, ""], + ["2024-01-01", 3, 2, 4, 3, 1, "", 0, "", 2, "", 1, "", 0, "", "Test note 1"], + ["2024-01-02", 2, 3, 3, 4, 1, "", 1, "", 2, "", 0, "", 1, "", "Test note 2"], + ["2024-01-03", 4, 1, 5, 2, 0, "", 0, "", 1, "", 1, "", 0, "", ""], ] @@ -40,9 +40,15 @@ def sample_dataframe(): 'sleep': [4, 3, 5], 'appetite': [3, 4, 2], 'bupropion': [1, 1, 0], + 'bupropion_doses': ['', '', ''], 'hydroxyzine': [0, 1, 0], + 'hydroxyzine_doses': ['', '', ''], 'gabapentin': [2, 2, 1], + 'gabapentin_doses': ['', '', ''], 'propranolol': [1, 0, 1], + 'propranolol_doses': ['', '', ''], + 'quetiapine': [0, 1, 0], + 'quetiapine_doses': ['', '', ''], 'note': ['Test note 1', 'Test note 2', ''] }) diff --git a/tests/test_data_manager.py b/tests/test_data_manager.py index f6e56f7..d4f6564 100644 --- a/tests/test_data_manager.py +++ b/tests/test_data_manager.py @@ -40,7 +40,8 @@ class TestDataManager: expected_headers = [ "date", "depression", "anxiety", "sleep", "appetite", "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", - "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", "note" + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ] assert headers == expected_headers @@ -78,7 +79,9 @@ class TestDataManager: # Write headers first writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) # Write sample data writer.writerows(sample_data) @@ -90,7 +93,9 @@ class TestDataManager: assert len(df) == 3 assert list(df.columns) == [ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ] # Check data types assert df["depression"].dtype == int @@ -101,16 +106,18 @@ class TestDataManager: """Test that loaded data is sorted by date.""" # Write data in random order unsorted_data = [ - ["2024-01-03", 1, 1, 1, 1, 1, 1, 1, 1, "third"], - ["2024-01-01", 2, 2, 2, 2, 2, 2, 2, 2, "first"], - ["2024-01-02", 3, 3, 3, 3, 3, 3, 3, 3, "second"], + ["2024-01-03", 1, 1, 1, 1, 1, "", 1, "", 1, "", 1, "", 0, "", "third"], + ["2024-01-01", 2, 2, 2, 2, 2, "", 2, "", 2, "", 2, "", 1, "", "first"], + ["2024-01-02", 3, 3, 3, 3, 3, "", 3, "", 3, "", 3, "", 0, "", "second"], ] with open(temp_csv_file, 'w', newline='') as f: writer = csv.writer(f) writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) writer.writerows(unsorted_data) @@ -143,13 +150,15 @@ class TestDataManager: writer = csv.writer(f) writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) writer.writerows(sample_data) dm = DataManager(temp_csv_file, mock_logger) # Try to add entry with existing date - duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, 1, 1, 1, "Duplicate"] + duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, "", 1, "", 1, "", 1, "", 0, "", "Duplicate"] result = dm.add_entry(duplicate_entry) assert result is False @@ -162,12 +171,14 @@ class TestDataManager: writer = csv.writer(f) writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) writer.writerows(sample_data) dm = DataManager(temp_csv_file, mock_logger) - updated_values = ["2024-01-01", 5, 5, 5, 5, 2, 2, 2, 2, "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) assert result is True @@ -185,12 +196,14 @@ class TestDataManager: writer = csv.writer(f) writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) writer.writerows(sample_data) dm = DataManager(temp_csv_file, mock_logger) - updated_values = ["2024-01-05", 5, 5, 5, 5, 2, 2, 2, 2, "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) assert result is True @@ -207,13 +220,15 @@ class TestDataManager: writer = csv.writer(f) writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) writer.writerows(sample_data) dm = DataManager(temp_csv_file, mock_logger) # Try to change date to one that already exists - updated_values = ["2024-01-02", 5, 5, 5, 5, 2, 2, 2, 2, "Updated note"] + updated_values = ["2024-01-02", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"] result = dm.update_entry("2024-01-01", updated_values) assert result is False @@ -228,7 +243,9 @@ class TestDataManager: writer = csv.writer(f) writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) writer.writerows(sample_data) @@ -249,7 +266,9 @@ class TestDataManager: writer = csv.writer(f) writer.writerow([ "date", "depression", "anxiety", "sleep", "appetite", - "bupropion", "hydroxyzine", "gabapentin", "propranolol", "note" + "bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses", + "gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", + "quetiapine", "quetiapine_doses", "note" ]) writer.writerows(sample_data)