
    0j                        d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	  ed      Z
dZe G d	 d
             Z	 d	 	 	 	 	 ddZ	 	 	 	 	 	 ddZy)u   
Paydown plan monitor — deviation detection.

Boundary 5 enforced: no Flask context, no direct DB session.
Inputs are plain Python objects (AmortizationResult + list of dicts).
    )annotationsN)	dataclass)Decimal)Anyz1.05   c                  h    e Zd ZU ded<   ded<   ded<   ded<   ded<   d	ed
<   ded<   d	ed<   ded<   y)CardMonitorStatusint
account_idstraccount_namer   planned_balanceactual_balancevariancebool	is_behinddatetime.date | Nonelast_update_dateis_staleprojected_payoff_dateN)__name__
__module____qualname____annotations__     5/var/www/html/financials/app/services/plan_monitor.pyr	   r	      s5    OO**Nr   r	   c                r   |xs t         j                  j                         }g }i }|D ]  }|d   }||vs|d   ||   d   kD  s|||<   ! | j                  D ]  }|d   }|d   }	|	st	        |	|      }
|j                  |      }|r?t        t        |d               }t        |d   d      r|d   j                         n|d   }nd}d}|d}n||z
  j                  t        kD  }||
t        d      kD  r||
t        z  kD  }nd	}||
}||
z
  }|j                  t        ||d
   |
||||||d   	              |S )u/  
    Compute per-card monitor status for the current month.

    result: AmortizationResult from amortization.py
    balance_updates: list of dicts with account_id, balance, updated_at — all updates ever recorded
    reference_date: the date to use for "this month" comparison (defaults to today)
    r   
updated_atschedulebalancedateNT0Fr   payoff_date)	r   r   r   r   r   r   r   r   r   )datetimer"   todayper_card_schedule_planned_balance_for_dategetr   r   hasattrdays_STALE_DAYS_DEVIATION_THRESHOLDappendr	   )resultbalance_updatesreference_dater&   statuseslatest_updateupdacct_id	card_datar    r   updater   r   r   r   r   s                    r   get_monitor_statusesr8      s    3hmm113E H &(M )l#-'3|+<}W?UVb?c+c%(M'")
 -- 3	L)Z( 4HeD ""7+$S	):%;<N 6,/8 |$))+L)  "N# #H 0066DH %/GCL*H&;O)OOII!,N!O3)">2+)-"+M":

 
	S3j Or   c                    | st        d      S | d   }| D ]-  }t        j                  j                  |d         }||k  r|}- n t        |d         S )z?Return the planned end-of-month balance closest to target date.r#   r   r"   r!   )r   r%   r"   fromisoformat)r    targetbestrowrow_dates        r   r(   r(   u   sd    
 s| A;D ==..s6{;vD 4	?##r   )N)r0   
list[dict]r1   r   returnzlist[CardMonitorStatus])r    r?   r;   zdatetime.dater@   r   )__doc__
__future__r   r%   dataclassesr   decimalr   typingr   r-   r,   r	   r8   r(   r   r   r   <module>rF      s    #  !  v  	 	 	 ,0SS )S 	Sl$$$ $r   