
    ih                        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ZddlmZm	Z	 ddl
mZ ddlmZ ddlmZmZmZmZ  ej        e          ZddlmZ 	 ddlmZ d	Zn# e$ r d
ZY nw xY w e                                            Zedz  Zedz  Zedz  Z dZ!dDdee"         dee         dee"         fdZ#dee"ef         dee"ef         fdZ$defdZ%defdZ&d Z'de"de(fdZ)de"dee"ef         fdZ*dedefdZ+dddee"ef         d ed!ee"         dee"         fd"Z,de-de(fd#Z.dEdee"ef         d!ee"         dee"         fd$Z/deee"ef                  fd%Z0d&eee"ef                  fd'Z1	 	 	 	 	 	 	 	 	 	 dFd(e"de"d)ee"         d*ee(         d+ee"         d,eee"ef                  dee"         deee"                  d-ee"         d.ee"         d/ee"         d0ee"         dee"ef         fd1Z2d2e"deee"ef                  fd3Z3dGd4e4deee"ef                  fd5Z5d2e"d6ee"ef         deee"ef                  fd7Z6dEd2e"d8ee"         deee"ef                  fd9Z7d2e"deee"ef                  fd:Z8d2e"deee"ef                  fd;Z9d2e"de4fd<Z:	 	 dDd2e"d=e4d>ee"         d?ee"         fd@Z;d2e"de4fdAZ<deee"ef                  fdBZ=d2e"de"fdCZ>dS )Hz
Cron job storage and management.

Jobs are stored in ~/.hermes/cron/jobs.json
Output is saved to ~/.hermes/cron/output/{job_id}/{timestamp}.md
    N)datetime	timedelta)Path)get_hermes_home)OptionalDictListAny)now)croniterTFcronz	jobs.jsonoutputx   skillskillsreturnc                     || r| gng }n(t          |t                    r|g}nt          |          }g }|D ]@}t          |pd                                          }|r||vr|                    |           A|S )zPNormalize legacy/single-skill and multi-skill inputs into a unique ordered list.N )
isinstancestrliststripappend)r   r   	raw_items
normalizeditemtexts         1/home/agentuser/.hermes/hermes-agent/cron/jobs.py_normalize_skill_listr   )   s    ~$,UGG"			FC	 	  !H		LL	J $ $4:2$$&& 	$D
**d###    jobc                     t          |           }t          |                    d          |                    d                    }||d<   |r|d         nd|d<   |S )zLReturn a job dict with canonical `skills` and legacy `skill` fields aligned.r   r   r   N)dictr   get)r!   r   r   s      r   _apply_skill_fieldsr%   :   s[    cJ":>>'#:#:JNN8<T<TUUF!Jx'-7&))4Jwr    pathc                 b    	 t          j        | d           dS # t          t          f$ r Y dS w xY w)z<Set directory to owner-only access (0700). No-op on Windows.i  N)oschmodOSErrorNotImplementedErrorr&   s    r   _secure_dirr-   C   sG    
u()   s    ..c                     	 |                                  rt          j        | d           dS dS # t          t          f$ r Y dS w xY w)z;Set file to owner-only read/write (0600). No-op on Windows.i  N)existsr(   r)   r*   r+   r,   s    r   _secure_filer0   K   sa    ;;== 	"HT5!!!!!	" 	"()   s   )/ AAc                      t                               dd           t                              dd           t          t                      t          t                     dS )z6Ensure cron directories exist with secure permissions.Tparentsexist_okN)CRON_DIRmkdir
OUTPUT_DIRr-    r    r   ensure_dirsr9   T   sS    NN4$N///TD111
r    sc                 >   |                                                                  } t          j        d|           }|st	          d|  d          t          |                    d                    }|                    d          d         }dddd	}|||         z  S )
u   
    Parse duration string into minutes.
    
    Examples:
        "30m" → 30
        "2h" → 120
        "1d" → 1440
    zD^(\d+)\s*(m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days)$zInvalid duration: 'z''. Use format like '30m', '2h', or '1d'      r   <   i  )mhd)r   lowerrematch
ValueErrorintgroup)r:   rD   valueunitmultiplierss        r   parse_durationrK   `   s     	
		AH\^_``E [YqYYYZZZAE;;q>>!D..K;t$$$r    schedulec                 `   |                                  } | }|                                 }|                    d          r5| dd                                          }t          |          }d|d| ddS |                                 }t          |          dk    rut          d |dd         D                       rTt          st          d	          	 t          |            n'# t          $ r}t          d
