
    $i^                         S r SSKrSSKJr  SSKJr  SSKJr  SSKJ	r	  SSK
Jr  SSKJr  SS	KJrJrJr  SS
KJrJr  \	" \5      r\ " S S5      5       r\ " S S5      5       r " S S5      rg)zMulti-runner Elo rating engine for harness racing.

Implements pairwise logistic Elo with support for:
- Multi-entity ratings (horse, driver, trainer)
- Condition adjustments (barrier, handicap)
- Rating deviation (RD) for uncertainty tracking
    N)	dataclass)date)Session)
get_logger)get_settings)get_distance_bucket)
EntityTypeRaceStarter)BarrierAdjustmentRepositoryHandicapAdjustmentRepositoryc                   n    \ rS rSr% Sr\\S'   Sr\S-  \S'   Sr\	\S'   Sr
\	S-  \S'   Sr\S-  \S	'   S
rg)RatingState   z#Current rating state for an entity.ratingNrdr   
race_countlast_race_idlast_race_date )__name__
__module____qualname____firstlineno____doc__float__annotations__r   r   intr   r   r   __static_attributes__r       A/root/tipsharks/tipsharks-elo-api/packages/core/ratings/engine.pyr   r      s>    -MBJ#L#*#"&ND4K&r    r   c                   t    \ rS rSr% Sr\\S'   \\S'   \\S'   \\S'   \\S'   Sr	\S-  \S	'   Sr
\S-  \S
'   Srg)RatingUpdate&   z$Rating update to apply after a race.entity_type	entity_id
old_rating
new_ratingdeltaNr   metar   )r   r   r   r   r   r	   r   r   r   r   r*   dictr   r   r    r!   r#   r#   &   s=    .NLBD$+r    r#   c                      \ rS rSrSrS-S\S-  4S jjrS\S\S\	4S	 jr
  S.S\S\S
\S\S-  S\S-  SS4S jjrS\S\4S jrS\S\S\4S jrS\S\S\4S jrS\S-  S\S-  S\S-  S\S\4
S jrS\S-  S\S-  S\S-  S\S\4
S jrS\S\S\\\4   S\\\4   S\4
S jrS \S\4S! jrS/S" jr S-S\S#\\   S$\S-  SS4S% jjrS\S#\\   S\\   4S& jrS\S\S'\S(\S)\S-  S*\\   SS4S+ jr S,r!g)0RatingEngine3   zMulti-runner Elo rating engine.N
db_sessionc                    [        5       R                  U l        Xl        0 U l        0 U l        0 U l        0 U l        0 U l        U R                  (       a-  U R                  R                  (       a  U R                  5         ggg)z}Initialize rating engine with configuration.

Args:
    db_session: Optional database session for loading/saving adjustments
N)r   r   settingsr/   statesbarrier_adjustmentshandicap_adjustmentsbarrier_adjustment_sampleshandicap_adjustment_samplesenable_adjustmentsload_adjustments_from_db)selfr/   s     r!   __init__RatingEngine.__init__6   sn     %--$ BD 8: 8:!<>'=?( ??t}}??))+  @?r    r%   r&   returnc                     X4nX0R                   ;  a]  [        U R                  R                  U R                  R                  (       a  U R                  R
                  OSSS9U R                   U'   U R                   U   $ )zGet current rating state or initialize new entity.

Args:
    entity_type: Type of entity
    entity_id: Entity ID

Returns:
    Current rating state
