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:
William Valentin
2025-07-31 09:50:45 -07:00
parent b8600ae57a
commit c755f0affc
22 changed files with 2801 additions and 394 deletions
+582 -171
View File
@@ -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,