|  d|           d}~ww xY wd| | dS d| v st          j        d|           r	 t          j        |                     dd                    }|j        |                                }d|                                d|                    d           dS # t          $ r}t          d|  d|           d}~ww xY w	 t          |           }t)                      t+          |          z   }d|                                d| dS # t          $ r Y nw xY wt          d| d          )uM  
    Parse schedule string into structured format.
    
    Returns dict with:
        - kind: "once" | "interval" | "cron"
        - For "once": "run_at" (ISO timestamp)
        - For "interval": "minutes" (int)
        - For "cron": "expr" (cron expression)
    
    Examples:
        "30m"              → once in 30 minutes
        "2h"               → once in 2 hours
        "every 30m"        → recurring every 30 minutes
        "every 2h"         → recurring every 2 hours
        "0 9 * * *"        → cron expression
        "2026-02-03T14:00" → once at timestamp
    zevery    Nintervalr?   )kindminutesdisplay   c              3   @   K   | ]}t          j        d |          V  dS )z^[\d\*\-,/]+$N)rC   rD   ).0ps     r   	<genexpr>z!parse_schedule.<locals>.<genexpr>   s@        *+!1%%     r    zOCron expressions require 'croniter' package. Install with: pip install croniterzInvalid cron expression 'z': r   )rP   exprrR   Tz^\d{4}-\d{2}-\d{2}Zz+00:00oncezonce at z%Y-%m-%d %H:%M)rP   run_atrR   zInvalid timestamp 'rQ   zonce in zInvalid schedule 'z'. Use:
  - Duration: '30m', '2h', '1d' (one-shot)
  - Interval: 'every 30m', 'every 2h' (recurring)
  - Cron: '0 9 * * *' (cron expression)
  - Timestamp: '2026-02-03T14:00:00' (one-shot at time))r   rB   
startswithrK   splitlenallHAS_CRONITERrE   r   	ExceptionrC   rD   r   fromisoformatreplacetzinfo
astimezone	isoformatstrftime_hermes_nowr   )	rL   originalschedule_lowerduration_strrQ   partsedtr\   s	            r   parse_schedulerq   u   s   $ ~~HH^^%%N   ** 
|))++ ..****
 
 	
 NNE
5zzQ3  /4RaRy      	pnooo	KX 	K 	K 	KIIIaIIJJJ	K 
 
 	
 h"(#8(CC	E'(8(8h(G(GHHB y ]]__,,..Ebkk2B&C&CEE  
  	E 	E 	EC8CCCCDDD	E	 **7!;!;!;;&&((,(,,
 
 	

     	CX 	C 	C 	C  sD   C$ $
D.DD+A0F 
G &F;;G AH 
HHrp   c                    t                      j        }| j        St          j                                                    j        }|                     |                              |          S |                     |          S )a  Return a timezone-aware datetime in Hermes configured timezone.

    Backward compatibility:
    - Older stored timestamps may be naive.
    - Naive values are interpreted as *system-local wall time* (the timezone
      `datetime.now()` used when they were created), then converted to the
      configured Hermes timezone.

    This preserves relative ordering for legacy naive timestamps across
    timezone changes and avoids false not-due results.
    N)rf   )rj   rf   r   r   rg   re   )rp   	target_tzlocal_tzs      r   _ensure_awareru      sf     $I	y<>>,,..5zzz**55i@@@==###r    last_run_atr   rw   c                    |                      d          dk    rdS |rdS |                      d          }|sdS t          t          j        |                    }||t	          t
                    z
  k    r|S dS )a  Return a one-shot run time if it is still eligible to fire.

    One-shot jobs get a small grace window so jobs created a few seconds after
    their requested minute still run on the next tick. Once a one-shot has
    already run, it is never eligible again.
    rP   r[   Nr\   )seconds)r$   ru   r   rd   r   ONESHOT_GRACE_SECONDS)rL   r   rw   r\   	run_at_dts        r   _recoverable_oneshot_run_atr|      s     ||Fv%%t t\\(##F th4V<<==IC),ABBBBBB4r    c                 6   d}d}|                      d          }|dk    r<|                      dd          dz  }|dz  }t          |t          ||                    S |d	k    rt          r	 t	                      }t          | d
         |          }|                    t                    }|                    t                    }	t          |	|z
  	                                          }|dz  }t          |t          ||                    S # t          $ r Y nw xY w|S )a(  Compute how late a job can be and still catch up instead of fast-forwarding.

    Uses half the schedule period, clamped between 120 seconds and 2 hours.
    This ensures daily jobs can catch up if missed by up to 2 hours,
    while frequent jobs (every 5-10 min) still fast-forward quickly.
    r   i   rP   rO   rQ   r<   r>   r=   r   rX   )r$   maxminrb   rj   r   get_nextr   rF   total_secondsrc   )
