
    %i7              
       N   S r SSKrSSKJs  Jr  SSKrSSKrSSK	r	SSK
Jr  SSKr\R                  R                  SS5        \R                  R                  SS5        \R                  R                  SS	5        \R                  R                  S
S5        \R                  R                  SS5        \R                  R                  SS5        \R                  R                  SS5        \R                  R                  SS5        SSKJr  \" 5         SSKJr  SSKJr  SSKJrJrJrJrJrJrJr  \R:                  R<                  r\R@                  RC                  \R@                  RE                  \#5      S5      r$S\%S\&S-  4S jr'S\%S\&SS4S jr(S\%S\S\%4S jr)S2S \*S!\*4S" jjr+S3S#\*S$\*S\,\*   4S% jjr-S#\*S\.\*\*4   4S& jr/S#\*S\.\*\*4   4S' jr0S(\*S)\,\*   S*\.\*\*4   S+\.\*\*4   S#\*4
S, jr1S(\*S)\,\*   S*\.\*\*4   S+\.\*\*4   4S- jr2\Rf                  S. 5       r4S/ r5S0 r6S1 r7g)4a=  Performance regression tests for race predictions.

Usage:
    # Run benchmarks and save baseline:
    pytest tests/test_performance_predictions.py --benchmark-only --benchmark-json=tests/benchmark_baseline.json

    # Run benchmarks and compare against saved baseline:
    pytest tests/test_performance_predictions.py --benchmark-only --benchmark-compare=tests/benchmark_baseline.json

    # Run tests without benchmarking:
    pytest tests/test_performance_predictions.py -m "not slow"

    # Run including benchmarks:
    pytest tests/test_performance_predictions.py -v
    N)dateELO_SCALE_Cz400.0
