
    .j_4                        d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	 ddl
mZ  ed      Z ed      Ze G d	 d
             ZddZddZ	 	 	 	 	 	 	 	 ddZddZddZddZddZ	 	 	 	 	 	 ddZy)u   
Amortization engine — all 5 paydown strategies.

Boundary 6 enforced: zero imports outside stdlib + decimal + datetime.
All monetary arithmetic uses Decimal. No ORM references anywhere.
    )annotationsN)	dataclass)DecimalROUND_HALF_UP)Anyz0.010c                  @    e Zd ZU ded<   ded<   ded<   ded<   d	ed
<   y)AmortizationResultstrstrategyintmonths_to_payoffr   total_interest_paiddatetime.datepayoff_datezlist[dict[str, Any]]per_card_scheduleN)__name__
__module____qualname____annotations__     5/var/www/html/financials/app/services/amortization.pyr
   r
      s    M  ++r   r
   c                8    | j                  t        t              S )N)rounding)quantize_CENTr   )ds    r   _centsr      s    ::em:44r   c                    | j                   dz
  |z   }| j                  |dz  z   }|dz  dz   }t        | j                  t	        j
                  ||      d         }t        j                  |||      S )N      )monthyearmindaycalendar
monthrangedatetimedate)dtmonthsr#   r$   r&   s        r   _add_monthsr-   "   sh    HHqL6!E77Ub[ DBJNE
bffh))$6q9
:C==uc**r   c                   | D ci c]&  }|d   t        t        t        |d                     ( }}| D ci c]  }|d   t        t        |d                }}| D ci c]&  }|d   t        t        t        |d                     ( }}| D ci c]  }|d   |d    }}| D ci c]  }|d   g 
 }	}| D ci c]  }|d   t         }
}t        j
                  j                         }d}d}t        d |j                         D              r!||k  r|d	z  }t        ||      }i }|D ]h  }||   }|t        k  r
t        ||<   ||   t        d
      z  t        d      z  }t        ||z        }t        ||z         ||<   |||<   |
|xx   |z  cc<   j |D ci c]	  }|t         }}|D ]6  }||   }|t        k  rt        ||   |      }t        ||z
        ||<   |||<   8 t        |      }| D cg c]  }||d      t        kD  s| }} ||||      }|D ]K  }|t        k  r n@||   }|t        k  rt        ||      }t        ||z
        ||<   ||xx   |z  cc<   ||z  }M |D ]  }||   }||   }t        ||z
        }|	|   j                  ||j                         t        t        |            t        t        |            t        t        |            t        t        t        ||   t                          d        t        d |j                         D              r||k  rt        ||      }t        t        |
j                         t                    }g }| D ]h  }|d   }t        d |	|   D        |      }|j                  |||   |t        ||      j                         t        t        |
|               |	|   d       j t!        |||||      S c c}w c c}w c c}w c c}w c c}w c c}w c c}w c c}w )a2  
    Core amortization loop for ordered-priority strategies.

    cards: list of dicts with keys: id, name, balance, apr, min_payment (all convertible to Decimal)
    extra: total extra monthly payment beyond minimums
    priority_fn(active_cards, balances, aprs) -> list of card IDs in priority order
    idbalanceaprmin_paymentnamer   X  c              3  .   K   | ]  }|t         kD    y wN_ZERO.0bs     r   	<genexpr>z _run_schedule.<locals>.<genexpr>C        3Aa%i3   r!   10012r#   r*   payment	principalinterestr0   c              3  R   K   | ]  }t        |d          t        k  s|d    ! ywr0   r#   Nr   r8   r:   rows     r   r<   z _run_schedule.<locals>.<genexpr>   %     Xcws9~7NRW7WS\X   '
'
account_idaccount_namepayoff_monthr   total_interestscheduler   r   r   r   r   )r   r   r   r8   r)   r*   todayanyvaluesr-   r%   append	isoformatmaxsumnextr
   ) cardsextrastrategy_namepriority_fncbalancesaprsminsnames	schedulesrP   rS   	month_num
max_months
month_dateinterest_this_monthcidbalmonthly_raterD   paymentspayremaining_extraactive_cardsordered_ids	extra_payrB   rC   r   grand_interestper_cardrO   s                                    r   _run_schedulert   *   sd    FKK$AiL(9 :;;KHK5:;AdGWS5]++;D;EJKAdGVGC-(8$9:;;KDK)./AQtWai/E/=B'C$'CI'CBG)HQ!D'5.)HN)HMM!EIJ
3!23
3	J8NQ	 	2
 35 		,C3-Ce|+0#C(9wu~5ELcL01H"3>2HSM'/$38+		, ?G'GsU