rL   	MIN_GRACE	MAX_GRACErP   period_secondsgracer   r   firstseconds
             r   _compute_grace_secondsr      s     II<<Dz!i33b8!#9c%33444v~~,~		--CHV,c22DMM(++E]]8,,F &5.!?!?!A!ABBN"a'Ey#eY"7"7888 	 	 	D	 s   *BD	 	
DDc                    t                      }| d         dk    rt          | ||          S | d         dk    rf| d         }|r5t          t          j        |                    }|t          |          z   }n|t          |          z   }|                                S | d         dk    rMt          sdS t          | d	         |          }|	                    t                    }|                                S dS )
zo
    Compute the next run time for a schedule.

    Returns ISO timestamp string, or None if no more runs.
    rP   r[   rv   rO   rQ   r]   r   NrX   )
rj   r|   ru   r   rd   r   rh   rb   r   r   )rL   rw   r   rQ   lastnext_runr   s          r   compute_next_runr     s    --C6!!*8SkRRRR	&	Z	'	'9% 	8 !7!D!DEEDi8888HH Yw7777H!!###	&	V	#	# 	4(#..==**!!###4r    c                  h   t                       t                                          sg S 	 t          t          dd          5 } t	          j        |           }|                    dg           cddd           S # 1 swxY w Y   dS # t          j        $ r 	 t          t          dd          5 } t	          j        | 	                                d          }|                    dg           }|r)t          |           t                              d           |cddd           cY S # 1 swxY w Y   Y dS # t          $ r3}t                              d	|           t          d
|           |d}~ww xY wt           $ r3}t                              d|           t          d|           |d}~ww xY w)zLoad all jobs from storage.rutf-8encodingjobsNF)strictz8Auto-repaired jobs.json (had invalid control characters)z#Failed to auto-repair jobs.json: %sz*Cron database corrupted and unrepairable: zIOError reading jobs.json: %szFailed to read cron database: )r9   	JOBS_FILEr/   openjsonloadr$   JSONDecodeErrorloadsread	save_jobsloggerwarningrc   errorRuntimeErrorIOError)fdatar   ro   s       r   	load_jobsr   @  sc   MMM 	H)S7333 	(q9Q<<D88FB''	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(  X X X	Xiw777 1z!&&((5999xx++ _dOOONN#]^^^                      	X 	X 	XLL>BBBOAOOPPVWW	X  H H H4a888?A??@@aGHs   B *A9,B 9A==B  A=B F1D5-A+D'D5$F1'D+	+D5.D+	/D55
E2?.E--E22F1>.F,,F1r   c                    t                       t          j        t          t          j                  dd          \  }}	 t          j        |dd          5 }t          j	        | t                                                      d|d	           |                                 t          j        |                                           d
d
d
           n# 1 swxY w Y   t          j        |t                     t!          t                     d
