
    i                     V   S r SSKrSSKrSSKrSSKrSSKrSSKrSSKrSSKrSSK	J	r	J
r
Jr  SSKJr  \R                  " S5      r\R                   " S\" \" \5      R'                  5       R(                  R(                  S-  5      5      r\" \" \5      R'                  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SrSrSrSrSrSr Sr!Sr"\R                   " S\" \RF                  " 5       S-  S-  5      5      r$\" \RF                  " 5       S-  5      \" \RF                  " 5       S-  S-  S-  S-  5      \" \" \5      R(                  S-  5      /r% " S S5      r&g)u  
Dashboard Scheduler — automated data refreshes, proactive WhatsApp nudges,
weekly reports, health data ingestion (Oura API + Health Connect fallback),
Gmail briefing integration, and 'on this day' insights.

Runs as a background thread inside the Flask app.

Health data pipeline:
  PRIMARY: Oura API v2 → sleep, steps, exercise, HRV, readiness (direct API pull)
  FALLBACK: Google Drive API → Health Connect zip → weight, body fat, blood pressure
  FALLBACK2: Filesystem watcher on local Google Drive sync + ~/clawd/health-imports/
    N)datetimedate	timedelta)Path	schedulerDASHBOARD_DATA_PATHzdashboard-data.jsonmemoryzchatgpt-kb.dbTWILIO_ACCOUNT_SID TWILIO_AUTH_TOKENTWILIO_PHONE_NUMBERBILL_PHONE_NUMBER            	     i,  HEALTH_CONNECT_DIRzGoogle DrivezMy DriveLibraryCloudStoragezGoogleDrive-syrros@gmail.comzhealth-importsc                       \ rS rSrSrS rS rS rS rS r	S r
