
    iS?                     T   d Z ddlZddlZddlZddlZddlZddlZddlmZmZm	Z	 ddl
mZ ej                  j                  dej                  j                  ej                  j                  e                   ddlmZ  ed        ej&                  dd      Z ej&                  d	d
      Z ej&                  dd      Z ej&                  dd      j/                  d      d   ZddddddddddddZddddd d!d"d#d$d%d&d&d'd(d)Zd*Z G d+ d,      Zd- Zd. Zd/ Zd0 Z e!d1k(  r e         yy)2u  
Prop Line Snapshot Worker — Captures genuine closing line data for CLV analysis.

PROBLEM: PPL rows are created via createMany({skipDuplicates:true}) with a unique key
         that includes oddsAmerican. Each odds change creates a NEW row, but snapshotAt
         is rarely set and hoursToGame is never set. PropLineHistory had fake timestamps.

SOLUTION: Every 2 hours, query SGO API for upcoming events with current player prop
          odds, and write genuine snapshots to PropLineHistory with actual hoursToGame.
          This gives us a real timeline of odds movement for CLV calculation.

RUN: PM2 process (auto-restart, never goes offline)
    N)datetimetimezone	timedelta)defaultdict)load_dotenvz/var/www/html/eventheodds/.envSPORTSGAMEODDS_API_KEY 47d6ce020d896ece307a284e8c78ff7fSPORTSGAMEODDS_BASE_URLz!https://api.sportsgameodds.com/v2SPORTSGAMEODDS_HEADERz	x-api-keySPORTS_DATABASE_URLzSpostgresql://eventheodds:eventheodds_dev_password@127.0.0.1:5433/eventheodds_sports?NBANHLMLBNCAABEPL
BUNDESLIGALA_LIGA
IT_SERIE_A
FR_LIGUE_1UEFA_CHAMPIONS_LEAGUEMLS)nbanhlmlbncaabepl
bundesligala_ligaserie_aligue_1champions_leaguemlspointsreboundsassiststhreePointersMadestealsblocks	turnoverspoints+rebounds+assistsfantasyScoregoalsshots_onGoalshotssaves)r$   r%   r&   r'   r(   r)   r*   r+   r,   r-   r.   shots_on_goalr/   r0   i   c                       e Zd ZdZd ZddZy)	SGOClientz.Lightweight SGO API client for prop snapshots.c                     t        j                         | _        | j                  j                  j	                  t
        t        i       y )N)requestsSessionsessionheadersupdateHEADER_NAMEAPI_KEY)selfs    9/var/www/html/eventheodds/scripts/prop_snapshot_worker.py__init__zSGOClient.__init__M   s/    '')##['$:;    c                 ^   t          d}||ddd}	 | j                  j                  ||d      }|j                  dk(  rt	        d| d	d
       g S |j                          |j                         }|j                  dg       S # t        $ r}t	        d| d| d
       g cY d}~S d}~ww xY w)z+Get upcoming events with odds for a league.z/eventstruefalse)leagueIDlimitoddsAvailablestarted   )paramstimeouti  z  SGO API 400 for u    — invalid leagueID?Tflushdataz  SGO API error for z: N)BASE_URLr7   getstatus_codeprintraise_for_statusjson	Exception)r<   	league_idrD   urlrH   resprL   es           r=   get_upcoming_eventszSGOClient.get_upcoming_eventsQ   s    
'"!#	

	<<##C#CD3&*9+5KLTXY	!!#99;D88FB'' 	(2aS9FI	s#   ?B 1B 	B,B'!B,'B,N)2   )__name__