'G'G 	 C3-Ce|d3i%C"39-HSMHSM	  !-#(FaHQtW,=,EFF!,$? 		)C%'3-Ce|OS1I"3?3HSMSMY&My(O		)  	C*3/HsmGw12IcN!!"",,.vg/ 	!23x 01vc(3-&?@A# 		Y 3!23
3	J8Nr eY/KC 5 5 7?@NH gXYs^X
 	!#J(&ul;EEG!&)<"=>!#
 	 "*" q L;K/'C)H0 (H Gs4   +O!"O&+O+O0%O58O:O?2PPc                $    d }t        | |d|      S )zHighest APR first.c                R    t        | fdd      D cg c]  }|d   	 c}S c c}w )Nc                    | d      S Nr/   r   )r_   ra   s    r   <lambda>z7calculate_avalanche.<locals>.priority.<locals>.<lambda>   s    d1T7m r   Tkeyreverser/   sortedactiver`   ra   r_   s     ` r   priorityz%calculate_avalanche.<locals>.priority   s&    !'4KUY!Z[A$[[[   $	avalanchert   r[   extra_monthlyr   s      r   calculate_avalancher      s    \{HEEr   c                $    d }t        | |d|      S )zLowest balance first.c                P    t        | fd      D cg c]  }|d   	 c}S c c}w )Nc                    | d      S rx   r   r_   r`   s    r   ry   z6calculate_snowball.<locals>.priority.<locals>.<lambda>       hqw>O r   )r{   r/   r}   r   s    `  r   r   z$calculate_snowball.<locals>.priority   s#    !'4O!PQA$QQQs   #snowballr   r   s      r   calculate_snowballr      s    Rz8DDr   c                $    d }t        | |d|      S )zHighest balance first.c                R    t        | fdd      D cg c]  }|d   	 c}S c c}w )Nc                    | d      S rx   r   r   s    r   ry   z=calculate_highest_balance.<locals>.priority.<locals>.<lambda>   r   r   Trz   r/   r}   r   s    `  r   r   z+calculate_highest_balance.<locals>.priority   s&    !'4OY]!^_A$___r   highest_balancer   r   s      r   calculate_highest_balancer      s    `/@(KKr   c                   | D ci c]&  }|d   t        t        t        |d                     ( c}| D ci c]  }|d   t        t        |d                }}| D ci c]&  }|d   t        t        t        |d                     ( }}| D ci c]  }|d   |d    }}| D ci c]  }|d   g 
 }}| D ci c]  }|d   t         }}t        j
                  j                         }d}	d}
