
    Sj!                      `   U d Z ddlmZ ddlmZmZ ddlmZmZm	Z	 ddl
Z
i ddddd	d
ddddddddddddddddddddddddddddddd d!d"d#d$d#d%Zeee	eef   f   ed&<   d'ed(efd)Zd'ed(efd*Zd+ed'ed(efd,Zd-ed'ed(efd.Ze G d/ d0             Zd1ed2ed3ed4ed(ef
d5Zd6ed7ee   d(eeef   fd8Zd6ed7ee	eef      d(eeef   fd9Zd6ed7ee	eef      d(eeef   fd:Zed;k(  r ed<d      Z eeddd=      Z ed> eed       d?       d@Zg dAZ eee      Z edBe dCe dD e ejC                                       g dEZ" eee"      Z# edFe# dD e e#jC                                       g dGZ$ eee$      Z% edHe% dD e e%jC                                       yy)Iz
Money and currency utilities for TripTalley.
All money amounts are stored as integer **minor units** (cents) with explicit currency.
No floats in storage or settlement - ensures exact math.
    )	dataclass)DecimalROUND_HALF_UP)DictListTupleNUSD)   centEURGBP)r
   pennyJPY)r   yenAUDCADCHFCNY)r
   fenSEK)r
   u   öreNOK)r
   u   øreDKKNZDMXN)r
   centavoSGDHKDKRW)r   wonINR)r
   paisa)r
   kopeck)r
   kurus)   millime)r$   fils)r$   baisa)BRLZARRUBTRYBHDKWDOMRJODCURRENCY_METRICScurrencyreturnc                 4    t         j                  | d      d   S )z7Get the minor-unit exponent for a currency (default 2).)r
    r   r0   getr1   s    2/srv/projects/triptalley/backend/app/core/money.pyget_exponentr9   +   s    '2155    c                 4    t         j                  | d      d   S )z.Get the name of the smallest unit for display.)r
   unit   r5   r7   s    r8   get_smallest_unitr>   0   s    +6q99r:   amountc                     t        |      }t        t        |             }t        d      |z  }t        ||z  j	                  t
                    S )zz
    Convert a decimal amount to integer minor units (cents, etc.).
    Uses Decimal to avoid float precision issues.
    
   roundingr9   r   strintto_integral_valuer   )r?   r1   exponentdecimal_amountfactors        r8   to_minorrK   5   sH    
 H%HS[)NR[H$F'::M:RSSr:   minorc                 R    t        |      }t        d      | z  }t        |       |z  S )z5Convert integer minor units back to a Decimal amount.rA   )r9   r   )rL   r1   rH   rJ   s       r8   
from_minorrN   @   s+    H%HR[XI%F5>F""r:   c                   z    e Zd ZU dZeed<   eed<   defdZdefdZ	de
fdZddZdd	Zd
edd fdZdedd fdZy)MoneyzCRepresents an amount in a specific currency as integer minor units.amount_minorr1   r2   c                 B    t        | j                  | j                        S )zConvert to Decimal for display.)rN   rQ   r1   selfs    r8   
to_decimalzMoney.to_decimalM   s    $++T]];;r:   c                 8    | j                    d| j                   S )N )rQ   r1   rS   s    r8   __repr__zMoney.__repr__Q   s    ##$Admm_55r:   c                     t        |t              sy| j                  |j                  k(  xr | j                  |j                  k(  S )NF)
isinstancerP   rQ   r1   rT   others     r8   __eq__zMoney.__eq__T   s8    %'  E$6$66Z4==ENN;ZZr:   c                     | j                   |j                   k7  r%t        d| j                    d|j                          t        | j                  |j                  z   | j                         S )NzCannot add z + r1   
ValueErrorrP   rQ   r[   s     r8   __add__zMoney.__add__Y   sU    ==ENN*{4==/U^^<LMNNT&&););;T]]KKr:   c                     | j                   |j                   k7  r%t        d| j                    d|j                          t        | j                  |j                  z
  | j                         S )NzCannot subtract z - r_   r[   s     r8   __sub__zMoney.__sub__^   sV    ==ENN*/c%..AQRSST&&););;T]]KKr:   rJ   c                 H    t        | j                  |z  | j                        S NrP   rQ   r1   )rT   rJ   s     r8   __mul__zMoney.__mul__c   s    T&&/??r:   divisorc                 H    t        | j                  |z  | j                        S re   rf   )rT   rh   s     r8   __truediv__zMoney.__truediv__f   s    T&&'14==AAr:   N)r\   rP   r2   rP   )__name__
__module____qualname____doc__rF   __annotations__rE   r   rU   rX   boolr]   ra   rc   rg   rj    r:   r8   rP   rP   G   sn    MM<G <6# 6[t [
L
L
@c @g @B3 B7 Br:   rP   rQ   from_currencyto_currencyratec                     t        |      }t        |      }t        t        |            t        d      ||z
  z  z  }t        |       |z  }t        |j	                  t
                    S )z
    Convert between currencies using a historical FX rate.
    Handles different minor-unit exponents (e.g., USD cents to JPY yen).
    
    Formula: minor_dest = round(minor_src * rate * 10^(exp_dest - exp_src))
    rA   rB   rD   )rQ   rr   rs   rt   exp_srcexp_destrJ   results           r8   convert_currencyry   j   se     =)GK(H SY72;8g3E#FGF\"V+F v'''?@@r:   total_minorparticipantsc                     t        |      }|dk(  ri S | |z  }| ||z  z
  }t        |      }i }t        |      D ]  \  }}|||k  rdndz   ||<    |S )z
    Split a total equally among participants using largest-remainder rounding.
    
    Returns: {user_id: owed_minor} for each participant
    The sum of all values equals total_minor exactly.
    r   r=   )lensorted	enumerate)	rz   r{   nbase	remaindersorted_participantsrx   iuser_ids	            r8   split_equalr   }   sz     	LAAv	!DdQh&I !.F 34 =
7q9}!!<w= Mr:   c                     t        d |D              }|dk7  rt        d|       i }|D ]-  \  }}t        |       t        |      z  t        d      z  }|||<   / i }d}|j                         D ]  \  }}	t	        |	      }
|
||<   ||
z  } | |z
  }|dkD  rct        |j                         D 
cg c]  \  }}
|t        	|
z
        |f c}
}d       }t        |      D ]  }||   d   }||xx   dz  cc<    |S c c}
}w )aN  
    Split a total by percentage weights (basis points, summing to 10000).
    
    Args:
        total_minor: Total amount to split
        participants: List of (user_id, weight_in_basis_points)
    
    Returns:
        {user_id: owed_minor} for each participant
        Sum equals total_minor exactly using largest-remainder.
    c              3   &   K   | ]	  \  }}|  y wre   rq   ).0_weights      r8   	<genexpr>z#split_percentage.<locals>.<genexpr>   s     <)!Vv<   i'  z,Weights must sum to 10000 basis points, got r   c                     | d    | d   fS )Nr=   r
   rq   )xs    r8   <lambda>z"split_percentage.<locals>.<lambda>   s    AaD5!A$- r:   )keyr=   )sumr`   r   itemsrF   r~   floatrange)rz   r{   total_weightsharesr   r   	raw_sharerx   total_flooredrawflooredr   remainder_allocationr   s                 r8   split_percentager      s?    <|<<LuG~VWW F' $K(76?:WU^K	#w$
 FM !c(!w ! m+I1}%%+\\^5!' uS7]+W5 5' 

 y! 	!A*1-a0G7Oq O	! M5s   -C:
c                     t        d |D              }|| k7  rt        d|  d|       |D ci c]  \  }}||
 c}}S c c}}w )at  
    Custom split where participants specify exact amounts.
    Validates that the sum equals total_minor.
    
    Args:
        total_minor: Total amount to split
        participants: List of (user_id, exact_minor_amount)
    
    Returns:
        {user_id: owed_minor} if validation passes
    
    Raises:
        ValueError: If the sum doesn't match total_minor
    c              3   &   K   | ]	  \  }}|  y wre   rq   )r   r   r?   s      r8   r   zsplit_custom.<locals>.<genexpr>   s     ?YQ&?r   z!Custom split amounts must sum to z, got )r   r`   )rz   r{   total_allocatedr   r?   s        r8   split_customr      sY     ?,??O+%/}F?BST
 	
 4@@GVO@@@s   ?__main__g      %@gq=
ףp?z	$10.50 = z EURi  )r=   r
   r$   zSplit $10.00 among z: z, sum=))r=   i  )r
   i  )r$   i  zSplit $10.00 by 50/30/20: ))r=   i  )r
   ,  )r$   r   zCustom split $10.00: )&rn   dataclassesr   decimalr   r   typingr   r   r   mathr0   rE   rF   ro   r9   r>   r   rK   rN   rP   ry   r   r   r   rk   
usd_amount
eur_amountprinttotalr{   splitr   valuesparticipants_pct	split_pctparticipants_customsplit_custom_resultrq   r:   r8   <module>r      sY   " * $ $ 0	;0	;0 
<0 
:	0
 
;0 
;0 
;0 
:0 
;0 
;0 
;0 
;0 
>0 
;0 
;0  
:!0" 
<#0$ 30 $sE#s(O+, :63 63 6
: : :
TU Tc Tc T#c #S #W # B B BDA3 As A A A%(A&S S	 d38n 2+# +T%S/5J +tTWY\T\~ +\Ac AeCHo1F A4PSUXPX> A2 z%'J!*eUDAJ	IjU34D
9: EL|,E	~RwfS=P<Q
RS 9 (89I	&ykI<L<L<N8O7P
QR 9&u.AB	!"5!6fSATA[A[A]=^<_
`a) r:   