Skip to content

Commit bcd88da

Browse files
Fix HSA balance chart to use actual balance history records
The previous calculation derived historical balances backwards from expenses, which incorrectly showed $40k starting balance when HSA expenses were "Open" (not reimbursed). Now uses hsa_balance_history table directly for the balance line, showing accurate contribution progression ($4k -> $8k -> $12k). Cumulative expenses are still calculated from expense records. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 272f594 commit bcd88da

File tree

1 file changed

+52
-48
lines changed

1 file changed

+52
-48
lines changed

app/services/hsa_balance_service.py

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,9 @@ def calculate_balance_history(
313313
) -> dict:
314314
"""Calculate historical HSA balance for charting.
315315
316-
Works backwards from current balance to derive historical balances.
317-
This ensures the chart shows an upward trajectory toward the current balance,
318-
even when historical expenses are added after setting the current balance.
316+
Uses actual hsa_balance_history records for balance values, ensuring
317+
the chart shows accurate contribution and balance progression.
318+
Cumulative expenses are calculated separately from HSA expense records.
319319
320320
Args:
321321
user_id: The user's ID
@@ -326,59 +326,67 @@ def calculate_balance_history(
326326
"""
327327
current_balance = self.get_balance(user_id)
328328

329-
# Get all HSA transactions ordered by date
330-
transactions = self.conn.execute('''
331-
SELECT date_of_service, amount_outflow, amount_inflow
329+
# Get HSA balance history records ordered by date
330+
history_records = self.conn.execute('''
331+
SELECT date(recorded_at) as record_date, new_balance
332+
FROM hsa_balance_history
333+
WHERE user_id = ?
334+
ORDER BY recorded_at ASC
335+
''', (user_id,)).fetchall()
336+
337+
# Get all HSA expenses for cumulative expense calculation
338+
expenses = self.conn.execute('''
339+
SELECT date_of_service, amount_outflow
332340
FROM expenses
333341
WHERE user_id = ? AND category = 'HSA'
334342
ORDER BY date_of_service ASC
335343
''', (user_id,)).fetchall()
336344

337-
if not transactions:
345+
# Build cumulative expenses by date
346+
expense_by_date: dict = {}
347+
cumulative = 0.0
348+
for expense in expenses:
349+
date_str = expense['date_of_service']
350+
outflow = float(expense['amount_outflow']) if expense['amount_outflow'] else 0
351+
cumulative += outflow
352+
expense_by_date[date_str] = cumulative
353+
354+
# If no history records, return current balance only
355+
if not history_records:
356+
total_expenses = cumulative if expenses else 0
338357
return {
339358
'dates': [current_date],
340359
'balances': [current_balance],
341-
'cumulative_expenses': [0]
360+
'cumulative_expenses': [total_expenses]
342361
}
343362

344-
# Calculate the total net effect of all transactions
345-
# net_effect = sum(inflow) - sum(outflow)
346-
# This represents total reimbursements drawn from the HSA
347-
total_net_effect = 0.0
348-
total_outflow = 0.0
349-
for transaction in transactions:
350-
outflow = float(transaction['amount_outflow']) if transaction['amount_outflow'] else 0
351-
inflow = float(transaction['amount_inflow']) if transaction['amount_inflow'] else 0
352-
total_net_effect += (inflow - outflow)
353-
total_outflow += outflow
354-
355-
# Work backwards: starting_balance = current_balance - net_effect_of_all_transactions
356-
# This gives us what the balance would have been before any transactions
357-
starting_balance = current_balance - total_net_effect
358-
359363
dates: List[str] = []
360364
balances: List[float] = []
361365
cumulative_expenses: List[float] = []
362366

363-
balance_at_point = starting_balance
364-
cumulative_total = 0.0
367+
# Add data points from balance history
368+
for record in history_records:
369+
date_str = record['record_date']
370+
balance = float(record['new_balance']) if record['new_balance'] else 0
365371

366-
for transaction in transactions:
367-
date_str = transaction['date_of_service']
368-
outflow = float(transaction['amount_outflow']) if transaction['amount_outflow'] else 0
369-
inflow = float(transaction['amount_inflow']) if transaction['amount_inflow'] else 0
370-
371-
# Record the balance BEFORE this transaction is applied
372+
# Only add if date not already present (avoid duplicates)
372373
if not dates or dates[-1] != date_str:
373374
dates.append(date_str)
374-
balances.append(balance_at_point)
375-
cumulative_expenses.append(cumulative_total)
376-
377-
# Apply the transaction effect
378-
balance_at_point += (inflow - outflow)
379-
cumulative_total += outflow
380-
381-
# Add current date as the final point with the actual current balance
375+
balances.append(balance)
376+
377+
# Find cumulative expenses up to this date
378+
cumulative_at_date = 0.0
379+
for exp_date, exp_cumulative in expense_by_date.items():
380+
if exp_date <= date_str:
381+
cumulative_at_date = exp_cumulative
382+
else:
383+
break
384+
cumulative_expenses.append(cumulative_at_date)
385+
else:
386+
# Update the balance for the same date to the latest value
387+
balances[-1] = balance
388+
389+
# Add current date as the final point if not already present
382390
if current_date not in dates:
383391
# Find correct chronological position
384392
insert_idx = len(dates)
@@ -387,21 +395,17 @@ def calculate_balance_history(
387395
insert_idx = i
388396
break
389397

390-
# Calculate cumulative expenses up to current date
398+
# Get cumulative expenses up to current date
391399
cumulative_at_current = 0.0
392-
for transaction in transactions:
393-
if transaction['date_of_service'] > current_date:
400+
for exp_date, exp_cumulative in expense_by_date.items():
401+
if exp_date <= current_date:
402+
cumulative_at_current = exp_cumulative
403+
else:
394404
break
395-
outflow = float(transaction['amount_outflow']) if transaction['amount_outflow'] else 0
396-
cumulative_at_current += outflow
397405

398406
dates.insert(insert_idx, current_date)
399407
balances.insert(insert_idx, current_balance)
400408
cumulative_expenses.insert(insert_idx, cumulative_at_current)
401-
else:
402-
# If current_date matches a transaction date, update that balance to current_balance
403-
idx = dates.index(current_date)
404-
balances[idx] = current_balance
405409

406410
return {
407411
'dates': dates,

0 commit comments

Comments
 (0)