__module____qualname____doc__r>   rX    r?   r=   r3   r3   J   s    8<r?   r3   c           	      @   | j                  di       }|sg S g }|j                         D ]2  \  }}|j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }	|dk7  s
|d	k7  s|	d
k7  rq|dv rvt        j                  |      }
|
s|j                  d      }|j                  d      }|j                  d      xs |j                  d      }d}|r	 t        t	        |            }||r	 t        t	        |            }d}|r	 t	        |      }|||j                  ||
|||d       5 |S # t
        t        f$ r Y \w xY w# t
        t        f$ r Y Xw xY w# t
        t        f$ r Y ]w xY w)zExtract player prop odds from an SGO event.

    Returns list of dicts: {playerExternalId, propType, lineValue, oddsAmerican, oddId}
    Only extracts over-side game-level player props.
    oddsstatID statEntityIDperiodID	betTypeIDsideIDgameouover)homeawayallrb   bookOddsfairOddsbookOverUnderfairOverUnderN)playerExternalIdpropType	lineValueoddsAmericanoddId)rN   items
PROP_STATSintfloat
ValueError	TypeErrorappend)eventr`   propsodd_idodd_datastat_id	entity_id	period_idbet_typeside_id	prop_typebook_odds_strfair_odds_strline_value_strodds_american
line_values                   r=   extract_player_propsr   g   s    99VR D	E JJL 5,,x,LL4	LLR0	<<R0,,x, (d"2g6G 33 NN7+	 !Z0 Z0!o6W(,,:W #E-$8 9  ] #E-$8 9 
">2
  J$6 )!#)
 	_5n L5 	* 
 	*  	* s6   E!E64F!E32E36FFFFc                    i }|D cg c]  }|d   	 }}|rwdj                  dgt        |      z        }| j                  d| d|D cg c]  }d| 	 c}       | j                         D ]  \  }}	}
|j	                  dd      }|	|
f||<   ! |D cg c]  }|d   |vs| }}|r|D ]
  }|d   }|j                  di       j                  d	d      }|j                  d
i       }|j                  di       j                  di       }|j                  dd      xs& |j                  dd      xs |j                  dd      }|r|s	 t        j                  |j	                  dd            }|j                  d      }| j                  d||||f       | j                         }|s|d   |d   f||<    |S c c}w c c}w c c}w # t        t        f$ r Y 1w xY w)u   Map SGO eventIDs to SportsGame rows.

    Strategy 1: Direct lookup via externalGameId = 'sgo:{eventID}'
    Strategy 2: Fallback — match by homeTeam + gameDate (±1 day)
    Returns: {sgo_event_id: (sg_id, game_date)}
    eventID,z%szz
            SELECT "externalGameId", id, "gameDate"
            FROM "SportsGame"
            WHERE "externalGameId" IN (z
)
        zsgo:rb   statusstartsAtteamsrj   nameslongmediumshortZz+00:00z%Y-%m-%da  
                SELECT id, "gameDate"
                FROM "SportsGame"
                WHERE league = %s
                  AND LOWER("homeTeam") = LOWER(%s)
                  AND "gameDate"::date BETWEEN %s::date - 1 AND %s::date + 1
                LIMIT 1
            r      )joinlenexecutefetchallreplacerN   r   fromisoformatstrftimerz   r{   fetchone)cureventscanon_leaguesg_mapevtsgo_event_idsplaceholderseidext_idsg_id	game_datesgo_eid	unmatchedr}   	starts_atr   
home_names	home_teamgame_dtgame_date_strrows                        r=   build_game_mapr      s!    F 066S^6M6xx]); ;< ( )5~ 6	 '44sSEl4		6
 ), 	1$FE9nnVR0G$i0F7O	1
 !'G#i.*FGIG 	/E	"C		(B/33JCIIIgr*E62.227B?J"vr2qjnnXr6RqV`VdVdelnpVqII"001B1B31QR ' 0 0 <
 KK  	=-HJ ,,.C"1vs1v.s9	/< M[ 7 5 H  	* s)   F:	F?
GG	6G		GGc                    t        j                  t        j                        }t	        dd d       t	        d|j                  d       d       t	        d d       t        j                  t              }d}d}d}d}	 |j                         }t        j                         D ]  \  }}	| j                  |	      }
|
st	        d| d	d       ,t        ||
|      }t        |      }t        |
      |z
  }d}d}d}d}|
