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:
+582
-171
@@ -10,17 +10,23 @@ from typing import Any
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
from medicine_manager import MedicineManager
|
||||
from pathology_manager import PathologyManager
|
||||
|
||||
|
||||
class UIManager:
|
||||
"""Handle UI creation and management for the application."""
|
||||
|
||||
def __init__(
|
||||
self, root: tk.Tk, logger: logging.Logger, medicine_manager: MedicineManager
|
||||
self,
|
||||
root: tk.Tk,
|
||||
logger: logging.Logger,
|
||||
medicine_manager: MedicineManager,
|
||||
pathology_manager: PathologyManager,
|
||||
) -> None:
|
||||
self.root: tk.Tk = root
|
||||
self.logger: logging.Logger = logger
|
||||
self.medicine_manager = medicine_manager
|
||||
self.pathology_manager = pathology_manager
|
||||
|
||||
def setup_application_icon(self, img_path: str) -> bool:
|
||||
"""Set up the application icon."""
|
||||
@@ -130,36 +136,31 @@ class UIManager:
|
||||
main_container.bind("<Enter>", on_mouse_enter)
|
||||
canvas.bind("<Enter>", on_mouse_enter)
|
||||
|
||||
# Create variables for symptoms
|
||||
symptom_vars: dict[str, tk.IntVar] = {
|
||||
"depression": tk.IntVar(value=0),
|
||||
"anxiety": tk.IntVar(value=0),
|
||||
"sleep": tk.IntVar(value=0),
|
||||
"appetite": tk.IntVar(value=0),
|
||||
}
|
||||
# Create variables for pathologies dynamically
|
||||
pathology_vars: dict[str, tk.IntVar] = {}
|
||||
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||
pathology_vars[pathology_key] = tk.IntVar(value=0)
|
||||
|
||||
# Create enhanced scales for symptoms
|
||||
symptom_labels: list[tuple[str, str]] = [
|
||||
("Depression", "depression"),
|
||||
("Anxiety", "anxiety"),
|
||||
("Sleep Quality", "sleep"),
|
||||
("Appetite", "appetite"),
|
||||
]
|
||||
# Create enhanced scales for pathologies dynamically
|
||||
pathology_configs = []
|
||||
for pathology in self.pathology_manager.get_all_pathologies().values():
|
||||
pathology_configs.append((pathology.display_name, pathology.key))
|
||||
|
||||
# Configure input frame columns for better layout
|
||||
input_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
for idx, (label, var_name) in enumerate(symptom_labels):
|
||||
self._create_enhanced_symptom_scale(
|
||||
input_frame, idx, label, var_name, 0, symptom_vars
|
||||
for idx, (label, var_name) in enumerate(pathology_configs):
|
||||
self._create_enhanced_pathology_scale(
|
||||
input_frame, idx, label, var_name, 0, pathology_vars
|
||||
)
|
||||
|
||||
# Medicine tracking section (simplified)
|
||||
# Medicine tracking section (simplified) - adjust row number dynamically
|
||||
medicine_row = len(pathology_configs)
|
||||
ttk.Label(input_frame, text="Treatment:").grid(
|
||||
row=4, column=0, sticky="w", padx=5, pady=2
|
||||
row=medicine_row, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
medicine_frame = ttk.LabelFrame(input_frame, text="Medicine")
|
||||
medicine_frame.grid(row=4, column=1, padx=0, pady=10, sticky="nsew")
|
||||
medicine_frame.grid(row=medicine_row, column=1, padx=0, pady=10, sticky="nsew")
|
||||
medicine_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Store medicine variables (checkboxes only) - dynamic based on medicine manager
|
||||
@@ -178,22 +179,25 @@ class UIManager:
|
||||
row=idx, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
|
||||
# Note and Date fields
|
||||
# Note and Date fields - adjust row numbers
|
||||
note_row = medicine_row + 1
|
||||
date_row = medicine_row + 2
|
||||
|
||||
note_var: tk.StringVar = tk.StringVar()
|
||||
date_var: tk.StringVar = tk.StringVar()
|
||||
|
||||
ttk.Label(input_frame, text="Note:").grid(
|
||||
row=5, column=0, sticky="w", padx=5, pady=2
|
||||
row=note_row, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
ttk.Entry(input_frame, textvariable=note_var).grid(
|
||||
row=5, column=1, sticky="ew", padx=5, pady=2
|
||||
row=note_row, column=1, sticky="ew", padx=5, pady=2
|
||||
)
|
||||
|
||||
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
|
||||
row=6, column=0, sticky="w", padx=5, pady=2
|
||||
row=date_row, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
ttk.Entry(input_frame, textvariable=date_var, justify="center").grid(
|
||||
row=6, column=1, sticky="ew", padx=5, pady=2
|
||||
row=date_row, column=1, sticky="ew", padx=5, pady=2
|
||||
)
|
||||
|
||||
# Set default date to today
|
||||
@@ -207,7 +211,7 @@ class UIManager:
|
||||
# Return all UI elements and variables
|
||||
return {
|
||||
"frame": main_container,
|
||||
"symptom_vars": symptom_vars,
|
||||
"pathology_vars": pathology_vars,
|
||||
"medicine_vars": medicine_vars,
|
||||
"note_var": note_var,
|
||||
"date_var": date_var,
|
||||
@@ -225,15 +229,17 @@ class UIManager:
|
||||
table_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Build columns dynamically
|
||||
columns: list[str] = ["Date", "Depression", "Anxiety", "Sleep", "Appetite"]
|
||||
col_labels: list[str] = ["Date", "Depression", "Anxiety", "Sleep", "Appetite"]
|
||||
col_settings: list[tuple[str, int, str]] = [
|
||||
("Date", 80, "center"),
|
||||
("Depression", 80, "center"),
|
||||
("Anxiety", 80, "center"),
|
||||
("Sleep", 80, "center"),
|
||||
("Appetite", 80, "center"),
|
||||
]
|
||||
columns: list[str] = ["Date"]
|
||||
col_labels: list[str] = ["Date"]
|
||||
col_settings: list[tuple[str, int, str]] = [("Date", 80, "center")]
|
||||
|
||||
# Add pathology columns dynamically
|
||||
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||
pathology = self.pathology_manager.get_pathology(pathology_key)
|
||||
if pathology:
|
||||
columns.append(pathology.display_name)
|
||||
col_labels.append(pathology.display_name)
|
||||
col_settings.append((pathology.display_name, 80, "center"))
|
||||
|
||||
# Add medicine columns dynamically
|
||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||
@@ -366,102 +372,59 @@ class UIManager:
|
||||
edit_win.bind("<Enter>", on_mouse_enter)
|
||||
canvas.bind("<Enter>", on_mouse_enter)
|
||||
|
||||
# Unpack values - handle both old and new CSV formats
|
||||
if len(values) == 10:
|
||||
# Old format: date, dep, anx, slp, app, bup, hydro, gaba, prop, note
|
||||
date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values
|
||||
bup_doses, hydro_doses, gaba_doses, prop_doses, quet_doses = (
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
)
|
||||
quet = 0
|
||||
elif len(values) == 14:
|
||||
# Old new format with dose tracking (without quetiapine)
|
||||
(
|
||||
date,
|
||||
dep,
|
||||
anx,
|
||||
slp,
|
||||
app,
|
||||
bup,
|
||||
bup_doses,
|
||||
hydro,
|
||||
hydro_doses,
|
||||
gaba,
|
||||
gaba_doses,
|
||||
prop,
|
||||
prop_doses,
|
||||
note,
|
||||
) = values
|
||||
quet, quet_doses = 0, ""
|
||||
elif len(values) == 16:
|
||||
# New format with quetiapine and dose tracking
|
||||
(
|
||||
date,
|
||||
dep,
|
||||
anx,
|
||||
slp,
|
||||
app,
|
||||
bup,
|
||||
bup_doses,
|
||||
hydro,
|
||||
hydro_doses,
|
||||
gaba,
|
||||
gaba_doses,
|
||||
prop,
|
||||
prop_doses,
|
||||
quet,
|
||||
quet_doses,
|
||||
note,
|
||||
) = values
|
||||
else:
|
||||
# Fallback for unexpected format
|
||||
self.logger.warning(f"Unexpected number of values in edit: {len(values)}")
|
||||
# Pad with default values
|
||||
values_list = list(values) + [""] * (16 - len(values))
|
||||
(
|
||||
date,
|
||||
dep,
|
||||
anx,
|
||||
slp,
|
||||
app,
|
||||
bup,
|
||||
bup_doses,
|
||||
hydro,
|
||||
hydro_doses,
|
||||
gaba,
|
||||
gaba_doses,
|
||||
prop,
|
||||
prop_doses,
|
||||
quet,
|
||||
quet_doses,
|
||||
note,
|
||||
) = values_list[:16]
|
||||
# Unpack values dynamically
|
||||
# Expected format: date, pathology1, pathology2, ...,
|
||||
# medicine1, medicine1_doses, medicine2, medicine2_doses, ..., note
|
||||
|
||||
# Create improved UI sections
|
||||
vars_dict = self._create_edit_ui(
|
||||
# Parse values dynamically
|
||||
values_list = list(values)
|
||||
|
||||
# Extract date
|
||||
date = values_list[0] if len(values_list) > 0 else ""
|
||||
|
||||
# Extract pathology values
|
||||
pathology_values = {}
|
||||
pathology_keys = self.pathology_manager.get_pathology_keys()
|
||||
for i, pathology_key in enumerate(pathology_keys):
|
||||
if i + 1 < len(values_list):
|
||||
pathology_values[pathology_key] = values_list[i + 1]
|
||||
else:
|
||||
pathology_values[pathology_key] = 0
|
||||
|
||||
# Extract medicine values and doses
|
||||
medicine_values = {}
|
||||
medicine_doses = {}
|
||||
medicine_keys = self.medicine_manager.get_medicine_keys()
|
||||
|
||||
# Start index after date and pathologies
|
||||
medicine_start_idx = 1 + len(pathology_keys)
|
||||
|
||||
for i, medicine_key in enumerate(medicine_keys):
|
||||
# Each medicine has 2 values: checkbox value and doses string
|
||||
checkbox_idx = medicine_start_idx + (i * 2)
|
||||
doses_idx = medicine_start_idx + (i * 2) + 1
|
||||
|
||||
if checkbox_idx < len(values_list):
|
||||
medicine_values[medicine_key] = values_list[checkbox_idx]
|
||||
else:
|
||||
medicine_values[medicine_key] = 0
|
||||
|
||||
if doses_idx < len(values_list):
|
||||
medicine_doses[medicine_key] = values_list[doses_idx]
|
||||
else:
|
||||
medicine_doses[medicine_key] = ""
|
||||
|
||||
# Extract note (should be the last value)
|
||||
note = values_list[-1] if len(values_list) > 0 else ""
|
||||
|
||||
# Create improved UI sections dynamically
|
||||
vars_dict = self._create_edit_ui_dynamic(
|
||||
main_container,
|
||||
date,
|
||||
dep,
|
||||
anx,
|
||||
slp,
|
||||
app,
|
||||
bup,
|
||||
hydro,
|
||||
gaba,
|
||||
prop,
|
||||
quet,
|
||||
pathology_values,
|
||||
medicine_values,
|
||||
medicine_doses,
|
||||
note,
|
||||
{
|
||||
"bupropion": bup_doses,
|
||||
"hydroxyzine": hydro_doses,
|
||||
"gabapentin": gaba_doses,
|
||||
"propranolol": prop_doses,
|
||||
"quetiapine": quet_doses,
|
||||
},
|
||||
)
|
||||
|
||||
# Add action buttons
|
||||
@@ -480,6 +443,105 @@ class UIManager:
|
||||
|
||||
return edit_win
|
||||
|
||||
def _create_edit_ui_dynamic(
|
||||
self,
|
||||
parent: ttk.Frame,
|
||||
date: str,
|
||||
pathology_values: dict[str, int],
|
||||
medicine_values: dict[str, int],
|
||||
medicine_doses: dict[str, str],
|
||||
note: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Create UI layout for edit window with dynamic pathologies and medicines."""
|
||||
vars_dict = {}
|
||||
row = 0
|
||||
|
||||
# Header with entry date
|
||||
header_frame = ttk.Frame(parent)
|
||||
header_frame.grid(row=row, column=0, sticky="ew", pady=(0, 20))
|
||||
header_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(
|
||||
header_frame, text="Editing Entry for:", font=("TkDefaultFont", 12, "bold")
|
||||
).grid(row=0, column=0, sticky="w")
|
||||
|
||||
vars_dict["date"] = tk.StringVar(value=str(date))
|
||||
date_entry = ttk.Entry(
|
||||
header_frame,
|
||||
textvariable=vars_dict["date"],
|
||||
font=("TkDefaultFont", 12),
|
||||
width=15,
|
||||
)
|
||||
date_entry.grid(row=0, column=1, sticky="w", padx=(10, 0))
|
||||
|
||||
row += 1
|
||||
|
||||
# Pathologies section
|
||||
pathologies_frame = ttk.LabelFrame(
|
||||
parent, text="Daily Pathologies", padding="15"
|
||||
)
|
||||
pathologies_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
|
||||
pathologies_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Create pathology scales dynamically
|
||||
for i, (pathology_key, value) in enumerate(pathology_values.items()):
|
||||
pathology = self.pathology_manager.get_pathology(pathology_key)
|
||||
if pathology:
|
||||
label = f"{pathology.display_name} ({pathology.scale_info})"
|
||||
self._create_symptom_scale(
|
||||
pathologies_frame, i, label, pathology_key, value, vars_dict
|
||||
)
|
||||
|
||||
row += 1
|
||||
|
||||
# Medications section
|
||||
meds_frame = ttk.LabelFrame(parent, text="Medications Taken", padding="15")
|
||||
meds_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
|
||||
meds_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Create medicine checkboxes dynamically
|
||||
med_vars = self._create_medicine_section_dynamic(meds_frame, medicine_values)
|
||||
vars_dict.update(med_vars)
|
||||
|
||||
row += 1
|
||||
|
||||
# Dose tracking section
|
||||
dose_frame = ttk.LabelFrame(parent, text="Dose Tracking", padding="15")
|
||||
dose_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
|
||||
dose_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
dose_vars = self._create_dose_tracking_dynamic(dose_frame, medicine_doses)
|
||||
vars_dict.update(dose_vars)
|
||||
|
||||
row += 1
|
||||
|
||||
# Notes section
|
||||
notes_frame = ttk.LabelFrame(parent, text="Notes", padding="15")
|
||||
notes_frame.grid(row=row, column=0, sticky="ew", pady=(0, 20))
|
||||
notes_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
vars_dict["note"] = tk.StringVar(value=str(note))
|
||||
note_text = tk.Text(
|
||||
notes_frame,
|
||||
height=4,
|
||||
width=50,
|
||||
wrap=tk.WORD,
|
||||
font=("TkDefaultFont", 10),
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
)
|
||||
note_text.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
|
||||
note_text.insert("1.0", str(note))
|
||||
|
||||
# Bind text widget to string var for easy access
|
||||
def update_note(*args):
|
||||
vars_dict["note"].set(note_text.get("1.0", tk.END).strip())
|
||||
|
||||
note_text.bind("<KeyRelease>", update_note)
|
||||
note_text.bind("<FocusOut>", update_note)
|
||||
|
||||
return vars_dict
|
||||
|
||||
def _create_edit_ui(
|
||||
self,
|
||||
parent: ttk.Frame,
|
||||
@@ -756,6 +818,114 @@ class UIManager:
|
||||
scale.bind("<KeyRelease>", update_value_label)
|
||||
update_value_label() # Set initial color
|
||||
|
||||
def _create_enhanced_pathology_scale(
|
||||
self,
|
||||
parent: ttk.Frame,
|
||||
row: int,
|
||||
label: str,
|
||||
key: str,
|
||||
value: int,
|
||||
vars_dict: dict[str, tk.IntVar],
|
||||
) -> None:
|
||||
"""Create enhanced pathology scale for new entry form."""
|
||||
# Ensure value is properly converted
|
||||
try:
|
||||
value = int(float(value)) if value not in ["", None] else 0
|
||||
except (ValueError, TypeError):
|
||||
value = 0
|
||||
|
||||
# Get pathology configuration
|
||||
pathology = self.pathology_manager.get_pathology(key)
|
||||
if not pathology:
|
||||
# Fallback for missing pathology
|
||||
pathology_info = f"{label} (0-10):"
|
||||
scale_min, scale_max = 0, 10
|
||||
scale_orientation = "normal"
|
||||
else:
|
||||
pathology_info = f"{pathology.display_name} ({pathology.scale_info}):"
|
||||
scale_min, scale_max = pathology.scale_min, pathology.scale_max
|
||||
scale_orientation = pathology.scale_orientation
|
||||
|
||||
# Label
|
||||
label_widget = ttk.Label(
|
||||
parent, text=pathology_info, font=("TkDefaultFont", 10, "bold")
|
||||
)
|
||||
label_widget.grid(row=row, column=0, sticky="w", padx=5, pady=8)
|
||||
|
||||
# Scale container
|
||||
scale_container = ttk.Frame(parent)
|
||||
scale_container.grid(row=row, column=1, sticky="ew", padx=(20, 5), pady=8)
|
||||
scale_container.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Scale with value labels
|
||||
scale_frame = ttk.Frame(scale_container)
|
||||
scale_frame.grid(row=0, column=0, sticky="ew")
|
||||
scale_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Current value display
|
||||
value_label = ttk.Label(
|
||||
scale_frame,
|
||||
text=str(value),
|
||||
font=("TkDefaultFont", 12, "bold"),
|
||||
foreground="#2E86AB",
|
||||
width=3,
|
||||
)
|
||||
value_label.grid(row=0, column=0, padx=(0, 10))
|
||||
|
||||
# Scale widget
|
||||
scale = ttk.Scale(
|
||||
scale_frame,
|
||||
from_=scale_min,
|
||||
to=scale_max,
|
||||
variable=vars_dict[key],
|
||||
orient=tk.HORIZONTAL,
|
||||
length=250,
|
||||
)
|
||||
scale.grid(row=0, column=1, sticky="ew")
|
||||
|
||||
# Scale labels
|
||||
labels_frame = ttk.Frame(scale_container)
|
||||
labels_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))
|
||||
|
||||
ttk.Label(labels_frame, text=str(scale_min), font=("TkDefaultFont", 8)).grid(
|
||||
row=0, column=0, sticky="w"
|
||||
)
|
||||
labels_frame.grid_columnconfigure(1, weight=1)
|
||||
mid_value = (scale_min + scale_max) // 2
|
||||
ttk.Label(labels_frame, text=str(mid_value), font=("TkDefaultFont", 8)).grid(
|
||||
row=0, column=1
|
||||
)
|
||||
ttk.Label(labels_frame, text=str(scale_max), font=("TkDefaultFont", 8)).grid(
|
||||
row=0, column=2, sticky="e"
|
||||
)
|
||||
|
||||
# Update label when scale changes
|
||||
def update_value_label_pathology(event=None):
|
||||
current_val = vars_dict[key].get()
|
||||
value_label.configure(text=str(current_val))
|
||||
# Change color based on value and orientation
|
||||
if scale_orientation == "inverted":
|
||||
# For inverted scales (like sleep, appetite), higher is better
|
||||
if current_val >= scale_max * 0.7:
|
||||
value_label.configure(foreground="#28A745") # Green for good
|
||||
elif current_val >= scale_max * 0.4:
|
||||
value_label.configure(foreground="#FFC107") # Yellow for medium
|
||||
else:
|
||||
value_label.configure(foreground="#DC3545") # Red for bad
|
||||
else:
|
||||
# For normal scales (like depression, anxiety), lower is better
|
||||
if current_val <= scale_max * 0.3:
|
||||
value_label.configure(foreground="#28A745") # Green for good
|
||||
elif current_val <= scale_max * 0.6:
|
||||
value_label.configure(foreground="#FFC107") # Yellow for medium
|
||||
else:
|
||||
value_label.configure(foreground="#DC3545") # Red for bad
|
||||
|
||||
scale.bind("<Motion>", update_value_label_pathology)
|
||||
scale.bind("<ButtonRelease-1>", update_value_label_pathology)
|
||||
scale.bind("<KeyRelease>", update_value_label_pathology)
|
||||
update_value_label_pathology() # Set initial color
|
||||
|
||||
def _create_medicine_section(
|
||||
self, parent: ttk.Frame, bup: int, hydro: int, gaba: int, prop: int, quet: int
|
||||
) -> dict[str, tk.IntVar]:
|
||||
@@ -903,6 +1073,202 @@ class UIManager:
|
||||
|
||||
return vars_dict
|
||||
|
||||
def _create_medicine_section_dynamic(
|
||||
self, parent: ttk.Frame, medicine_values: dict[str, int]
|
||||
) -> dict[str, tk.IntVar]:
|
||||
"""Create medicine checkboxes dynamically."""
|
||||
vars_dict = {}
|
||||
|
||||
# Create a grid layout for medicines
|
||||
medicine_items = []
|
||||
for medicine_key, value in medicine_values.items():
|
||||
medicine = self.medicine_manager.get_medicine(medicine_key)
|
||||
if medicine:
|
||||
medicine_items.append(
|
||||
(
|
||||
medicine_key,
|
||||
value,
|
||||
medicine.display_name,
|
||||
medicine.dosage_info,
|
||||
medicine.color,
|
||||
)
|
||||
)
|
||||
|
||||
# Create medicine cards in a 2-column layout
|
||||
for i, (key, value, name, dose, _color) in enumerate(medicine_items):
|
||||
row = i // 2
|
||||
col = i % 2
|
||||
|
||||
# Medicine card frame
|
||||
med_card = ttk.Frame(parent, relief="solid", borderwidth=1)
|
||||
med_card.grid(row=row, column=col, sticky="ew", padx=5, pady=5)
|
||||
parent.grid_columnconfigure(col, weight=1)
|
||||
|
||||
vars_dict[key] = tk.IntVar(value=int(value))
|
||||
|
||||
# Checkbox with medicine name
|
||||
check_frame = ttk.Frame(med_card)
|
||||
check_frame.pack(fill="x", padx=10, pady=8)
|
||||
|
||||
checkbox = ttk.Checkbutton(
|
||||
check_frame,
|
||||
text=f"{name} ({dose})",
|
||||
variable=vars_dict[key],
|
||||
style="Medicine.TCheckbutton",
|
||||
)
|
||||
checkbox.pack(anchor="w")
|
||||
|
||||
return vars_dict
|
||||
|
||||
def _create_dose_tracking_dynamic(
|
||||
self, parent: ttk.Frame, medicine_doses: dict[str, str]
|
||||
) -> dict[str, Any]:
|
||||
"""Create dose tracking interface dynamically."""
|
||||
vars_dict = {}
|
||||
|
||||
# Create notebook for organized dose tracking
|
||||
notebook = ttk.Notebook(parent)
|
||||
notebook.pack(fill="both", expand=True)
|
||||
|
||||
for medicine_key, dose_str in medicine_doses.items():
|
||||
medicine = self.medicine_manager.get_medicine(medicine_key)
|
||||
if not medicine:
|
||||
continue
|
||||
|
||||
# Create tab for each medicine
|
||||
tab_frame = ttk.Frame(notebook)
|
||||
notebook.add(tab_frame, text=medicine.display_name)
|
||||
|
||||
# Configure tab layout
|
||||
tab_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Quick dose entry section
|
||||
entry_frame = ttk.LabelFrame(tab_frame, text="Add New Dose", padding="10")
|
||||
entry_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5)
|
||||
entry_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Dose entry
|
||||
dose_entry_var = tk.StringVar()
|
||||
vars_dict[f"{medicine_key}_dose_entry"] = dose_entry_var
|
||||
|
||||
dose_entry = ttk.Entry(entry_frame, textvariable=dose_entry_var, width=12)
|
||||
dose_entry.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Quick dose buttons
|
||||
quick_frame = ttk.Frame(entry_frame)
|
||||
quick_frame.grid(row=0, column=1, padx=10, pady=5, sticky="w")
|
||||
|
||||
# Create the dose StringVar that will be used for saving
|
||||
dose_string_var = tk.StringVar(value=str(dose_str))
|
||||
vars_dict[f"{medicine_key}_doses"] = dose_string_var
|
||||
|
||||
# Punch button - updated to use the StringVar properly
|
||||
def create_punch_callback(med_key, entry_var, dose_var):
|
||||
def punch_dose():
|
||||
dose = entry_var.get().strip()
|
||||
if dose:
|
||||
from datetime import datetime
|
||||
|
||||
timestamp = datetime.now().strftime("%H:%M")
|
||||
new_dose = f"{timestamp}: {dose}"
|
||||
|
||||
current_doses = dose_var.get()
|
||||
if current_doses and current_doses.strip():
|
||||
dose_var.set(current_doses + f"\n{new_dose}")
|
||||
else:
|
||||
dose_var.set(new_dose)
|
||||
|
||||
entry_var.set("")
|
||||
|
||||
return punch_dose
|
||||
|
||||
punch_btn = ttk.Button(
|
||||
quick_frame,
|
||||
text=f"Take {medicine.display_name}",
|
||||
command=create_punch_callback(
|
||||
medicine_key, dose_entry_var, dose_string_var
|
||||
),
|
||||
width=15,
|
||||
)
|
||||
punch_btn.grid(row=0, column=0, padx=5)
|
||||
|
||||
# Quick dose buttons
|
||||
quick_doses = self.medicine_manager.get_quick_doses(medicine_key)
|
||||
for i, dose in enumerate(quick_doses[:3]): # Limit to 3 quick doses
|
||||
|
||||
def create_quick_callback(d, entry_var=dose_entry_var):
|
||||
return lambda: entry_var.set(d)
|
||||
|
||||
btn = ttk.Button(
|
||||
quick_frame,
|
||||
text=f"{dose}mg",
|
||||
command=create_quick_callback(dose),
|
||||
width=8,
|
||||
)
|
||||
btn.grid(row=0, column=i + 1, padx=2)
|
||||
|
||||
# Dose history section
|
||||
history_frame = ttk.LabelFrame(
|
||||
tab_frame, text="Dose History (HH:MM: dose)", padding="10"
|
||||
)
|
||||
history_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=5)
|
||||
history_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Dose display text area
|
||||
dose_text = tk.Text(
|
||||
history_frame,
|
||||
height=3,
|
||||
width=40,
|
||||
wrap=tk.WORD,
|
||||
font=("Consolas", 9),
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
)
|
||||
dose_text.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
|
||||
|
||||
# Populate with existing doses using the proper formatting method
|
||||
self._populate_dose_history(dose_text, dose_str)
|
||||
|
||||
# Bind text widget to update string var - fixed closure issue
|
||||
def create_update_callback(text_widget, dose_var):
|
||||
def update_doses(*args):
|
||||
content = text_widget.get("1.0", tk.END).strip()
|
||||
dose_var.set(content)
|
||||
|
||||
return update_doses
|
||||
|
||||
update_callback = create_update_callback(dose_text, dose_string_var)
|
||||
dose_text.bind("<KeyRelease>", update_callback)
|
||||
dose_text.bind("<FocusOut>", update_callback)
|
||||
|
||||
# Also update text widget when StringVar changes (for punch button)
|
||||
def create_var_to_text_callback(text_widget, string_var):
|
||||
def update_text_from_var(*args):
|
||||
current_text = text_widget.get("1.0", tk.END).strip()
|
||||
var_content = string_var.get()
|
||||
if current_text != var_content:
|
||||
text_widget.delete("1.0", tk.END)
|
||||
text_widget.insert("1.0", var_content)
|
||||
|
||||
return update_text_from_var
|
||||
|
||||
var_to_text_callback = create_var_to_text_callback(
|
||||
dose_text, dose_string_var
|
||||
)
|
||||
dose_string_var.trace("w", var_to_text_callback)
|
||||
|
||||
# Scrollbar for dose text
|
||||
dose_scroll = ttk.Scrollbar(
|
||||
history_frame, orient="vertical", command=dose_text.yview
|
||||
)
|
||||
dose_scroll.grid(row=0, column=1, sticky="ns")
|
||||
dose_text.configure(yscrollcommand=dose_scroll.set)
|
||||
|
||||
# Store reference to text widget for save function
|
||||
vars_dict[f"{medicine_key}_dose_text"] = dose_text
|
||||
|
||||
return vars_dict
|
||||
|
||||
def _get_quick_doses(self, medicine_key: str) -> list[str]:
|
||||
"""Get common dose amounts for quick selection."""
|
||||
return self.medicine_manager.get_quick_doses(medicine_key)
|
||||
@@ -922,14 +1288,21 @@ class UIManager:
|
||||
|
||||
for dose_entry in doses_str.split("|"):
|
||||
if ":" in dose_entry:
|
||||
timestamp, dose = dose_entry.split(":", 1)
|
||||
try:
|
||||
dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
|
||||
time_str = dt.strftime("%I:%M %p")
|
||||
formatted_doses.append(f"• {time_str} - {dose}")
|
||||
except ValueError:
|
||||
# Handle cases where the timestamp might be malformed
|
||||
# Split on the last colon to separate timestamp from dose
|
||||
parts = dose_entry.rsplit(":", 1)
|
||||
if len(parts) == 2:
|
||||
timestamp, dose = parts
|
||||
try:
|
||||
dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
|
||||
time_str = dt.strftime("%I:%M %p")
|
||||
formatted_doses.append(f"• {time_str} - {dose}")
|
||||
except ValueError:
|
||||
# Handle cases where the timestamp might be malformed
|
||||
formatted_doses.append(f"• {dose_entry}")
|
||||
else:
|
||||
formatted_doses.append(f"• {dose_entry}")
|
||||
else:
|
||||
formatted_doses.append(f"• {dose_entry}")
|
||||
|
||||
if formatted_doses:
|
||||
text_widget.insert(1.0, "\n".join(formatted_doses))
|
||||
@@ -1029,51 +1402,70 @@ class UIManager:
|
||||
if note_text_widget:
|
||||
note_content = note_text_widget.get(1.0, tk.END).strip()
|
||||
|
||||
# Extract dose data from the editable text widgets
|
||||
# Extract dose data dynamically from all medicines
|
||||
dose_data = {}
|
||||
medicine_list = [
|
||||
"bupropion",
|
||||
"hydroxyzine",
|
||||
"gabapentin",
|
||||
"propranolol",
|
||||
"quetiapine",
|
||||
]
|
||||
for medicine in medicine_list:
|
||||
dose_text_key = f"{medicine}_doses_text"
|
||||
self.logger.debug(f"Processing {medicine}...")
|
||||
medicines = self.medicine_manager.get_all_medicines()
|
||||
for medicine_key in medicines:
|
||||
dose_var_key = f"{medicine_key}_doses"
|
||||
dose_text_key = f"{medicine_key}_dose_text"
|
||||
self.logger.debug(f"Processing {medicine_key}...")
|
||||
|
||||
if dose_text_key in vars_dict and isinstance(
|
||||
vars_dict[dose_text_key], tk.Text
|
||||
):
|
||||
raw_text = vars_dict[dose_text_key].get(1.0, tk.END).strip()
|
||||
self.logger.debug(f"Raw text for {medicine}: '{raw_text}'")
|
||||
# Prioritize Text widget if it exists (it has the most current data)
|
||||
if dose_text_key in vars_dict:
|
||||
# Read directly from Text widget
|
||||
dose_text_widget = vars_dict[dose_text_key]
|
||||
raw_text = dose_text_widget.get(1.0, tk.END).strip()
|
||||
self.logger.debug(
|
||||
f"Raw text from Text widget for {medicine_key}: '{raw_text}'"
|
||||
)
|
||||
elif dose_var_key in vars_dict:
|
||||
# Fall back to StringVar
|
||||
if isinstance(vars_dict[dose_var_key], tk.StringVar):
|
||||
raw_text = vars_dict[dose_var_key].get().strip()
|
||||
elif isinstance(vars_dict[dose_var_key], tk.Text):
|
||||
raw_text = vars_dict[dose_var_key].get(1.0, tk.END).strip()
|
||||
else:
|
||||
raw_text = str(vars_dict[dose_var_key]).strip()
|
||||
self.logger.debug(
|
||||
f"Raw text from StringVar for {medicine_key}: '{raw_text}'"
|
||||
)
|
||||
else:
|
||||
raw_text = ""
|
||||
self.logger.debug(f"No dose data found for {medicine_key}")
|
||||
|
||||
if raw_text:
|
||||
parsed_dose = self._parse_dose_history_for_saving(
|
||||
raw_text, vars_dict["date"].get()
|
||||
)
|
||||
dose_data[medicine] = parsed_dose
|
||||
self.logger.debug(f"Parsed dose for {medicine}: '{parsed_dose}'")
|
||||
dose_data[medicine_key] = parsed_dose
|
||||
self.logger.debug(
|
||||
f"Parsed dose for {medicine_key}: '{parsed_dose}'"
|
||||
)
|
||||
else:
|
||||
self.logger.debug(f"No text widget found for {medicine}")
|
||||
dose_data[medicine] = ""
|
||||
dose_data[medicine_key] = ""
|
||||
|
||||
self.logger.debug(f"Final dose_data: {dose_data}")
|
||||
|
||||
callbacks["save"](
|
||||
edit_win,
|
||||
vars_dict["date"].get(),
|
||||
vars_dict["depression"].get(),
|
||||
vars_dict["anxiety"].get(),
|
||||
vars_dict["sleep"].get(),
|
||||
vars_dict["appetite"].get(),
|
||||
vars_dict["bupropion"].get(),
|
||||
vars_dict["hydroxyzine"].get(),
|
||||
vars_dict["gabapentin"].get(),
|
||||
vars_dict["propranolol"].get(),
|
||||
vars_dict["quetiapine"].get(),
|
||||
note_content,
|
||||
dose_data,
|
||||
# Build dynamic callback arguments
|
||||
callback_args = [edit_win, vars_dict["date"].get()]
|
||||
|
||||
# Add pathology values
|
||||
pathologies = self.pathology_manager.get_all_pathologies()
|
||||
for pathology_key in pathologies:
|
||||
callback_args.append(vars_dict[pathology_key].get())
|
||||
|
||||
# Add medicine values
|
||||
medicines = self.medicine_manager.get_all_medicines()
|
||||
for medicine_key in medicines:
|
||||
callback_args.append(vars_dict[medicine_key].get())
|
||||
|
||||
# Add note and dose data
|
||||
callback_args.extend([note_content, dose_data])
|
||||
|
||||
self.logger.debug(
|
||||
f"Calling save callback with {len(callback_args)} arguments"
|
||||
)
|
||||
callbacks["save"](*callback_args)
|
||||
|
||||
save_btn = ttk.Button(
|
||||
button_frame,
|
||||
@@ -1139,7 +1531,16 @@ class UIManager:
|
||||
# Try 24-hour format fallback
|
||||
time_obj = datetime.strptime(time_part.strip(), "%H:%M")
|
||||
|
||||
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
|
||||
# Try different date formats
|
||||
try:
|
||||
entry_date = datetime.strptime(date_str, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
try:
|
||||
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
|
||||
except ValueError:
|
||||
# If both fail, try ISO format
|
||||
entry_date = datetime.fromisoformat(date_str)
|
||||
|
||||
full_timestamp = entry_date.replace(
|
||||
hour=time_obj.hour,
|
||||
minute=time_obj.minute,
|
||||
@@ -1169,7 +1570,17 @@ class UIManager:
|
||||
except ValueError:
|
||||
# Try 12-hour format
|
||||
time_obj = datetime.strptime(time_part, "%I:%M")
|
||||
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
|
||||
|
||||
# Try different date formats
|
||||
try:
|
||||
entry_date = datetime.strptime(date_str, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
try:
|
||||
entry_date = datetime.strptime(date_str, "%m/%d/%Y")
|
||||
except ValueError:
|
||||
# If both fail, try ISO format
|
||||
entry_date = datetime.fromisoformat(date_str)
|
||||
|
||||
full_timestamp = entry_date.replace(
|
||||
hour=time_obj.hour,
|
||||
minute=time_obj.minute,
|
||||
|
||||
Reference in New Issue
Block a user