Nr   )r   r   r   )r2   r   r1   initial_rating	enable_rd
initial_rd)r9   r%   r&   keys       r!   get_or_init_ratingRatingEngine.get_or_init_ratingL   se     &kk!*}}33/3}}/F/F4==++D DKK
 {{3r    r   r   r   c                 :    X4n[        UUUS9U R                  U'   g)zLoad existing rating state from database.

Args:
    entity_type: Type of entity
    entity_id: Entity ID
    rating: Current rating
    rd: Current rating deviation
    last_race_date: Date of last race
)r   r   r   N)r   r2   )r9   r%   r&   r   r   r   rA   s          r!   load_rating_stateRatingEngine.load_rating_statea   s(    " &&)
Cr    xc                     US:  a  SS[         R                  " U* 5      -   -  $ [         R                  " U5      nUSU-   -  $ )zYLogistic sigmoid function.

Args:
    x: Input value

Returns:
    Value between 0 and 1
r         ?)mathexp)r9   rG   exp_xs      r!   sigmoidRatingEngine.sigmoidy   sA     6#!,--e$$r    c                 x   U R                   R                  nUnU R                   R                  (       a  U R                  X5      nUR                  b|  U R                   R
                  nUS:  a`  UR                  U-  nU R                   R                  S:X  a  [        R                  " U5      nOU R                   R                  S:X  a  SnX7-  nU R                   R                  b  [        X@R                   R                  5      nU R                   R                  b  [        X@R                   R                  5      nU$ )u  Compute effective K-factor based on rating deviation.

When RD is enabled, adjust K-factor proportionally to entity's uncertainty:
- High RD (new/inactive) → larger K → faster rating changes
- Low RD (established) → smaller K → more stable ratings

Args:
    entity_type: Type of entity (HORSE, DRIVER, TRAINER)
    entity_id: Entity ID

Returns:
    Effective K-factor for this entity
r   sqrtnonerI   )r1   
elo_k_baser?   rB   r   r@   rd_scaling_moderJ   rP   	elo_k_minmax	elo_k_maxmin)r9   r%   r&   base_kk_effstater@   ratios           r!   get_effective_k_factor#RatingEngine.get_effective_k_factor   s     )) ==""++KCExx#!]]55
>!HHz1E}}44> $		% 066&@ #"NE=="".}}667E=="".}}667Er    starterracec                    U R                  [        R                  UR                  5      nUR                  nU R
                  R                  (       a`  UR                  (       aO  U R                  [        R                  UR                  5      nX@R
                  R                  UR                  -  -  nU R
                  R                  (       a`  UR                  (       aO  U R                  [        R                  UR                  5      nX@R
                  R                  UR                  -  -  nU R
                  R                  (       a  UR                  bJ  U R!                  UR"                  R$                  UR&                  UR(                  UR                  5      nXG-  nUR*                  bZ  UR*                  S:w  aJ  U R-                  UR"                  R$                  UR&                  UR(                  UR*                  5      nXH-  nU$ )u   Compute effective rating for a starter.

R_eff = R_horse + α*R_driver + β*R_trainer + barrier_adj + handicap_adj

Args:
    starter: Starter instance
    race: Race instance

Returns:
    Effective rating
r   )rB   r	   HORSEhorse_idr   r1   enable_driver	driver_idDRIVERdriver_weight_alphaenable_trainer
trainer_idTRAINERtrainer_weight_betar7   barrier_get_barrier_adjustmentmeetingvenue
start_type
distance_m
handicap_m_get_handicap_adjustment)	r9   r^   r_   horse_stater_effdriver_statetrainer_statebarrier_adjhandicap_adjs	            r!   compute_effective_rating%RatingEngine.compute_effective_rating   s   " --j.>.>@P@PQ"" ==&&7+<+<22:3D3DgFWFWXL]]669L9LLLE ==''G,>,> 33""G$6$6M ]]669M9MMME ==++*"::LL&&OOOOOO	 $ !!-'2D2D2I#<<LL&&OOOO&&	  %r    rn   ro   rp   rk   c                 ,   U R                   R                  (       d  g[        UU R                   R                  U R                   R                  U R                   R
                  S9nXXT4nSSXT4nU R                  UUU R                  U R                  5      $ )zGet barrier adjustment from learned table.

Args:
    venue: Venue name
    start_type: mobile/standing
    distance_m: Distance in meters
    barrier: Barrier number

Returns:
    Adjustment value (default 0.0)
        modebucket_sizeN)	r1   adj_barrier_enabledr   distance_bucketsdistance_bucket_modedistance_bucket_size_resolve_adjustmentr3   r5   )r9   rn   ro   rp   rk   distance_bucketrA   
global_keys           r!   rl   $RatingEngine._get_barrier_adjustment   s    $ }}00-MM**33::	
 /; D/;
''$$++	
 	
r    rq   c                 ,   U R                   R                  (       d  g[        UU R                   R                  U R                   R                  U R                   R
                  S9nXXT4nSSXT4nU R                  UUU R                  U R                  5      $ )zGet handicap adjustment from learned table.

Args:
    venue: Venue name
    start_type: mobile/standing
    distance_m: Distance in meters
    handicap_m: Handicap in meters

Returns:
    Adjustment value (default 0.0)
r|   r}   N)	r1   adj_handicap_enabledr   r   r   r   r   r4   r6   )r9   rn   ro   rp   rq   r   rA   r   s           r!   rr   %RatingEngine._get_handicap_adjustment  s    $ }}11-MM**33::	
 />D/>
''%%,,	
 	
r    rA   r   adjustmentssamplesc                     X4 Hf  nXS;  a  M
  U R                   R                  S:  a-  UR                  US5      nX`R                   R                  :  a  MQ  X5   nU R                  U5      s  $    g)Nr   r|   )r1   adj_min_samplesget_clamp_adjustment)r9   rA   r   r   r   	candidatecount