D ]Q  }|d
   }|j                  |      }|s|\  }}d}|rX|j                    |j#                  t        j                        n|}t%        dt'        ||z
  j)                         dz  d            }t+        |      }|s|D ]  }|d   }|d   }|d   }|d   }|j-                  d|t/        |      ||f       |j1                         }|r|d   |k(  r|dz  }V|du } |duxr |d   |k7  }!|r|d   nd}"|j-                  d|t/        |      |||||||"|"r||"z
  nd| f       |dz  }| r|dz  }|!s|dz  } |j3                          T ||z  }||z  }||z  }||z  }t	        d| dt        |
       d| d| d| d| d| d| dd        |j-                  d       |j4                  }#|j3                          |#dkD  rt	        d|# d d       |j7                          |j7                          t	        d"| d| d| d| d#	d       |S # t8        $ r>}$|j;                          t	        d!|$ d       t=        j>                          Y d}$~$nd}$~$ww xY w# |j7                          w xY w)$z/Run one full snapshot cycle across all leagues.
A=================================================================TrJ   u   PROP SNAPSHOT CYCLE — z%Y-%m-%d %H:%M:%S UTCr   z  [z] No upcoming eventsr   N)tzinfo     rq   rr   rs   rt   a  
                        SELECT "oddsAmerican"
                        FROM "PropLineHistory"
                        WHERE "playerExternalId" = %s
                          AND "gameId" = %s
                          AND "propType" = %s
                          AND "lineValue" = %s
                        ORDER BY "snapshotAt" DESC
                        LIMIT 1
                    r   a  
                        INSERT INTO "PropLineHistory"
                        (league, "gameId", "playerExternalId", "propType",
                         "snapshotAt", "hoursToGame", "lineValue", "oddsAmerican",
                         "previousLine", "lineChange", "isOpening", "createdAt")
                        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW())
                    z] z	 events, z	 mapped (u    unmatched) → z snapshots (z new, z
 changed, z unchanged)a  
            UPDATE "PlayerPropLine" ppl
            SET "isClosingLine" = TRUE
            FROM (
                SELECT DISTINCT ON (plh."playerExternalId", plh."gameId", plh."propType", plh."lineValue")
                    plh."playerExternalId", plh."gameId"::bigint AS game_id,
                    plh."propType", plh."lineValue"
                FROM "PropLineHistory" plh
                JOIN "SportsGame" sg ON plh."gameId" = sg.id::text
                WHERE sg."gameDate" BETWEEN NOW() - INTERVAL '3 hours' AND NOW()
                ORDER BY plh."playerExternalId", plh."gameId", plh."propType", plh."lineValue",
                         plh."hoursToGame" ASC NULLS LAST
            ) closing
            WHERE ppl."playerExternalId" = closing."playerExternalId"
              AND ppl."gameId" = closing.game_id
              AND ppl."propType" = closing."propType"
              AND ppl."lineValue" = closing."lineValue"
              AND ppl."isClosingLine" IS NOT TRUE
        z	  Marked z PPL rows as isClosingLinez  ERROR in snapshot cycle: z

  TOTAL: z unchanged/skipped)) r   nowr   utcrP   r   psycopg2connectDB_URLcursorLEAGUESrv   rX   r   r   rN   r   r   maxroundtotal_secondsr   r   strr   commitrowcountcloserS   rollback	traceback	print_exc)%clientr   conntotal_snapshots	total_newtotal_changedtotal_skippedr   r   sgo_league_idr   r   matchedr   