S # t"          $ r( 	 t          j        |           n# t&          $ r Y nw xY w w xY w)zSave all jobs to storage..tmpz.jobs_dirsuffixprefixwr   r   )r   
updated_atr=   )indentN)r9   tempfilemkstempr   r   parentr(   fdopenr   dumprj   rh   flushfsyncfilenore   r0   BaseExceptionunlinkr*   )r   fdtmp_pathr   s       r   r   r   ]  ss   MMM#I,<(=(=fU]^^^LBYr3111 	!QIt;==3J3J3L3LMMqYZ[[[[GGIIIHQXXZZ   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	
8Y'''Y   	Ih 	 	 	D	sU   D A3CD CD C1D 
ED21E2
D?<E>D??Epromptnamerepeatdeliveroriginmodelproviderbase_urlscriptc           	         t          |          }||dk    rd}|d         dk    r|d}||rdnd}t          j                    j        dd         }t	                                                      }t          ||          }t          |t                    r!t          |          	                                nd}t          |	t                    r!t          |	          	                                nd}t          |
t                    r4t          |
          	                                
                    d	          nd}|pd}|pd}|pd}t          |t                    r!t          |          	                                nd}|pd}| p|r|d         ndpd
}i d|d|p|dd         	                                d| d|d|r|d         ndd|d|d|d|d|d|                    d|          d|ddddddddddd |t          |          dddd||d!}t                      }|                    |           t          |           |S )"a2  
    Create a new cron job.

    Args:
        prompt: The prompt to run (must be self-contained, or a task instruction when skill is set)
        schedule: Schedule string (see parse_schedule)
        name: Optional friendly name
        repeat: How many times to run (None = forever, 1 = once)
        deliver: Where to deliver output ("origin", "local", "telegram", etc.)
        origin: Source info where job was created (for "origin" delivery)
        skill: Optional legacy single skill name to load before running the prompt
        skills: Optional ordered list of skills to load before running the prompt
        model: Optional per-job model override
        provider: Optional per-job provider override
        base_url: Optional per-job base URL override
        script: Optional path to a Python script whose stdout is injected into the
                prompt each run.  The script runs before the agent turn, and its output
                is prepended as context.  Useful for data collection / change detection.

    Returns:
        The created job dict
    Nr   rP   r[   r<   r   local   /zcron jobidr   2   r   r   r   r   r   r   r   rL   schedule_displayrR   r   )times	completedenabledTstate	scheduled	paused_atpaused_reason
created_at)next_run_atrw   last_status
last_errorlast_delivery_errorr   r   )rq   uuiduuid4hexrj   rh   r   r   r   r   rstripr$   r   r   r   r   )r   rL   r   r   r   r   r   r   r   r   r   r   parsed_schedulejob_idr   normalized_skillsnormalized_modelnormalized_providernormalized_base_urlnormalized_scriptlabel_sourcer!   r   s                          r   
create_jobr   p  s   H %X..O fkk v&((V^ $1(('Z\\crc"F
--
!
!
#
#C-eV<<-7s-C-CMs5zz'')))3=h3L3LV#h----///RV?I(TW?X?Xb#h----//66s;;;^b'/4-5-5/9&#/F/FPF))+++D)1TS7HR033dbXbLf1SbS)//11 	& 	#	
 	):D"1%% 	! 	' 	' 	# 	O 	O//	8DD 	
 
  	4!" 	#$ 	T%& 	'( 	c)* (88#9  C> ;;DKKdOOOJr    r   c                 f    t                      }|D ]}|d         | k    rt          |          c S  dS )zGet a job by ID.r   N)r   r%   )r   r   r!   s      r   get_jobr     sG    ;;D , ,t9&s+++++ 4r    include_disabledc                 R    d t                      D             }| sd |D             }|S )z2List all jobs, optionally including disabled ones.c                 ,    g | ]}t          |          S r8   r%   rU   js     r   
<listcomp>zlist_jobs.<locals>.<listcomp>  s!    888q""888r    c                 >    g | ]}|                     d d          |S )r   T)r$   r   s     r   r   zlist_jobs.<locals>.<listcomp>  s+    :::a155D#9#9::::r    )r   )r   r   s     r   	list_jobsr     s9    88IKK888D ;::4:::Kr    updatesc           
         t                      }t          |          D ]\  }}|d         | k    rt          i ||          }d|v }d|v sd|v rJt          |                    d          |                    d                    }||d<   |r|d         nd|d<   |r|d         }t          |t                    rt          |          }||d<   |                    d|                    d|                    d                              |d<   |                    d	          d
k    rt          |          |d<   |                    dd          rF|                    d	          d
k    r-|                    d          st          |d                   |d<   |||<   t          |           t          ||                   c S dS )zCUpdate a job by ID, refreshing derived schedule fields when needed.r   rL   r   r   r   Nr   rR   r   pausedr   r   T)
r   	enumerater%   r   r$   r   r   rq   r   r   )	r   r   r   ir!   updatedschedule_changedr   updated_schedules	            r   
update_jobr     s   ;;DD//  ,  ,3t9%&8&8&899%0w'W"4"4 5gkk'6J6JGKKX`LaLa b b 1GH7HR033dGG 	L&z2 *C00 7#12B#C#C &6
#*1++" $$Y<N0O0OPP+ +G&' {{7##x//)9:J)K)K&;;y$'' 	KGKK,@,@H,L,LU\U`U`anUoUo,L%5gj6I%J%JGM"Q$"47+++++4r    reasonc                 h    t          | ddt                                                      |d          S )z Pause a job without deleting it.Fr   )r   r   r   r   )r   rj   rh   )r   r   s     r   	pause_jobr     s=    $0022#		
 	
  r    c           	      ~    t          |           }|sdS t          |d                   }t          | dddd|d          S )z=Resume a paused job and compute the next future run from now.NrL   Tr   r   r   r   r   r   )r   r   r   )r   r!   r   s      r   
resume_jobr     sY    
&//C t"3z?33K !&	
 	
	 	 	r    c           	          t          |           }|sdS t          | ddddt                                                      d          S )z1Schedule a job to run on the next scheduler tick.NTr   r   )r   r   rj   rh   )r   r!   s     r   trigger_jobr   .  sX    
&//C t !&==2244	
 	
	 	 	r    c                      t                      }t          |          } fd|D             }t          |          |k     rt          |           dS dS )zRemove a job by ID.c                 ,    g | ]}|d          k    |S )r   r8   )rU   r   r   s     r   r   zremove_job.<locals>.<listcomp>C  s'    111!qw&00A000r    TF)r   r`   r   )r   r   original_lens   `  r   
remove_jobr  ?  sV    ;;Dt99L1111t111D
4yy<$t5r    successr   delivery_errorc                    t                      }t          |          D ]F\  }}|d         | k    r3t                                                      }||d<   |rdnd|d<   |s|nd|d<   ||d<   |                    d	          r|d	                             d
d          dz   |d	         d
<   |d	                             d          }|d	         d
         }	|3|dk    r-|	|k    r'|                    |           t          |            dS t          |d         |          |d<   |d         d|d<   d
|d<   n|                    d          dk    rd|d<   t          |            dS Ht          	                    d|            dS )uK  
    Mark a job as having been run.
    
    Updates last_run_at, last_status, increments completed count,
    computes next_run_at, and auto-deletes if repeat limit reached.

    ``delivery_error`` is tracked separately from the agent error — a job
    can succeed (agent produced output) but fail delivery (platform down).
    r   rw   okr   r   Nr   r   r   r   r   r<   r   rL   r   Fr   r   r   r   z0mark_job_run: job_id %s not found, skipping save)
r   r   rj   rh   r$   popr   r   r   r   )
r   r  r   r  r   r   r!   r   r   r   s
             r   mark_job_runr  J  s    ;;DD// ! !3t9--))++C!$C)0!=gC-4 >$C)7C%& wwx   
-0]->->{A-N-NQR-RHk* H))'22M+6	$yE7I7IHHQKKKdOOOFF "2#j/3!G!GC =!)!&I*G!!X--*GdOOOFFA D NNEvNNNNNr    c                 z   t                      }|D ]}|d         | k    r|                    di                               d          }|dvr dS t                                                      }t	          |d         |          }|r0||                    d          k    r||d<   t          |            dS  dS dS )u  Preemptively advance next_run_at for a recurring job before execution.

    Call this BEFORE run_job() so that if the process crashes mid-execution,
    the job won't re-fire on the next gateway restart.  This converts the
    scheduler from at-least-once to at-most-once for recurring jobs — missing
    one run is far better than firing dozens of times in a crash loop.

    One-shot jobs are left unchanged so they can still retry on restart.

    Returns True if next_run_at was advanced, False otherwise.
    r   rL   rP   r   rO   Fr   T)r   r$   rj   rh   r   r   )r   r   r!   rP   r   new_nexts         r   advance_next_runr  |  s     ;;D  t977:r**..v66D///uu--))++C'J==H H(>(>>>%-M"$tt55  5r    c            	      f   t                      } t                      }d t          j        |          D             }g }d}|D ]}|                    dd          s|                    d          }|st          |                    di           | |                    d                    }|sm||d<   |}t                              d	|                    d
|d                   |           |D ]}|d         |d         k    r	||d<   d} nt          t          j
        |                    }	|	| k    r|                    di           }
|
                    d          }t          |
          }|dv r| |	z
                                  |k    r~t          |
|                                           }|rZt                              d|                    d
|d                   |||           |D ]}|d         |d         k    r	||d<   d} n|                    |           |rt!          |           |S )aO  Get all jobs that are due to run now.

    For recurring jobs (cron/interval), if the scheduled time is stale
    (more than one period in the past, e.g. because the gateway was down),
    the job is fast-forwarded to the next future run instead of firing
    immediately.  This prevents a burst of missed jobs on gateway restart.
    c                 ,    g | ]}t          |          S r8   r   r   s     r   r   z get_due_jobs.<locals>.<listcomp>  s!    DDDq""DDDr    Fr   Tr   rL   rw   rv   z:Job '%s' had no next_run_at; recovering one-shot run at %sr   r   rP   r	  zSJob '%s' missed its scheduled time (%s, grace=%ds). Fast-forwarding to next run: %s)rj   r   copydeepcopyr$   r|   r   inforu   r   rd   r   r   r   rh   r   r   )r   raw_jobsr   due
needs_saver!   r   recovered_nextrjnext_run_dtrL   rP   r   r
  s                 r   get_due_jobsr    s    --C{{HDDDM(,C,CDDDD
CJ 9 9wwy$'' 	77=)) 	8
B''GGM22  N
 " !/C%HKKLD	**  
   d8s4y(((6B}%!%JE )
 $H$:8$D$DEE#wwz2..H<<''D
 +844E+++{1B0Q0Q0S0SV[0[0[ ,HcmmooFF KK:D	22     ' " "d8s4y0008B}-)-J!E 1 JJsOOO (Jr    c                    t                       t          | z  }|                    dd           t          |           t	                                          d          }|| dz  }t          j        t          |          dd          \  }}	 t          j
        |dd	
          5 }|                    |           |                                 t          j        |                                           ddd           n# 1 swxY w Y   t          j        ||           t!          |           n5# t"          $ r( 	 t          j        |           n# t&          $ r Y nw xY w w xY w|S )zSave job output to file.Tr2   z%Y-%m-%d_%H-%M-%Sz.mdr   z.output_r   r   r   r   N)r9   r7   r6   r-   rj   ri   r   r   r   r(   r   writer   r   r   re   r0   r   r   r*   )r   r   job_output_dir	timestampoutput_filer   r   r   s           r   save_job_outputr    s   MMM&(N555&&':;;I i#4#4#44K#N(;(;FS]^^^LBYr3111 	!QGGFOOOGGIIIHQXXZZ   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	
8[)))[!!!!   	Ih 	 	 	D	 sU   D3 'AD7D3 DD3 
D'D3 3
E%>EE%
E E%E  E%)NN)N)
NNNNNNNNNN)F)?__doc__r  r   loggingr   r(   rC   r   r   r   pathlibr   hermes_constantsr   typingr   r   r	   r
   	getLogger__name__r   hermes_timer   rj   r   rb   ImportErrorresolve
HERMES_DIRr5   r   r7   rz   r   r   r%   r-   r0   r9   rF   rK   rq   ru   r|   r#   r   r   r   r   r   r   boolr   r   r   r   r   r  r  r  r  r  r8   r    r   <module>r*     s        				 				  ( ( ( ( ( ( ( (       , , , , , , , , , , , , , , , , , ,		8	$	$ * * * * * *!!!!!!LL   LLL _&&((
{"	 
  # x} X\]`Xa    "T#s(^ S#X    d    t      %c %c % % % %*VS VT#s(^ V V V Vr$h $8 $ $ $ $. "&	  38n	 #	
 c]   6T c    @ tCH~ HSM U]^aUb    HH4S#X' H H H H:Dc3h(    ,  !'+"&"" c ccc 3-c SM	c
 c]c T#s(^$c C=c T#Yc C=c smc smc SMc 
#s(^c c c cLC HT#s(^4      d38n1E    $s $T#s(^ $c3h8P $ $ $ $N
 
c 
8C= 
HT#s(^<T 
 
 
 
s xS#X7    & c3h 8    "s t     EI15/O /O /Ot /OHSM /O!)#/O /O /O /OdS T    8Ld4S>* L L L L^C       s   A A)(A)