ELO_K_BASEz24.0INITIAL_RATINGz1500.0ENABLE_DRIVERtrueENABLE_TRAINERENABLE_ADJUSTMENTSfalse	ENABLE_RDTAB_MOCK_MODE)reload_settings)PredictionEngine)
EntityType)DriverRepositoryHorseRepositoryMeetingRepositoryRaceRepositoryRatingSnapshotRepositoryStarterRepositoryTrainerRepositoryzbenchmark_baseline.jsonkeyreturnc                    [         R                  R                  [        5      (       d  g [	        [        5       n[
        R                  " U5      nSSS5        WR                  S/ 5      nU H>  nUR                  SS5      nXP:X  d  M  UR                  S0 5      R                  SS5      s  $    g! , (       d  f       Ne= f! [
        R                  [        [        4 a     gf = f)z<Load a benchmark baseline value from JSON file if it exists.N
benchmarksname statsmean)ospathexistsBENCHMARK_BASELINE_FILEopenjsonloadgetJSONDecodeErrorKeyError	TypeError)r   fdatar   br   s         G/root/tipsharks/tipsharks-elo-api/tests/test_performance_predictions.py_load_baseliner/   =   s    77>>122	)*a99Q<D +XXlB/
A55$D{uuWb)--fd;;   +*   (I6 s4   C B04C 	#C -C 0
B>:C C#"C#	test_namecurrent_meanc                    [        U 5      nUb  US:  a  X-  nSoCU:  oU(       d  [        R                  " SU4SX445      S[        R                  " 5       ;   d  [        R
                  " U5      (       a  [        R                  " U5      OS[        R                  " U5      S.-  n[        R                  " SU  S	US
-  S SUS
-  S SUS-
  S-  S S3	5      S-   SU0-  n[        [        R                  " U5      5      eS=pTggg)zLCheck that the current benchmark result is within 10% of the saved baseline.Nr   g?<z%(py0)s < %(py3)sslowdown_ratiopy0py3z$Performance regression detected for :   z.2fzms vs baseline zms (   d   .1fz% slower, limit is 10%)
>assert %(py5)spy5)
r/   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgAssertionError_format_explanation)r0   r1   baseliner6   @py_assert2@py_assert1@py_format4@py_format6s           r.   _check_benchmark_baselinerO   N   s   i(H1%0 $ 	
$ 	
 	
 		
~ 	
 	
 
6		
 	
 		
~ 	
 	
 
			
~ 	
 	
 
			
 	
 	
 		
29+Rd"3'x$s6K L!#s*3//FH	
 	
 	
 		
 	
 !-    
meeting_idmeeting_datevenuec                 V    [         R                  " U UUR                  5       USS.5      $ )NH)meetingr   r   category)r   upsert	isoformat)sessionrQ   rR   rS   s       r.   _create_test_meetingr[   ]   s2    ##! **,		
 rP   race_numberdistancec                 l    SUS-  -   n[         R                  " U UR                  UUSSSUS S3S.5      $ )	N      mobilepacez2025-05-06T02dz:00:00+12:00)r\   r]   
start_typegaitadvertised_start_string)r   rX   id)rZ   rV   r\   r]   hours        r.   _create_test_raceri   i   sM    b !D  

& ")4T#Jl'K	

 
rP   countstart_idc                     / n[        U5       H2  nX$-   n[        R                  " XSU 35        UR                  U5        M4     U$ )N
PerfHorse_)ranger   rX   append)rZ   rj   rk   	horse_idsihorse_ids         r.   _create_test_horsesrs   x   sI    I5\<wJqc2BC"  rP   c                 ~    0 n[        U5       H+  n[        R                  " U SU 35      nUR                  X#'   M-     U$ )z<Create test drivers and return a dict of index -> driver_id.PerfDriver_)rn   r   rX   rg   )rZ   rj   
driver_idsrq   drivers        r.   _create_test_driversrx      sA    J5\!((Ks2CD		
  rP   c                 ~    0 n[        U5       H+  n[        R                  " U SU 35      nUR                  X#'   M-     U$ )z>Create test trainers and return a dict of index -> trainer_id.PerfTrainer_)rn   r   rX   rg   )rZ   rj   trainer_idsrq   trainers        r.   _create_test_trainersr}      sA    K5\#**7l1#4FG   rP   race_idrp   rv   r{   c                    SSK nUR                  U[        U[        U5      5      5      n[	        [        U5      5       Vs/ s H  oU[        U5      -     PM     n	n[	        [        U5      5       Vs/ s H  oU[        U5      -     PM     n
n[        [        XyU
SS95       HE  u  nu  pn[        R                  " U USUS-
   3USUS-
   3USU 3USU 3US	-   US	-   S
.	US	-   S9  MG     gs  snf s  snf )zFCreate test starters for a race with horse/driver/trainer assignments.r   NFstrictrm   '  ru   rz   r<   	r   rr   
horse_name	driver_iddriver_name
trainer_idtrainer_namerunner_numberbarrierplacing)	randomsampleminlenrn   	enumeratezipr   rX   )rZ   r~   rp   rv   r{   rj   r   selected_horsesrq   selected_driversselected_trainersrr   d_idt_ids                 r.   _create_test_startersr      s,    mmIs5#i./IJO16s?7K1L1LA1s:&'1L   49_9M3N3NaAK(()3N   &/O/@O&!!HD 	  $X%5$67$ *8e+;*<=!!,QC0"".qc 2!"Qq5
 E	
&s    C,2C1c                 n   U H-  n[         R                  " U [        R                  UUSSSS0S9  M/     UR	                  5        H-  n[         R                  " U [        R
                  UUSSSS0S9  M/     UR	                  5        H-  n[         R                  " U [        R                  UUSSSS0S9  M/     g)z1Create initial rating snapshots for all entities.g     p@g     u@
race_count   )entity_type	entity_idas_of_race_idratingrdmetaN)r   rX   r   HORSEvaluesDRIVERTRAINER)rZ   r~   rp   rv   r{   rr   r   r   s           r.   _create_rating_snapshotsr      s      ''"((!"	
  !!# ''"))!"	
 $ ""$ ''"**!"	
 %rP   c              #     #    SSK nSn[        U S[        SSS5      S5      n[        XSS	5      nU R	                  5         [        XS
S9n[        X5      n[        X5      nU R	                  5         [        XR                  XVXr5        U R	                  5         [        XR                  XVU5        U R	                  5         [        U S[        SSS5      S5      n[        XSS	5      n	U R	                  5         UR                  U[        U[        U5      5      5      n
[        [        U
5      5       Vs/ s H  oU[        U5      -     PM     nn[        [        U
5      5       Vs/ s H  oU[        U5      -     PM     nn[        [!        XUSS95       HL  u  nu  pn["        R$                  " U U	R                  SUS
-
   3USUS
-
   3USU 3USU 3US-   US-   S.	SS9  MN     U R	                  5         U	R                  U4v   gs  snf s  snf 7f)aE  Create 1 race with 10 starters and rating history for prediction benchmarks.

Uses the same db_session as the test. All changes are rolled back
after the test via the db_session fixture.

Sets up:
- A previous meeting/race with rating snapshots (so the engine can load them)
- A current meeting/race with starters to predict
r   N
   perf_pred_previ     r<   Auckland  i N  )rk   perf_pred_currr   Fr   rm   ru   rz   r   r   )r   r[   r   ri   flushrs   rx   r}   r   rg   r   r   r   r   rn   r   r   r   rX   )
db_sessionr   num_startersprev_meeting	prev_racerp   rv   r{   curr_meeting	curr_racer   rq   r   r   rr   r   r   s                    r.   fixture_prediction_datar      s=     L ($d4A&6
L "*AtDI $JuMI%j?J'
AK LL)  LL)  ($d4A&6
L "*AtDI mmIs<Y/PQO16s?7K1L1LA1s:&'1L   49_9M3N3NaAK(()3N   &/O/@O&!!HD 	  LL$X%5$67$ *8e+;*<=!!,QC0"".qc 2!"Qq5
 	
&( 
,,	
!!;s   D,H.HH H:BHc                 
   Uu  p4SSK Jn  SSKJnJn  UR                  U5      R                  U" UR                  5      5      R                  UR                  U:H  5      R                  5       nSoU	Lo(       d  [        R                  " SU
4SX45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U	5      S.-  n[        R"                  " S	5      S
-   SU0-  n[%        [        R&                  " U5      5      eS=pUR                  U5      R                  UR(                  U:H  5      R+                  5       n[-        U5      n	SoU:  o(       Gd0  [        R                  " SU4SX45      S[        R                  " 5       ;   d  [        R                  " [,        5      (       a  [        R                   " [,        5      OSS[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U	5      [        R                   " U5      S.-  n[        R"                  " S[-        U5       35      S-   SU0-  n[%        [        R&                  " U5      5      eS=n	=p[/        U5      nU " UR0                  X5      nSn	UU	Lo(       d  [        R                  " SU
4SUU	45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U	5      S.-  nSSU0-  n[%        [        R&                  " U5      5      eS=pUR2                  n	[-        U	5      nSnUU:  nU(       Gd:  [        R                  " SU4SUU45      S[        R                  " 5       ;   d  [        R                  " [,        5      (       a  [        R                   " [,        5      OSS[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U	5      [        R                   " U5      [        R                   " U5      S.-  n[        R"                  " S5      S-   SU0-  n[%        [        R&                  " U5      5      eS=n	=n=nn[5        U S5      (       a:  U R6                  b,  U R6                  R9                  S5      nUb  [;        SU5        gggg) a   Benchmark PredictionEngine.predict_race() on a 10-starter field.

Uses pytest-benchmark to measure execution time across multiple runs.

After the benchmark runs, compares the result against a saved
baseline if one exists in tests/benchmark_baseline.json.
r   
joinedloadRaceStarterNis notz%(py0)s is not %(py3)sracer7   zRace not found in test datar?   r@   r_   )>=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)sr   starters)r8   py1r9   py6zExpected >=8 starters, got z
>assert %(py8)spy8resultassert %(py5)s>zP%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.predictions
})
} > %(py8)sr8   r   r9   r@   r   zShould generate predictionsz
>assert %(py10)spy10r   r   test_prediction_performance)sqlalchemy.ormr   packages.core.storage.modelsr   r   queryoptionsrV   filterrg   firstrA   rB   rC   rD   rE   rF   rG   rH   rI   r~   allr   r   predict_racepredictionshasattrr   r'   rO   )	benchmarkr   r   r~   _r   r   r   r   rK   rL   rM   rN   r   @py_assert5@py_assert4@py_format7@py_format9enginer   @py_assert7@py_assert6@py_format11mean_vals                           r.   r   r   >  s    )JG *: 		DLL)	*	7"	#		 	 :t::::4::::::4::::4:::t:::::::::::(//70JKOOQHx=LALALLLL=LLLLLL3LLLL3LLLLLLxLLLLxLLL=LLLALLLL!<S]OLLLLLLLLj)F v**D;F6666!!E3!"EQE"Q&EEEE"QEEEEEE3EEEE3EEEEEEvEEEEvEEE!EEE"EEEQEEEE(EEEEEEEE y'""y'B??&&v.%&CXN   (C"rP   c                    Uu  p#SSK Jn  SSKJnJn  U R                  U5      R                  U" UR                  5      5      R                  UR                  U:H  5      R                  5       nSoULo(       d  [        R                  " SU	4SXx45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U5      S.-  n
S	S
U
0-  n[#        [        R$                  " U5      5      eS=pU R                  U5      R                  UR&                  U:H  5      R)                  5       n[+        U 5      n[,        R.                  " 5       nUR1                  X|5      n[,        R.                  " 5       U-
  nSoULo(       d  [        R                  " SU	4SX45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U5      S.-  n
S	S
U
0-  n[#        [        R$                  " U5      5      eS=pUR2                  n[5        U5      nSnUU:  nU(       Gd#  [        R                  " SU4SUU45      S[        R                  " 5       ;   d  [        R                  " [4        5      (       a  [        R                   " [4        5      OSS[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U5      [        R                   " U5      [        R                   " U5      S.-  nSSU0-  n[#        [        R$                  " U5      5      eS=n=n=nnSnUU:  o(       d  [        R                  " SU	4SUU45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U5      S.-  n
[        R6                  " SUS-  S S35      S-   S
U
0-  n[#        [        R$                  " U5      5      eS=pg)z5Assert single race prediction completes within 100ms.r   r   r   Nr   r   r   r7   r   r@   r   r   r   r   r   zassert %(py10)sr   皙?r3   r5   elapsedzPrediction took r;   r>   ms, expected < 200msr?   )r   r   r   r   r   r   r   rV   r   rg   r   rA   rB   rC   rD   rE   rF   rH   rI   r~   r   r   timeperf_counterr   r   r   rG   )r   r   r~   r   r   r   r   r   rK   rL   rM   rN   r   r   startr   r   r   r   r   r   r   s                         r.   "test_prediction_baseline_thresholdr   g  s   (JG): 		DLL)	*	7"	#		 	 t444t(//70JKOOQHj)FE  0F!E)G666!!&3!"&Q&"Q&&&&&"Q&&&&&&3&&&&3&&&&&&v&&&&v&&&!&&&"&&&Q&&&&&&&V7T>VVVV7TVVVVVV7VVVV7VVVTVVVV-gnS-AAUVVVVVVVrP   c                   ^  Uu  p#SSK Jn  SSKJnJn  U 4S jnXuR
                  U'    U" U5      n[        R                  " 5       n	UR                  SU S35      n
[        R                  " 5       U	-
  nU
R                  nSnX:H  o(       d  [        R                  " SU4S	X45      S
[        R                  " 5       ;   d  [        R                  " U
5      (       a  [        R                  " U
5      OS
[        R                  " U5      [        R                  " U5      S.-  n[        R                   " SU
R                   SU
R"                  SS  35      S-   SU0-  n[%        [        R&                  " U5      5      eS=n=pU
R)                  5       nSnUU;   nU(       d  [        R                  " SU4SUU45      [        R                  " U5      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                  " U5      OSS.-  nSSU0-  n[%        [        R&                  " U5      5      eS=nnUS   n[+        U5      nSnUU:  nU(       d  [        R                  " SU4SUU45      S[        R                  " 5       ;   d  [        R                  " [*        5      (       a  [        R                  " [*        5      OS[        R                  " U5      [        R                  " U5      [        R                  " U5      S.-  nSSU0-  n[%        [        R&                  " U5      5      eS=n=n=nnSnUU:  o(       d  [        R                  " SU4S UU45      S![        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                  " U5      OS![        R                  " U5      S".-  n[        R                   " S#US$-  S% S&35      S'-   SU0-  n[%        [        R&                  " U5      5      eS=nnUR
                  R-                  5         g! UR
                  R-                  5         f = f)(zTest the /v1/races/{race_id}/predictions API endpoint response time.

Uses FastAPI TestClient to call the full API stack including
serialization. Asserts response time < 200ms.
r   )
TestClient)appget_dbc               3      >#    T v   g 7f)N )r   s   r.   override_get_db5test_prediction_api_endpoint.<locals>.override_get_db  s     s   	z
/v1/races/z/predictions   )==)z3%(py2)s
{%(py2)s = %(py0)s.status_code
} == %(py5)sresponse)r8   py2r@   zExpected 200, got r:   Nz
>assert %(py7)spy7r   )in)z%(py1)s in %(py3)sr,   )r   r9   r   r@   r   )z/%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} > %(py7)sr   )r8   r   py4r   zassert %(py9)spy9r   r3   r5   r   r7   zAPI prediction endpoint took r;   r>   r   r?   )fastapi.testclientr   apps.backend.api.mainr   r   dependency_overridesr   r   r'   status_coderA   rB   rC   rD   rE   rF   rG   textrH   rI   r%   r   clear)r   r   r~   r   r   r   r   r   clientr   r   r   rL   r   @py_assert3rN   @py_format8r,   @py_assert0rK   rM   r   r   @py_format10s   `                       r.   test_prediction_api_endpointr    s    )JG .1 (7V$)C!!#::
7)<@A##%-   	N$'	N '	N 	N<M<M	N 	N 	NGMv	N 	N5M5M	N	N 	NDMI	N	N 	NDMI	N 	N 	NDMI	N$'	N 	N<M<M	N 4 45Rds8K7LM	N 	N 	N:M:M	N 	N }}$}$$$$$}$$$}$$$$$$$$$$$$$$$$$&+s&'+!+'!+++++'!++++++s++++s+++&+++'+++!+++++++ 	TdN	T 	TBSBS	Td	T 	TMSV	T 	T;S;S	T	T 	TJS)	T	T 	TJS)	T	T 	TBSBS	T*7T>#*>>RS	T 	T 	T@S@S	T 	T
 	  &&(  &&(s   O1P5 5Q)r   )r   )8__doc__builtinsrC   _pytest.assertion.rewrite	assertionrewriterA   r%   r    r   datetimer   pytestenviron
setdefaultpackages.core.common.settingsr   !packages.core.ratings.predictionsr   r   r   "packages.core.storage.repositoriesr   r   r   r   r   r   r   markslow
pytestmarkr!   joindirname__file__r#   strfloatr/   rO   r[   intri   listrs   dictrx   r}   r   r   fixturer   r   r   r  r   rP   r.   <module>r     s      	    

  mW - 

  lF + 

  & 1 

  ov . 

  & / 

  *G 4 

  k7 + 

  ov . :   > 3   [[
'',,GGOOH8   "	
 	
E 	
d 	
	c 	 	c 	S C  s tCy  c3h # $sCx. %
%
 Cy%
 S#X	%

 c3h%
 %
P'
'
 Cy'
 S#X	'

 c3h'
Z M" M"j&ORW:')rP   