S	 rS
 rS rS rS rS rS rS rS r\rS rS rS rS rS rS rS rS rS rS rS rS r S r!S r"S  r#S! r$S" r%S#r&g$)%DashboardScheduler?   zCBackground scheduler that runs data refreshes and proactive nudges.c                     Xl         SU l        S U l        SU l        S U l        S U l        S U l        S U l        SU l        [        5       U l
        S U l        S U l        S U l        0 U l        S U l        S U l        SU l        g )NFr   )dmrunningthread_last_refresh_last_morning_last_evening_last_weekly_last_briefing_update_last_health_checkset_ingested_files_gdrive_connector_gmail_briefing_gdrive_last_file_id_last_email_nudges_last_new_music_last_health_daily_last_pregame_rapid)selfdashboard_managers     0/Users/bsyrros/clawd/whatsapp-agent/scheduler.py__init__DashboardScheduler.__init__B   s    #!! %)""#"u!%#$(!"$#"&#$     c                     U R                   (       a  gSU l         [        R                  " U R                  SS9U l        U R                  R                  5         [        R                  S5        g)z+Start the scheduler in a background thread.NT)targetdaemonzScheduler started)r   	threadingThread	_run_loopr   startloggerinfor.   s    r0   r:   DashboardScheduler.startU   sH    <<&&dnnTJ'(r3   c                 <    SU l         [        R                  S5        g)zStop the scheduler.FzScheduler stoppedN)r   r;   r<   r=   s    r0   stopDashboardScheduler.stop^   s    '(r3   c                 
   [         R                  " S5        U R                  5         U R                  (       Gar   [        R
                  " 5       n[         R                   " 5       U R                  -
  [        :  a*  U R                  5         [         R                   " 5       U l        UR                  [        :X  an  UR                  [        :  aZ  UR                  [        S-   :  aC  U R                  UR                  5       :w  a%  U R                  5         UR                  5       U l        UR                  [         :X  an  UR                  ["        :  aZ  UR                  ["        S-   :  aC  U R$                  UR                  5       :w  a%  U R'                  5         UR                  5       U l        UR)                  5       [*        :X  ag  UR                  [,        :X  aS  UR                  S:  aC  U R.                  UR                  5       :w  a%  U R1                  5         UR                  5       U l        SUR                  s=::  a  S::  aW  O  OT[         R                   " 5       U R2                  -
  [4        :  a*  U R7                  5         [         R                   " 5       U l        UR                  S:X  aS  UR                  S:  aC  U R8                  UR                  5       :w  a%  U R;                  5         UR                  5       U l        / SQnUR                  U;   a  UR                  S:  ay  U R<                  R?                  UR                  5      UR                  5       :w  aB  U RA                  UR                  5        UR                  5       U R<                  UR                  '   UR)                  5       S:X  ac  UR                  S:X  aS  UR                  S:  aC  U RB                  UR                  5       :w  a%  U RE                  5         UR                  5       U l!        U RF                  UR                  :w  a!  U R                  5         UR                  U l#        [         R                   " 5       U RH                  -
  S	:  a   U RK                  U5        O [         R                  " S5        U R                  (       a  GMq  gg! [L         a"  n[N        RQ                  S
U 35         SnANSSnAff = f! [L         a!  n[N        RS                  SU 3SS9   SnANSnAff = f)u0   Main scheduler loop — checks every 30 seconds.
      r   r      r            r   i  z*Rapid pregame check error (non-critical): NzScheduler loop error: Texc_infor   )*timesleep_update_briefing_datar   r   nowr   DATA_REFRESH_INTERVAL_refresh_datahourMORNING_NUDGE_HOURminuteMORNING_NUDGE_MINr    r   _send_morning_nudgeEVENING_NUDGE_HOUREVENING_NUDGE_MINr!   _send_evening_nudgeweekdayWEEKLY_REPORT_DOWWEEKLY_REPORT_HOURr"   _send_weekly_reportr$   HEALTH_CHECK_INTERVAL_check_health_connectr,   _daily_health_grabr*   get_send_email_digestr+   _send_new_musicr#   r-   _maybe_rapid_pregame_refresh	Exceptionr;   debugerror)r.   rO   EMAIL_NUDGE_HOURSes       r0   r9   DashboardScheduler._run_loopc   s    	

2""$lllNJlln 99;!3!337LL&&()-D& HH 22JJ"33JJ!2Q!66&&#((*4,,.),D& HH 22JJ"33JJ!2Q!66&&#((*4,,.),D& KKM%66HH 22JJN%%3,,.(+
D% &Q&IIK$"9"99=RR..0.2iikD+ HHMJJN++sxxz9++-.1hhjD+ %0!HH 11JJN++//9SXXZG++CHH58;
D++CHH5 KKMQ&HHMJJN((CHHJ6((*+.88:D( --9..014D. 99;!9!99S@W99#> A JJrNc lllV % W'QRSQT%UVVW  J5aS9DIJs<   P2S -R, ,
S6SS SS 
T%TTc                 v    [         R                  S5        U R                  R                  5       nSnU R	                  5       nU(       aU  SU;  a  0 US'   X1S   S'   [
        R                  " 5       R                  5       US   S'   Sn[         R                  SU 35        [        R                  " 5       R                  5       nUR                  S5      U:w  a  XAS'   SnU R                  U5      nU(       a  XQS	'   Sn U R                  5       nUbM  U[
        R                  " 5       R                  5       S.US'   Sn[         R                  S[        U5       S35        U(       ai  SSKJn  U" [%        U R                  R&                  5      S-   SS9n	U	   U R                  R)                  U5        S
S
S
5        [         R                  S5        g
g
! [         a"  n[         R                  SU 35         S
nANS
nAff = f! , (       d  f       NT= f! [         a!  n[         R+                  SU 3SS9   S
nAg
S
nAff = f)z?Refresh live data in dashboard-data.json (crypto prices, etc.).z!Running scheduled data refresh...Fcrypto	cro_pricecro_updatedTzCRO price refreshed: $r   	nextEventNgamesupdatedbettingzBetting picks refreshed: z gamesz-Betting picks refresh failed (non-critical): r   )FileLockz.lockrC   )timeoutzDashboard data refreshedzData refresh failed: rJ   )r;   r<   r   read_fetch_cro_pricer   rO   	isoformatr   todayra   _get_next_event_fetch_betting_pickslenre   rf   filelockrt   str	file_path_writerg   )
r.   datachangedrm   	today_strupcomingpicksri   rt   locks
             r0   rQ    DashboardScheduler._refresh_data   s   1	EKK;<77<<>DG --/I4'%'DN.7X{+080H0H0JX}-4YK@A 

..0Ixx9,(V ++D1H$,[!
R113$!&#+<<>#;#;#='DO #GKK";CJ<v NO -DGG$5$5 6 @"MGGNN4( 67   RLQCPQQR T  	ELL04tLD	Es[   C9H <A G 6H G<.H 
G9G4/H 4G99H <
H
H 
H8H33H8c                     [         R                  " SSSS.SS9nUR                  S:X  a/  UR                  5       R                  S0 5      R                  S5      $  g	! [         a"  n[
        R                  SU 35         S	nAg	S	nAff = f)
zFetch CRO price from CoinGecko.z-https://api.coingecko.com/api/v3/simple/pricezcrypto-com-chainusd)idsvs_currenciesrC   )paramsru      zCRO price fetch failed: N)requestsra   status_codejsonre   r;   warning)r.   respri   s      r0   rw   #DashboardScheduler._fetch_cro_price   s    		;<<?1EJD
 3&yy{'92>BB5II '   	;NN5aS9::	;s   AA 
B&BBc           	      ,   [         R                  " 5       nUR                  5       nUR                  S/ 5       HY  nUR                  SS5      U:  d  M  US   US   UR                  SS5      UR                  SS5      UR                  SS5      S.s  $    g	)
zFind the next upcoming event.scheduler   r   titlerL   locationnote)r   r   rL   r   r   N)r   ry   rx   ra   )r.   r   ry   now_strevents        r0   rz   "DashboardScheduler._get_next_event  s    

//#XXj"-Eyy$/"7^!&M!IIfb1 %		*b 9!IIfb1  . r3   c                     U R                   c   SSKJn  U" 5       U l         U R                   SLa  U R                   $ S$ ! [         a)  n[        R                  SU 35        SU l          SnANKSnAff = f)z%Lazy-load the Google Drive connector.Nr   )get_connectorz&Google Drive connector not available: F)r'   gdrive_healthr   re   r;   rf   )r.   r   ri   s      r0   _get_gdrive_connector(DashboardScheduler._get_gdrive_connector  sr    !!)/7)6& *.)?)?u)Lt%%VRVV  /EaSIJ).&&/   > 
A1A,,A1c                     U R                   c   SSKJn  U" 5       U l         U R                   SLa  U R                   $ S$ ! [         a)  n[        R                  SU 35        SU l          SnANKSnAff = f)z$Lazy-load the Gmail briefing module.Nr   )get_gmail_briefingzGmail briefing not available: F)r(   gmail_briefingr   re   r;   rf   )r.   r   ri   s      r0   _get_gmail_briefing&DashboardScheduler._get_gmail_briefing  sr    '-='9';$ (,';';5'Ht##RdR  -=aSAB',$$-r   c                      U R                  5       nU(       a#  UR                  (       a  U R                  U5        gU R                  5         g! [         a!  n[
        R                  SU 3SS9   SnAgSnAff = f)u   
Watch for new Health Connect zip files.

Strategy:
  1. PRIMARY: Google Drive API — search for files, download, parse
  2. FALLBACK: Filesystem watcher on local sync folders
NzHealth Connect check failed: TrJ   )r   	available_check_health_connect_gdrive _check_health_connect_filesystemre   r;   rg   )r.   gdriveri   s      r0   r_   (DashboardScheduler._check_health_connect)  sj    	M//1F&**11&9 113 	MLL8<tLL	Ms   9A A 
A8A33A8c                     UR                  SS9nU(       d  gUS   nUS   nX@R                  :X  a  gUS   S:  a#  [        R                  SUS	    S
US    S35        g[        R	                  SUS	    35        UR                  XCS	   5      nU(       d&  [        R                  S5        U R                  5         gU R                  U5      nU(       aE  X@l        U R                  R                  U5        U R                  SU 35        U R                  5         UR                  SS9  g! [         a1  n[        R                  SU 3SS9  U R                  5          SnAgSnAff = f)z8Check Google Drive for new Health Connect files via API.   max_age_hoursNr   idsize  zSkipping small Drive file: name ( bytes)z"New Health Connect file in Drive: u.   Download failed — falling back to filesystemu5   Luke here — health data auto-imported from Drive!

r   max_age_daysz"Google Drive health check failed: TrJ   )search_health_connect_filesr)   r;   r   r<   download_filerg   r   _ingest_health_filer&   add_send_whatsapprN   cleanup_old_downloadsre   )r.   r   fileslatestfile_id
local_pathresultri   s           r0   r   /DashboardScheduler._check_health_connect_gdrive>  sg   (	466R6HE1XFTlG 333 f~
*!<VF^<LBvV\~N^^efgKK<VF^<LMN  --gf~FJMN557 --j9F,3)$$((4##&]^d]e$fg**, ((a(8 	4LL=aSADLQ1133	4s/   D( D( +D( AD( ;A,D( (
E#2'EE#c                 b   [         /[        -   nU GH  n[        R                  R	                  U5      (       d  M*  [        R                  R                  US5      [        R                  R                  US5      [        R                  R                  USS5      /nU GH	  n[        R                  " USS9 H  nXPR                  ;   a  M  [        R                  R                  U5      n[        R                  " 5       U-
  S-  nUS:  a  MV  [        R                  R                  U5      S:  a  M{  [        R                  S	U 35        U R                  U5      nU(       d  M  U R                  R                  U5        U R                  S
U 35        U R!                  5         M     GM     GM     g)z9Fallback: watch local filesystem for Health Connect zips.Health Connect*.ziphealth_connect*.zip**T	recursiver   r   r   z/New Health Connect file detected (filesystem): u*   Luke here — health data auto-imported!

N)r   HEALTH_CONNECT_ALT_DIRSospathisdirjoinglobr&   getmtimerL   getsizer;   r<   r   r   r   rN   )	r.   search_dirs
search_dirpatternspatternzip_pathmtime	age_hoursr   s	            r0   r   3DashboardScheduler._check_health_connect_filesystemj  sS   )*-DD%J77==,, Z)>?Z)>?Z/DEH $ $		'T BH#7#77 GG,,X6E!%u!4 <I 2~ wwx0:= KK"QRZQ[ \]!55h?Fv,,00:++.Z[aZb,cd224# !C $ &r3   c                      SSK Jn  U" XR                  5      $ ! [         a!  n[        R                  SU 3SS9   SnAgSnAff = f)z!Ingest a Health Connect zip file.r   )ingest_health_connectz!Health Connect ingestion failed: TrJ   N)health_connectr   r   re   r;   rg   )r.   r   r   ri   s       r0   r   &DashboardScheduler._ingest_health_file  sD    	<(77;; 	LL<QC@4LP	s    
AA  Ac                    [         R                  S5        / n SSKJn  U" U R                  5      nU(       a;  SUR                  5       ;  a'  UR                  U5        [         R                  S5        O[         R                  SU 35         U R                  5       nU(       a  UR                  U5        U(       aL  U R                  5         SR                  U5      nU R                  SU S35        [         R                  S5        g
[         R                  S5        U R                  S5        g
! [         a!  n[         R                  SU 3SS	9   S
nANS
nAff = f)u   
Daily 8 AM health data pull.

Pipeline:
  1. Oura API → sleep, steps, exercise, HRV, readiness
  2. Health Connect (Drive) → weight, body fat, blood pressure
  Each part is independent — Oura failure doesn't block HC and vice versa.
  Sends a WhatsApp summary of what was updated.
z)=== Daily health grab starting (8 AM) ===r   ingest_oura_datanot configuredu#   Daily health grab — Oura: SUCCESSu   Daily health grab — Oura: u#   Daily health grab — Oura failed: TrJ   Nz

u)   Good morning! 📊 Health data updated.

u   

Dashboard updated. — LukezDaily health grab: SUCCESSz"Daily health grab: no data updatedu   Morning check-in — no new health data today. Oura data will pull automatically if configured. For weight, upload a Health Connect export to Drive or text me 'health'. — Luke)r;   r<   oura_apir   r   lowerappendre   r   _daily_health_connect_pullrN   r   r   )r.   	summariesr   oura_resultri   	hc_resultcombineds          r0   r`   %DashboardScheduler._daily_health_grab  s+    	?@			U1*4773K/{7H7H7JJ  -AC:;-HI
 335	Y' &&({{9-H=* ./
 KK45KK<=0)  	UNN@DtNT	Us   AD. 3D. .
E8EEc                    U R                  5       nU(       GaW  UR                  (       GaE  [        SS5       GH4  n UR                  SS9nU(       a  US   nUS   nXPR                  :X  a  [
        R                  SUS    35          g	US
   S:  a$  [
        R                  SUS    SUS
    S35        M~  UR                  XTS   5      nU(       aa  U R                  U5      nU(       aI  XPl        U R                  R                  U5        UR                  SS9  [
        R                  S5        Us  $ O[
        R                  SU S35         US:  d  GM  [        R                  " S5        GM7      U R!                  5       nU(       a  SU;  a  [
        R                  S5        U$ g	! [         a%  n[
        R                  SU SU 35         S	nANS	nAff = f! [         a"  n[
        R                  SU 35         S	nAg	S	nAff = f)z
Pull weight/body fat from Health Connect via Google Drive.
Retries up to 3 times with 2-minute intervals.
Returns summary string or None.
   rI   r   r   r   r   z"Already ingested today's HC file: r   Nr   r   zSkipping small file: r   r   r   r   zDaily HC pull: SUCCESS (Drive)zDaily HC pull attempt z/3: no files found yetz/3 failed:    x   zNo Health Connectz#Daily HC pull: SUCCESS (filesystem)z*Daily HC pull filesystem fallback failed: )r   r   ranger   r)   r;   r<   r   r   r   r&   r   r   re   rL   rM   trigger_health_ingest)	r.   r   attemptr   r   r   r   r   ri   s	            r0   r   -DashboardScheduler._daily_health_connect_pull  s    ++-f&&& A;U">>R>PE!&q"(,"&?&??"KK*LVTZ^L\(]^#'!&>J6"NN-B6&>BRRTU[\bUcTddk+lm$%+%9%9'&>%R
%%)%=%=j%IF%<C 9 $ 4 4 8 8 D & < <! < L &,L M'-&<WIE[$\]
 Q;JJsO? 'D	M//1F-V;AB  ! UNN%;G9KPQs#STTU  	MNNGsKLL	MsC   A
F&+F&5A9F&2F&13G &
G0GG
H"G??Hc           	          U R                  5       nU(       a  UR                  (       d  gUR                  5       nUR                  S5      (       d  gSSSS.nUR                  US5      nU S3/nUR	                  S5        UR                  S	5      nU(       a  UR	                  S
US    SUS    S35        UR                  S/ 5      nU(       aK  UR	                  S5        UR	                  S5        U H#  n	UR	                  SU	S    SU	S   SS  35        M%     UR                  S/ 5      n
U
(       aU  U(       dN  UR	                  S5        UR	                  S5        U
SS  H#  n	UR	                  SU	S    SU	S   SS  35        M%     UR                  S/ 5      nU(       a  UR	                  S[        U5       35        U(       aB  US   S:  a9  U R                  SR                  U5      5        [        R                  U S35        g[        R                  U S35        g! [         a!  n	[        R                  SU	 3S S!9   Sn	A	gSn	A	ff = f)"z4Send a Gmail digest via WhatsApp at scheduled times.Nr   MorningMiddayEveningrF   r   u    Inbox Update — LukeunreadzUnread: total_unreadr   primaryz	 primary)needs_replyzNeeds reply:u     — fromz: subject2   	importantz
Important:r   meetingsz
Meeting invites: r   
z email digest sentu+    email digest skipped — no primary unreadzEmail digest failed: TrJ   )r   r   generate_briefingra   r   r|   r   r   r;   r<   rf   re   rg   )r.   rR   gmailr   labelslabellinesr   r   ri   r   r   s               r0   rb   %DashboardScheduler._send_email_digest  s"   .	E,,.E**,D88K(( #i@FJJtR(Ew456ELLXXh'Fx~(>'?r&BSATT]^_((="5KR ^,$ALL6!F)Bq|CR7H6I!JK % b1IR \*"2AALL6!F)Bq|CR7H6I!JK ' xx
B/H23x=/BC &+a/##DIIe$45ug%789w&QRS 	ELL04tLD	Es)   (H! &H! F5H! H! !
I+IIc                 R    SSK nUR                  5       (       d  gUR                  SS9nU(       a3  SU;  a-  SU;  a'  U R                  U5        [        R                  S5        g[        R                  S5        g! [         a!  n[        R                  S	U 3S
S9   SnAgSnAff = f)z*Send new music releases on Friday morning.r   N   limitzNo new dropsznot connectedzNew Music Friday sentz'New Music Friday: nothing new this weekzNew Music Friday failed: TrJ   )	spotify_moduleis_availablenew_releasesr   r;   r<   rf   re   rg   )r.   r  musicri   s       r0   rc   "DashboardScheduler._send_new_music6  s    	I!!..00"//b/9Eu4PU9U##E*34FG 	ILL4QC84LH	Is#   A; AA; %A; ;
B&B!!B&c                 	    U R                   R                  5       nUS   n[        R                  " 5       nUR	                  S/ 5      n[        U5      S:  a&  US   US   -
  nUS:  a  SOS S	[        U5      S
 S3nOSnUR	                  S/ 5       Vs/ s H*  nUR	                  S5      UR                  5       :X  d  M(  UPM,     nnUR	                  S/ 5       Vs/ s HN  nUR                  5       UR	                  SS5      s=:  a   U[        SS9-   R                  5       ::  d  MH  O  ML  UPMP     n	nUR	                  S0 5      R	                  SS5      n
[        U
[        [        45      (       a  SU
 3O
[        U
5      nSUR                  S5       S3/nUR                  S5        UR                  SUS    SU S35        UR                  SUR	                  SS5       SUR	                  S S!5       S"35        UR                  S#U 35        UR	                  S$0 5      nUR	                  S%5      (       aQ  UR                  S&US%    S'UR	                  S(S!5       S)UR	                  S*S!5       S+UR	                  S,S5       S-3	5        U(       ad  UR                  S5        UR                  S.5        U H<  nUR	                  S/5      (       a  S	US/    3OSnUR                  S0US1    U 35        M>     U	(       aK  UR                  S5        UR                  S25        U	S3S  H   nUR                  S0US    S4US1    35        M"     U R                  5       nU(       a  UR                   (       as   UR#                  5       nUR	                  S55      (       aL  UR	                  S65      (       a6  UR                  S5        UR                  S75        UR                  US6   5        U R+                  5       nU(       a%  UR                  S5        UR                  S9U 35        UR                  S5        UR                  S:5        S;R-                  U5      nU R/                  U5        [&        R1                  S<5        g3s  snf s  snf ! [$         a"  n[&        R)                  S8U 35         S3nANS3nAff = f! [$         a!  n[&        R3                  S=U 3S>S?9   S3nAg3S3nAff = f)@z#Send morning briefing via WhatsApp.weight	series30dr   r   downup .1f lb this weektrackingr   r   r   r   daysrl   rm   N/A$u*   Good morning Bill — Luke here with your 
%A, %B %-dz
 briefing:zWeight: currentz lb ()zGoal pace: pacez lb/wk | ETA: ~etaWeeks? weekszCRO: rM   total_hourszSleep: zh (bedtimeu   →	wake_timeu
   ) · HRV: avg_hrvz mszTODAY:rL     r   z
COMING UP:N    — r   summary_textzEMAIL:z(Gmail briefing in morning nudge failed: ON THIS DAY: u   Reply anytime — Luke's here.r  zMorning nudge sentzMorning nudge failed: TrJ   )r   rv   r   ry   ra   r|   absrx   r   
isinstanceintfloatr~   strftimer   r   r   r  re   r;   rf   _get_on_this_dayr   r   r<   rg   )r.   r   wry   seriesweek_changetrendri   events_todayevents_upcomingcrocro_strr  rM   time_strr  
email_datainsightmessages                      r0   rV   &DashboardScheduler._send_morning_nudgeI  s   N	F77<<>DXAJJLE UU;+F6{a$Rj6":5%01_6$?q[AQRU@VVcd"  88J33a55=EOO$55 3  
  88J33a??$quuVR'8cUYTUEV=V<a<a<cc c 3   ((8R(,,[%@C#-cC<#@#@#ic#hG B%..Q]B^A__ijkELLLL8AiL>ugQ?@LL;quuVU';&<OAEER\^aLbKccijkLL5	*+ HHWb)Eyy''wu]';&<C		)UX@Y?ZZ]^c^g^ghsux^y]z  {E  FK  FO  FO  PY  [`  Fa  Eb  be  f  gR X&%A23%%--1V9+RHLL2aj\(!<= & R \*(!,ALL2ai[aj\!BC - ,,.EQ!&!8!8!:J!~~k22z~~n7U7UR(X.Z%?@
 ++-GR }WI67LLLL9:ii&G(KK,-{V ! QLL#KA3!OPPQ   	FLL1!5LE	Fsv   BR, 'Q3>Q3R, AQ8%Q8)Q8/H;R, +A2Q= BR, 3
R, =
R)R$R, $R))R, ,
S6SSc           	      F    U R                   R                  5       nUS   n[        R                  " 5       nSUR	                  S5       3/nUR                  S5        UR                  SUS    SUR                  SS	5       S
35        U[        SS9-   nUR                  S/ 5       Vs/ s H*  nUR                  S5      UR                  5       :X  d  M(  UPM,     nnU(       ax  UR                  S5        UR                  SUR	                  S5       S35        U H<  nUR                  S5      (       a  SUS    3OSnUR                  SUS    U 35        M>     O5UR                  S5        UR                  SUR	                  S5       S35        UR                  S0 5      R                  S/ 5      n	U	 H  n
U
R                  SS5      n [        R                  " USUR                   3-   S5      R                  5       nX<s=::  a  U[        SS9-   ::  ag  O  Me  U
R                  SS5      n[        U[        [        45      (       a  US   U-
  OSnUS:  a  SOS  S!U S"3nUR                  S#U S$U S%U 35        M  M     UR                  S5        UR                  S&5        S'R#                  U5      nU R%                  U5        [&        R)                  S(5        g,s  snf ! [        [         4 a     GM?  f = f! [*         a!  n[&        R-                  S)U 3S*S+9   S,nAg,S,nAff = f)-uW   Send evening check-in via WhatsApp. No weight nagging — Bill weighs in mornings only.r  u   Evening check-in from Luke — r   r   zToday's weight: r!   lb | Pace: r#  r   lb/wkr   r  r   r   z
TOMORROW (z%Az):rL   r  r+  r   z Nothing scheduled for tomorrow (z).
healthPlanweeklyCheckpoints%A, %B %d %Y   r5   r%  r   abovezon track forz target  lbz
Checkpoint alert: z target is u    lb — you're u?   Anything to update? (BP, events, signals, supplements) — Luker  zEvening nudge sentzEvening nudge failed: TrJ   N)r   rv   r   ry   r3  r   ra   r   rx   r   strptimeyearr0  r1  r2  
ValueError	TypeErrorr   r   r;   r<   re   rg   )r.   r   r5  ry   r  tomorrowri   tomorrow_eventsr=  checkpointscpcp_date_strcp_dater5   diffstatusr@  s                    r0   rY   &DashboardScheduler._send_evening_nudge  s   1	F77<<>DXAJJLE6u~~l7S6TUVELLLL+AiL>aeeFTYFZE[[abc ya00H88J33a55=H$6$6$88 3   R z(*;*;D*A)B"EF(A23%%--1V9+RHLL2aj\(!<= ) R ?@Q@QRV@W?XXZ[\ ((<4889LbQK! ffVR0	&//%**>N0NP^_ddfGD59!3D+DD!##!68B6CQV<8X8Xq|f4^_/3axG^#LHU[T\\_!`';K=TZS[[jkqjr%st	 E " LLLLZ[ii&G(KK,-G6 #I.   	FLL1!5LE	Fsd   B%K5 ''KKC/K5 AKK5 A!K8AK5 K5 K2-K5 1K22K5 5
L ?LL c                     U R                   R                  5       nUS   nUS   n[        R                  " 5       nUR	                  S/ 5      n[        U5      S:  aQ  [        U5      S:  a  US   OUS   nUS   nXv-
  n[        USS 5      S-  n	[        U5      S	:  a  US   OUS   n
Xz-
  nO	SnUS
   n	SnU[        SS9-
  R                  5       nUR	                  S/ 5       Vs/ s H5  nXR	                  SS5      s=::  a  UR                  5       :  d  M/  O  M3  UPM7     nnU[        SS9-   R                  5       nUR	                  S/ 5       Vs/ s H6  nUR                  5       UR	                  SS5      s=::  a  U:  d  M0  O  M4  UPM8     nnSUR                  S5       3/nUR                  S5        UR                  S5        UR                  S5        UR                  SUS
    S35        UR                  SUS S35        UR                  SU	S S35        UR                  SUS S35        UR                  SUS    SUR	                  SS5       S35        UR                  S UR	                  S!S"5       S#35        UR                  S5        UR                  S$5        UR                  SUS
    S%US    S&35        UR                  S'UR	                  S(S5      S S)35        UR	                  S*0 5      nUR                  S5        UR                  S+5        UR                  S,UR	                  S-S"5       S.UR	                  S/S"5       S0UR	                  S1S25       S335        UR                  S4UR	                  SS5       35        UR	                  S50 5      R	                  S65      nU(       a%  UR                  S5        UR                  S7U 35        U(       a/  UR                  S5        UR                  S8[        U5       S935        U(       aX  UR                  S5        UR                  S:[        U5       S;35        USS<  H   nUR                  S=US    S>US?    35        M"      SSKnUR                  S<S@9nU(       a"  UR                  S5        UR                  U5        U R                  5       nU(       a%  UR                  S5        UR                  SAU 35        UR                  S5        UR                  SB5        SCR                  U5      nU R!                  U5        ["        R%                  SD5        gs  snf s  snf ! [         a     Nf = f! [         a!  n["        R'                  SEU 3SFSG9   SnAgSnAff = f)Hz,Send weekly trend summary on Sunday morning.r  bodyFatr  r   r  r   r  N   r!  r  r   r   r   u(   WEEKLY DASHBOARD REPORT — Week ending z
%B %-d, %Yz(========================================WEIGHTz  Current: rJ  z  This week: z+.1fz  7-day avg: r  z  30-day change: z  Goal: goalrC  r#  r  rD  z  ETA: ~r$  r%  r&  zBODY FATz	% (goal: z%)z
  Change: deltaVsLast%bloodPressurezBLOOD PRESSUREz  Last reading: systolic/	diastolicr   rV  unknownr"  z  Date: rl   rm   zCRO: $zTHIS WEEK: z events completedzNEXT WEEK (z	 events):rD   r+  r,  r   r
  r.  u   Have a great week! — Luker  zWeekly report sentzWeekly report failed: TrJ   )r   rv   r   ry   ra   r|   sumr   rx   r3  r   r  weekly_music_summaryre   r4  r   r   r;   r<   rg   )r.   r   r5  bfry   r6  
week_startweek_endr7  
weekly_avgmonth_startmonth_changepast_week_startri   past_eventsnext_week_endnext_eventsr  bpr;  r  r  r?  r@  s                           r0   r]   &DashboardScheduler._send_weekly_report  sH   g	F77<<>DXAiBJJLEUU;+F 6{a+.v;!+;VBZ
!":&3 -1
+.v;"+<fQi&)'5y\
   %ya'88CCEO88J33a"eeFB&7K%//:KK K 3   #YA%66AACM88J33a??$fb(9IMI I 3  
 @|@\?]^_ELL"LL LL"LL;q|nC89LL=T(:#>?LL=C(8<=LL,\$,?sCDLL8AfI;l155;O:PPVWXLL8AEE*c$:#;6BC LLLL$LL;r)}oYr&zl"MNLL:bff]A&>t%DAFG /2.BLLLL)*LL+BFF:s,C+DAbff[Z]F^E__abdbhbhiqs|b}a~~  A  BLL8BFF65$9#:;< ((8R(,,[9CR vcU^, R {3{+;*<<MNOR {3{+;*<IFG$RaALL2ai[aj\!BC )%&;;!;DLL$LL'
 ++-GR }WI67LLLL67ii&G(KK,-Yn     	FLL1!5LE	Fst   C"U $.T?T?T? 0U /UUUKU -<U	 )BU ?
U 	
UU UU 
V#U??Vc           
          [         R                  R                  [        5      (       d  g[        R
                  " [        5      nUR                  5       n[        R                  " 5       n/ nS H  n UR                  UR                  U-
  S9nUR                  5       SS nUR                  SUS-   45        UR                  5       nU(       aI  US   n	US   R                  S	S
5      SS n
UR                  U-
  nUR                  SU SU	 SU
 S35        M  M     UR!                  5         U(       a  US   $ g! [         a     M  f = f! ["         a"  n[$        R'                  SU 35         SnAgSnAff = f)zEQuery knowledge base for conversations from this date in prior years.N)r   rH  r   )rL  rC   a  
                        SELECT c.title, substr(m.content, 1, 150), c.create_time
                        FROM messages m
                        JOIN conversations c ON c.id = m.conversation_id
                        WHERE m.role = 'user'
                        AND c.create_time LIKE ?
                        AND length(m.content) > 50
                        ORDER BY length(m.content) DESC
                        LIMIT 1
                    r^  r   r   r  r  d   (z) You were working on 'u   ' — "z..."zOn This Day lookup failed: )r   r   existsKNOWLEDGE_DB_PATHsqlite3connectcursorr   ry   replacerL  rx   executefetchoner   rM  closere   r;   r   )r.   conncry   insightsyear_offset	past_datedate_prefixrowr   snippetrL  ri   s                r0   r4  #DashboardScheduler._get_on_this_dayA  sh   ,	77>>"344??#45DAJJLEH  ) %5::3K LI"+"5"5"7"<KII 	 &+-	/ **,C #A"%a&..s";DS"A$zzK7 !D61HxX_W``e(fg	 #  )2 JJL{" "   	NN8<=	sB   (E	 AE	 2B#D8!E	 8
EE	 EE	 	
E5E00E5c           
      &	  ^  SSK Jn  U" 5       nUR                  (       d  [        R	                  S5        g/ SQn/ nU GH  nUR                  USS9nU GH  nUS   US	   UR                  S
S5      UR                  SS5      UR                  5       S.nUS:X  Ga  UR                  US   US	   5      n	U R                  U5      n
US   nUS	   nX:X  a  UO
U
(       a  UOSnU	S:X  a/  SUS'   SUS'   U
(       a  U
 S3OU S3SU(       a  U S3OS/US'   GOU	S:X  ae  SSK J
n  UR                  U0 5      nUR                  S5      S:X  a  UOUnUU:X  a  UOUnSU S3US'   S US'   U
(       a  U
 S3OU S3SU S3/US'   GOU	S!:X  a  S"US'   S#/US'   S$US'   GOS%US'   S&US'   U
(       a  U
 S3OU S3S/US'   GOtUR                  U5      nUR                  S'S(5      nUR                  S)UR                  5       5      nUR                  S*S+5      nUR                  S,S-5      nUUS.'   U R                  U5      n
US   nUS	   nX:X  a  UO
U
(       a  UOSnUS:X  a  SUS'   U(       a$  S/US'   U
(       a  S0U
 S13S2S3U
 3/US'   O/ S4QUS'   OUS5::  a3  S6U 3US'   U
(       a  S0U
 S13U(       a  U S73OS8S9U
 3/US'   Ot/ S:QUS'   OlS;US'   U
(       a  S0U
 S13S<S3U
 3/US'   OP/ S=QUS'   OHUS(:X  a,  S>U 3US'   S US'   U
(       a  S0U
 S13S2S3U
 3/US'   O/ S?QUS'   OUS@:X  a  SAUS'   SB/US'   S$US'   UR                  U5        GM     GM	     SSS(S@SC.mUR                  U4SD jSE9   SSFKJn  U" U5      n[        R	                  SG[!        U5       SH[#        SI U 5       5       SJ35        U$ ! [$         aN    [        R'                  SK5        [        R	                  SG[!        U5       SL[#        SM U 5       5       SJ35         U$ [(         aX  n[        R'                  SNU 35        [        R	                  SG[!        U5       SO[#        SP U 5       5       SJ35         SnAU$ SnAff = f! [(         a!  n[        R+                  SQU 3SRSS9   SnAgSnAff = f)Tz}
Fetch today's betting picks using Bill's Predictable Tempo strategy.
Returns structured data for the dashboard Bet365 card.
r   )get_analystz0Betting picks: Odds API not configured, skippingN
nhleplseriealigue1laligaliga_portugaleuropa
conferenceuclmlsr   
days_ahead	home_team	away_team	game_timer   commence_time)homeawayrL   r  sportr  
controlledu   ★ CONTROLLEDtagprimeratingz
 Moneylinez +1.5 PucklinezUnder 7.5 Goalsz Under 3.5 Team TotalzOver 1.5 Goalslegsmixed)NHL_TEAM_PROFILEStempolowu
   MIXED — z anchorsplayablechaoticu   ⚡ CHAOTICu   Skip — high-event matchupavoidz	~ NEUTRALneutraltierrH  r  is_cupF	avg_goalsg@r  u   ★ CUP KNOCKOUTzDouble Chance: z or DrawGoals Range: 1-4 GoalszOver 3 Corners for )Under 2.5 GoalszGoals Range: 0-2 GoalsTotal Corners Under 9.5gffffff@u   ★ LOW-EVENT z# Goals Range: Not Between 2-3 Goalsr  zOver 2 Corners for )r  r  zTotal Corners Over 5u
   ★ TIER 1Over 1 Goals)zGoals Range: 1-5 Goalsr  zTotal Corners Over 6u   TIER 2 — )r  zGoals Range: 0-3 Goalsr  r   u	   ⚠ AVOIDu   Skip — unpredictable tempo)r  r  r  r  c                 H   > TR                  U R                  SS5      S5      $ )Nr  r  rH  ra   )prating_orders    r0   <lambda>9DashboardScheduler._fetch_betting_picks.<locals>.<lambda>  s    \%5%5aeeHi6PRS%Tr3   key)validate_picks_batchzBetting picks: z games processed + validated, c              3   R   #    U  H  oR                  S 5      S:X  d  M  Sv   M     g7fr  r  r   Nr  .0r  s     r0   	<genexpr>:DashboardScheduler._fetch_betting_picks.<locals>.<genexpr>%       "ReuuX'7Q11e   '	'z prime targetsu3   pregame_validator not found — skipping validationz  games processed (unvalidated), c              3   R   #    U  H  oR                  S 5      S:X  d  M  Sv   M     g7fr  r  r  s     r0   r  r  )  r  r  u>   Pregame validation failed — picks pass through unvalidated: z% games processed (validation error), c              3   R   #    U  H  oR                  S 5      S:X  d  M  Sv   M     g7fr  r  r  s     r0   r  r  -  r  r  zBetting picks fetch failed: TrJ   )sports_bettingr  r   r;   r<   get_upcoming_eventsra   upperclassify_nhl_matchup_extract_favoriter  classify_soccer_matchupr   sortpregame_validatorr  r|   rd  ImportErrorr   re   rg   )r.   r  analystsportsr   srq   gpickr  favr  r  underdogr  home_panchor
non_anchorleague_classr  r  r  r  r  ri   r  s                            @r0   r{   'DashboardScheduler._fetch_betting_pickss  s   
{	2!mG$$NOxFE  33A!3DA !+ !+ !k2 6)*)C!"D Ez ' < <Q{^Q{^ \"44Q7 ~ ~+.;4CDT L0*:DK-4DN693%z 2$~?V 1FN8*,A BTd,DL
 #g-H%6%:%:4%DF-3ZZ-@E-ITtF174TJ,6vhh*GDK-7DN:=3%~ 6fX^C\ 1#-,.C D,DL
 #i/*7DK,I+JDL-4DN*5DK-6DN:=3%~ 6dV>CZ 1,DL (/'F'Fq'I+//: , 0 0!'') D!-!1!1(E!B$0$4$4[#$F	(-W"44Q7 ~ ~+.;4CDT19-4DN%0@U#&*9#h(G(@*=cU(C4&DL4&DL "+c!10>ug.FU#&*9#h(G\d8*4W(Xj{*=cU(C4&DL4&DL 1;U#&*9#h(G(6*=cU(C4&DL4&DL "QY,7w*?DK-7DN"&5cU($C$<&9#$?0"V0"V "QY*5DK,J+KDL-4DNLL&c  r &'A!aPLJJTJU
dB,U3oc%j\9W""Re"RRSSac d L  dTUoc%j\9Y""Re"RRSSac d L  d!_`a_bcdoc%j\9^""Re"RRSSac d d Ld  	LL7s;dLK	sV   3Q% L-Q% %AN* (Q% *AQ">Q% 	Q"
AQQ% Q""Q% %
R/RRc                     SSK JnJn  U" 5       nUR                  (       d  gSn/ SQnU H  nUR	                  USS9nU H  n	 U	R                  SS	5      n
U
(       d  M  [        R                  " U
R                  S
S5      5      R                  SS9nU[        R                  " 5       -
  R                  5       S-  nSUs=:  a  S::  a  O  M  Sn  OM     U(       d  M    O   U(       d  g[        R                  S5        [        R                  " 5       U l         SSKJn  ['        U5      nUR)                  5         [        R                  SU S35        U R-                  5       nUb  U R.                  R1                  5       nU[        R2                  " 5       R5                  5       S.US'   U R.                  R7                  U5        [        R                  S['        U5       S35        U R9                  5         gg! [        [        4 a     GM  f = f! [*         a     Nf = f! [:         a"  n[        R=                  SU 35         SnAgSnAff = f)u  
Rapid pregame validation cycle — runs every 15 min when any game
starts within 90 minutes. Clears the validation cache so fresh
data (Claude analysis, scraping) is fetched for imminent games.
Then re-fetches betting picks and updates briefing + dashboard.
r   )r  
SPORT_KEYSNFr  r   r  r  r   Zz+00:00)tzinfo<   Z   TuE   RAPID PREGAME: Games within 90 min — running fresh validation cycle)_validation_cachezRAPID PREGAME: Cleared z cached validationsrp   rs   z&RAPID PREGAME: Dashboard updated with z re-validated pickszRapid pregame refresh failed: )r  r  r  r   r  ra   r   fromisoformatrz  utcnowtotal_secondsrM  rN  r;   r<   rL   r-   r  r  r|   clearr  r{   r   rv   rO   rx   writerN   re   r   )r.   rO   r  r  r  has_imminentr  r  rq   r  ctr  minutes_untilr  
cache_sizer   r   ri   s                     r0   rd   /DashboardScheduler._maybe_rapid_pregame_refresh5  s   :	A>!mG$$ !L<F 33A!3DA
!UU?B7!$$,$:$:2::c8;T$U$]$]ei$]$j	)2X__5F)F(U(U(WZ\(\}22+/L! 3   < "  KK_`'+yy{D$? !23
!'')5j\ATUV
 --/E ww||~"'||~779#Y d#DSZLPcde **, !- '	2 ! !"  "  	ANN;A3?@@	As    H   H  G8H  A+G8
H  G8H  H  )/H  :H B#H  8HH  HH  
HH  HH   
I*IIc                      UR                  S5      (       a2  US   S   S   R                  S/ 5      nU(       a  [        US S9nUS   $ g! [        [        [        4 a     gf = f)	zGExtract the favorite team name from bookmaker data (internal use only).
bookmakersr   marketsh2hc                     U S   $ )Nprice )xs    r0   r  6DashboardScheduler._extract_favorite.<locals>.<lambda>~  s    7r3   r  r   N)ra   minKeyError
IndexErrorrN  )r.   gamer  r  s       r0   r  $DashboardScheduler._extract_favoritex  st    	xx%%<(+I6::5"Ec';<Cv;&  *i0 		s   AA A#"A#c                 t
    U R                   R                  5       nUS   n[        R                  " 5       n[        R
                  " 5       nUR                  nUS:  a  SnOUS:  a  SnOSnUR                  S/ 5      n[        U5      S:  a-  US	   US
   -
  nUS:  a  SOSn	U	 S[        U5      S S3n
US:*  nOSn
SnUR                  S/ 5       Vs/ s HQ  nUR                  S5      UR                  5       :X  d  M(  US   UR                  SS5      UR                  SS5      S.PMS     nnUR                  S/ 5       Vs/ s Hh  nUR                  5       UR                  SS5      s=:  a   U[        SS9-   R                  5       ::  d  MH  O  ML  US   US   UR                  SS5      S.PMj     snSS nSnUR                  S0 5      R                  S / 5      nU H  n UR                  SS5      n[        R                  " USUR                   3-   S!5      R                  5       nUUs=::  a  U[        S"S9-   ::  a4  O  Mf  UR                  S#S5      n[        US$   U-
  S%5      nUUUUS&:*  S'.n  OM     UR                  S(0 5      R                  S)5      nU R#                  5       nSnU R%                  5       nU(       a"  UR&                  (       a   UR)                  5       nU R1                  5       nU(       a  UR&                  OS+U(       a  UR&                  OS+S,.nU R3                  5       nUR                  5       UUR5                  S-5      US$   U
UUR                  S.S5      UR                  S/S5      S0.UUUUUUR                  S1S5      UR                  S25      UUU(       a  S3U0OSS4.n[7        [8        5      R:                  S5-  nSSKnUR?                  [A        UR:                  5      S6S79u  n n! [B        RD                  " U S85       n"[F        RH                  " UU"S"S+S99  SSS5        [B        RJ                  " U![A        U5      5        [,        RM                  S:5        gs  snf s  snf ! [        [         4 a     GM  f = f! [*         a#  n[,        R/                  S*U 35         SnAGNSnAff = f! , (       d  f       N= f! [*         a<    [B        RN                  RQ                  U!5      (       a  [B        RR                  " U!5        e f = f! [*         a!  n[,        RU                  S;U 3SS<9   SnAgSnAff = f)=z;Generate briefing data JSON for the dashboard morning card.r  rG   zGood morning   zGood afternoonzGood eveningr  r   r  r  r   r  r  r  r  r  r  Tr   r   r   rL   r   r   )r   rL   r   r   r  )r   r   rL   NrD   rE  rF  rG  rH  r5   r!  r   g      ?)r   r5   rU  on_trackrl   rm   z%Gmail briefing for dashboard failed: F)driver  r   r#  r$  )r!  r8  trend_positiver#  	eta_weeksfocusrM   rq   )	generatedgreetingdate_displayr  r9  r:  
checkpointrm   on_this_dayr  rM   emailgoogle_statusrs   briefing.jsonz	.json.tmp)dirsuffixr5  )indentensure_asciizBriefing data updatedzBriefing update failed: rJ   )+r   rv   r   ry   r   rO   rR   ra   r|   r/  rx   r   rK  rL  roundrM  rN  r4  r   r   r  re   r;   rf   r   r{   r3  r   r   parenttempfilemkstempr~   r   fdopenr   dumprz  r<   r   ru  unlinkrg   )#r.   r   r5  ry   rO   rR   r   r6  r7  trend_direction
trend_textr  ri   r9  r:  checkpoint_alertrQ  rR  rS  rT  r5   rU  rm   r  email_briefingr  r   r  betting_picksbriefingbriefing_pathr  fdtmp_pathfs#                                      r0   rN   (DashboardScheduler._update_briefing_data  s   {	H77<<>DXAJJLE,,.C 88Dby)+) UU;+F6{a$Rj6":5,7!O& /0#k2B31G}U
!,!1'
!%
 *b11A55=EOO$55 d!G*aeeFB.?QUUS]_aMbc1   *b11A??$quuVR'8cUYTUEV=V<a<a<cc Tc T!G*aivrARS1 q	O  $((<4889LbQK!"$&&"4K&//%**>N0NP^_ddfGD59!3D+DD!#!!4$Qy\F%:A>$/&,$((,	,(  E	 "$ 2.22;?I //1K "N,,.EN%*%<%<%>N
 //1F-3)),1uM !557M !]]_$ %| < |'&4EE&!,!"z1!5 !-#2.&*'2.'*'!.7DG]3$)H0 !!45<<NM#++M4H4H0IR]+^LBYYr3'1IIh!%H (

8S%7834s. #I.  ! NLL#H!LMMNR ('  77>>(++IIh'
  	HLL3A37$LG	Hs   CT 'Q <-Q )T ?AQ%
Q% Q%./T A Q*>T  .Q*.A!T R  DT 3S 
R2"=S  
T *Q?:T >Q??T 
R/R*$T *R//T 2
S <S AT		T 
T7T22T7c                    [        [        [        [        [        /5      (       d  [
        R                  S5        g S[         S3n[        R                  " U[        [        4S[         3S[         3US.SS9nUR                  S	;   a#  [
        R                  S
[        U5       S35        g[
        R                  SUR                   SUR                  SS  35        g! [         a"  n[
        R                  SU 35         SnAgSnAff = f)z'Send a WhatsApp message via Twilio API.u<   Twilio credentials not configured — skipping WhatsApp sendNz+https://api.twilio.com/2010-04-01/Accounts/z/Messages.jsonz	whatsapp:)FromToBodyr	  )authr   ru   )r      zWhatsApp message sent (z chars)zWhatsApp send failed: r,  r   zWhatsApp send error: )allr
   r   r   r   r;   r   r   postr   r<   r|   rg   textre   )r.   r@  urlr   ri   s        r0   r   !DashboardScheduler._send_whatsapp  s    &(9;NPabccNNYZ	6?@R?SSabC==(*;<'(;'<=%&7%89#
 	D :-5c'l^7KL5d6F6F5GuTYYW[X[_L]^_ 	6LL0455	6s   A/C ,2C 
D)DDc                 $    U R                  5         g)z;Manually trigger morning briefing (callable from WhatsApp).zMorning briefing sent!)rV   r=   s    r0   trigger_morning_briefing+DashboardScheduler.trigger_morning_briefing   s      "'r3   c                 $    U R                  5         g)z8Manually trigger weekly report (callable from WhatsApp).zWeekly report sent!)r]   r=   s    r0   trigger_weekly_report(DashboardScheduler.trigger_weekly_report%  s      "$r3   c                 8    U R                  5       nU=(       d    S$ )z1Get on-this-day insight (callable from WhatsApp).z8No conversations found from this date in previous years.)r4  )r.   r?  s     r0   get_on_this_day"DashboardScheduler.get_on_this_day*  s    '')TTTr3   c                    [         R                  S5         SSKJn  [         R                  S5        U" U R                  5      n[         R                  S[        U5      SS  35        U(       a&  SUR                  5       ;  a  U R                  5         U$ U=(       d    S	$ ! [         a2  n[         R                  S
U 3SS9  S[        U5      SS  3s SnA$ SnAff = f)z<Manually trigger Oura API data pull. Returns summary string.ztrigger_oura_ingest: startingr   r   z)trigger_oura_ingest: oura_api imported OKztrigger_oura_ingest: result = Nr   r   zNo data returned from Oura API.zOura manual ingest failed: TrJ   zOura API error: rs  )
r;   r<   r   r   r   r~   r   rN   re   rg   )r.   r   r   ri   s       r0   trigger_oura_ingest&DashboardScheduler.trigger_oura_ingest/  s    34	51KKCD%dgg.FKK8VTc9J8KLM*&,,.@**,>>> 	5LL6qc:TLJ%c!fTcl^44	5s$   A=B  
B   
C*'CCCc                 N   U R                  5       nU(       ap  UR                  (       a_   UR                  SS9nU(       aH  U R                  U5      nU(       a0  U R                  R                  U5        U R                  5         SU 3$ [        /[        -   n[        [        [        5      R                  5      nUR!                  U5        SnSnU H  n	["        R$                  R'                  U	5      (       d  M)  ["        R$                  R)                  U	S5      ["        R$                  R)                  U	S5      ["        R$                  R)                  U	S	S5      4 HI  n
[*        R*                  " U
S
S9 H-  n["        R$                  R-                  U5      nX:  d  M)  UnUnM/     MK     M     U(       d  gU R                  U5      nU(       a-  U R                  R                  U5        U R                  5         U$ g! [         a#  n[        R                  SU 35         SnAGNSnAff = f)zRManually trigger Health Connect ingestion. Tries Drive API first, then filesystem.H   r   z(via Google Drive)
z)Drive fetch failed during manual ingest: Nr   r   r   r   Tr   zCNo Health Connect zip files found in Google Drive or local folders.z3Health Connect ingestion failed. Check server logs.)r   r   fetch_latest_health_connectr   r&   r   rN   re   r;   r   r   r   r~   r   r   r  r   r   r   r   r   r   r   )r.   r   r   r   ri   r   dash_dirlatest_filelatest_mtimer   r   r   r   s                r0   r   (DashboardScheduler.trigger_health_ingest?  s    ++-f&&	P#??b?Q
!55jAF,,00<224!5fX>>
 **-DDt/07788$%J77==,,Z)>?Z)>?Z/DE
 !%		'T BHGG,,X6E+',&.	 !C & X))+6  $$[1&&(MDC  P!J1#NOOPs   AG7 7
H$HH$c                     [        [        5      R                  S-  nUR                  5       (       a+  [	        U5       n[
        R                  " U5      sSSS5        $ g! , (       d  f       g= f)z%Return current briefing data as dict.r  N)r   r   r  ru  openr   load)r.   r  r  s      r0   get_briefing_data$DashboardScheduler.get_briefing_datap  sS    0188?J!!m$yy| %$ %$s   A
A,)r'   r)   r(   r&   r#   r*   r!   r$   r,   r    r+   r-   r   r"   r   r   r   N)'__name__
__module____qualname____firstlineno____doc__r1   r:   r@   r9   rQ   rw   rz   r   r   r_   r   r   r   r`   r   _daily_health_connect_grabrb   rc   rV   rY   r]   r4  r{   rd   r  rN   r   r)  r,  r/  r2  r   r>  __static_attributes__r  r3   r0   r   r   ?   s    M%&))
Xx3Ej"	W	SM**4X 5D/b3l "40EhI&PFh3FniFZ.d@DAAF
}HB66(
%
U
5 /Ebr3   r   )'rD  r   r   r   rw  loggingr7   rL   r   r   r   r   pathlibr   	getLoggerr;   getenvr~   __file__resolver  r   rv  r
   r   r   r   rS   rU   rW   rX   r[   r\   rP   r^   r  r   r   r   r  r3   r0   <module>rM     s   
        . . 			;	' iiX ''..1FFG  X..077>>IO[\ YY3R8 II126 ii 5r: II126           YY		n$z12 
 		n$%		i.03QQT^^_ !((+;;< w wr3   