
    ejv(              
       0   d Z ddlmZ ddlmZ ddlmZmZmZ ddl	m
Z
mZmZ e G d d             Ze G d	 d
             Z G d d      Zedk(  rdddddddgZddddddddddddgZddiZdZej+                  eeee      Z ed       ej1                         D ]#  \  ZZedk\  rdndZ ede de dedz  d       % ej9                  e      Z ed        eD ]5  Z edej>                   d!ej@                   d"ejB                  dz  d       7 y#y#)$z
Settlement engine for TripTalley.
Computes who owes whom after tallying all expenses in a trip.
Uses debt simplification to minimize the number of transfers.
    	dataclass)Decimal)DictListTuple   )Moneyconvert_currency
from_minorc                       e Zd ZU dZeed<   eed<   eed<   edefd       Z	edefd       Z
edefd       Zdefd	Zdefd
Zy)Balancez,A user's net balance in a specific currency.user_idamount_minorcurrencyreturnc                      | j                   dkD  S Nr   r   selfs    3/srv/projects/triptalley/backend/app/core/settle.pyis_positivezBalance.is_positive         1$$    c                      | j                   dk  S r   r   r   s    r   is_negativezBalance.is_negative   r   r   c                 ,    t        | j                        S N)absr   r   s    r   
abs_amountzBalance.abs_amount   s    4$$%%r   c                 B    t        | j                  | j                        S r   r   r   r   r   s    r   
to_decimalzBalance.to_decimal        $++T]];;r   c                 p    | j                   dk\  rdnd}| t        | j                   | j                         S )Nr   + )r   r   r   )r   signs     r   __repr__zBalance.__repr__#   s7    ''1,s"
4#4#4dmmDEFFr   N)__name__
__module____qualname____doc__int__annotations__strpropertyboolr   r   r!   r   r$   r*    r   r   r   r      s~    6LM%T % % %T % % &C & &<G <G# Gr   r   c                   R    e Zd ZU dZeed<   eed<   eed<   eed<   defdZdefdZ	y	)
Paymentz#A single payment in the settlement.from_user_id
to_user_idr   r   r   c                 B    t        | j                  | j                        S r   r#   r   s    r   r$   zPayment.to_decimal0   r%   r   c           	      |    d| j                    d| j                   dt        | j                  | j                         S )NUser 
    → User : )r7   r8   r   r   r   r   s    r   r*   zPayment.__repr__3   s@    t(()DOO3DBT..>?A 	Ar   N)
r+   r,   r-   r.   r/   r0   r1   r   r$   r*   r4   r   r   r6   r6   (   s5    -OM<G <A# Ar   r6   c                       e Zd ZdZedee   dee   deeeeef   e	f   dedee
e
f   f
d       Zedee
e
f   dee   fd	       Zed
ee   dedede	ded   f
d       Zy)SettlementEnginez9
    Computes trip settlement from expense balances.
    expenses_dataparticipants_datafx_ratessettlement_currencyr   c           	         i }i }|D ],  }|d   }||vrg ||<   ||   j                  |d   |d   f       . | D ]  }|d   }	|d   }
|d   }|d   }|||f}||vrt        d| d	| d
|       t        |
||||         }|j                  |d   g       }|j                  |	d      |z   ||	<   |D ]3  \  }}|||f}t        |||||         }|j                  |d      |z
  ||<   5  |S )a  
        Compute net balances for all participants in the settlement currency.
        
        Args:
            expenses_data: List of expense records with:
                - expense_id, trip_id, payer_id, amount_minor, original_currency, spent_on
            participants_data: List of participant records with:
                - expense_id, user_id, share_minor
            fx_rates: Historical FX rates keyed by (spent_on_date, currency_from, currency_to)
            settlement_currency: The canonical currency for netting
        
        Returns:
            Dict mapping user_id to net balance (positive = owed, negative = owes)
            in the settlement currency
        
expense_idr   share_minorpayer_idr   original_currencyspent_onzNo FX rate for u   →z on r   )append
ValueErrorr   get)r@   rA   rB   rC   balancesexpense_participantspexp_idexpenserG   r   rH   rI   fx_keyamount_settlementparticipantsr   rF   fx_key_shareshare_settlements                       r   compute_balancesz!SettlementEngine.compute_balances=   s   , $& BD" 	RA|_F11/1$V, (//9q?O0PQ		R %  	PGz*H">2L '(; <z*H  13FGFX% %&7%8<O;PPTU]T^_  !1/1D ! 033GL4I2NL "*h!:=N!NHX )5 P$ (*;=PQ#3!24G\*$  %-LL!$<?O$O!P3 	PD r   rM   c                 b   | sg S g }g }| j                         D ]8  \  }}|dkD  r|j                  ||f       |dk  s%|j                  || f       : |j                  d d       |j                  d d       g }d\  }}|t        |      k  r|t        |      k  r||   \  }}	||   \  }
}t	        |	|      }|dkD  r|j                  t        |
||d             ||	|z
  f||<   |