t        d j                         D              r|	|
k  r}|	d	z  }	t        ||	      }i }D ]h  }|   }|t        k  r
t        ||<   ||   t        d
      z  t        d      z  }t        ||z        }t        ||z         |<   |||<   ||xx   |z  cc<   j D ci c]	  }|t         }}D ]6  }|   }|t        k  rt        ||   |      }t        ||z
        |<   |||<   8 | D cg c]  }|d      t        kD  s|d    }}t        fd|D              }|t        kD  r|t        kD  rt        |      }t        |      D ]  \  }}|t        k  r nw|t        |      d	z
  k(  rt        ||         }n$t        ||   z  |z        }t        ||   |      }|t        kD  s`t        |   |z
        |<   ||xx   |z  cc<   ||z  } D ]  }||   }||   }t        ||z
        }||   j                  |	|j                         t        t        |            t        t        |            t        t        |            t        t        t!        |   t                          d        t        d j                         D              r|	|
k  r}t        ||	      }t        t        |j                         t                    }g }| D ]h  }|d   }t#        d ||   D        |	      }|j                  |||   |t        ||      j                         t        t        ||               ||   d       j t%        d|	|||      S c c}w c c}w c c}w c c}w c c}w c c}w c c}w c c}w )z6Distribute extra proportionally by balance each month.r/   r0   r1   r2   r3   r   r4   c              3  .   K   | ]  }|t         kD    y wr6   r7   r9   s     r   r<   z)calculate_proportional.<locals>.<genexpr>   r=   r>   r!   r?   r@   c              3  (   K   | ]	  }|     y wr6   r   )r:   ri   r`   s     r   r<   z)calculate_proportional.<locals>.<genexpr>   s     <#<s   rA   c              3  R   K   | ]  }t        |d          t        k  s|d    ! ywrF   rG   rH   s     r   r<   z)calculate_proportional.<locals>.<genexpr>   rJ   rK   rL   proportionalrR   )r   r   r   r8   r)   r*   rS   rT   rU   r-   r%   rY   	enumeratelenrV   rW   rX   rZ   r
   ) r[   r   r_   ra   rb   rc   rd   rP   rS   re   rf   rg   rh   ri   rj   rk   rD   rl   rm   
active_ids	total_balrn   irq   sharerB   rC   r   rr   rs   rO   r`   s                                   @r   calculate_proportionalr      s   EJK$AiL(9 :;;KH5:;AdGWS5]++;D;EJKAdGVGC-(8$9:;;KDK)./AQtWai/E/=B'C$'CI'CBG)HQ!D'5.)HN)HMM!EIJ
3!23
3	J8NQ	 	2
24 		,C3-Ce|+0#C(9wu~5ELcL01H"3>2HSM'/$38+		, ?G'GsU
'G'G 	 C3-Ce|d3i%C"39-HSMHSM	  (-J!40AE0IagJ
J<<<	u!6$]3O#J/ 13"e+J!++ #OXc] CI"=8C=#@9#LME #E8C=/ JIu$$*8C=9+D$EHSMSMY.M#y0O1  	C*3/HsmGw12IcN!!"",,.vg/ 	!23x 01vc(3-&?@A# 		Y 3!23
3	J8Nr eY/KC 5 5 7?@NH gXYs^X
 	!#J(&ul;EEG!&)<"=>!#
 	 "*" q L;K/'C)H, (H Ks4   +Q"Q
 +QQ'Q:QQ#)Q(>Q(c                   | D ci c]&  }|d   t        t        t        |d                     ( }}| D ci c]  }|d   t        t        |d                }}| D ci c]&  }|d   t        t        t        |d                     ( }}| D ci c]  }|d   |d    }}| D ci c]J  }|d   t        t        t        t        |j	                  |d   t
                                ||d            L }}| D ci c]  }|d   g 
 }}| D ci c]  }|d   t
         }	}t        j                  j                         }
d}d}t        d |j                         D              r||k  r|d	z  }t        |
|      }t        |j                               D ]3  }||   }|t
        k  r*||   j                  ||j                         d
d
d
d
d       <||   t        d      z  t        d      z  }t        ||z        }t        ||z         }|	|xx   |z  cc<   t!        ||   |      }t        ||z
        }t        |t
              ||<   t        ||z
        }||   j                  ||j                         t        t        |            t        t        |            t        t        |            t        t        ||               d       6 t        d |j                         D              r||k  rt        |
|      }t        t#        |	j                         t
                    }g }| D ]h  }|d   }t%        d ||   D        |      }|j                  |||   |t        |
|      j                         t        t        |	|               ||   d       j t'        d||||      S c c}w c c}w c c}w c c}w c c}w c c}w c c}w )z
    User-specified per-card monthly payments.
    custom_allocations: {card_id: monthly_payment_amount}
    Allocations below minimum are raised to minimum.
    r/   r0   r1   r2   r3   r   r4   c              3  .   K   | ]  }|t         kD    y wr6   r7   r9   s     r   r<   z#calculate_custom.<locals>.<genexpr>/  r=   r>   r!   z0.00rA   r?   r@   c              3  R   K   | ]  }t        |d          t        k  s|d    ! ywrF   rG   rH   s     r   r<   z#calculate_custom.<locals>.<genexpr>Z  rJ   rK   rL   customrR   )r   r   r   rX   getr8   r)   r*   rS   rT   rU   r-   listkeysrV   rW   r%   rY   rZ   r
   )r[   custom_allocationsr_   r`   ra   rb   rc   allocationsrd   rP   rS   re   rf   rg   ri   rj   rk   rD   new_balrB   rC   r   rr   rs   rO   s                            r   calculate_customr     s    FKK$AiL(9 :;;KHK5:;AdGWS5]++;D;EJKAdGVGC-(8$9:;;KDK)./AQtWai/E/ 
 	 	
$73155aguEFGH4M
 	
K  >C'C$'CI'CBG)HQ!D'5.)HN)HMM!EIJ
3!23
3	J8NQ	 	2
( 	C3-Ce|#%%&&002%!' &%'  9wu~5ELcL01HS8^,G38++c*G4GWw./G/HSMw12IcN!!"",,.vg/ 	!23x 01vhsm45# /		 3!23
3	J8NH eY/KC 5 5 7?@NH gXYs^X
 	!#J(&ul;EEG!&)<"=>!#
 	 "*" W L;K/ (D)Hs)   +N#"N(+N-N2%AN7:N<O)r   r   returnr   )r+   r   r,   r   r   r   )r[   
list[dict]r\   r   r]   r   r   r
   )r[   r   r   r   r   r
   )r[   r   r   zdict[int, Decimal]r   r
   )__doc__
__future__r   r'   r)   dataclassesr   decimalr   r   typingr   r   r8   r
   r   r-   rt   r   r   r   r   r   r   r   r   <module>r      s    #   ! *  , , ,5+kkk k
 k`FEL`FZZ*Z Zr   