adjustments           r!   r    RatingEngine._resolve_adjustment3  sm     *I+}},,q0Iq1==888$/J))*55 + r    r   c                     U R                   R                  b  [        XR                   R                  5      nU R                   R                  b  [	        XR                   R                  5      nU$ N)r1   adj_clamp_minrU   adj_clamp_maxrW   )r9   r   s     r!   r   RatingEngine._clamp_adjustmentE  sP    ==&&2Z)D)DEJ==&&2Z)D)DEJr    c                    U R                   (       d  g[        R                  " U R                   5      nU Hc  nUR                  UR                  UR
                  UR                  4nUR                  U R                  U'   UR                  U R                  U'   Me     [        R                  " U R                   5      nU Hc  nUR                  UR                  UR
                  UR                  4nUR                  U R                  U'   UR                  U R                  U'   Me     [        R!                  S[#        U5       S[#        U5       S35        g)z@Load barrier and handicap adjustments from database into memory.NzLoaded z barrier adjustments and z# handicap adjustments from database)r/   r   get_allrn   ro   r   rk   r   r3   sample_countr5   r   rq   r4   r6   loggerinfolen)r9   barrier_adjsadjrA   handicap_adjss        r!   r8   %RatingEngine.load_adjustments_from_dbL  s    3::4??KC99cnnc.A.A3;;OC,/NND$$S)363C3CD++C0   5<<T__M C99cnnc.A.A3>>RC-0^^D%%c*474D4DD,,S1 !
 	c,'((A=!""EG	
r    startersuse_global_onlyc                 T   U R                   (       a  U R                  R                  (       d  gU R                  R                  (       d  U R                  R                  (       d  gU Vs/ s H&  oDR
                  c  M  UR                  (       a  M$  UPM(     nn[        U5      U R                  R                  :  a  gU R                  R                  nSU R                  l        0 nU H   nU R                  X5      XxR                  '   M"     X`R                  l        Uc  U R                  R                  OUn[        U5       GH  u  pUR                  (       d  M  XxR                     n
UR
                  nSnSn[        U5       Hs  u  pX:X  d  UR                  (       d  M  XR                     nU R                  U
U-
  U R                  R                  -  5      nXR
                  :  a  SOSnUU-  nUU-  nMu     X-
  [!        [        U5      S-
  S5      -  nUU R                  R"                  -  nU R                  R                  (       a  UR$                  b  ['        UR(                  U R                  R*                  U R                  R,                  U R                  R.                  S9nU(       a  Su  nnO"UR0                  R2                  UR4                  nn[6        R8                  " U R                   UUUUR$                  UU R                  R:                  S9  U R                  R                  (       d  GM  UR<                  c  GM  UR<                  S	:w  d  GM  ['        UR(                  U R                  R*                  U R                  R,                  U R                  R.                  S9nU(       a  Su  nnO"UR0                  R2                  UR4                  nn[>        R8                  " U R                   UUUUR<                  UU R                  R:                  S
9  GM     gs  snf )ab  Learn barrier and handicap adjustments from a completed race.

Uses performance residuals: if a horse performs better than expected
given its rating, attribute some of that to favorable conditions.

Args:
    race: Race instance
    starters: List of starters with results
    use_global_only: If True, only update global adjustments (no venue-specific)
NFr|   rI      r}   NN)rn   ro   r   rk   r)   learning_rater   )rn   ro   r   rq   r)   r   ) r/   r1   r7   r   r   placingdid_not_finishr   min_finishersry   idadj_global_only	enumeraterb   rM   elo_scale_crU   adj_update_scalerk   r   rp   r   r   r   rm   rn   ro   r   increment_sampleadj_learning_raterq   r   )r9   r_   r   r   svalid_starterssaved_enableeffective_ratingsr^   irt   r   expected_sum
actual_sumjotherr_otherexpectedactualr)   scaled_deltar   rn   ro   s                           r!   learn_adjustments_from_race(RatingEngine.learn_adjustments_from_raced  sE    dmm&F&F11MM66  
!99AAQAQAx 	 
 ~!<!<< }}77+0(%G,0,I,I',Xjj) & ,8(
 & MM))  	 $N3JA##%jj1EooG LJ%n56+HH5<<DMM<U<U(UV '-- 7S(f$
 6  .#c.6IA6Mq2QQE 4==#A#AAL }}00W__5P"5OOMM22;; $ B B	# #(2%E:(,(:(:DOO:E+<<OO)$3#OO&"&--"A"A 222&&2&&!+"5OOMM22;; $ B B	# #(2%E:(,(:(:DOO:E,==OO)$3&11&"&--"A"AO 4/
s   )P%=P%P%c           	      	   U Vs/ s H&  o3R                   c  M  UR                  (       a  M$  UPM(     nnU Vs/ s H$  o3R                  (       d  UR                   b  M"  UPM&     nn0 nU H  nUR                   XgR                  '   M     U R                  R                  (       a>  U(       a7  [        UR                  5       SS9nU H  nUS-   XgR                  '   M     XE-   n	OUn	[        U	5      U R                  R                  :  a<  [        R                  SUR                   SU R                  R                   S35        / $ [        U	5      n
/ n0 nU	 H   nU R                  Xq5      XR                  '   M"     [        U	5       GH`  u  pUR                  (       d  M  XR                     nUR                   nUR                  U;   a  XnR                     nSnSn[        U	5       H  u  nnUU:X  d  UR                  (       d  M  UUR                     nUR                   nUR                  U;   a  UUR                     nUU:X  a;  U R                  R                  S	:X  a  M{  U R                  R                  S
:X  a  SOSnO
UU:  a  SOSnU R                  UU-
  U R                  R                   -  5      nUUU-
  -  nUS-  nM     U R                  R"                  S:X  a  UnO"U R                  R"                  S:X  a  U
nOU
S-
  nUS::  a  GM  U R%                  [&        R(                  UR                  5      U R                  R*                  -  nUUU-  -  nUR,                  (       a  UR,                  R.                  OSnU R1                  [&        R(                  UR                  UUR                  UU5        U R                  R2                  (       ay  UR4                  (       ah  UU R                  R6                  -  U R                  R8                  -  nU R1                  [&        R:                  UR4                  UUR                  UU5        U R                  R<                  (       d  GM  UR>                  (       d  GM  UU R                  R@                  -  U R                  RB                  -  nU R1                  [&        RD                  UR>                  UUR                  UU5        GMc     U$ s  snf s  snf )ub  Process a race and compute rating updates.

Uses pairwise logistic Elo:
- For each pair (i, j), compute expected outcome E_ij
- Update based on actual outcome S_ij (1 if i beat j, 0 otherwise)
- ΔR_i = K * (1/(n-1)) * Σ_j (S_ij - E_ij)

Args:
    race: Race instance
    starters: List of starters in race

Returns:
    List of rating updates to apply
Nr   )defaultr   zSkipping race z - fewer than z
 finishersr|   skiphalfg      ?rI   comparisonsn)#r   r   r   r1   dnf_treated_as_lastrU   valuesr   r   r   debugry   r   rb   tie_handlingrM   r   pairwise_normalizerr\   r	   ra   horse_k_scalerm   meeting_date_apply_updaterc   rd   rf   driver_k_scalere   rg   rh   rj   trainer_k_scaleri   )r9   r_   r   r   	finishersdnf_startersplacing_by_idr^   	max_placer   r   updatesr   r   	starter_ir_eff_i	placing_i	delta_sumr   r   	starter_jr_eff_j	placing_js_ije_ij
normalizerrY   delta_r	race_datedriver_deltatrainer_deltas                                  r!   process_raceRatingEngine.process_race  sO   "  
!99AAQAQAx 	 
 $,U8a/?/?1998U(* G(/M**% ! ==,,M002A>I',5Mjj) (&5N&N~!<!<<LL 	8S8S7TT^_ I %G,0,I,I',Xjj) & &n5LA%%'5G!))I||},),,7	 IK ). 996!3!3+ILL9%--	<<=0 -ill ;I 	)}}11V; "&--"<"<"F3CD"+i"73SD ||Ww%6$--:S:S$ST TD[(	q - !:2 }}00MA(
22c9
U
Q ++J,<,<i>P>PQ----.  y:56G 6:\\11tI   "" }}**y/B/Bmm778mm223 
 ""%%'' GG }}+++	0D0D0Dmm778mm334 
 ""&&((!GGq 6B C
 Vs   S)S)S)!S.S.r)   race_idr   r   c                    U R                  X5      nUR                  nX-   n	U R                  R                  b  [	        XR                  R                  5      n	U R                  R
                  b  [        XR                  R
                  5      n	X-
  nU R                  R                  (       Ga*  UR                  Gb  UR                  (       a  U(       a  XWR                  -
  R                  n
U
S:  a  U R                  R                  b  [        XR                  R                  5      n
XR                  R                  -  n[        UR                  U-   U R                  R                  5      Ul        [	        U R                  R                  U R                  R                  5      n[	        UR                  U-
  U R                  R                   5      Ul        Xl        U=R"                  S-  sl        XGl        XWl	        UR'                  [)        UUUU	UUR                  SUR"                  0S95        [*        R-                  UR.                   SU SUS S	U	S S
US S3
UR                  (       a  SUR                  S 3OS-   5        g)zApply rating update to an entity.

Args:
    entity_type: Type of entity
    entity_id: Entity ID
    delta: Rating change
    race_id: Race ID
    race_date: Date of the race
    updates: List to append update to
Nr   r   r   )r%   r&   r'   r(   r)   r   r*    z: z.1fz -> u    (Δz+.1f)z, RD= )rB   r   r1   
rating_minrU   
rating_maxrW   r?   r   r   daysrd_inflation_cap_daysrd_inflation_per_dayrd_maxrd_decay_per_racerd_decay_floorrd_minr   r   appendr#   r   r   value)r9   r%   r&   r)   r   r   r   rZ   r'   r(   days_inactive	inflationdecays                r!   r   RatingEngine._apply_updatet  s   & ''?\\
'
==##/Z)A)ABJ==##/Z)A)ABJ' =="""uxx';##	!*-A-A!A G G 1$}}::F(+)==+N+N) !.0R0R RI"588i#79M9MNEH 779U9UVE588e+T]]-A-ABEH "A$( 	'#%%88 %"2"2
	
 	  !9+R#d:c"2$uTl!E).uxxn%r;	
r    )r5   r3   r/   r6   r4   r1   r2   r   r   )r<   N)"r   r   r   r   r   r   r:   r	   r   r   rB   r   r   rE   rM   r\   r   r
   ry   strrl   rr   tupler+   r   r   r8   listboolr   r#   r   r   r   r   r    r!   r-   r-   3   sq   ),7T> ,, % 25 	 4  &*

 
 	

 DL
 t
 

0% %5 %$* $ $QV $L66 6 
	6p&
Tz&
 $J&
 $J	&

 &
 
&
P$
Tz$
 $J$
 $J	$

 $
 
$
L  %,'	
 eSj! 
$E e 
2 SW{{$(M{DH4K{	{zQ Qg Q4CU QfF
F
 F
 	F

 F
 $;F
 l#F
 
F
r    r-   )r   rJ   dataclassesr   datetimer   sqlalchemy.ormr   packages.core.common.loggingr   packages.core.common.settingsr   packages.core.common.utilsr   packages.core.storage.modelsr	   r
   r   "packages.core.storage.repositoriesr   r   r   r   r   r#   r-   r   r    r!   <module>r     sv     !  " 3 6 : B B
 
H	 ' ' ' 	 	 	G

 G

r    