feat: Implement dose calculation fix and enhance legend feature
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled

- Fixed dose calculation logic in `_calculate_daily_dose` to correctly parse timestamps with multiple colons.
- Added comprehensive test cases for various dose formats and edge cases in `test_dose_calculation.py`.
- Enhanced graph legend to display individual medicines with average dosages and track medicines without dose data.
- Updated legend styling and positioning for better readability and organization.
- Created new tests for enhanced legend functionality, including handling of medicines with and without data.
- Improved mocking for matplotlib components in tests to prevent TypeErrors.
This commit is contained in:
William Valentin
2025-07-30 14:22:07 -07:00
parent d14d19e7d9
commit b76191d66d
12 changed files with 1042 additions and 68 deletions

View File

@@ -163,6 +163,10 @@ class GraphManager:
"quetiapine",
]
# Track medicines with and without data for legend
medicines_with_data = []
medicines_without_data = []
for medicine in medicines:
dose_column = f"{medicine}_doses"
if self.toggle_vars[medicine].get() and dose_column in df.columns:
@@ -174,23 +178,66 @@ class GraphManager:
# Only plot if there are non-zero doses
if any(dose > 0 for dose in daily_doses):
medicines_with_data.append(medicine)
# Scale doses for better visibility
# (divide by 10 to fit with 0-10 scale)
scaled_doses = [dose / 10 for dose in daily_doses]
# Calculate total dosage for this medicine across all days
total_medicine_dose = sum(daily_doses)
non_zero_doses = [d for d in daily_doses if d > 0]
avg_dose = total_medicine_dose / len(non_zero_doses)
# Create more informative label
label = f"{medicine.capitalize()} (avg: {avg_dose:.1f}mg)"
self.ax.bar(
df.index,
scaled_doses,
alpha=0.6,
color=medicine_colors.get(medicine, "#DDA0DD"),
label=f"{medicine.capitalize()} (mg/10)",
label=label,
width=0.6,
bottom=-max(scaled_doses) * 1.1 if scaled_doses else -1,
)
has_plotted_series = True
else:
# Medicine is toggled on but has no dose data
if self.toggle_vars[medicine].get():
medicines_without_data.append(medicine)
# Configure graph appearance
if has_plotted_series:
self.ax.legend()
# Get current legend handles and labels
handles, labels = self.ax.get_legend_handles_labels()
# Add information about medicines without data if any are toggled on
if medicines_without_data:
# Add a text note about medicines without dose data
med_list = ", ".join(medicines_without_data)
info_text = f"Tracked (no doses): {med_list}"
labels.append(info_text)
# Create a dummy handle for the info text (invisible)
from matplotlib.patches import Rectangle
dummy_handle = Rectangle(
(0, 0), 1, 1, fc="w", fill=False, edgecolor="none", linewidth=0
)
handles.append(dummy_handle)
# Create an expanded legend with better formatting
self.ax.legend(
handles,
labels,
loc="upper left",
bbox_to_anchor=(0, 1),
ncol=2, # Display in 2 columns for better space usage
fontsize="small",
frameon=True,
fancybox=True,
shadow=True,
framealpha=0.9,
)
self.ax.set_title("Medication Effects Over Time")
self.ax.set_xlabel("Date")
self.ax.set_ylabel("Rating (0-10) / Dose (mg)")
@@ -239,12 +286,8 @@ class GraphManager:
continue
try:
if ":" in entry:
# Extract dose part after the timestamp
_, dose_part = entry.split(":", 1)
else:
# Handle cases where there's no timestamp
dose_part = entry
# Extract dose part after the last colon (timestamp:dose format)
dose_part = entry.split(":")[-1] if ":" in entry else entry
# Extract numeric part from dose (e.g., "150mg" -> 150)
dose_value = ""