
    $i.                    Z   % S SK JrJrJrJrJrJrJr  S SKJ	r	  S SK
Jr  S SKJr  S SKrS SKrS SKJr  S SKJrJr  S SKJrJrJrJr  S SKrS S	KJrJr  S SKrS SKrS SKr S S
K!J"r"  S SK#r#\" \$5      RJ                  r&\	" \&S-  5        \RN                  S   r(\" \(5      r)\)\RN                  S      r*\" 5       r+\" SS9r,\RZ                  " \R\                  SS9  \R^                  " \05      r1\RN                  Re                  SS5      r3Sr4Sr5\"" S/SS9r6SS\7S\S-  S\84S jjr9S\8S\8S\:4S jr;S \8S\84S! jr< " S" S#\5      r= " S$ S%\5      r> " S& S'\5      r? " S( S)\5      r@ " S* S+\5      rA " S, S-\5      rB " S. S/\5      rC " S0 S1\5      rD " S2 S3\5      rE " S4 S5\5      rF " S6 S7\5      rG " S8 S9\5      rH " S: S;\5      rI " S< S=\5      rJ " S> S?\5      rK " S@ SA\5      rL " SB SC\5      rM " SD SE\5      rN " SF SG\5      rO/ SHQrP/ SIQrQ/ SJQrR/ SKQrS/ SLQrT/ SMQrU/ SNQrV/ SOQrW/ SPQrX/ SQQrY/ SRQrZ/ SSQr[/ STQr\/ SUQr]/ SVQr^/ SWQr_SX r`SY\\7   S\\7   4SZ jraS[\bS\\8S\\7   4S] jrcS\\7   4S^ jrdSqeS\\7   4S_ jrf " S` Sa5      rg " Sb Sc5      rh " Sd Se5      riSfSgShSi.rj\7\8\b4   \kSj'   Sk\8Sl\8S\l\:\b4   4Sm jrmSk\8Sl\8SS4Sn jrnSoroSp\7S\74Sq jrpS\\7   4Sr jrqS\\7   4Ss jrr SSt\7\8\4   S-  S\\7   4Su jjrsSv\8S\7S-  4Sw jrtSqu\#R                  S-  \kSx'   SyqwSz rx\+R                  S{5      S| 5       rzSY\\7   S}\8S~\7S\\8S\\7   4
S jr{S\7S}\8S\74S jr|\" SSS9\" SSS94S\S\\8   S\\8   S\84S jjr}S\8S\7S-  4S jr~\" SSS94S\\8   S\7S-  4S jjr\,GR                  S\LS9S\I4S j5       r\,GR                  S\LS9S\J4S j5       r\,Re                  S\KS9\" \5      4S\7S-  4S jj5       r\,Re                  S5      S 5       r\,Re                  S5      S 5       r\,Re                  S\\?   S9\" SSS9\" SSS9\" SSS9\" SgSS94S\\8   S\\8   S\\\8   S\b4S jj5       r\,Re                  S5      Sv\84S j5       r\,Re                  S5      S 5       r\,Re                  S5      \" SSS9\" ShSS94S\\8   S\b4S jj5       r\,Re                  S5      S\8S\b4S j5       r\,Re                  S5      Sv\84S j5       rSS\\8   S\bS\74S jjrS\7Sv\8S\74S jr\,GR                  S5      S\B4S j5       r\,GR                  S5      \" \}5      4S\7Sk\84S jj5       r\,Re                  S5      \" \}5      4Sk\84S jj5       r\,GR#                  S5      \" \}5      4S\8Sk\84S jj5       r\,GR                  S5      \" \}5      4S\ESk\84S jj5       r\,Re                  S5      \" \}5      4Sk\84S jj5       r\,GR#                  S5      \" \}5      4S\8Sk\84S jj5       r\,Re                  S5      \" \}5      4Sk\84S jj5       r\,GR                  S5      \" \}5      4S\8Sk\84S jj5       r\,GR                  S\OS9\" \}5      4S\MSk\84S jj5       r\,GR                  S\OS9\" \}5      4S\NSk\84S jj5       r\,Re                  S5      S 5       r\,Re                  S5      \" \}5      4Sk\84S jj5       r\,GR9                  S5      \" \}5      4S\HSk\84S jj5       r\,Re                  S5      \" SSS94S\\\8   4S jj5       r\,Re                  S5      S 5       r\,Re                  S5      S 5       r\+GRC                  \,5        \+GRE                  \SS/S/S/S9  \+R                  S5      S 5       rg)    )FastAPI	APIRouterHTTPExceptionQueryDependsHeaderResponse)load_dotenv)CORSMiddleware)AsyncIOMotorClientN)Path)	BaseModelField)ListOptionalDictAny)datetime	timedelta)CryptContextz.env	MONGO_URLDB_NAMEz/api)prefixz4%(asctime)s - %(name)s - %(levelname)s - %(message)s)levelformatJWT_SECRET_KEYz#dev-secret-key-change-in-productionHS256i`'  bcryptauto)schemes
deprecateddataexpires_deltareturnc                     U R                  5       n[        R                  " 5       U(       a  UO[        [        S9-   nUR                  SU05        [        R                  " U[        [        S9$ )z6Create a JWT access token with the given data payload.minutesexp)	algorithm)
copyr   utcnowr   ACCESS_TOKEN_EXPIRE_MINUTESupdatepyjwtencode
SECRET_KEY	ALGORITHM)r"   r#   	to_encodeexpires       ;/root/tipsharks/tipsharks-client/tests/../backend/server.pycreate_access_tokenr5   /   sV    		I__ 	:;F
 eV_%<<	:CC    plain_passwordhashed_passwordc                 ,    [         R                  X5      $ )z3Verify a plain text password against a bcrypt hash.)pwd_contextverify)r7   r8   s     r4   verify_passwordr<   ;   s    n>>r6   passwordc                 ,    [         R                  U 5      $ )z(Hash a plain text password using bcrypt.)r:   hash)r=   s    r4   get_password_hashr@   @   s    H%%r6   c                       \ rS rSr% Sr\" S S9r\\S'   \	\S'   \\S'   \\S'   \\S	'   S
r
\\   \S'   \	\S'   \\S'   Sr\\S'   Sr\\S'   / r\\   \S'   Srg
)RunnerH   z:Generic runner - can be horse, greyhound, or harness horsec                  >    [        [        R                  " 5       5      $ Nstruuiduuid4 r6   r4   <lambda>Runner.<lambda>K       C

,=r6   default_factoryidnumbernameridertrainerNweightbarrierform        win_probabilityplace_probabilitybadgesrJ   )__name__
__module____qualname____firstlineno____doc__r   rP   rG   __annotations__intrU   r   floatrY   rZ   r[   r   __static_attributes__rJ   r6   r4   rB   rB   H   sd    D$=>B>K
IJL"FHUO"L
I OU "u"FDIr6   rB   c                       \ rS rSr% \" S S9r\\S'   \\S'   \\S'   \\S'   \	\S'   \\S	'   \\S
'   \\S'   \\S'   / r
\\   \S'   Srg)RaceX   c                  >    [        [        R                  " 5       5      $ rE   rF   rJ   r6   r4   rK   Race.<lambda>Y   rM   r6   rN   rP   racing_typetrackrace_number
start_timedistance
race_class
conditionsprize_moneyrunnersrJ   N)r\   r]   r^   r_   r   rP   rG   ra   rb   r   rr   r   rB   rd   rJ   r6   r4   rf   rf   X   sL    $=>B>JMOOGT&\r6   rf   c                   p    \ rS rSr% \\S'   \\S'   \\S'   \\S'   \\S'   \\S'   \\S'   \\S	'   \\S
'   Srg)RaceListIteme   rP   rj   rk   rl   rm   rn   ro   rp   runner_countrJ   N)	r\   r]   r^   r_   rG   ra   rb   r   rd   rJ   r6   r4   rt   rt   e   s3    GJMOOr6   rt   c                   *    \ rS rSr% \\S'   \\S'   Srg)	TipReasonq   texttyperJ   Nr\   r]   r^   r_   rG   ra   rd   rJ   r6   r4   rx   rx   q   s    
I
Ir6   rx   c                       \ rS rSr% \" S S9r\\S'   \\S'   \\S'   \\S'   \\S'   \\	   \S	'   \\
   \S