league_newleague_changedleague_skippedleague_totalr}   r   mappingr   r   hours_to_gamer   r~   proppidptlvr`   lastis_new
is_changedprevious_oddsclosing_markedrW   s%                                        r=   snapshot_cycler      s!   
,,x||
$C	Bvh-t$	$S\\2I%J$K
LTXY	VHT"F#DOIMMAkkm+2==? \	'L-//>FL>)=>dK $C>F&kGFg-IJNNL CI& **S/#* y !%HQHXHXH`i//x||/DfoG$'5'C-1N1N1PSW1WYZ+[$\M,U3! .,D12Cj)Bk*B/D KK 	! s5z2r2	4 <<>DQ4&!+ !T\F!%T!1!Ed1goJ/3DGM KK ! %c%j#r]B%2?-T !A%L"a
!&!+].,b GCJ |+O#I^+M^+MC~RF}IgY G[ 0 ? \'7z.AQQ\^ s\	~ 	  	& AIn--GHPTU		 	

	K(YKvO:m_4GIPTV  +A3/t<
 	

s2   F:L >B3L 	M$&4MM' M$$M' 'M9c                     t        dd       t        dd       t        dt         dt        dz  dd	d       t        d
dj                  t        j	                                d       t        dt
        dd  dd       t        dd       t               } 	 | j                  j                  t         dd      }|j                         }t        d|j                  dd       d       	 t        j                  t
              }|j                         }|j                  d       |j!                         d   }t        d|ddd       |j                  d       |j                  d       |j                  d       |j                  d       |j#                          |j%                          t        d d       d}	 |d"z  }	 t+        |       }t        d#| d$t        dz  dd%d       t1        j2                  t               G# t        $ r}t        d| d       Y d}~:d}~ww xY w# t        $ r/}t        d!| d       t'        j(                  d"       Y d}~d}~ww xY w# t        $ rJ}t        d#| d&| d       t-        j.                          t        d't        dz  dd(d       Y d}~d}~ww xY w))u+   Main loop — runs forever as a PM2 worker.r   TrJ   zPROP SNAPSHOT WORKER STARTINGz
Interval: zs (r   z.1fzh)z	Leagues: z, zDB: NrY   z...z/account/usage
   )rI   zSGO API: credits remaining = creditsRemainingr   zWARNING: SGO API check failed: z&SELECT COUNT(*) FROM "PropLineHistory"r   zPropLineHistory: r   z existing rowszCREATE INDEX IF NOT EXISTS "PropLineHistory_player_game"
                       ON "PropLineHistory" ("playerExternalId", "gameId")zpCREATE INDEX IF NOT EXISTS "PropLineHistory_snapshot"
                       ON "PropLineHistory" ("snapshotAt")zwCREATE INDEX IF NOT EXISTS "PropLineHistory_league_game"
                       ON "PropLineHistory" (league, "gameId")zCREATE INDEX IF NOT EXISTS "PropLineHistory_clv_lookup"
                       ON "PropLineHistory" ("playerExternalId", "gameId", "propType", "lineValue", "snapshotAt" DESC)zDB: OK, indexes verifiedzDB connection failed: r   z
Cycle z complete. Next in zh. Sleeping...z	 FAILED: zWill retry in zh...)rP   SNAPSHOT_INTERVALr   r   keysr   r3   r7   rN   rM   rR   rS   r   r   r   r   r   r   r   sysexitr   r   r   timesleep)r   rV   usagerW   r   r   countcycles           r=   mainr   {  s   	($	
)6	J()->t-CC,H
KSWX	Idii/0
1>	DS
!.	($[FA~~!!XJn"=r!J		-eii8JC.P-QRZ^_
'kkm<=q!!%.9F 	 N 	O > 	? B 	C z 	{

(5
 E

	Q"6*EHUG#67H7Mc6RR`aimn 	

$% 9  A/s34@@A.  &qc*$7  	QHUG9QC0=!N#4T#9#">dC4PP	QsK   AH 1CH2 
'I- 	H/H**H/2	I*;%I%%I*-	K 6A J;;K __main__)"r]   osr   r   r   r5   r   r   r   r   collectionsr   pathinsertdirnameabspath__file__dotenvr   getenvr;   rM   r:   splitr   r   rw   r   r3   r   r   r   r   rZ   r^   r?   r=   <module>r      sx   
 
     2 2 # 277??277??8#<= >  , - ")),.P
Q299.0STbii/=	(*  
A  
G  
G  HK  
L  MN  
O
 /  ,8""#
$   :BJ7tRj6&r zF r?   