
    tFjO3                        d 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	 ddl
mZ  ej                  d      Z e	e      j                         j                   Zedz  Zedz  Zd	Zed
z  Zedz  ZdZdZ ej2                         Z ej2                         ZdddddddddddddddddddddddddddgZdefdZdefdZde fdZ!de"fd Z#d!e"ddfd"Z$d:d#Z%de"fd$Z&d%ed&ede fd'Z'd%ed&ed(e"ddfd)Z(d*edede"fd+Z)d,e*dede fd-Z+de"fd.Z,	 d;d/ed0ed1ed2ed3ed4e ddfd5Z-d,e*dede"fd6Z.d<d7edz  ddfd8Z/dej`                  fd9Z1y)=u  
winston_scheduler.py — Winston pipeline background scheduler (DVI-816).

Runs as a daemon thread registered by app.py at startup. Every minute it
checks all enabled Winston schedules and fires the pipeline for any that are
due, subject to idempotency key (schedule_id, scheduler_run_date).

Date logic by profile
---------------------
  prev_biz   Previous business day. On Mondays also covers Sat + Sun.
             Weekdays only (Sat/Sun produce no targets → schedule skips).
  today      Today's date. Weekdays only (Sat/Sun skipped).

Default seeds
-------------
On first startup with an absent or empty winston_schedules.json the scheduler
seeds three canonical profiles: 7am (prev_biz), 11am (today), 3pm (today).
    N)datedatetime	timedelta)Path)ZoneInfoztogen.winston_schedulerzwinston_schedules.jsonzwinston_settings.jsonzAmerica/Indiana/Indianapoliszwinston_scheduler_log.jsonzwinston_run_records.jsoni  d   zdefault-7amz7am Runweekdays   prev_bizT)idnamecadencecustom_dayshourminuteprofileenabledzdefault-11amz11am Run   todayzdefault-3pmz3pm Run   returnc                  |   t         j                         r	 t        j                  t         j	                               } t        | t              rA| j                  d      }t        |t              r |j                         r|j                         S t        S t        S # t        j                  t        f$ r Y t        S w xY w)z8Business timezone the schedule hours are interpreted in.schedule_timezone)_SETTINGS_FILEis_filejsonloads	read_text
isinstancedictgetstrstripJSONDecodeErrorOSError_DEFAULT_TIMEZONE)datatzs     (/var/www/html/togen/winston_scheduler.py_schedule_timezoner*   ]   s    	::n6689D$%XX12b#&288:88:%  $$g. 		s   A7B B;:B;c                      	 t        j                  t        t                           S # t        $ r5 t
        j                  dt                      t        j                         cY S w xY w)zACurrent time in the business timezone (schedule hours are local).u<   Invalid schedule timezone %r — falling back to system time)r   nowr   r*   	Exceptionlog	exception     r)   
_now_localr2   k   sO    ||H%7%9:;; T(*	,||~s   %( ;A&%A&c                     t         j                         rT	 t        j                  t         j	                               } t        | t              rt        | j                  d            S 	 yy# t        j                  t        f$ r Y yw xY w)zGlobal Dry Run master switch (DVI-908).

    When enabled in winston_settings.json every scheduled run executes in
    dry-run mode (no ClickUp/SharePoint/draft writes), regardless of the
    per-schedule flag.
    dry_runF)r   r   r   r   r   r   r    boolr!   r$   r%   r'   s    r)   _global_dry_runr7   u   sv     	::n6689D$%DHHY/00 &  $$g. 		s   AA* *BBc                  0   t         j                         r\	 t        j                  t         j	                               } t        | t              r!| D cg c]  }t        |t              s| c}S 	 g S g S c c}w # t        j                  t        f$ r Y g S w xY wN)
_SCHEDULES_FILEr   r   r   r   r   listr    r$   r%   )r'   ss     r)   _load_schedulesr=      s     	::o779:D$%#'?a:a+>?? & I2I @$$g. 	I	)   ;A8 A3'A3+A8 3A8 8BB	schedulesc                 X    t         j                  t        j                  | d             y )N   indent)r:   
write_textr   dumps)r?   s    r)   _save_schedulesrF      s    tzz)A>?r1   c                     t               ryt        j                         j                         } g }t        D ](  }t        |      }| |d<   | |d<   |j                  |       * t        |       t        j                  dt        |             y)z6Write the three default schedules when none exist yet.N
created_at
updated_atz$Seeded %d default Winston schedules.)r=   r   utcnow	isoformat_DEFAULT_SCHEDULESr    appendrF   r.   infolen)r,   seededr<   entrys       r)   seed_schedules_if_emptyrR      sz    
//

%
%
'CF Q!l!le	
 FHH3S[Ar1   c                      t         j                         r<	 t        j                  t         j	                               } t        | t              r| S g S g S # t        j                  t        f$ r Y g S w xY wr9   )	_SCHED_LOG_FILEr   r   r   r   r   r;   r$   r%   r6   s    r)   _load_sched_logrU      si     	::o779:D%dD149r9 I $$g. 	I	s   8A A A0/A0schedule_idscheduler_run_datec                 @     t         fdt               D              S )Nc              3   r   K   | ].  }|j                  d       k(  xr |j                  d      k(   0 yw)rV   rW   N)r!   ).0erV   rW   s     r)   	<genexpr>z_already_ran.<locals>.<genexpr>   sF        	
m+ 	:	"#'99	:s   47)anyrU   )rV   rW   s   ``r)   _already_ranr^      s"      !"  r1   run_idsc                 v   t         5  t               }|j                  | ||t        j                         j                         d       |t         d  }	 t        j                  t        j                  |d             d d d        y # t        $ r t        j                  dt               Y .w xY w# 1 sw Y   y xY w)N)rV   rW   r_   fired_atrA   rB   zFailed to persist %s)_SCHED_LOG_LOCKrU   rM   r   rJ   rK   _MAX_LOG_ENTRIESrT   rD   r   rE   r%   r.   r/   )rV   rW   r_   entriess       r)   _record_ranre      s    	 C!#&"4 )335	
 	 ++,-	C&&tzz'!'DEC C  	CMM0/B	CC Cs*   AB/*B#B,)B/+B,,B//B8r   c                 8   |j                         }| dk(  rn|dk(  r:|t        d      z
  j                         |t        d      z
  j                         gS d|cxk  rdk  r"n g S |t        d      z
  j                         gS g S |dk\  rg S |j                         gS )u   Return pipeline target date strings for this profile + today.

    Returns [] when the weekday guard rules out firing (e.g. profile=prev_biz
    on a Saturday means no prior business day was missed — the 7am run never
    fires on weekends).
    r   r   rA   )days         )weekdayr   rK   )r   r   wds      r)   target_datesrm      s     
B*7**557**557  "\\ 	 YA..99;<<		Qw	OOr1   schedulec                     | j                  dd      }|j                         }|dk(  ry|dk(  r|dk  S |dk(  r| j                  d      xs g }||v S y)	Nr   dailyTr	   ri   customr   F)r!   rk   )rn   r   r   rl   rg   s        r)   _is_schedule_dayrr      sa    ll9g.G	B'*Qw(||M*0bTzr1   c                  0   t         j                         r\	 t        j                  t         j	                               } t        | t              r!| D cg c]  }t        |t              s| c}S 	 g S g S c c}w # t        j                  t        f$ r Y g S w xY w)z?Read legacy winston_run_records.json (read-only after DVI-825).)
_RUN_RECORDS_FILEr   r   r   r   r   r;   r    r$   r%   )r'   rs     r)   _load_run_recordsrv      s      "	::/99;<D$%#'?a:a+>?? & I2I @$$g. 	I	r>   schedule_nametarget_daterun_idtriggerpipeline_idr4   c                    ddl m} t        j                  d||| |       	 |j	                  ||||xs d |      }|j                  d      s|j                  d      rdnd}t        j                  d|||       |j                  |       y # t        $ r t        j                  d	||       Y 4w xY w# |j                  |       w xY w)
Nr   )winston_pipelinezJWinston scheduler: pipeline start date=%s run_id=%s schedule=%r dry_run=%s)rz   ry   r{   r4   errorconfig_errorsdonez<Winston scheduler: pipeline done date=%s run_id=%s status=%sz3Winston scheduler: pipeline error date=%s run_id=%s)	togenr}   r.   rN   runr!   r-   r/   _clear_cancel)	rw   rx   ry   rz   r{   r4   _wpsummarystatuss	            r)   _pipeline_threadr   	  s    -HHY&-:	"''+wv&1&9T7  L$[[1W[[5QX^Off	.
 	&!  bK[Z`ab 	&!s$   AB  B.+B1 -B..B1 1Cc           
      R   ddl }| j                  dd      }	 ddlm} |j	                  |      }t        ||      }|s)t        j                  d| j                  d      ||       g S t               xs t        | j                  d            }g }|D ]  }	t        |j                               }