'   \" \R                  S9r\\S'   Sr\\\\4      \S'   Srg)Tipv   c                  >    [        [        R                  " 5       5      $ rE   rF   rJ   r6   r4   rK   Tip.<lambda>w   rM   r6   rN   rP   race_idbet_typerecommended_bet
confidencerr   reasons
created_atN	race_inforJ   )r\   r]   r^   r_   r   rP   rG   ra   r   rB   rx   r   r+   r   r   r   r   r   rd   rJ   r6   r4   r~   r~   v   se    $=>B>LMO&\)_ AJA*.IxS#X'.r6   r~   c                   .    \ rS rSr% \\S'   Sr\\S'   Srg)	TipCreate   r   best_betr   rJ   N)r\   r]   r^   r_   rG   ra   r   rd   rJ   r6   r4   r   r      s    LHcr6   r   c                   p    \ rS rSr% \" S S9r\\S'   Sr\\S'   \	\S'   \" \
R                  S9r\
\S'   S	rg
)SavedTip   c                  >    [        [        R                  " 5       5      $ rE   rF   rJ   r6   r4   rK   SavedTip.<lambda>   rM   r6   rN   rP   default_useruser_idtipsaved_atrJ   N)r\   r]   r^   r_   r   rP   rG   ra   r   r~   r   r+   r   rd   rJ   r6   r4   r   r      s4    $=>B>!GS!	Hx?Hh?r6   r   c                       \ rS rSr% \" S S9r\\S'   Sr\\S'   \\S'   \\S'   \	\S	'   \
\   \S
'   Sr\\S'   \" \R                  S9r\\S'   Sr\\   \S'   Sr\\\\4      \S'   Srg)Schedule   c                  >    [        [        R                  " 5       5      $ rE   rF   rJ   r6   r4   rK   Schedule.<lambda>   rM   r6   rN   rP   r   r   r   r   minutes_beforechannelsactivestatusr   Nscheduled_timer   rJ   )r\   r]   r^   r_   r   rP   rG   ra   r   rb   r   r   r   r+   r   r   r   r   r   r   rd   rJ   r6   r4   r   r      sy    $=>B>!GS!LM3iFC AJA)-NHX&-*.IxS#X'.r6   r   c                   R    \ rS rSr% \\S'   Sr\\S'   Sr\\S'   S/r	\
\   \S'   S	rg
)ScheduleCreate   r   r   r      r   pushr   rJ   N)r\   r]   r^   r_   rG   ra   r   r   rb   r   r   rd   rJ   r6   r4   r   r      s+    LHcNC!(Hd3i"r6   r   c                       \ rS rSr% \" S S9r\\S'   Sr\\S'   \\S'   \\S'   \\S	'   S
r	\
\   \S'   S
r\
\   \S'   S
r\
\   \S'   Sr\\S'   Sr\\S'   \" \R                   S9r\\S'   Srg
)Notification   c                  >    [        [        R                  " 5       5      $ rE   rF   rJ   r6   r4   rK   Notification.<lambda>   rM   r6   rN   rP   r   r   r{   titlebodyNtor   schedule_idr   channelsentr   r   rJ   )r\   r]   r^   r_   r   rP   rG   ra   r   r   r   r   r   r   r   r   r+   r   rd   rJ   r6   r4   r   r      s|    $=>B>!GS!
IJ
IB!GXc]!!%K#%GSFC AJAr6   r   c                      \ rS rSr% Sr\\S'   Sr\\S'   Sr\	\S'   / r
\\   \S'   S	S
S
S.r\\\4   \S'   Sr\\   \S'   Sr\\   \S'   S
r\\S'   / r\\   \S'   / r\\   \S'   / r\\   \S'   / r\\   \S'   Sr\\	   \S'   Srg)UserPreferences   r   rP   r   default_bet_typer   default_lead_timefavorite_tracksTF)r   smsemailnotification_channelsNquiet_hours_startquiet_hours_endonboarding_completepreferred_racing_typespreferred_race_classespreferred_distancespreferred_conditionsmin_prize_moneyrJ   )r\   r]   r^   r_   rP   rG   ra   r   r   rb   r   r   r   r   boolr   r   r   r   r   r   r   r   r   rd   rJ   r6   r4   r   r      s    B&c&s!#OT#Y#.4T	? 
 (,x}+%)OXc]) %% 	 DI  )+DI*%'c'&($s)(%)OXc])r6   r   c                   .   \ rS rSr% Sr\\   \S'   Sr\\	   \S'   Sr
\\\      \S'   Sr\\\\4      \S'   Sr\\   \S'   Sr\\   \S'   Sr\\   \S	'   Sr\\\      \S
'   Sr\\\      \S'   Sr\\\      \S'   Sr\\\      \S'   Sr\\	   \S'   Srg)UserPreferencesUpdate   Nr   r   r   r   r   r   r   r   r   r   r   r   rJ   )r\   r]   r^   r_   r   r   rG   ra   r   rb   r   r   r   r   r   r   r   r   r   r   r   r   r   rd   rJ   r6   r4   r   r      s    &*hsm*'+x}++/OXd3i(/7;8DdO4;'+x}+%)OXc])*.$.26HT#Y/626HT#Y/6/3$s),304(49-4%)OXc])r6   r   c                   >    \ rS rSr% \\S'   \\S'   Sr\\   \S'   Srg)UserRegister   r   r=   NrR   rJ   )	r\   r]   r^   r_   rG   ra   rR   r   rd   rJ   r6   r4   r   r      s    JMD(3-r6   r   c                   *    \ rS rSr% \\S'   \\S'   Srg)	UserLogin   r   r=   rJ   Nr|   rJ   r6   r4   r   r      s    JMr6   r   c                   H    \ rS rSr% \\S'   \\S'   Sr\\   \S'   \\S'   Sr	g)UserResponse   rP   r   NrR   r   rJ   )
r\   r]   r^   r_   rG   ra   rR   r   r   rd   rJ   r6   r4   r   r      s     GJD(3-r6   r   c                   .    \ rS rSr% \\S'   Sr\\S'   Srg)TokenResponse   access_tokenbearer
token_typerJ   N)r\   r]   r^   r_   rG   ra   r   rd   rJ   r6   r4   r   r      s    Jr6   r   c                   R    \ rS rSr% \\S'   \\S'   \\S'   \\S'   Sr\\   \S'   Srg)	SendNotificationRequest   r   subjectr   r   Nr   rJ   )	r\   r]   r^   r_   rG   ra   r   r   rd   rJ   r6   r4   r   r      s$    GL
IL!GXc]!r6   r   c                   \    \ rS rSr% \\S'   \\S'   \\S'   \\S'   \\S'   Sr\\   \S'   S	r	g)
ScheduleNotificationRequest   r   r   r   r   r   Nr   rJ   )
r\   r]   r^   r_   rG   ra   r   r   r   rd   rJ   r6   r4   r   r      s*    GL
IL!GXc]!r6   r   c                   R    \ rS rSr% \\S'   \\S'   \\S'   \\S'   Sr\\   \S'   Sr	g)	NotificationResponsei  rP   r   r   r   NerrorrJ   )
r\   r]   r^   r_   rG   ra   r   r   r   rd   rJ   r6   r4   r   r     s%    GKLE8C=r6   r   thoroughbredharness	greyhound)

FlemingtonRandwickzMoonee Valley	Caulfieldz
Eagle FarmRosehillDoombenSandownMorphettvilleAscot)
zMenangle ParkzTabcorp Park MeltonAlbion ParkzGloucester ParkzAlexandra ParkHobart
LauncestonBendigoBallarat
Cranbourne)
zWentworth ParkzThe MeadowszSandown Parkr   
CanningtonDaptoRichmondz
Angle ParkIpswichr   )Silver CometThunder StrikeGolden ArrowzMidnight StarzPhoenix RisingzStorm ChaserzLucky FortunezWild Spiritz	Iron WillzShadow DancerzBlazing GloryzCrystal DreamzNoble KnightzSpeed DemonzRoyal FlashzOcean BreezezMountain KingzDesert RosezFlying EaglezBlack Diamondz	Red BaronzWhite LightningzBlue ThunderzGreen MachinezPurple Haze)zLochinvar ArtzKing Of Swingz	Copy ThatzSelf AssuredzBettor's DelightzAmerican Idealz
Tiger TaraLazaruszChristen MezAlta OrlandozSpirit Of St Louisz
Poster BoyzBetter Eclipsez	Sky MajorzTornado ValleyzMajestic SonzCenturion ATMzCaptain CrunchzBeach Musicz
Mach Alert)zFernando BalezWow She's FastzTornado TearszZambora BrockieKoblenzzOrson AllenzSimon Told HelenzShima Shinez
Fanta BalezAston RupeezGood Odds Haradaz
Dyna PattyzUp Hill JillzBarcia BalezRaw Abilityz	Beach BoxzCosmic RumblezFlying PenskezJarick BalezZipping Kyrgios)
J. McDonald	D. Oliver	H. BowmanzC. WilliamszD. LanezJ. KahzM. Zahraz	B. Melhamz	L. Curriez	C. Newitt)
L. McCarthyD. MoranG. Dixon
A. Herlihy	C. AlfordzK. Gathz	J. Caldowz	N. Purdonz
D. Hancock	M. Purdon)
	C. WallerzG. WaterhousezA. CummingszC. MaherzD. PaynezM. PricezL. Freedmanz
J. O'BrienzP. MoodyzD. Hayes)
E. Buterr   z	B. Purdonr  r   z
K. ManningzA. Turnbullz	G. SugarszD. Aikenz	S. Alcorn)