||z
  f||<   ||   d	   dk(  r|d	z  }||   d	   dk(  r|d	z  }|t        |      k  r|t        |      k  r|S )
ar  
        Simplify a set of balances into minimal payments using a greedy algorithm.
        
        The algorithm:
        1. Separate creditors (positive balance) and debtors (negative balance)
        2. Greedily match the largest creditor with the largest debtor
        3. Settle the smaller amount, adjust balances, repeat until all settled
        
        This minimizes the number of transfers (at most N-1 for N participants).
        
        Args:
            balances: Dict mapping user_id to net balance
            
        Returns:
            List of Payment objects representing minimal transfer set
        r   c                     | d   S Nr	   r4   xs    r   <lambda>z0SettlementEngine.simplify_debt.<locals>.<lambda>   s
    QqT r   T)keyreversec                     | d   S rZ   r4   r[   s    r   r]   z0SettlementEngine.simplify_debt.<locals>.<lambda>   s
    1Q4 r   )r   r   
SETTLEMENT)r7   r8   r   r   r	   )itemsrJ   sortlenminr6   )rM   	creditorsdebtorsr   balancepaymentsc_idxd_idxcreditor_idcreditor_amt	debtor_id
debtor_amtsettle_amounts                r   simplify_debtzSettlementEngine.simplify_debt   s   $ I 	 ( 0 	4GW{  '7!341'23		4 	>486uc)n$W)=(1%(8%K$+EN!Iz  j9Mq !**!.)	!  !,\M-IJIe'm)CDGEN "a'
u~a A%
/ c)n$W)=2 r   ri   from_currencyto_currencyfx_rateDisplayPaymentc                    ddl m} | G d d             }g }| D ]i  }t        |j                  |j                  ||      }|j                   ||j                  |j                  |j                  |j                  ||             k |S )a  
        Convert canonical settlement payments to a viewer's home currency.
        
        Args:
            payments: Payments in the settlement currency
            from_currency: The settlement currency
            to_currency: The viewer's home currency
            fx_rate: FX rate from settlement to home currency
        
        Returns:
            List of DisplayPayment with both canonical and converted amounts
        r   r   c                   V    e Zd ZU eed<   eed<   eed<   eed<   eed<   eed<   defdZy	)
<SettlementEngine.convert_for_display.<locals>.DisplayPaymentr7   r8   r   r   amount_display_minordisplay_currencyr   c                     d| j                    d| j                   dt        | j                  | j                         dt        | j
                  | j                         d	S )Nr;   r<   r=   z ())r7   r8   r   r   r   ry   rz   r   s    r   r*   zESettlementEngine.convert_for_display.<locals>.DisplayPayment.__repr__   sb     1 12*T__<MR$T%6%6FG H%d&?&?AVAVWXXY[ \r   N)r+   r,   r-   r/   r0   r1   r*   r4   r   r   ru   rx      s2    OM"%%!!\# \r   ru   )r7   r8   r   r   ry   rz   )dataclassesr   r   r   r   rJ   r7   r8   )	ri   rr   rs   rt   r   ru   display_paymentspaymentamount_displays	            r   convert_for_displayz$SettlementEngine.convert_for_display   s    & 	*		\ 	\ 
	\  	G-$$  	N ##N$11"--$11 ))%3!,% 	   r   N)r+   r,   r-   r.   staticmethodr   r   r   r1   floatr/   rW   r6   rq   r   r4   r   r   r?   r?   8   s     BDzB:B uS#s]+U23B !	B
 
c3hB BH ?S#X ?4= ? ?B 3 w-3 3  3  	3 
 
	3  3 r   r?   __main__   i'  USD
2024-01-15)rE   trip_idrG   r   rH   rI   i  )rE   r   rF      )r   r   r   g      ?z	Balances:r'   r(   z  User r=   $d   z.2fz

Payments:r<   z: $N)"r.   r}   r   decimalr   typingr   r   r   moneyr
   r   r   r   r6   r?   r+   expensesrT   rB   rC   rW   rM   printrb   r   rh   r)   rq   ri   rO   r7   r8   r   r4   r   r   <module>r      s   "  $ $ 6 6 G G G4 A A A@  @ H z !!&$	
	H Qt<Qt<Qq9L 	%cH    00,*=H 
+$NN, =lsy4&'#+c):;<=
  --h7H	- ]'z!,,s1>>RUCUVYBZ[\]S r   