d	| j                  d
d       }|j                  |
       t        j                  t         | j                  dd      |	|
|||fd      }|j#                           |S # t
        $ r |}Y w xY w)zPStart pipeline background threads for all target dates. Returns list of run_ids.r   Nr   r   )winston_date_logicuH   Winston scheduler: schedule %r — no target dates (profile=%s today=%s)r   r4   z
scheduler:r    T)targetargsdaemon)uuidr!   r   r   resolve_base_ruler-   rm   r.   rN   r7   r5   r"   uuid4rM   	threadingThreadr   start)rn   r   r   r   _wdl	base_ruledatesr4   r_   rx   ry   rz   ts                r)   _fire_scheduler     s!   ll9g.G4**73	 E*E[f%w	7	 @4Y(?#@GG 
TZZ\"x||D"567v#,,vr*K'SZ[

 	
	
 N3  	s   D D&%D&r,   c                 n   | xs
 t               } | j                         }|j                         }t               D ]  }|j	                  d      s|j	                  d      }|s)t        ||      s6|j	                  d      | j                  k7  s|j	                  d      | j                  k7  rst        ||      r't        j                  d|j	                  d      |       t        j                  d|j	                  d      | j                  | j                  |       t        ||      }t        |||        y)	a   Fire any enabled schedules whose time-of-day matches now.

    ``now`` is evaluated in the business timezone (schedule hours are local
    business hours, not the server's UTC), so a 07:00 schedule fires at 7am
    local rather than 7am UTC (DVI-895).
    r   r   r   r   u7   Winston scheduler: schedule %r already ran %s — skip.r   uA   Winston scheduler: schedule %r due at %02d:%02d on %s — firing.N)r2   r   rK   r=   r!   rr   r   r   r^   r.   debugrN   r   re   )r,   r   	today_strrn   sidr_   s         r)   scanr   B  s    