J. BrittonzA. AzzopardizR. BordazK. GreenoughzA. LordzM. KavanaghzC. HalsezG. HallzD. IrwinzJ. Sanderson)Group 1Group 2Group 3ListedOpenzBenchmark 88zBenchmark 78Maiden)r  r  r  Free For AllPacersTrotterszC0-C1r
  )r  r  r  r  zGrade 5r
  NoviceMixed)GoodSoftHeavyFirm	Syntheticc                      SR                  [        S5       V s/ s H#  n [        [        R                  " SS5      5      PM%     sn 5      $ s  sn f )N-         )joinrangerG   randomrandint)_s    r4   generate_formr    s7    88qBAS2./BCCBs   *A
rr   c           
      J   Sn/ nU  H  nUS   R                  S5       Vs/ s H  n[        U5      PM     nn[        U5      [        U5      -  n[	        SSU-
  5      [
        R                  " SS5      -   nUS   nS[        US-
  5      S	-  -
  n	UR                  S
5      n
SnU
(       a  SU
S-
  S-  -
  nXy-  U-  nUR                  U5        X-  nM     [        U 5       GH6  u  pUS:  a
  X-   U-  S-  OSn[        [        S[	        SU5      5      S5      US'   [        [        SUS   S-  5      S5      US'   / US'   US   R                  S5       Vs/ s H  n[        U5      PM     nn[        S U 5       5      (       a  US   R                  S5        US   S:X  a  US   R                  S5        US   S:  a-  [
        R
                  " 5       S:  a  US   R                  S5        US   S::  d  GM  US   S::  d  GM"  US   R                  S5        GM9     U $ s  snf s  snf )zBCalculate win/place probabilities based on form and random factorsr   rW   r  r      rV   g      ?   gQ?rU   6   g{Gz?d   
   _      r  rY   g      @rZ   r[   c              3   *   #    U  H	  oS :*  v   M     g7f)   NrJ   ).0ps     r4   	<genexpr>*calculate_probabilities.<locals>.<genexpr>  s     .~!Av~s   
ConsistentzLast Start Winner   g      ?Valuer)  zFront-runner)splitrb   sumlenmaxr  uniformabsgetappend	enumerateroundminall)rr   total_scorescoresrunnerxform_positionsavg_position
base_scorerV   barrier_factorrU   weight_factorscoreiwin_probs                  r4   calculate_probabilitiesrI    s@   KF*0.*>*>s*CD*CQ#a&*CD>*S-@@ B-.11EE
 #s7Q;/$66 H%6B;$"66M+m;e) . w'	6AAoFI+s22$)#b#a2B*CQ$G !&+CF;L4MPS4S,TVW&X"# x*0.*>*>s*CD*CQ#a&*CD.~...8##L1!!8##$78#$r)fmmo.C8##G,!!nQ&71&<8##N3! ($ NO E: Es   HH countrj   c                 (   US:X  a  [         n[        n[        nSnO/US:X  a  [        n[        n[
        nSnO[        nS/S-  n[        nSn[        R                  " U[        U [        U5      5      5      n/ n[        U 5       GH  n[        [        R                  " 5       5      US-   U[        U5      :  a  Xh   OSUS-    3US	   (       a  [        R                   " U5      OS[        R                   " U5      U(       a!  [#        [        R$                  " S
S5      S5      OSUS-   ['        5       S	S	/ [#        [        R$                  " SS5      S5      [#        [        R$                  " SS5      S	5      S.n	UR)                  U	5        GM     [+        U5      $ )z5Generate mock runners for a race based on racing typer   Tr   F r%  r  Runner r   r#  <   Ng      ?g     I@    )rP   rQ   rR   rS   rT   rU   rV   rW   rY   rZ   r[   odds
elo_rating)THOROUGHBRED_NAMESJOCKEYSTRAINERSHARNESS_NAMESDRIVERSHARNESS_TRAINERSGREYHOUND_NAMESGREYHOUND_TRAINERSr  sampler;  r3  r  rG   rH   rI   choicer:  r5  r  r8  rI  )
rJ  rj   namesriderstrainers_list
has_weightavailable_namesrr   rG  r?  s
             r4   generate_mock_runnersrb  $  sS    n$" 
			!(
*
mmE3uc%j+ABOG5\djjl#!e&'#o*>&>"gaRSeWDU.4QiV]]6*R}}]3:DeFNN2r2A6$1u!O !"&..d3Q7tT :A>
" 	v% ( #7++r6   c                  &   / n [         R                  " 5       n[         GH  nUS:X  a  [        n[        n/ SQnO'US:X  a  [
        n[        n/ SQnO[        n[        n/ SQn[        S5       GH  n[        R                  " U[        S[        U5      5      5       GHY  n[        R                  " SS	5      nUR                  S
SSSS9[!        US9-   n	[        SUS-   5       GH  n
U	[!        U
S-  [        R                  " SS5      -   S9-   nX:  d  M2  US:X  a  [        R                  " SS5      nO[        R                  " SS5      n[#        [$        R&                  " 5       5      UUU
UR)                  5       [        R*                  " U5      [        R*                  " U5      [        R*                  " [,        5      [        R*                  " / SQ5      [/        X5      S.
nU R1                  U5        GM     GM\     GM     GM     U R3                  S S9  U $ )zEGenerate upcoming mock races for the next 2 days for all racing typesr   )i  rO  ix  i@  rP  i  i`	  r   )iI  i  iZ  i  i
  ),    i  iX  i  i  r'  r)  r"  r%  r  r   )hourminutesecondmicroseconddaysr     r!  r&   r         )a  iP  i$ i iI i i  
rP   rj   rk   rl   rm   rn   ro   rp   rq   rr   c                     U S   $ )Nrm   rJ   r@  s    r4   rK   %generate_mock_races.<locals>.<lambda>  s    Q|_r6   )key)r   r+   RACING_TYPESTHOROUGHBRED_TRACKSTHOROUGHBRED_CLASSESHARNESS_TRACKSHARNESS_CLASSESGREYHOUND_TRACKSGREYHOUND_CLASSESr  r  r[  r;  r3  r  replacer   rG   rH   rI   	isoformatr\  
CONDITIONSrb  r8  sort)racesnowrj   tracksclasses	distances
day_offsetrk   	num_races	base_timerace_numrm   rv   races                 r4   generate_mock_racesr  Q  s   E
//
C#|.((F*GBII%#F%GI &F'G6I(Jvs1c&k/BC"NN1b1	KKAaQ ( :./	 !&aQ 7H!*Y (2r10E E. "J
 "'&+5+1>>!Q+?L+1>>!R+@L #&djjl"3+6%*+3*4*>*>*@(.i(@*0--*@*0--
*C+1== U, (=\'W  T*5 !8 D #) $r 
JJ,J-Lr6   c                  0    [         c
  [        5       q [         $ )z/Return cached races, generating once at startup)_cached_racesr  rJ   r6   r4   get_cached_racesr    s     +-r6   c            	           \ rS rSrSrSS\S-  4S jjrSS\S\\	   4S jjr
S	\S\	4S
 jrS	\\-  S\	4S jr SS\S-  S\S\S\	4S jjrS\S\S\	4S jrSS\S-  S\	4S jjrS rSrg)EloApiClienti  z.HTTP client for the tipsharks-elo-api service.Nbase_urlc                     U=(       d     [         R                  R                  SS5      U l        [        R
                  " SS9U l        g )NELO_API_URLzhttp://localhost:8000g      $@)timeout)osenvironr7  r  httpxAsyncClient_client)selfr  s     r4   __init__EloApiClient.__init__  s7      
BJJNN2%
 ((6r6   limitr$   c                    #    U R                   R                  U R                   S3SU0S9I S h  vN nUR                  5         UR	                  5       $  N$7f)Nz	/v1/racesr  paramsr  r7  r  raise_for_statusjson)r  r  responses      r4   	get_racesEloApiClient.get_races  sW     ))}}oY'%0@ * 
 
 	!!#}}	
s   -AA%Ar   c                    #    U R                   R                  U R                   SU 35      I S h  vN nUR                  5         UR	                  5       $  N$7f)N
/v1/races/r  r  r   r  s      r4   get_raceEloApiClient.get_race  sI     ))T]]O:gY*OPP!!#}} Qs   .AA%Ac                    #    U R                   R                  U R                   SU S35      I S h  vN nUR                  5         UR	                  5       $  N$7f)Nr  z/predictionsr  r  s      r4   get_predictionsEloApiClient.get_predictions  sQ     ))}}oZy=
 
 	!!#}}	
s   /AA%Aentity_typeoffsetc                   #    Uc  / 0 S.nS H}  n U R                   R                  U R                   SU 3X#S.S9I Sh  vN nUR                  S:X  a6  UR	                  5       nUS   R                  UR                  S/ 5      5        M}  M     U$ U R                   R                  U R                   SU 3X#S.S9I Sh  vN nUR                  5         UR	                  5       $  N! [         a     M  f = f N77f)	zGet top ratings for horses, drivers, or trainers.

Args:
    entity_type: One of "horses", "drivers", "trainers" or None for all.
    limit: Maximum results per type.
    offset: Pagination offset.
Nr"   metahorsesdriverstrainers/v1/ratings/)r  r  r     r"   )r  r7  r  status_coder  extend	Exceptionr  )	r  r  r  r  resultetrespr"   r  s	            r4   get_ratingsEloApiClient.get_ratings  s      "-F7	!%!1!1==/bT:).A "2 " D ''3.#yy{v--dhhvr.BC / 8 M))}}o\+7"5 * 
 
 	!!#}}! ! 
sF   C8.C%C# AC%6C8>C6?$C8#C%%
C3/C82C33C8	entity_idc                    #    U R                   R                  U R                   SU SU 35      I Sh  vN nUR                  5         UR	                  5       $  N$7f)z8Get rating for a specific entity (horse/driver/trainer).r  /Nr  )r  r  r  r  s       r4   get_entity_ratingEloApiClient.get_entity_rating  sU     ))}}o\+a	{C
 
 	!!#}}	
s   1AA%A	race_datec                    #    0 nU(       a  XS'   U R                   R                  U R                   S3US9I Sh  vN nUR                  5         UR	                  5       $  N$7f)z$Get upcoming races from the Elo API.r  z/v1/races/upcomingr  Nr  )r  r  r  r  s       r4   get_upcoming_racesEloApiClient.get_upcoming_races  sc     "+;))}}o/0 * 
 
 	!!#}}	
s   8A!A%A!c                 T   #    U R                   R                  5       I S h  vN   g  N7frE   )r  acloser  s    r4   closeEloApiClient.close  s     ll!!###s   (&()r  r  rE   )2   )Nr$  r   )r\   r]   r^   r_   r`   rG   r  rb   listdictr  r  r  r  r  r  r  rd   rJ   r6   r4   r  r    s    87t 7S $t* c d 
S3Y 4  OP:58HK	B3 3 4 	#* 	 	$r6   r  c                   L    \ rS rSrSrS r\S\4S j5       rS\	S\	S\4S jr
S	rg
)TwilioClienti  z-SMS client using the Twilio API via raw HTTP.c                     [         R                  R                  S5      U l        [         R                  R                  S5      U l        [         R                  R                  S5      U l        g )NTWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENTWILIO_PHONE_NUMBER)r  r  r7  account_sid
auth_tokenphone_numberr  s    r4   r  TwilioClient.__init__  sC    ::>>*>?**..)<=JJNN+@Ar6   r$   c                 Z    [        U R                  U R                  U R                  /5      $ )z-True when all Twilio credentials are present.)r<  r  r  r  r  s    r4   enabledTwilioClient.enabled  s&     D$$doot7H7HIJJr6   r   r   c                 x  #    U R                   (       d  [        R                  SX5        g [        R                  " 5        ISh  vN nUR                  SU R                   S3U R                  XS.U R                  U R                  4SS9I Sh  vN nUR                  5         UR                  5       R                  S	S
5      n[        R                  SX5         SSS5      ISh  vN   g N N^ N	! , ISh  vN  (       d  f       g= f! [         a   n[        R                  SX5         SnAgSnAff = f7f)z@Send an SMS via Twilio. Falls back to logging if not configured.z0[Twilio Fallback] SMS to %s (not configured): %sTNz+https://api.twilio.com/2010-04-01/Accounts/z/Messages.json)FromToBody      .@)r"   authr  sidunknownzTwilio SMS sent to %s: sid=%szTwilio SMS failed to %s: %sF)r  loggerinfor  r  postr  r  r  r  r  r7  r  r   )r  r   r   clientr  r  es          r4   send_smsTwilioClient.send_sms  s     ||KKJBU	((**f!'(()9"&"3"32L**DOO<  "- "  ))+mmo))%;;RE +** +***  	LL6>	s   )D:D C-D 	AC3C/AC3D 'C1(D ,D:-D /C31D 3D
9C<:D
D 	D:
D 
D7D2-D:2D77D:)r  r  r  N)r\   r]   r^   r_   r`   r  propertyr   r  rG   r  rd   rJ   r6   r4   r  r    sC    7B
 K K K C D r6   r  c                       \ rS rSrSrS r SS\S\S\S\S-  S	\4
S
 jjr SS\S\S\S\S-  S	\4
S jjr	 SS\S\S\S\S-  S	\4
S jjr
Srg)EmailClienti  z9Email client supporting SendGrid and Resend via raw HTTP.c                     [         R                  R                  S5      U l        [         R                  R                  S5      U l        [         R                  R                  SS5      U l        g )NSENDGRID_API_KEYRESEND_API_KEY
FROM_EMAILznotifications@tipsharks.com)r  r  r7  sendgrid_key
resend_key
from_emailr  s    r4   r  EmailClient.__init__!  sD    JJNN+=>**..)9:**..7TUr6   Nr   r   r   htmlr$   c                    #    U R                   (       a  U R                  XX45      I Sh  vN $ U R                  (       a  U R                  XX45      I Sh  vN $ [        R                  SXU5        g NG N7f)zOSend email via SendGrid or Resend. Falls back to logging if neither configured.Nz.[Email Fallback] To: %s, Subject: %s, Body: %sT)r  _send_sendgridr  _send_resendr  r  )r  r   r   r   r  s        r4   
send_emailEmailClient.send_email&  sa      ,,R$EEE??**2CCCDbSWX FCs!   'A5A1*A5A3A53A5c                   #     SSU0/0/SU R                   0USUS./S.nU(       a  US   R                  SUS.5        [        R                  " 5        IS h  vN nUR	                  SUS	S
U R
                   30SS9I S h  vN nUR                  5         [        R                  SU5         S S S 5      IS h  vN   g Ne N> N	! , IS h  vN  (       d  f       g = f! [         a   n[        R                  SX5         S nAgS nAff = f7f)Nr   r   z
text/plain)r{   value)personalizationsfromr   contentr  z	text/htmlz%https://api.sendgrid.com/v3/mail/sendAuthorizationBearer r  r  headersr  zSendGrid email sent to %sTzSendGrid email failed to %s: %sF)r  r8  r  r  r  r  r  r  r  r  r   	r  r   r   r   r  payloadr  r  r  s	            r4   r  EmailClient._send_sendgrid2  s    	&*gr]O%<$= $//2"%1DAB	G 	"));*NO((**f!'; ,8I8I7J.KL 	 "- "  ))+7< +** +***  	LL:BB	s   DAC B>C %CC +C-C 8C9C =D>C  CC C
CCC DC 
D(D>DDDc                   #     U R                   U/UUS.nU(       a  XES'   [        R                  " 5        IS h  vN nUR                  SUSSU R                   30SS9I S h  vN nUR                  5         [        R                  SU5         S S S 5      IS h  vN   g	 Ne N> N	! , IS h  vN  (       d  f       g = f! [         a   n[        R                  S
X5         S nAgS nAff = f7f)N)r  r   r   rz   r  zhttps://api.resend.com/emailsr  r  r  r  zResend email sent to %sTzResend email failed to %s: %sF)
r  r  r  r  r  r  r  r  r  r   r	  s	            r4   r  EmailClient._send_resendM  s     	d"	G "&((**f!'3 ,7H.IJ 	 "- "  ))+5r: +** +***  	LL8"@	s   C-6C  B C  %B&#B"$+B&C  B$C  C- C  "B&$C  &B=,B/-B=9C  <C-=C   
C*
C% C-%C**C-)r  r  r  rE   )r\   r]   r^   r_   r`   r  rG   r   r  r  r  rd   rJ   r6   r4   r  r    s    CV DH

 #
+.
69Dj
	
 DH #+.69Dj	8 DH #+.69Dj	 r6   r  r%  r  r$  r   r   r   RATE_LIMITSr   r   c                    #    [         R                  U5      nUc  g[        R                  " 5       R	                  S5      n[
        R                  R                  XUS.5      I Sh  vN nU(       a  US   OSn[        SX%-
  5      nXR:  U4$  N&7f)z^Check if user has exceeded the rate limit for *channel*.

Returns (allowed, remaining_count).
N)Tr   %Y-%m-%d-%Hr   r   hour_bucketrJ  r   )	r  r7  r   r+   strftimedbrate_limitsfind_oner4  )r   r   r  r  docrJ  	remainings          r4   check_rate_limitr  r  s     
 OOG$E}//#,,];K''L C  CLQEAu}%I=)##s   A#B%B&'Bc                    #    [         R                  " 5       R                  S5      n[        R                  R                  XUS.SSS00SS9I Sh  vN   g N7f)	zOIncrement the rate-limit counter for *user_id* / *channel* in the current hour.r  r  z$incrJ  r  TupsertN)r   r+   r  r  r  
update_oner  s      r4   increment_rate_limitr    sT     //#,,];K
..
#
#L	'1 $   s   AAAArd  elo_racec                 j   U R                  S0 5      =(       d    0 nU R                  S5      =(       d2    UR                  S5      =(       d    UR                  S5      =(       d    SnU R                  S5      =(       dJ    UR                  S5      =(       d2    UR                  S5      =(       d    UR                  S5      =(       d    SnU R                  S	5      =(       d    U R                  S
5      =(       d    / n/ nU GHC  nUR                  SS5      nU(       d!  SR                  S [        S5       5       5      nUR                  S[        [        R
                  " 5       5      5      UR                  SS5      UR                  SUR                  SSUR                  SS5       35      5      UR                  S5      =(       d    UR                  S5      =(       d    SUR                  SS5      UR                  S5      UR                  S5      =(       d    UR                  SS5      USS/ S.nUR                  U5        GMF     U(       a  [        U5      nU R                  SU R                  S[        R                  " 5       R                  5       5      5      n	[        U	[        5      (       a  U	R                  5       n
O[        U	5      n
U R                  S[        [        R
                  " 5       5      5      UUU R                  SS5      U
U R                  S 5      =(       d    U R                  S!S"5      U R                  S#5      =(       d    U R                  S$S%5      U R                  S&5      =(       d    U R                  S'S(5      U R                  S)5      =(       d    U R                  S*S+5      US,.
$ )-zHTransform an elo-api race dict into the client-backend Race dict format.meetingrj   categoryr   rk   venuerR   Unknownstartersrr   rW   rL  r  c              3   b   #    U  H%  n[        [        R                  " S S5      5      v   M'     g7f)r  r  N)rG   r  r  )r*  r  s     r4   r,  &_transform_elo_race.<locals>.<genexpr>  s#     I1Cq" 566s   -/r  rP   rQ   r   
horse_namerM  jockeydriverrT   rU   rV   rX   rP   rQ   rR   rS   rT   rU   rV   rW   rY   rZ   r[   race_datetimerm   rl   r  
distance_mrn   rO  ro   classr	  track_conditionrp   r  rq   stakesrp  rq  )r7  r  r  rG   rH   rI   r8  rI  r   r+   r~  
isinstance)r   r"  rj   rk   r&  rr   starterrW   r?  	raw_startrm   s              r4   _transform_elo_racer5    s   ll9b)/RG 	]# 	;;}%	;;z"	 	  	W 	;;w	;;w	 ;;v	  
 ||J'H8<<	+BHbHG{{62&88IaIID ++dC

$56kk(A.KKFggkk(A.F-G$HI [[*Igkk(.CIr{{9b1kk(+{{9-IXq1I"!$
  	v+ . )'2 \8??#4#>#>#@AI )X&&((*
^
 ll4TZZ\!23"||M15 LL.P(,,z42Pll<0QHLL&4Qll#45 .<<f-||M2Shll8U6S r6   c            	      6  #     [        5       n  U R                  SS9I Sh  vN nU R                  5       I Sh  vN   [        U[        5      (       a2  UR                  S5      =(       d    UR                  S5      =(       d    / nU(       d'  [        R                  S5        [        5       I Sh  vN $ / nU H  n UR                  [        U5      5        M      U(       d'  [        R                  S
5        [        5       I Sh  vN $ [        R                  " 5       R                  5       nU H  nXVS'   M	     [        R                   R#                  0 5      I Sh  vN   [        R                   R%                  U5      I Sh  vN   [        R'                  S[)        U5      5        U$  GN GNp! U R                  5       I Sh  vN    f = f GN! [         a3  n[        R                  SUR                  SS	5      U5         SnAGMN  SnAff = f GN N N! [*        R,                   a3  n[        R                  SU5        [        5       I Sh  vN  s SnA$ SnAf[         a3  n[        R/                  SU5        [        5       I Sh  vN  s SnA$ SnAff = f7f)zFetch races from elo-api, transform, cache in MongoDB, and return.

Falls back to mock data (cached in MongoDB) if elo-api is unavailable.
r  r  Nr  r"   z1elo-api returned empty list; falling back to mockz#Failed to transform elo race %s: %srP   r  z.No elo races transformed; falling back to mock
_cached_atzCached %d races from elo-apiz4elo-api connection failed (%s); falling back to mockzAUnexpected error fetching from elo-api (%s); falling back to mock)r  r  r  r2  r  r7  r  warning_cache_mock_racesr8  r5  r  r   r+   r~  r  r  delete_manyinsert_manyr  r3  r  RequestErrorr   )
elo_client	elo_racestransformedr   excnow_isor  s          r4   fetch_and_cache_racesrC    s    
2)!^
	%(222<<I""$$$ i&&!g.M)--2GM2INNNO*,,,!H""#6x#@A " NNKL*,,, //#--/D!(   hh""2&&&hh"";///2C4DEK =$*""$$$ -  9LLy1
  - 	'/
  )MsS&(((( )O	
 '(((()s&  J
H F, F&F, H F)A4H .G
/H 2J3H ;G-H HH JAH H&H  H$H %J&F, )H ,G GGH 
H
'H>H H

H H H J(#IIIJJJ##JJ	JJJJJc                  d  #    [        5       n [        R                  " 5       R                  5       nU  H  nXS'   M	     [        R
                  R                  0 5      I Sh  vN   [        R
                  R                  U 5      I Sh  vN   [        R                  S[        U 5      5        U $  NL N'7f)z9Generate mock races, persist in MongoDB, and return them.r8  Nz5Cached %d mock races in MongoDB (elo-api unavailable))r  r   r+   r~  r  r  r;  r<  r  r  r3  )r  rB  r  s      r4   r:  r:    s     !Eoo))+G$\ 
((

r
"""
((

u
%%%
KKGUTL #%s$   AB0B, &B0B.&B0.B0filtersc                    #    U =(       d    0 n[         R                  R                  U5      R                  S5      nUR	                  S5      I Sh  vN nU H  nUR                  SS5        M     U$  N!7f)z-Get races from MongoDB with optional filters.r  N_id)r  r  findr  to_listpop)rE  queryraces_cursorr  r  s        r4   get_races_from_dbrM  %  sb      MrE88=='--c2L&&s++E L ,s   AA4A2"A4r   c                   #    [         R                  R                  SU 05      I Sh  vN nU(       a  UR                  SS5        U$  [	        5       n UR                  U 5      I Sh  vN nUR                  5       I Sh  vN   U(       a\  [        U5      n[        R                  " 5       R                  5       US'   [         R                  R                  SU 0USS9I Sh  vN   U$  [        5       nU H  nUS   U :X  d  M  Us  $    g N N N! UR                  5       I Sh  vN    f = f NL! [         a   n[        R                  SX5         SnANlSnAff = f7f)u8   Look up a single race: DB → elo-api → mock fallback.rP   NrG  r8  Tr  z(Failed to fetch race %s from elo-api: %s)r  r  r  rJ  r  r  r  r5  r   r+   r~  replace_oner  r  r9  r  )r   r  r>  r   r@  rA  r  rs           r4   get_race_by_idrQ  1  s7     ""D'?33DQ!^
	%'0099H""$$$-h7K(0(9(C(C(EK%((&&gD&QQQ	  ET7gH  7 4 :$*""$$$
 R QA7PPQs   %ED	E
D/ D 'D(D ,D/ ?D A D/  D-!D/ &EED D/ D*#D&$D**D/ /
E9EEEE_scheduler_taskFc                    #    Sq [        R                  S5        [         (       Ga   [        R                  " 5       n [
        R                  R                  SSU 0S.5      R                  S5      I Sh  vN nU GH}  n UR                  SS	5      nUR                  S
S5      nUR                  SS5      nUR                  SS5      nSnUS:X  a$  [        5       nUR                  XE5      I Sh  vN nOCUS:X  a%  [        5       n	U	R                  XFU5      I Sh  vN nO[        R                  SU5        SnU(       a  SOSn
U(       a  SOSn[
        R                  R                  SUS   0SXS.05      I Sh  vN   U(       as  [
        R                  R!                  [#        [$        R&                  " 5       5      UR                  SS5      SUUUUS[        R                  " 5       S.	5      I Sh  vN   GM}  GM     [,        R.                  " S5      I Sh  vN   [         (       a  GM  gg GN GN@ GN N NE! [(         ao  n[        R+                  SUR                  SS5      U5        [
        R                  R                  SUS   0SS[#        U5      S.05      I Sh  vN     SnAGM1  SnAff = f! [(         a   n[        R+                  SU5         SnANSnAff = f N7f) zHBackground loop that checks and sends scheduled notifications every 60s.TzNotification scheduler started	scheduledz$lte)r   r   r$  Nr   r   r   rL  r   r   Fr   r   zScheduled push notification: %sr   failedzProvider returned an errorrP   $set)r   r   r   r   scheduled_tip)	rP   r   r{   r   r   r   r   r   r   z/Failed to process scheduled notification %s: %sr  z%Notification scheduler loop error: %srN  )_scheduler_runningr  r  r   r+   r  scheduled_notificationsrH  rI  r7  r  r  r  r  r  notifications
insert_onerG   rH   rI   r  r   asynciosleep)r  duenotifr   r   r   r   successtwilioemail_client
new_statuserror_fieldrA  s                r4   _notification_scheduler_loopre  X  s     
KK01

=	G//#C2277)'-sm
 gclC 1#ii	6:G4,B 99VR0D#ii	26G#G%'!-(.(A"A G+'2}(4(?(?T(R"R$EtL"&+2J*1$7SK44??uT{+J!MN    ..99&)$**,&7+099Y+O(7)0(,&(+2*0.6oo.?
   7 l mmBA 
" #B #S ! 	LLI		$	2
 44??uT{+Hs3x!HI   	  	GLL@#FF	G 	 s   $K!AJ2 2H)3
J2 >A.H6,H,-*H6H/AH67H28A6H6.H4/H63J2 ;K!KK!'K!)J2 ,H6/H62H64H66
J/ AJ*J J*#J2 *J//J2 2
K<KK!KK!startupc                     #    [        5       I Sh  vN q[        R                  S[	        [        5       S35        [
        R                  " [        5       5      qg NI7f)aO  Fetch races from Elo API (with mock fallback) and start background scheduler.

On startup, attempts to fetch race data from the tipsharks-elo-api service.
Falls back to generated mock data if the Elo API is unreachable.
The Elo API URL is configurable via the ``ELO_API_URL`` environment variable
(default: ``http://localhost:8000``).
NzInitialized z races at startup)	rC  r  r  r  r3  r\  create_taskre  rR  rJ   r6   r4   initialize_datari    sI      011M
KK,s=122CDE ))*F*HIO	 2s   AAA
Ar   selected_runnerc           	        ^ / nUS:X  a  SnOUS:X  a  SnOSnTS   R                  S5       Vs/ s H  n[        U5      PM     nnUS   S	::  a(  UR                  S
US    US   S:X  a  SOS S3SS.5        [        U S SS9n[	        U4S j[        U5       5       S5      n	U	S::  a  UR                  SU	 S3SS.5        STR                  S/ 5      ;   a  UR                  SSS.5        STR                  S/ 5      ;   a  UR                  SSS.5        TR                  SS5      n
U
(       a2  U(       a+  U
S;   a%  UR                  S U S!U
 S"US:X  a  S#OS$ 3SS.5        TR                  S%S5      nU(       a  US&;   a  UR                  S'U S(3SS.5        US):X  a,  TR                  S*S5      nUS	::  a  UR                  S+SS.5        TS,   S-:  a  UR                  S.S/S.5        USS0 $ s  snf )1z$Generate reasoning bullets for a tipr   r*  r   r+  NrW   r  r   r'  zStrong recent form - placed r  stndz last startpositiverz   r{   c                     U S   $ NrY   rJ   rs  s    r4   rK   &generate_tip_reasons.<locals>.<lambda>  
    15F3Gr6   Tru  reversec              3   N   >#    U  H  u  pUS    TS    :X  d  M  US-   v   M     g7f)rP   r  NrJ   )r*  rG  rP  rj  s      r4   r,  'generate_tip_reasons.<locals>.<genexpr>  s1      	
1w/$// AE1s   %%r)  zRanked #z in our probability modelr.  r[   zConsistently finishes in top 3r0  z+Good value based on current odds assessmentrS   rL  )r   r   r   r   r   zTop  z in the saddlesulkyrT   )r  r  r  zTrained by z - proven track recordr   rV   zFavorable inside box drawrY   r   z)Higher risk pick - consider smaller stakecautionr!  )r1  rb   r8  sortednextr9  r7  )rr   r   rj  rj   r   
rider_termr@  rA  sorted_runnersrankrS   rT   rV   s     `          r4   generate_tip_reasonsr    sW    G n$
			!

 '6f&=&C&CC&HI&Hc!f&HNIaA6~a7H6IR`abRcghRh$nrIss~"	
 G)GQUVN	
!.1	

 	
D qyv%>?T	

 **8R88 @*UV/%%h33BJW	

 ,E 
 
 NN":,awh;ZhKhxnu>vw& !!)R0G7EE"7)+ABJW	

 k!!%%i3a<NN$?TU ()B.@)T	
 2A;K Js   G)r  c                    U R                  SU R                  S/ 5      5      nU R                  SS5      n[        US SS9nUS;   a,  US	   nUS
   S:  a  SOUS
   S:  a  SOSnSUS    SUS    3nOUS:X  a,  US	   nUS   S:  a  SOUS   S:  a  SOSnSUS    SUS    3nOUS:X  a(  US	   US   pUnSnSUS    SUS    SU	S    SU	S    3nO{US:X  a(  US	   US   pUnSnSUS    SUS    SU	S    SU	S    3nOMUS :X  a'  US	   US   US!   pnUnSnS"US    S#U	S    S#U
S    3nO US	   nUS
   S:  a  SOSnS$US    SUS    3n[        X!XS5      n[        [        R
                  " 5       5      U S%   UUUUUU[        R                  " 5       R                  5       U S&   U S'   U S(   U S)   U S*   U S+   US,.S-.
$ ).Generate a tip for a racerr   r  rj   r   c                     U S   $ rq  rJ   rs  s    r4   rK   generate_tip.<locals>.<lambda>  rs  r6   Trt  )winr   r   rY      highr   mediumlowWIN on #rQ   rx  rR   placerZ   r  #   z
PLACE on #quinellar  z
QUINELLA #z & #exactazEXACTA #u    → #trifectar'  z
TRIFECTA #z / #zBEST BET: WIN on #rP   rk   rl   rm   rn   ro   rp   rk   rl   rm   rn   ro   rp   rj   )
rP   r   r   r   r   rr   rj   r   r   r   )	r7  r|  r  rG   rH   rI   r   r+   r~  )r  r   rr   rj   r  selectedr   r   r1r2r3r   s               r4   generate_tipr    s   hhy$((8R"89G((=.9K G)GQUVN&&!!$ )*R/ %&782=5 	
 %Xh%7$8(6:J9KL	W	!!$ +,r1 %&9:R?U 	
 'x'9&:!HV<L;MN	Z	"N1$5B
Ha6
|48~Qr&zlS 	 
X	"N1$5B
r(|nAbj\2h<."V*V 	 
Z	#A&q(9>!;L
Hd2h<.R\NK 	
 "!$'(9:R?VX
.x/A.B!HVDTCUV"7hLG $**,:* "oo'113'].|,Z(|,|,&
 r6   r  )alias	X-User-IDr  authorization	x_user_idc                   #    U(       a_  UR                  S5      (       aI  U[        S5      S n [        R                  " U[        [
        /S9nUR                  S5      nU(       a  U$  U(       a  U$ S[        R                  " 5       R                  SS  3nX`R                  S'   U$ ! [        R                   a    [        R                  S5         Nif = f7f)	u.  Extract a user ID from JWT Bearer token, X-User-ID header, or generate anonymous.

Priority:
1. Authorization: Bearer <token> — validates JWT, returns user_id from 'sub' claim
2. X-User-ID header — returns as-is (anonymous/legacy)
3. Generates a new anonymous ID and sets X-User-ID response header
r  N
algorithmssubz,Invalid JWT token, falling back to anonymousanon_r  r  )
startswithr3  r.   decoder0   r1   r7  
PyJWTErrorr  debugrH   rI   hexr  )r  r  r  tokenr
  r   new_ids          r4   get_user_idr  `  s      11)<<c)n./	Ill5*)MG";;u-G   TZZ\%%cr*+,F$*[!M  	ILLGH	Is)   ,C8B' 'A C')CCCCr   c                    #    [         R                  R                  SU 05      I Sh  vN nU(       a  [        US   5      US'   U$  N7f)z0Look up a user by email in the users collection.r   NrG  )r  usersr  rG   )r   users     r4   get_user_by_emailr    s@     ""GU#344D$u+&UK 5s   %AAAc                   #    U (       a  U R                  S5      (       d  gU [        S5      S n [        R                  " U[        [
        /S9nUR                  S5      nU(       aV  [        R                  R                  SU05      I Sh  vN nU(       a&  UR                  SS5        UR                  SS5        U$ g N2! [        R                   a     gf = f7f)zEExtract and return the current user from a JWT Bearer token, or None.r  Nr  r  rP   rG  password_hash)r  r3  r.   r  r0   r1   r7  r  r  r  rJ  r  )r  r  r
  r   r  s        r4   get_current_user_from_tokenr    s       8 8 C C#i.*+E
,,uji[I{{5)**D'?;;D%$/  <
  s;   -CAC  B>0C  <C>C   CCCCz/auth/register)response_model	user_datac                   #    [        U R                  5      I Sh  vN nU(       a
  [        SSS9e[        [        R
                  " 5       5      nUU R                  [        U R                  5      U R                  [        R                  " 5       S.n[        R                  R                  U5      I Sh  vN   [        SU0S9n[        US9$  N N7f)	z+Register a new user and return a JWT token.Nre  zEmail already registeredr  detail)rP   r   r  rR   r   r  r"   r   )r  r   r   rG   rH   rI   r@   r=   rR   r   r+   r  r  r[  r5   r   )r  existingr   user_docr  s        r4   register_userr    s      'y77H4NOO $**,G*9+=+=>oo'H ((

h
'''  eW%56Ee,,# 8 (s"   CCBC3C4CCz/auth/login
login_datac                    #    [        U R                  5      I Sh  vN nU(       d
  [        SSS9e[        U R                  US   5      (       d
  [        SSS9e[        SUS   0S9n[        US	9$  NT7f)
z+Authenticate a user and return a JWT token.N  zInvalid email or passwordr  r  r  rP   r  r  )r  r   r   r<   r=   r5   r   )r  r  r  s      r4   
login_userr    st      #:#3#344D4OPP:.._0EFF4OPPeT$Z%89Ee,, 5s   A2A0AA2z/auth/mecurrent_userc                 t   #    U (       d
  [        SSS9e[        U S   U S   U R                  S5      U S   S9$ 7f)	z0Return the current authenticated user's profile.r  zNot authenticatedr  rP   r   rR   r   )rP   r   rR   r   )r   r   r7  )r  s    r4   get_current_user_endpointr    sK     
 4GHH7#f%-	 s   68r  c                     #    SS/ SQS.$ 7f)NzRacing Tips APIz1.0.0r   )messageversionracing_typesrJ   rJ   r6   r4   rootr    s      %@ s   	z/healthc                     #    SS0$ 7f)Nr   healthyrJ   rJ   r6   r4   health_checkr    s     i  s   z/raceszFilter by date (today/tomorrow))descriptionzFilter by track namez6Filter by racing type (thoroughbred/harness/greyhound)zLimit resultsdaterk   r  c                   #    [        5       I Sh  vN nSnU(       d  SnObUS   R                  S5      nU(       aG   [        R                  " U5      n[        R                  " 5       U-
  R                  5       [        :  a  SnU(       a  [        5       I Sh  vN n[        R                  " 5       nUR                  5       n	U	[        SS9-   n
U S:X  a@  U Vs/ s H2  n[        R                  " US	   5      R                  5       U	:X  d  M0  UPM4     nnOEU S
:X  a?  U Vs/ s H2  n[        R                  " US	   5      R                  5       U
:X  d  M0  UPM4     nnU(       a8  U Vs/ s H+  oR                  5       US   R                  5       ;   d  M)  UPM-     nnU(       a*  U Vs/ s H  oS   UR                  5       :X  d  M  UPM     nn/ nUSU  H[  nUR                  [        US   US   US   US   [        R                  " US	   5      US   US   US   [        US   5      S9	5        M]     U$  GN ! [        [        4 a    Sn GNf = f GNs  snf s  snf s  snf s  snf 7f)zGet list of upcoming racesNFTr   r8  r  rj  todayrm   tomorrowrk   rj   rP   rl   rn   ro   rp   rr   )	rP   rj   rk   rl   rm   rn   ro   rp   rv   )rM  r7  r   fromisoformatr+   total_secondsCACHE_TTL_SECONDS
ValueError	TypeErrorrC  r  r   lowerr8  rt   r3  )r  rk   rj   r  r  should_refetch	cached_atcached_timer  r  r  rP  r  s                r4   r  r    sU     $%%E N !HLL.	&&44Y?OO%3-/$56 &*N +-- //
CHHJEya((Hw
! 6 6q G L L NRW WAu 	 
 
	 
%%ao6;;=I  	 
 !IEq[[]aj6F6F6H%HEI!MEq}%59J9J9L%LEM F6E]T7m,jm,#11!L/B:\?\? 9.
	
  M{ &  	* &!%& .

 J Ns   I#H0+I#AH3 I#I?I#/I
II#/III##(III#"I IA+I#3I	I#I		I#I#z/races/{race_id}c                 Z   #    [        U 5      I Sh  vN nU(       a  U$ [        SSS9e N7f)z'Get detailed race info including horsesN  Race not foundr  )rQ  r   )r   r  s     r4   r  r  6  s/       ((D
C0@
AA )s   +)+z/races/next/upcomingc                     #    [        5       I Sh  vN n U (       d  [        5       I Sh  vN n U (       a  U S   $ [        SSS9e N3 N7f)zGet the next upcoming raceNr   r  zNo upcoming racesr  )rM  rC  r   )r  s    r4   get_next_racer  ?  sB      $%%E+--Qx
C0C
DD &-s   AAAAAAz/ratingsz5Entity type: horses, drivers, trainers (default: all)zMax results per typer  c                 X  #     [        5       n U (       aJ  SSSS.R                  U R                  5       U R                  5       5      nUR                  X1S9I Sh  vN nOUR                  US9I Sh  vN nUR	                  5       I Sh  vN   U$  N6 N  N
! UR	                  5       I Sh  vN    f = f! [
        R                   a+  n[        R                  SU5        [        X5      s SnA$ SnAf[         a+  n[        R                  S	U5        [        X5      s SnA$ SnAff = f7f)
a  Get top ratings from the Elo API.

Proxies to the tipsharks-elo-api service. Falls back to mock/sample
rating data if the Elo API is unreachable.

Args:
    entity_type: One of ``horses``, ``drivers``, ``trainers``, or None for all.
    limit: Maximum results per entity type.
r  r  r  r  )r  r  Nr7  z4Elo API unreachable for ratings (%s); returning mockz%Unexpected error fetching ratings: %s)r  r7  r  r  r  r  r=  r  r9  _generate_mock_ratingsr  r   )r  r  r>  entity_type_pathr  rA  s         r4   r  r  N  s     :!^
	%&( *$ #k'');+<+<+>?	 !
  *55 0  6     *55E5BB""$$$ C$*""$$$ :MsS%k99 :<cB%k99:s   D*
B5 AB BB 2B3B 7B5 
BB5 D*B B B5 B2+B.,B22B5 5D'	 C/)D'*D*/D'< D"D'D*"D''D*z"/ratings/{entity_type}/{entity_id}r  c                   #     [        5       n UR                  X5      I Sh  vN nUR                  5       I Sh  vN   U$  N N! UR                  5       I Sh  vN    f = f! [        R                   a'  n[
        R                  SU UU5        [        SSS9eSnAf[         a    e [         a'  n[
        R                  SU UU5        [        SSS9eSnAff = f7f)a  Get rating for a specific entity (horse/driver/trainer) from the Elo API.

Falls back to a mock response if the Elo API is unreachable.

Args:
    entity_type: ``horses``, ``drivers``, or ``trainers``.
    entity_id: The entity's numeric ID in the Elo system.
Nz9Elo API unreachable for rating %s/%s (%s); returning mocki  zBElo API unavailable and no mock data available for specific entityr  z*Unexpected error fetching rating %s/%s: %s)
r  r  r  r  r=  r  r9  r   r  r   )r  r  r>  r  rA  s        r4   r  r  w  s     
!^
	%%77OOF""$$$ P$*""$$$ 

G		
 W
 	
   

8		
 W
 	


sv   C
A$ A AA A$ AA$ CA A$ A!AA!!A$ $C8"BC1"CCCz/tips/{race_id}c                   #     [        5       n [        U 5      nUR                  U5      I Sh  vN n UR                  5       I Sh  vN   UR                  S/ 5      nU(       d
  [        SSS9e[        X05      $  NK! [        [        4 a"    [
        R                  SU 5        [        S5      ef = f Ni! UR                  5       I Sh  vN    f = f! [        R                  [        [        4 aR  n[
        R                  SU U5        [        U 5      I Sh  vN  nU(       d
  [        SS	S9e[        US
5      s SnA$ SnAff = f7f)u$  Get Elo-powered predictions/tips for a race from the Elo API.

Proxies to ``GET /v1/races/{race_id}/predictions`` on the Elo API.
Falls back to local tip generation using mock data if the Elo API is
unreachable.

Args:
    race_id: Race ID (string — will be parsed to int for the Elo API).
Nz?Race ID %s is not numeric; falling back to local tip generationzNon-numeric race IDpredictionsr  z&No predictions available for this racer  zOElo API unreachable for tips/race %s (%s); falling back to local tip generationr  r   )r  rb   r  r  r  r  r  r  r7  r   _transform_predictions_to_tipr  r=  r9  rQ  r  )r   r>  
numeric_idr  r  rA  r  s          r4   get_tips_for_racer    s/    ".!^
	%WJ%55jAAF ""$$$ jj3(P  -V==% BI& 	4KKQ 233	4 %*""$$$ 
I6 
.]	
 $G,,,C8HIID*--
.s   E
C A< A:A< C B11C 9E:A< <2B..B3 1C 3CC
CC E0%D=D!D=7E8E=EEc                    SSSSSSSS.SS	S
SSSSS.SSSSSSSS./SSSSSSSS.SS	SSSSSS.SSSSSSSS./SSS S!S"S#SS.SS	S$S%S&S'SS.SSS(S)S*S+SS./S,.nU (       aI  U R                  5       R                  S-5      S--   nUR                  U/ 5      nUS.U [        U5      US/S0.S1.$ / nUR	                  5        H  nUR                  US.U 5        M     U[        U5      US/S0.S1.$ )2z:Generate mock rating data when the Elo API is unavailable.horser  r   g     @g     @U@   r$  )r  r  entity_nameratingrd
race_countas_of_race_idr'  r   g     p@g     V@   r)  r   g     @g     S@    r+  r   g      @g     @P@   r   g     `@g      R@x   r   g      T@r&  rT   r  g     Л@g     W@r  r  g     X@g      V@   r  g     @g      W@   r  sNr   )totalr  r  r  )r  rstripr7  r3  valuesr  )r  r  mock_ratingsr  r"   all_dataet_datas          r4   r  r    s   
  '-  !$  '/  !$  '-  !$'
>  (, !!$  () !!$  ()  !$'
>  )* !!$  )+ !!$  )) !!$'
wXLt  '',s2B'%L!$i%1E
 	

 #**,GOOGFUO, - !(meqI
 	
r6   prediction_responsec                    U R                  S/ 5      nU(       d
  [        SSS9e[        US SS9nUS   nUR                  S	S5      S
-  nUR                  SS5      S
-  nUS:  a  SO	US:  a  SOSn[        UR                  S[        R
                  " 5       5      5      UR                  SS5      UR                  SSUR                  SS5       35      UR                  SS5      UR                  SS5      SUR                  SS5      S[        US5      [        US5      / S.nSUS S3S S!.S"UR                  S#S5      S$ 3S S!./n	US:X  a  U	R                  S%S S!.5        [        [        R
                  " 5       5      US&S'US(    S)US*    3U[        U5       V
Vs/ s H  u  p[        UR                  S[        R
                  " 5       5      5      UR                  SU
S-   5      UR                  SSUR                  SS5       35      UR                  SS5      UR                  SS5      SUR                  SS5      S[        UR                  S	S5      S
-  S5      [        UR                  SS5      S
-  S5      / S.PM     snn
U	[        R                  " 5       R                  5       U R                  S+S,5      U R                  S-S5      [        R                  " 5       R                  5       U R                  S.S5      S/S0S1S2.S3.	$ s  snn
f )4zETransform an Elo API predictions response into the client tip format.r  r  zNo predictions availabler  c                 &    U R                  SS5      $ )NrY   r   )r7  )r+  s    r4   rK   /_transform_predictions_to_tip.<locals>.<lambda>J  s    155):A#>r6   Trt  r   rY   r$  rZ   r  r  r   r  r  horse_idpredicted_placingr  r)  zHorse #?driver_namerL  trainer_nameNrV   r,  zElo win probability: z.1f%rn  ro  zEffective rating: effective_ratingz.0fu,   Strong confidence — top rated in the fieldr   r  rQ   rx  rR   r$  r%  rl   r.  r	  r  r   r  )	rP   r   r   r   r   rr   r   r   r   )r7  r   r|  rG   rH   rI   r:  r8  r9  r   r+   r~  )r  r   r  sorted_predstop_predrH  
place_probr   r?  r   rG  r+  s               r4   r  r  B  s   %))-<K4NOO >L AH||-q1C7H115;J#b=(R-hUJ (,,z4::<89,,2A6\WX\\*c5R4S+TUmR0<<3<<	1- 1-":q1F )#a8*M(6H!)LS(QR	
G VCZX	

 $**,%fX&6%7q8HI  ",/
 0 !%%
DJJL9:%% 3QU;lgaeeJ6L5M,NO}b155455A.#(/@!)Ds)JA#N%*1551Da+H3+NPQ%R 0
  oo'113(,,Wi@.22=!D"//+557+//a@  $
1! !
s   C&Lz/tips/generatetip_requestc                    #    [        U R                  5      I Sh  vN nU(       d
  [        SSS9e[        XR                  5      nU$  N,7f)r  Nr  r  r  )rQ  r   r   r  r   )r  r  r   s      r4   generate_tip_endpointr	    sE        3 344D4DEE
t11
2CJ 5s   A
A-A
z
/tips/savetip_datac                   #    [        [        R                  " 5       5      UU [        R                  " 5       R                  5       S.n[        R                  R                  UR                  5       5      I Sh  vN   U$  N7f)zSave a tip for later reference)rP   r   r   r   N)
rG   rH   rI   r   r+   r~  r  
saved_tipsr[  r*   )r
  r   	saved_tips      r4   save_tipr    sa      $**,OO%//1	I --
"
"9>>#3
444 5s   A5B 7A>8B z/tips/savedc                    #    [         R                  R                  SU 05      R                  SS5      R	                  S5      I Sh  vN nU H  n[        US   5      US'   M     U$  N 7f)zGet user's saved tipsr   r   r$  NrG  )r  r  rH  r  rI  rG   )r   tipsr   s      r4   get_saved_tipsr    sg      mm  )W!56;;JKSSTWXX 	 U_E
 K	 	Y   AA)A'!A)z/tips/saved/{tip_id}tip_idc                    #    [         R                  R                  XS.5      I Sh  vN nUR                  S:X  a
  [	        SSS9eSS0$  N"7f)	zDelete a saved tiprP   r   Nr   r  zTip not foundr  r  zTip deleted)r  r  
delete_onedeleted_countr   )r  r   r  s      r4   delete_saved_tipr    sM      ==++6,NOOFq ODD}%% Ps   %AA
#Az
/schedulesschedule_datac                 f  #    [        U R                  5      I Sh  vN nU(       d
  [        SSS9e[        R                  " US   5      nU[        U R                  S9-
  n[        [        R                  " 5       5      UU R                  U R                  U R                  U R                  S[        R                  " 5       R                  5       UR                  5       US   US	   US   US
   US   S.S.
n[        R                  R!                  UR#                  5       5      I Sh  vN   U$  GN N	7f)z#Create a scheduled tip notificationNr  r  r  rm   r&   r   rk   rl   rn   ro   )rk   rl   rm   rn   ro   )
rP   r   r   r   r   r   r   r   r   r   )rQ  r   r   r   r  r   r   rG   rH   rI   r   r   r+   r~  r  	schedulesr[  r*   )r  r   r  
race_startr   schedules         r4   create_scheduler    s    
   5 566D4DEE''\(:;J)M4P4P"QQN $**, ((!**'66!**oo'113(224'].|,Z(|,
H& ,,
!
!(--/
222O9 76 3s"   D1D,D	D1%D/&D1/D1c                    #    [         R                  R                  U SS.5      R                  SS5      R	                  S5      I Sh  vN nU H  n[        US   5      US'   M     U$  N 7f)zGet user's scheduled tipsr   )r   r   r   r  r$  NrG  )r  r  rH  r  rI  rG   )r   r  r  s      r4   get_schedulesr!    sf      llGx HI		"		 
 qx=% 	s   AA*A(!A*z/schedules/{schedule_id}r   c                    #    [         R                  R                  XS.SSS005      I Sh  vN nUR                  S:X  a
  [	        SSS	9eS
S0$  N"7f)zCancel a scheduled tipr  rV  r   	cancelledNr   r  zSchedule not foundr  r  zSchedule cancelled)r  r  r  modified_countr   )r   r   r  s      r4   cancel_scheduler%    sa      <<**/&8[:Q1R F !4HII+,,s   *AA#Az/notificationsc                    #    [         R                  R                  SU 05      R                  SS5      R	                  S5      I Sh  vN nU H  n[        US   5      US'   M     U$  N 7f)zGet user's notification historyr   r   r  r$  NrG  )r  rZ  rH  r  rI  rG   )r   rZ  ns      r4   get_notificationsr(    sg      ##Y$89	lB			 
 qx=% 	r  z*/notifications/mark-read/{notification_id}notification_idc                 r   #    [         R                  R                  XS.SSS005      I Sh  vN   SS0$  N7f)zMark a notification as readr  rV  r   readNr  zNotification marked as read)r  rZ  r  )r)  r   s     r4   mark_notification_readr,  	  sJ     
 


%
%3fx>P5Q   455s   *75	7z/notifications/sendrequestc                 *  #    U R                   =(       d    UnU R                  S;  a  [        SSU R                   S3S9e[        X R                  5      I Sh  vN u  p4U(       d9  [        SSU R                   S	[        R                  U R                  S
5       S3S9e[        [        R                  " 5       5      nSnSn U R                  S:X  aD  [        5       nUR                  U R                  U R                  5      I Sh  vN n	U	(       d  SnSnOU R                  S:X  aO  [        5       n
U
R                  U R                  U R                  U R                  5      I Sh  vN n	U	(       d  SnSnO"[         R#                  SUU R                  5         UUSU R                  U R                  U R                  U R                  UU[(        R*                  " 5       S.
n[,        R.                  R1                  U5      I Sh  vN   US:X  a  [3        X R                  5      I Sh  vN   [5        UUU R                  US   US9$  GN GNA N! [         a    e [$         a-  nSn[        U5      n[         R'                  SU5         SnANSnAff = f N Nl7f)z9Send an immediate notification via the specified channel.r  re  zUnsupported channel 'z'. Use one of: sms, email, pushr  Ni  zRate limit exceeded for z	. Limit: r  z/hour. Try again later.r   r   rU  zSMS provider returned an errorr   z Email provider returned an errorz!Push notification for user %s: %szNotification send failed: %s)
rP   r   r{   r   r   r   r   r   r   r   r   )rP   r   r   r   r   )r   r   r   r  r  r7  rG   rH   rI   r  r  r   r   r  r  r   r  r  r  r   r   r+   r  rZ  r[  r  r   )r-  r   effective_user_idallowedr  r)  r   	error_msgra  r`  rb  rA  notification_docs                r4   send_notificationr3    s_      27 66*7??*; <+ ,
 	
  00A??SSG*7??*; <%//'//3?@ A#$
 	
 $**,'OF I:??e#!^F"OOGJJEEG!<	__'&=L(33

GOOW\\ G !>	KK3! $jj??oo' 


%
%&6
777 "#4ooFFF#L1 } T$ F   :H	3S99:$ 8 	Gs   AJIA(JAI II JAI $I	%I 4J5!I A+JJ"J$J%JI 	I J#JJJJJz/notifications/schedulec                   #    U R                   [        R                  " 5       ::  a
  [        SSS9e[	        [
        R                  " 5       5      nUUU R                  U R                  U R                  U R                  U R                   U R                  S[        R                  " 5       S.
n[        R                  R                  U5      I Sh  vN   [        USU R                  US   S9$  N7f)	z+Schedule a notification for later delivery.re  z$scheduled_time must be in the futurer  rT  )
rP   r   r   r   r   r   r   r   r   r   Nr   )rP   r   r   r   )r   r   r+   r   rG   rH   rI   r   r   r   r   r   r  rY  r[  r   )r-  r   r)  scheduled_docs       r4   schedule_notificationr6  l  s      !229
 	

 $**,'O jj????!00??oo'M 
$
$
/
/
>>> .	  ?s   C
C,C*C,z/notifications/providersc                    #    [        [        R                  R                  S5      [        R                  R                  S5      [        R                  R                  S5      /5      n [	        [        R                  R                  S5      5      n[	        [        R                  R                  S5      5      nSU U (       a  SOSS	.U=(       d    UUUS
.S.SSS	.S.0$ 7f)zKReturn available notification providers based on environment configuration.r  r  r  r  r  	providersra  N)	availableprovider)sendgridresend)r9  r8  Tzbuilt-inr  )r<  r  r  r7  r   )twilio_configuredsendgrid_configuredresend_configureds      r4   get_notification_providersr@    s      JJNN/0JJNN./JJNN01	
 rzz~~.@ABRZZ^^,<=> 	.(9Ht
 1E4E 3/ "&
 s   CCz/user/preferencesc                 (  #    [         R                  R                  SU 05      I Sh  vN nU(       dG  [        U S9R	                  5       n[         R                  R                  U5      I Sh  vN   [        U S9$ UR                  SS5        U$  Nf N#7f)zGet user preferencesrP   NrP   rG  )r  preferencesr  r   
model_dumpr[  rJ  )r   prefsdefault_prefss      r4   get_preferencesrG    sx      ..))4/::E'73>>@nn''666'**	IIeTL ; 	7s"   %BBAB,B-"BBupdatesc                   #    U R                  5       R                  5        VVs0 s H  u  p#Uc  M
  X#_M     nnnU(       d
  [        SSS9e[        R                  R                  SU0SU0SS9I Sh  vN   [        R                  R                  SU05      I Sh  vN nU(       a  UR                  S	S5        U$ [        US
9$ s  snnf  NW N07f)zUpdate user preferencesNre  zNo updates providedr  rP   rV  Tr  rG  rB  )	r  itemsr   r  rC  r  r  rJ  r   )rH  r   kvupdate_dictrE  s         r4   update_preferencesrN    s     
 %,LLN$8$8$:L$:DAa414$:KL4IJJ
..
#
#T7Ofk5JSW
#
XXX..))4/::E		%g&& M
 Y:s2   "C	CC:C1C	2(CC/CCz/trackszFilter by racing typec                    #    U S:X  a	  [         SS.$ U S:X  a	  [        SS.$ U S:X  a	  [        SS.$ [         [        -   [        -   [         [        [        S.S.$ 7f)z+Get list of available tracks by racing typer   )r  rj   r   r   r   )
all_tracksby_type)rw  ry  r{  )rj   s    r4   
get_tracksrR    sh     
 n$-nMM			!(CC		#*;GG .>AQQ 3)-
 	
s   AAz
/bet-typesc                  P   #    SSSS.SSSS.SS	S
S./SSSS.SSSS.SSSS./S.$ 7f)z)Get available bet types with descriptionsr   zBest Betz)Our recommended pick based on all factorsrP   rR   r  r  WinzRunner must finish firstr  PlacezRunner must finish in top 3r  Quinellaz1Pick 2 runners to finish 1st and 2nd in any orderr  Exactaz)Pick 2 runners in exact 1st and 2nd orderr  Trifectaz+Pick 3 runners in exact 1st, 2nd, 3rd order)simpleadvancedrJ   rJ   r6   r4   get_bet_typesr\    st      !"J
 %8RS<
 !"R  J !"L
 s   $&z/race-optionsc                     #    SSSSS.SSSS	S.S
SSSS./SSSS.SSSS.SSSS.SSSS.SSSS.SSSS.SSSS./SSSS S!/S".S#S$S%S!S&/S".S'S(S)S&S*/S"./S+S+S,S.S-S-S.S.S/S/S0S.S1S1S2S.S3S3S4S./S5.$ 7f)6z8Get available race filter options including racing typesr   Thoroughbredz%Traditional horse racing with jockeysr  )rP   rR   r  iconr   Harnessz Trotters and pacers with driversz	car-sportr   	Greyhoundz
Dog racingpawr  zHighest level racesrT  r  zSecond tier feature racesr  zThird tier feature racesr  zListed feature racesr	  zOpen handicap racesr  zOpen class - harness/greyhoundr
  zYet to win a racesprintSprintzShort distance racesr   rO  )rP   rR   r  r  middlezMiddle DistancezMid-range racesrP  stayingStayingzLong distance racesi'  r  zFirm, fast trackr  zRain-affected, yieldingr  zVery wet trackr  zHard, dry trackr  zAll-weather track)r  race_classesr  rp   rJ   rJ   r6   r4   get_race_optionsri  	  s>     %&F	  !A#	 "#+	
* i@UV!:  !9
 X>TU6:OP$&?
 X>QR'
.  5T	 )0	  !4	
* 6:LM6:STG<LM6:KL!#2

}I Is   A.A0T*)allow_credentialsallow_originsallow_methodsallow_headersshutdownc                     #    Sq [        b:  [        R                  5          [        I Sh  vN   Sq[
        R                  S5        [        R                  5         g N0! [        R                   a     NDf = f7f)z=Clean up MongoDB client and background scheduler on shutdown.FNzNotification scheduler stopped)	rX  rR  cancelr\  CancelledErrorr  r  r  r  rJ   r6   r4   shutdown_db_clientrs  k	  sf      " 	!!! 45
LLN "%% 		s7   A7	A AA ,A7A A41A73A44A7rE   )Nr$  )fastapir   r   r   r   r   r   r	   dotenvr
   starlette.middleware.corsr   motor.motor_asyncior   r  loggingpathlibr   pydanticr   r   typingr   r   r   r   rH   r   r   r  r  jwtr.   passlib.contextr   r\  __file__parentROOT_DIRr  	mongo_urlr  r  app
api_routerbasicConfigINFO	getLoggerr\   r  r7  r0   r1   r,   r:   r  rG   r5   r   r<   r@   rB   rf   rt   rx   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rv  rw  ry  r{  rS  rV  rY  rT  rW  rU  rX  rZ  rx  rz  r|  r  r  rI  rb   rb  r  r  r  r  r  r  r  ra   tupler  r  r  r5  rC  r:  rM  rQ  rR  TaskrX  re  on_eventri  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r	  r  r  deleter  r  r!  r%  r(  r,  r3  r6  r@  rG  putrN  rR  r\  ri  include_routeradd_middlewarers  rJ   r6   r4   <module>r     s9   W W W  4 2 	   % , ,  (    ( >   Hv  JJ{#		I	&BJJy!" i f%
   
,,U 
		8	$ ZZ^^,.ST
	) H:&A	Dd 	D9t3C 	Ds 	D?C ?# ?$ ?
& & &Y  
9 
	9 		 
	/) 	/	 
@y @
/y 
/#Y #B9 B*i *.*I *$9 	 
9 I "i "") " 9   8   :00  	 		  <
D.T$Z .DJ .b*, *,3 *,4: *,Z?T$Z ?F $t* Q$ Q$n" "JH H\ T#s(^ $C $# $%c	:J $& c d   H$ H4 HV7)T$Z 7)t	d 	 &*	#s(^d"		$Z	# $+ F (,$ + F R iJ J&T$ZT#&T9=TLOT	$ZTnNt Ns Nt Nl $*$o#F%d+>C= } 		H3 4$;  $*$o#FC=	D[0 !-@-< - A-. }=
- 
- >
- 
<8 '(C D+ 9"   	! !
 l);<2ST 3IJ!&R" r7G
3-GC=G #G G =GT "#BC B $B &'E (E 
!&Q" s(>?	%:#%: 	%: %:P 45*
*
*
 6*
Z !",.S ,. #,.^j
 j
S j
SW j
ZLt Lc Ld L` !"Y  # 29+2F 	T 	C 	 	 (/(< #   )*7>{7K &3 & & +& 29+2F ! ,/   F '.{'; 	 	 	 -.;B;;O -s -S - /-  !+2;+? 	S 	 "	 =>)0)=66#&6 ?6 &7KL ;'T$TT MTn *;OP ;' (   Q F *+ ,D #$)0)= 
3 
 %
 #$3:;3G'"'-0' %'$ 	!&t9P!Q
#
 
, ! !H  K !K^   :    %%%   j r6   