CHHJE!I#% -||I&ll4 %0<<388+x||H/E/SY'IIOll6*I7Tf%sxxY	H 51CG,%-r1   c                      d } t        j                  | dd      }|j                          t        j	                  d       |S )z0Start the scheduler daemon thread and return it.c                      t                	 t        j                  d       	 t                !# t        $ r t
        j                  d       Y !w xY w)N<   zWinston scheduler scan error)rR   timesleepr   r-   r.   r/   r0   r1   r)   _loopzstart_loop.<locals>._loopi  sC    !JJrN>   ><=>s   
- AATzwinston-scheduler)r   r   r   zWinston scheduler loop started.)r   r   r   r.   rN   )r   r   s     r)   
start_loopr   f  s9    > 	d9LMAGGIHH./Hr1   )r   N)r   Fr9   )2__doc__r   loggingr   r   r   r   r   pathlibr   zoneinfor   	getLoggerr.   __file__resolveparent	_BASE_DIRr:   r   r&   rT   rt   rc   _MAX_RUN_RECORDSLockrb   run_records_lockrL   r"   r*   r2   r5   r7   r;   r=   rF   rR   rU   r^   re   rm   r    rr   rv   r   r   r   r   r   r0   r1   r)   <module>r      sp  &     . .  g12N**,33	 88 77 3  << ::    ).."!9>>#  	 	 	+ LC H  " @t @ @B& c s t CS Cc CD CT C(# d t 0
t 
D 
T 
&	4 	" =B"C "c "3 "QT ""%"59"FJ""!T !$ !4 !P-ho - -HI$$ r1   