
    @jq                    <   S SK JrJrJrJ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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  S SKJr  \" \5      r\
R4                  " S5      R7                  \
R8                  5        SrSrSrS	r S
r!Sr"Sr#Sr$\	RJ                  RM                  \	RJ                  RO                  \(5      5      r)\	RT                  RW                  S5      =(       da    \	RJ                  RY                  \	RT                  RW                  S5      =(       d$    \	RT                  RW                  S5      =(       d    \)SS5      r-\	RJ                  RY                  \-S5      r.\	R^                  " \.SS9  S r0\0" S5      r1\0" S5      r2\0" S5      r3Sr4\	RJ                  RY                  \.S5      r5\	RJ                  RY                  \.S5      r6\	RJ                  RY                  \.S5      r7S r8S r9S r:S  r;S! r<\Rz                  " 5       r>Sq?/ S SS".q@S# rAS$ rBS% rCS& rDS' rES( rFS) rGS* rHS+ rIS, rJS- rKSS. jrLS/ rMS0 rNS1 rOS2 rPS3 rQS4 rRS5 rSS6 rTS7rUS7rVSrWS8rXS9rYS:rZS;r[S<r\S=r]S>r^S r_S?r`SraS@rbSArcSBrdSCreSrfSDrgSErhSFriSCrjSGrk\	RJ                  RY                  \)SH5      rlSSISJSBSCSKSL.SSMSMSNSSSSSSSO.	S7SPSQSPSR.SSSSTSUSR.SV.S7SPSQSPSR.S7SPSQSPSR.SV.SW.SX.rmSY rnSZ ro\n" 5       qpSN[        R                  S[0 5      S\'   \Rz                  " 5       rrS7SS0 S].rsS^ rtS_ ruSS` jrvSa rwSb rxSc rySSd jrzSe r{\R                  " Sf5      q}\{" 5       q~0 SgSh_SiSj_SkSh_SlSh_SmSj_SnSj_SoSp_SqS _SrSh_SsSp_StSp_SuSp_SvSp_SwSp_SxSy_SzSh_S{Sh_0 S|Sh_S}Sh_S~Sp_SSp_SSj_SSj_SS_SSj_SSj_SSh_SSh_SS_S\R                  " S5      _SS_SS_S\W_SSj_ESSy\aSjS\fSjSS.Eq\" 5       r\" 5       r\" 5       rSqSqSq\" 5       r\" 5       r\" 5       r\" 5       r\" 5       r\" 5       r\" 5       r\" 5       rSqSqSqSPqSPrSqS rS rS rS rS rS r\" 5         S rS r\" 5         \GR9                  S5      S 5       r\GR9                  S5      S 5       r\GR9                  S5      S 5       r\GR9                  SSS/S9S 5       r\GR9                  SSS/S9S 5       r\GRD                  S 5       rS rS rS rS rS r\" 5         S rS rS rS rS rS rS rS rSS jrS rS rSS jrS rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS r\GR9                  S5      S 5       r\GR9                  SSS/S9S 5       r\GR9                  S5      S 5       r\S:X  Ga  \GR                  " \wSS9r\GR                  5         \GR                  " \SS9r\GR                  5         \GR                  " \TSS9r\GR                  5         \GR                  " \SS9r\GR                  5         \GR                  " \SS9r\GR                  5         \GR                  " \SS9r\GR                  5         \GR                  S\" \	RT                  RW                  SS5      5      SS9  gg)    )Flaskjsonifysend_from_directoryrequestN)datetime	timedelta)dequewerkzeugCOM3i`	  g?x   iX  i  {Gz?NWALS_DATA_DIRLOCALAPPDATAAPPDATANWALScannerPlatformdatapeetTexist_okc                 t   [         R                  R                  [        U 5      n[         R                  R	                  U5      (       a  U$ / n[         R                  R                  [        5      n [         R                  " U5       GH  nUR                  5       (       a.  UR                  R                  5       R                  S5      (       d  MI  [         R                  R                  [         R                  R                  UR                  5      5      [         R                  R                  [        5      :X  a  M  [         R                  R                  UR                  U 5      n[         R                  R                  U5      (       d  GM  UR                  U5        GM     UR#                  S SS9  U(       dY  [         R                  R                  [        U 5      n[         R                  R                  U5      (       a  UR                  U5        U HV  n [%        USSS9 n[&        R(                  " U5        SSS5        [*        R,                  " XQ5        [/        S	U S
U 35          U$    U$ ! [          a     Nf = f! , (       d  f       NL= f! [         [0        [&        R2                  4 a     M  f = f)zDImport the freshest valid legacy history file on first V014 startup.znwal-scanner-platform-vc                 @    [         R                  R                  U 5      $ N)ospathgetmtime)r   s    peet_server_hazcams_wu.py<lambda>*migrate_legacy_peet_file.<locals>.<lambda>D   s    RWW%5%5d%;    T)keyreverserutf-8encodingNzMigrated PEET history: z -> )r   r   joinPEET_DATA_DIRexistsdirname
SCRIPT_DIRscandiris_dirnamelower
startswithnormcaseabspathisfileappendOSErrorsortopenjsonloadshutilcopy2print
ValueErrorJSONDecodeError)filenametarget
candidatesplatform_parententry	candidatelocal_legacysources           r   migrate_legacy_peet_filerF   0   s   WW\\-2F	ww~~fJggooj1O
ZZ0E<<>>)9)9);)F)FG`)a)aww

 ;<@P@PQ[@\\UZZ:Iww~~i((!!), 1 OO;TOJ ww||J977>>,''l+		iw76		&! 8LL++I;d6(CD M   M'   87
 T%9%9: 		sC   1DI3 I3 J&J=/J3
J ?J 
J	JJ76J7zpeet_daily_extremes.jsonzpeet_10min_gust_samples.jsonzpeet_rain_24hr_history.jsoniQ zwxpanel_minute_history.sqlite3zwxpanel_rainfall_seed.jsonzwxpanel_climate_normals.jsonc                       [        [        SSS9 n [        R                  " U 5      nS S S 5        [	        W[
        5      (       a  U$ 0 $ ! , (       d  f       N'= f! [         a    0 s $ f = fNr"   r#   r$   )r6   WX_CLIMATE_NORMALS_FILEr7   r8   
isinstancedict	Exceptionfr   s     r   _load_climate_normalsrO   d   s_    )3AQ1D B!$--t525 BA  	-   A A	A A 	
AA A)(A)c                 ,   [         R                  " [         R                  R                  [        5      SS9  [        S-   n[        USSS9 n[        R                  " XSS9  S S S 5        [         R                  " U[        5        g ! , (       d  f       N*= f	NTr   z.tmpwr#   r$      indent)	r   makedirsr   r)   rI   r6   r7   dumpreplacer   tmprN   s      r   _save_climate_normalsr\   m   sa    KK 784H&(C	c3	)Q		$!$ 
*JJs+, 
*	)   B
Bc                       [        [        SSS9 n [        R                  " U 5      nS S S 5        [	        W[
        5      (       d  0 $ U$ ! , (       d  f       N'= f! [         a    0 s $ f = frH   )r6   WX_RAIN_SEED_FILEr7   r8   rJ   rK   rL   rM   s     r   _load_rainfall_seedr`   u   s]    #S7;q1D <$%%I	 <;
  	rP   c                 ,   [         R                  " [         R                  R                  [        5      SS9  [        S-   n[        USSS9 n[        R                  " XSS9  S S S 5        [         R                  " U[        5        g ! , (       d  f       N*= frR   )	r   rW   r   r)   r_   r6   r7   rX   rY   rZ   s      r   _save_rainfall_seedrb      sa    KK 12TBF"C	c3	)Q		$!$ 
*JJs%& 
*	)r]   c                    [        5       n[        U[        5      (       a3  UR                  S5      =(       d    0 R                  [	        U 5      0 5      O0 n0 nU=(       d    0 R                  5        HX  u  pE [        U5      n[        U5      nSUs=::  a  S::  a/  O  M-  [        R                  " U5      (       a  US:  a  XsU'   MT  MV  MX  MZ     U$ ! [         a     Mk  f = f)Nyears      r   )r`   rJ   rK   getstritemsintfloatmathisfiniterL   )yearr   rawoutkvmns           r   _seed_months_for_yearru      s    		D8B48N8N'		 b%%c$i4TVC
C	r  "	!fAaaA||a 0 0Q!VA 6< 0| # J  		s   6%C%C
CC)ri   updatedEpochhistoryStartEpochc                     [         R                  " [        SS9n U R                  S5        U R                  S5        U R                  S5       Vs1 s H  oS   iM	     nnSU;  a  U R                  S5        S	U;  a  U R                  S
5        SU;  a  U R                  S5        SU;  a  U R                  S5        U R	                  5         U R                  S5        U $ s  snf )N
   timeoutzPRAGMA journal_mode=WALa  CREATE TABLE IF NOT EXISTS minute_obs (
        ts INTEGER PRIMARY KEY, temp REAL, dewpoint REAL, humidity REAL, pressure REAL, wind REAL,
        heat_index REAL, wind_chill REAL, rain_rate REAL, rain_today REAL,
        wind_avg_2min_max REAL, gust_10min_max REAL
    )zPRAGMA table_info(minute_obs)re   
rain_todayz1ALTER TABLE minute_obs ADD COLUMN rain_today REALhumidityz/ALTER TABLE minute_obs ADD COLUMN humidity REALwind_avg_2min_maxz8ALTER TABLE minute_obs ADD COLUMN wind_avg_2min_max REALgust_10min_maxz5ALTER TABLE minute_obs ADD COLUMN gust_10min_max REALz>CREATE INDEX IF NOT EXISTS idx_minute_obs_ts ON minute_obs(ts))sqlite3connectWX_HISTORY_DBexecutecommit)connrowcolss      r   _history_connr      s    ??="5DLL*+LL 	 
 #ll+JKLKsFKDL4HIFG$&OPt#LMKKMLLQRK Ms   Cc                      [        U 5      n[        R                  " U5      (       a  U$ S $ ! [        [        4 a     g f = fr   )rk   rl   rm   	TypeErrorr<   valuert   s     r   
_valid_numr      s@    %LMM!$$q.$.z" s   ', , ??c                 :   [        [        R                  " 5       5      nXS-  -
  n[        U:X  a  g [        U R	                  S5      5      nUc  g [        U R	                  S5      5      n[        U R	                  S5      5      n[        U R	                  S5      5      n[        U R	                  S5      5      n[        U R	                  S5      5      n[        U R	                  S5      5      n	[        U R	                  S	5      5      n
U	c  [        U R	                  S
5      5      n	U
c  [        U R	                  S5      5      n
U
nUb  US:  a  [        X65      OS nU	b  U	O[        U R	                  S5      5      nUb  US::  a  US:  a  [        X=5      OS n [           [        5       nUR                  SX#XFX[XXxX45        UR                  5         UR                  5         S S S 5        Uqg ! , (       d  f       N= f! [         a  n[        SU 35         S nAg S nAff = f)N<   OutdoorTempOutdoorDewpointPressure
OutdoorHumRainRate	RainTodayMinuteMax2MinAverageWindMinuteMax10MinGustWindAverageRecentmaxgustP   
WindLatest2      zINSERT OR REPLACE INTO minute_obs(
                ts,temp,dewpoint,humidity,pressure,wind,heat_index,wind_chill,rain_rate,rain_today,
                wind_avg_2min_max,gust_10min_max
            ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)zWX HISTORY write error: )rj   time_last_history_minuter   rg   calc_heat_index_fcalc_wind_chill_f_history_lockr   r   r   closerL   r;   )snapshotnow	minute_tstempdewpressurer}   	rain_rater|   wind_avg_maxgust_maxwind_legacyhiwc_windwcr   es                    r   record_minute_historyr      s   
diik
CRx Iy(hll=12D|
X\\"34
5C(,,z23H(,,|45H8<<
34IHLL56J hll+EFGL(,,';<=H!(,,}"=>hll?;<K.6.Btrz	4	*W[B*6lJx||T`Ga<bG-4-@TRZT[_`T`	4	)fjB. ?DLL 1 X"	]is	u
 KKM4::<   ) ]  .(,--.s1   G8 AG'
G8 '
G51G8 8
HHHc           	      H   US:X  a+  U [        U R                  5       S9-
  R                  SSSSS9nO7US:X  a  U R                  SSSSSS9nOUS:X  a  U R                  SSSSSSS	9nOS nU(       a  [        UR	                  5       5      OS [        U R	                  5       5      4$ )
Nweekdaysr   )hourminutesecondmicrosecondmonthre   )dayr   r   r   r   rn   )r   r   r   r   r   r   )r   weekdayrY   rj   	timestamp)now_dtperiodstarts      r   _period_boundsr      s    ))9::CCRS[\ijCk	7	1!1A!L	6	Q1!1ARST%*3u !c&:J:J:L6MMMr   c                 l    Uc  SOSnUc  U4OX4nU R                  SU 3U5      R                  5       nU$ )Nts<=?ts>=? AND ts<=?zSELECT MIN(temp),MAX(temp),MIN(dewpoint),MAX(dewpoint),
        MIN(pressure),MAX(pressure),MAX(wind),MAX(heat_index),MIN(wind_chill),MAX(rain_rate)
        FROM minute_obs WHERE r   fetchone)r   start_tsend_tswhereparamsr   s         r   _range_statsr      sQ    'G->E"*fY0AF
,, $g)*022:(*  Jr   c                 2   [        UR                  UR                  UR                  5      nU[	        SS9-   nU R                  S[        UR                  5       5      [        UR                  5       5      45      R                  5       nU(       a  US   b  U$ S $ )Nre   r   z?SELECT MIN(temp),MAX(temp) FROM minute_obs WHERE ts>=? AND ts<?r   )	r   rn   r   r   r   r   rj   r   r   )r   r   r   endr   s        r   _day_temp_statsr      s|    SXXcii0E
)#
#C
,,XEOO-.s3==?/CDFFNhj #a&,36$6r   c                     Uc  gU R                  SX45      R                  5       nU Vs/ s H  oDS   c  M
  [        US   5      PM     nnU(       a  [        U5      $ S$ s  snf )a  Return accumulated rainfall for a calendar period from RainToday snapshots.

RainToday is a running daily total and resets at midnight.  Summing each local
day's maximum preserves the actual daily accumulation without multiplying the
total by every one-minute archive row.
Nz
        SELECT date(ts, 'unixepoch', 'localtime') AS local_day, MAX(rain_today)
        FROM minute_obs
        WHERE ts>=? AND ts<=? AND rain_today IS NOT NULL AND rain_today>=0
        GROUP BY local_day
    re   r   fetchallrk   sumr   r   r   rowsr"   valss         r   _period_rainfallr      sn     << 
 	
 &XZ 	  $	8t!t+%!+tD	83t9&$& 
9s
   	AAc                   ^	^
 [        U5      m
[        USS5      nU R                  S[        UR	                  5       5      [        U5      45      R                  5       n0 m	U H>  u  pVnUc  M  T	R                  [        U5      S5      [        U5      -   T	[        U5      '   M@     [        T	5      [        T
5      -  nU(       d  g[        U	U
4S jU 5       5      $ )a*  Combine optional manually seeded monthly totals with live archived rainfall.

A seeded month replaces archive-derived rainfall for that calendar month,
preventing double counting when a client imports completed months during
station onboarding. Unseeded months continue to use live minute history.
re   a=  
        SELECT CAST(strftime('%m', ts, 'unixepoch', 'localtime') AS INTEGER) AS local_month,
               date(ts, 'unixepoch', 'localtime') AS local_day, MAX(rain_today)
        FROM minute_obs
        WHERE ts>=? AND ts<=? AND rain_today IS NOT NULL AND rain_today>=0
        GROUP BY local_month, local_day
    N        c              3   h   >#    U  H'  nTR                  UTR                  US 5      5      v   M)     g7f)r   Nrg   ).0rs   archive_by_monthseedss     r   	<genexpr>+_year_rainfall_with_seed.<locals>.<genexpr>  s-     I&Quyy,003788&s   /2)
ru   r   r   rj   r   r   rg   rk   setr   )r   rn   r   r   r   r   _dayvalmonthsr   r   s            @@r   _year_rainfall_with_seedr     s      
%E
4!
E	  eoo
 #f+	.
0 19
 	  S?)9)=)=c%j)MeTWj)XSZ( !  3u:-FI&IIIr   c           
      Z   U (       d  U $ [        U 5      nSS[        4SS[        4SS[        4SS[        4S	S
[        4SS[        4SS[        4SS[        4/nU HE  u  p4n[        [        R                  U5      5      nUc  M)  [        X   5      nUc  UOU" Xv5      X'   MG     [        U5      $ )zMerge parser-known daily extrema into archive stats for periods containing today.

The 1-minute archive can miss brief gusts and, when newly created, does not include
observations from earlier in the current day. The live daily-extremes engine does.
r   LowTempTodayre   HighTempTodayrT   LowDewpointTodayr   HighDewpointToday   LowPressToday   HighPressToday   HighGustToday	   HighRainRateToday)listminmaxr   daily_extremesrg   tuple)period_statsr   mappingsidxr    fnlivecurs           r   _merge_today_extremesr      s     	l	D 
NC 1os";	
$q*=s&C	
OS!A'7#=	
OS!A':C#@	H 
**3/0<ty!+$2c<	  ;r   c                 L   [         R                  " [        U 5      5      nUR                  5       UR                  5       :X  a  gUR                  5       U[	        SS9-
  R                  5       :X  a  g[
        R                  S:w  a  UR                  S5      $ UR                  S5      $ )NTodayre   r   	Yesterdayntz	%a %b %-dz	%a %b %#d)r   fromtimestamprj   dater   r   r-   strftime)tsr   ds      r   _display_dayr  8  sx    SW%Avvxvvx&**0022&(ggo1::k"R1::k;RRr   c                    Uc  SOSnUc  U4OX4nU R                  SU S3U5      R                  5       n/ nU Hp  u  px[        U5      n	[        U5      n
U	b  U
b  SU
s=:  a  S::  d  O  M2  [        X5      nUc  MB  [        R
                  " U5      (       d  M_  UR                  U5        Mr     U(       a  [        U5      [        U5      4$ S$ )a  Recompute dew-point extrema from archived FINAL temp + humidity.

V064 made live dew point derived from the final selected/calibrated temperature
and humidity. Older minute rows can contain a stale pre-V064 dewpoint column, so
longer-period extrema must not trust that stored derived value. Recomputing here
also keeps month/year/all-time dew statistics consistent with the values WxPanel
actually displayed.
r   r   z,SELECT temp, humidity FROM minute_obs WHERE z. AND temp IS NOT NULL AND humidity IS NOT NULLr   d   NN)	r   r   r   calc_dewpoint_frl   rm   r3   r   r   )r   r   r   r   r   r   r   r   r}   thr  s               r   _derived_dew_extremar  @  s      'G->E"*fY0BF<<FugM{|  E  F  O  O  QDDt*X"6a9	!a,3,A!=T]]1--KKN  &*CIs4y!;|;r   c                    Uc  SOSnUc  U4OX4nU R                  SU 3U5      R                  5       nU(       a  US   c  g [        U5      nU(       a  SS[        4SS[        4SS	[        4S
S[        4SS[        4SS[        4SS[        4SS[        4SS[        4SS[        4SS[        4SS[        4SS[        4/nU HE  u  pn[        [        R                  U
5      5      nUc  M)  [        Xy   5      nUc  UOU" X5      Xy'   MG     / SQn[        [        X5      5      n[        XU5      u  nnUb  UUS'   Ub  UUS '   UR                  S!5      US"'   U$ )#Nr   r   a  SELECT
        MIN(temp), MAX(temp),
        MIN(CASE WHEN wind_chill IS NOT NULL AND wind_chill < temp THEN wind_chill ELSE temp END),
        MAX(CASE WHEN heat_index IS NOT NULL AND heat_index > temp THEN heat_index ELSE temp END),
        MIN(dewpoint), MAX(dewpoint), MIN(humidity), MAX(humidity),
        MIN(pressure), MAX(pressure), MAX(rain_rate),
        MAX(COALESCE(gust_10min_max,wind)), MAX(wind_avg_2min_max)
        FROM minute_obs WHERE r   r   re   r   rT   LowFeelsLikeTodayr   HighFeelsLikeTodayr   r   r   r   r   LowHumToday   HighHumToday   r   r   r   ry   r      r   rf   HighAvgWindToday)tempLowtempHighfeelsLow	feelsHighdewLowdewHighhumLowhumHighpressureLowpressureHighrainRateHighwindHighwindAvgHighr  r  r$  gustHigh)r   r   r   r   r   r   r   rg   rK   zipr  )r   r   r   include_todayr   r   r   r   live_mapir    r   rr   r   keysrp   dew_lowdew_highs                     r   _period_extremesr.  V  s   'G->E"*fY0AF   %g) +1	2 3;(*  #a&.	cD~c"Aoc#:"3'+?(D!#&*=c'B}S!1^C"8s#Q'7$<#C("_S)A2FXY\B]
 !HA"++C01Ay(47#CA"S)DG	 !
 
bDS^C -TVDGXH!IGGJ'C
OJr   c                  4   [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S	5      5      [        [        R                  S
5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      [        [        R                  S5      5      S.$ )Nr   r   r  r  r   r   r  r  r   r   r   r   r  )r  r  r  r  r  r  r  r   r!  r"  r#  r$  r&  r%  )r   r   rg    r   r   _daily_extreme_payloadr1    s   n00@A~11/BC~112EFG 2 23G HI^//0BCDn001DEF^//>?n00@A!."4"4_"EF">#5#56F#GH">#5#56I#JK~11/BC~11/BC!."4"45G"HI r   c                    [        [        UR                  UR                  UR                  5      R                  5       5      n[        UR                  UR                  UR                  5      [        SS9-   nU R                  SU[        UR                  5       5      45      R                  5       nU Vs0 s H   owS   c  M
  US   c  M  US   US   US   /_M"     nnUR                  S5      n	XR                  5       s=::  a  U::  aK  O  U$ [        [        R                  S5      5      n
[        [        R                  S5      5      nU
b  Ub  X/X'   U$ s  snf )	zEReturn daily observed high/low rows for climate-average calculations.re   r   z
        SELECT date(ts,'unixepoch','localtime') d, MIN(temp), MAX(temp)
        FROM minute_obs WHERE ts>=? AND ts<? GROUP BY d ORDER BY d
    rT   r   %Y-%m-%dr   r   )rj   r   rn   r   r   r   r   r   r   r  r  r   r   rg   )r   	start_dayend_dayr   r   end_dtr   r"   rp   	today_keylor   s               r   _daily_observed_statsr9    s/   )..GQQSTHGLLw{{;I1<MMF	  
#f&&()*
, -5HJ 	 %)RDqaD	1	1!QqT{	DCRooj)IKKM,W, J n((89jI[I[\kIl>m2>bn7CNJ 	Ss   5	EE
Ec           	      	   [        5       nUR                  S5      =(       d    0 n[        U[        5      (       d  / $ / nUR                  UR	                  S5      5      =(       d    0 n[        UR                  S5      5      n[        UR                  S5      5      n[        UR                  S5      5      n	[        [        R                  S5      5      n
[        [        R                  S5      5      nU
b0  Ub-  [        X-
  5      oR                  SU(       a  US	 S
3OSS.5        Ub0  Ub-  [        X-
  5      oR                  SU(       a  US	 S
3OSS.5        UR                  SS9R                  5       n[        UR                  5       U-
  R                  S-   5       Vs/ s H  o[        US9-   PM     nn/ nU H  nUR                  UR	                  S5      5      =(       d    0 n[        UR                  S5      5      [        UR                  S5      5      [        UR                  S5      5      [        UR                  S5      5      4n[        S U 5       5      (       a  / n  OUR                  U5        M     U(       Ga<  [        XUR                  5       U5      n[!        U5      [!        U5      :X  Ga=  UR#                  5        Vs/ s H  nUS   PM
     nnUR#                  5        Vs/ s H  nUS   PM
     nn[%        U5      [!        U5      -  n[%        U5      [!        U5      -  n[%        S ['        UU5       5       5      [!        U5      -  n[%        S U 5       5      [!        U5      -  n[%        S U 5       5      [!        U5      -  n[%        S U 5       5      [!        U5      -  nSUU4SUU4SUU44 H5  u  nnn [        UU -
  5      oR                  UU(       a  US	 S
3OSS.5        M7     UR                  S5      =(       d    0 R                  S5      n![%        S U 5       5      n"U R)                  S5      R+                  5       S   n#U#(       a$  [,        R.                  " U#5      R                  5       OS n$U!b>  U$b;  U$U::  a5  [1        U!5      U"-
  nUR                  S[3        U5      S :  a  US! OS"S.5        U$ s  snf s  snf s  snf )#Ndailyz%m-%dhighlowmeanr   r   zToday High vs Normal+d   °   0°labelr   zToday Low vs Normalre   )r   r   precipc              3   (   #    U  H  oS L v   M
     g 7fr   r0  )r   rr   s     r   r   $_climate_insights.<locals>.<genexpr>  s     '$QDy$s   r   c              3   4   #    U  H  u  pX-   S -  v   M     g7frT   Nr0  )r   r  ls      r   r   rF    s     C?CAa?s   c              3   *   #    U  H	  oS    v   M     g7f)r   Nr0  r   r"   s     r   r   rF         2k!k   c              3   *   #    U  H	  oS    v   M     g7fre   Nr0  rK  s     r   r   rF    rL  rM  c              3   *   #    U  H	  oS    v   M     g7frH  r0  rK  s     r   r   rF    s     41A$rM  zMTD Avg High DeparturezMTD Avg Low DeparturezMTD Avg Temp Departurer   rainfallc              3   *   #    U  H	  oS    v   M     g7f)r   Nr0  rK  s     r   r   rF    rL  rM  SELECT MIN(ts) FROM minute_obszMTD Rain vs Normal (in)g{Gzt?z+.2fz0.00)rO   rg   rJ   rK   r  r   r   roundr3   rY   r  ranger   r   anyr9  lenvaluesr   r'  r   r   r   r  rk   abs)%r   r   periodsprofiler;  ri   todaythtltm	actual_hi	actual_lodepmonth_startr*  r   normal_rowsr  r"   r   obsrr   highslowsactual_avg_hiactual_avg_loactual_avg_meannorm_hinorm_lo	norm_meanrC  actualnorm
month_rainnormal_rainfirst_ts	first_days%                                        r   _climate_insightsrt    s1   !#G
++g

$"EeD!!	
E
))FOOG,
-
3E%))F#$BEIIe4D)Eb*UZU^U^_eUfJgr++O<=ITbTfTfguTvIwy),7MgjY\]_X``bVcpu.v!w),7LfiX[\^W__aUbot.u!v 1%**,K16k8Q7W7WXY7Y1Z	[1ZAiQ'
'1ZD	[K
))AJJw'
(
.Bv'
155<(@AEERXMAZ[efgfkfkltfu[vw'$'''KE4   !$6;;=Hs8s4y !$.A1Q4E.CJJL5QLqadL5Qe*SZ/Ms4yT?RC3uT?CCCJNO2k223{3CCG2k223{3CCG444S5EEI)-@(w?)/)D&!fT
 &+&e]`sSUhVXMfk5l(m& KK(.B33J?
2k22>?HHJ1M=E(((27794	 !i&;	[@Xj!+-CLL";UXY\U]_dUds4jjpqrLC 
\ /5Qs   S
S-Sc                     U R                  SX45      R                  5       nU Vs/ s H  oDS   c  M
  [        US   5      PM     nnU(       a  [        U5      $ S $ s  snf )Nz
        SELECT date(ts, 'unixepoch', 'localtime') AS local_day, MAX(rain_today)
        FROM minute_obs WHERE ts>=? AND ts<=? AND rain_today IS NOT NULL AND rain_today>=0
        GROUP BY local_day
    re   r   r   s         r   _rolling_rainfallrv    sc    	  
	
 $8:	 	
  $	8t!t+%!+tD	83t9&$& 
9s
   	AAc                 d    US:X  a  SOSnU R                  SU S3X45      R                  5       nU$ )NwarmestDESCASCzXSELECT temp,ts FROM minute_obs WHERE ts>=? AND ts<=? AND temp IS NOT NULL ORDER BY temp z LIMIT 1r   )r   r   r   whichorderr   s         r   _warmest_coolestr}    sS    9$&%Eopuovv~  BJ  AR  	S  	\  	\  	^CJr   c                 H    U R                  SX45      R                  5       nU$ )Na  
        SELECT local_day, day_rain FROM (
          SELECT date(ts,'unixepoch','localtime') local_day, MAX(rain_today) day_rain
          FROM minute_obs WHERE ts>=? AND ts<=? AND rain_today IS NOT NULL
          GROUP BY local_day
        ) ORDER BY day_rain DESC LIMIT 1
    r   )r   r   r   r   s       r   _wettest_dayr    s0      
	 $8:  Jr   c                    [         R                  " 5       n[        U R                  S5      5      n/ n0 n [           [        5       nUR                  S5      R                  5       S   n[        UR                  5       5      n[        U[        SS9-
  R                  5       5      nUR                  SUS-
  US-   U45      R                  5       n	UbH  U	(       aA  U	S   b;  [        U5      [        U	S   5      -
  n
UR                  SU
(       a  U
S	 S
3OSS.5        [        5       US'   S H  n[        X5      u  p[        X\US5      nU(       d  M&  US;   a  [!        X\U5      US'   O@US:X  a  [#        XQR$                  U5      US'   O U(       a  ['        U[        U5      U5      US'   XU'   M     UR)                  [+        XQU5      5        / SQnU GHR  u  nnUR                  U5      nU(       d  M!  SUR                  S5      S4SUR                  S5      S4SUR                  S5      S4SUR                  S5      S4SUR                  S5      S4SUR                  S 5      S4S!UR                  S"UR                  S#5      5      S$4S%UR                  S&5      S$4S'UR                  S(5      S)4/	nU Hn  u  nnnUc  M  US:X  a  [        [-        U5      5       S
3nO+US$:X  a  [        [-        U5      5       S*3nO[-        U5      S+ nUR                  U S,U 3US.5        Mp     GMU     S- HP  u  nnUR                  U5      nU(       d  M   UR                  S5      c  M4  UR                  US.-   US   S+ S.5        MR     [        U[        S/S09-
  R                  5       5      n['        UUU5      nUb  UR                  S1US+ S.5        [        US25      u  nn[/        UUUS35      n[/        UUUS45      nU(       a#  UR                  S5[        US   5       S
3S.5        U(       a#  UR                  S6[        US   5       S
3S.5        / S7QnU(       a  UR                  S85        U GH'  u  nn US9:X  a  [        U5      U4O[        UU5      u  n!n"[1        UU!U"5      n#U#(       d  M?  U#S:   c  MG   [         R2                  " U#S   S;5      n$U$R5                  5       UR5                  5       :X  a  S<n%O|US2:X  a  U$R7                  S=5      n%OdUS9:X  a<  U$R$                  UR$                  :w  a"  U$R7                  S>5      R9                  S?S,5      n%O"U$R7                  S@5      R9                  S?S,5      n% UR                  U [-        U#S:   5      S+ SA3S.5        GM*     UR>                  S::X  a  UR$                  S:-
  SB4OUR$                  UR>                  S:-
  4u  n&n'[A        URB                  [D        RF                  " U&U'5      S:   5      n([I        U[        U&U'U(5      5      n)U)(       aF  UR                  SC[        U)S:   5       S
3S.5        UR                  SD[        U)S   5       S
3S.5        [A        URB                  [D        RF                  " UR$                  S:-
  UR>                  5      S:   5      n*[I        U[        UR$                  S:-
  UR>                  U*5      5      n+U+(       aF  UR                  SE[        U+S:   5       S
3S.5        UR                  SF[        U+S   5       S
3S.5        URK                  5         S S S 5        X4[        [L        RL                  " 5       5      W[O        [Q        5       R                  S5      =(       d    0 5      [R        SG.SH.$ ! [:         a    [=        U#S   5      n% GN@f = f! , (       d  f       N= f! [:         a;  n,[U        SIU, 35        / 0 [        [L        RL                  " 5       5      S SJ.s S n,A,$ S n,A,ff = f)KNr   rS  r      )hourszRSELECT temp,ts FROM minute_obs WHERE ts BETWEEN ? AND ? ORDER BY ABS(ts-?) LIMIT 1i  z24 HR Temp Changer?  r@  rA  rB  r;  )r   r   rn   allT)r   r   rQ  rn   ))r;  Day)r   Week)r   Month)rn   Year)r  zAll-Timez	Temp Highr  r   zTemp Lowr  zDew Highr  zDew Lowr  zMax Heat Indexr  zMin Wind Chillr  zMax Gustr&  r$  windzMax Avg Windr%  zMax Rain Rate (in/hr)r#  rainratez MPH.2f ))r   zThis Week Rainfall)r   zThis Month Rainfall)rn   zThis Year Rainfallz (in)r  r   z7-Day Rainfall (in)r   rx  coolestzWeek Warmest TempzWeek Coolest Temp))r   zWeek Wettest Day)r   zMonth Wettest Day)rn   zYear Wettest Day)r  zAll-Time Wettest Dayr  re   r3  r   z%az	%b %d, %Yz 0z%b %d"rf   zLast Month This Date HighzLast Month This Date LowzLast Year This Date HighzLast Year This Date Low)
configuredfile)ri   rZ  rv   rw   climateProfilezWX HISTORY stats error: )ri   rZ  rv   rw   )+r   r   r   rg   r   r   r   r   rj   r   r   rT  r3   r1  r   r.  r   r   rn   rv  extendrt  rk   r}  r  strptimer  r  rY   rL   rh   r   r   r   calendar
monthranger   r   r   boolrO   rI   r;   )-r   r   current_tempri   rZ  r   firstnow_tsr?   oldchanger   r   r   experiod_defsr    titlespecssuffixr   kindr   rC  seven_startseven
week_start_warmcoolwet_periods
wet_period	wet_title	wet_startwet_endwetwet_dt	wet_labelpypmpdaylmlydaylyr   s-                                                r   build_history_statsr    sf   \\^Fhll=9:L
EGebD,,?@IIKANEv'')*Fyr22==?@Fq$Tk&+f=??Gxz 'CCF4F\*5Q=8&9V\VBKrBRbghi 45GG7 .v =#D&>2!11'7f'M:6)'?kkSY'Z:'8c%j'P:$&FO 8 LL*4w?@ oK(	E;;s#8 
!3F;ZyHYZ`<ay 1&99RVVHEUV\:]%bff[&9&ACSTVTZTZ[eTfgmBnz"&&2D EfM#BFF=$9&A,RVVN-CJO (-OF3t{Hv~eCj0A/B"-Eu%c
2C1DD/I#(:c"2%LLUG1VH*=e!LM (- )& !~u;;v&2"&&,8LL%-2j>RUBV!XY !~ VI1$55@@BCK#DV<E &;uSkST'v6LJq!$z&CD!$z&CD&9U4PQ7^DTTVBWXY&9U4PQ7^DTTVBWXYoKk(()GH(3$
99CU9J3u:f"5P^_efpPq!	' i833q6-<'00Q
C!;;=&++-77'/6??4;P'.6;;3KW]WfWfgrWsW{W{  }A  BE  XFY(.(@(H(Hc(RiLL)uSV}S>QQR<S!TU )4 )/a6;;q=$fkk&,,WX.=YEBrVZZ 3 3Br :1 =>DtHR4$89B&AUSUVWSX\NZ\J]^_&@ERTUVRWL>Y[I\]^fjj!4!4V[[]6<<!PQR!STEtHV[[]6<<$NOB&@ERTUVRWL>Y[I\]^&?5QSTUQV<.XZH[\]JJL @ s499;?Odi157L7N7R7RSZ7[7a_a1c  nE  #FG 	G %;CFi;c ]D  b(,-Rs499;7G\`aabs   ^7 D^&G4^&^&E^&^&'>^%^&&^=^&>A^?^& !^!G^&,A^7 ^#^&"^##^&&
^40^7 7
_<0_71_<7_<c                        [        [        5      n [        U 5        [        U 5      q[        [        S'   [        R                  " S5        MK  ! [
         a  n[        SU 35         S nAN5S nAff = f)NHistoricalStatszWX HISTORY thread error: ry   )	rK   latest_weatherr   r  _history_stats_cacherL   r;   r   sleep)snapr   s     r   history_threadr  \  si    
	3n%D!$'!4T!: .BN,- 	

2   	3-aS122	3s   2A 
A/A**A/FCW0168z-1zcwop.aprs.neti8  i,  gQkA@g{GUPeetWxPanelzhttps://backend.hazcams.comzflorence-al-us-003re   ry   
KALFLORE15865f7f79r   zOhttps://weatherstation.wunderground.com/weatherstation/updateweatherstation.phpzweather_source_config.json192.168.1.66r   r   enabledhostporttxidpoll_secondstimeout_secondsdavisderived	temperaturer}   dewpointr   wind_latestwind_averagewind_directiongustrainr         ?)r  offset
multipliermultiplier2g       @g(\?gHPx?r  r}   r  r   )weatherlink_livefallback_to_peetsourcescalibrationc                  D    [        [        SSS9 n [        R                  " U 5      nS S S 5        [        R
                  " [        R                  " [        5      5      n[        W[        5      (       GaU  UR                  UR                  5        VVs0 s H  u  p4US;  d  M  X4_M     snn5        [        UR                  S5      [        5      (       a  US   R                  US   5        [        UR                  S5      [        5      (       a  US   R                  US   5        [        UR                  S5      [        5      (       a  S Hy  nUS   R                  U5      n[        U[        5      (       d  M.  S	 HE  nUR                  U5      n[        U[        5      (       d  M+  US   U   U   R                  U5        MG     M{     U$ ! , (       d  f       GN= f! [         a2    [        R
                  " [        R                  " [        5      5      n GNf = fs  snnf )
Nr"   r#   r$   r  r  r  r  r  r  r  r  )r6   SOURCE_CONFIG_FILEr7   r8   rL   loadsdumpsDEFAULT_SOURCE_CONFIGrJ   rK   updateri   rg   )	rN   cfgmergedrq   rr   rE   src_cfgmetric
metric_cfgs	            r   load_weather_source_configr    s   <$cG<))A,C =
 ZZ

#89:F#t		oq@n7ntqtopcgg01488%&--c2D.EFcggi($//9$$S^4cggm,d33+m,008!'4009F!(V!4J!*d33}-f5f=DDZP :	 , M+ =< <jj$9:;<
 ps9   G GG H
"H

GG G 8HHc                     [        [        SSS9 n[        R                  " XSS9  S S S 5        g ! , (       d  f       g = f)NrS   r#   r$   rT   rU   )r6   r  r7   rX   )r  rN   s     r   save_weather_source_configr    s+    	 #	8A		## 
9	8	8s   /
=r  r  	availablelast_success_epoch
last_errorrX  c                 .    SSSSS.R                  U 5      $ )Nr   g	@ ?g	@ p?gMbP?)re   rT   r   r   r   )	rain_sizes    r   _rain_tip_inchesr    s    
ze<@@KKr   c                 x     [        U 5      n[        R                  " U5      (       a  U$ S $ ! [         a     g f = fr   )rk   rl   rm   rL   r   s     r   _finite_numberr    s;    %LMM!$$q.$. s   ', , 
99c           
      4   [        U [        5      (       a  U R                  S5      OS n[        U[        5      (       a  UR                  S/ 5      O/ n[        U[        5      (       d  0 $ S nS nU HX  n[        U[        5      (       d  M  UR                  S5      nUS:X  a  Uc  Ub  UR                  S5      U:X  a  UnUS:X  d  MV  UnMZ     Uc9  U H3  n[        U[        5      (       d  M  UR                  S5      S:X  d  M1  Un  O   0 nU(       a  SSS	S
SSSSSS.	n	U	R	                  5        H(  u  p[        UR                  U5      5      nUc  M$  XU
'   M*     UR                  S5      n[        U5      nUbE  SSSSS.nUR	                  5        H*  u  p[        UR                  U5      5      nUc  M$  X-  X'   M,     U(       a*  [        UR                  S5      5      nUb  UUS'   US-  US'   [        U[        5      (       a  UR                  S5      OS US'   U$ )Nr   
conditionsdata_structure_typere   r  r   r   hum	dew_pointwind_speed_lastwind_speed_avg_last_2_minwind_dir_lastwind_dir_scalar_avg_last_2_minwind_speed_hi_last_10_min wind_dir_at_hi_speed_last_10_min)	r  r}   r  r  r  r  wind_direction_averager  gust_bearingr  rainfall_dailyrainfall_last_60_minrainfall_last_24_hrrain_rate_last)r|   rain_1hr	rain_24hrr   bar_sea_levelr   E=?pressure_mbr  device_timestamp)rJ   rK   rg   r   ri   r  r  )payloadr  r   r  issbarconddstrX  mappingout_keyin_keyr   r  
tip_inchesrain_fieldsr   s                    r   parse_weatherlink_live_payloadr    s   ",Wd";";7;;vD/9$/E/E,+2Jj$''	
C
C$%%hh,-!8|txx/47!8C  {D$%%$((3H*IQ*N 
 F
!#,7-&F/>

  '}}OG 1C"%w  /
 GGK(	%i0
!.22-	K $/#4#4#6$SWWV_5?&)&6FO $7
 !#''/":;!)F:$,{$:F=!3=dD3I3I$tFMr   c            	      p   [         R                  S0 5      n U R                  SS5      (       d  [        S5        g [        U R                  S5      =(       d    S5      n[	        U R                  S5      =(       d    S5      nU R                  S	5      n[        S
[	        U R                  S5      =(       d    S
5      5      n[        S[        U R                  S5      =(       d    S5      5      nSU SU S3n[        SU SU SU S35          [        R                  R                  USS0S9n[        R                  R                  XuS9 n[        R                  " UR                  5       R                  SSS95      n	S S S 5        W	R                  S5      (       a#  [        [        U	R                  S5      5      5      e[!        XS9n
U
(       d  [        S 5      e["           S[$        S!'   [	        [&        R&                  " 5       5      [$        S"'   S [$        S#'   U
[$        S$'   S S S 5        [&        R*                  " U5        GM5  ! , (       d  f       N= f! , (       d  f       N8= f! [(         aJ  n["           S[$        S!'   [        U5      [$        S#'   S S S 5         S nAMu  ! , (       d  f        S nAN= fS nAff = f)%Nr  r  Fz WeatherLink Live source disabledr  r  r  r   r  ry   r  re   r  r   zhttp://:z/v1/current_conditionszWeatherLink Live polling z every zs (txid=)TAcceptapplication/jsonheadersrz   r#   rY   errorserror)r  z(no ISS current-condition values returnedr  r  r  rX  )WEATHER_SOURCE_CONFIGrg   r;   rh   rj   r   rk   urllibr   Requesturlopenr7   r  readdecodeRuntimeErrorr  _davis_lockdavis_weatherr   rL   r  )r  r  r  r  r  r  urlreqrespr  rX  r   s               r   weatherlink_live_threadr-  +  s    

#
#$6
;C779e$$01swwv0.1Dswwv$"%D776?Dr3sww~6<"=>L!U377+<#=#BCDOD64& 6
7C	%cU',xvQ
OP
	5..((xAS6T(UC'''E**TYY[%7%7	%7%RS F{{7##"3w{{7';#<==3GGF"#MNN-1k*69$))+6F23.2l+*0h'	  	

< '  FE 
  	5-2k*.1!fl+ 	5sn   =I! 3H?;A"I! AII! ?
I	I! 
II! I! !
J5+J01JJ0
J-	%J0-J00J5c            	          [            [        [        R                  S5      5      [        R                  S5      [        R                  S5      [	        [        R                  S5      =(       d    0 5      S.sS S S 5        $ ! , (       d  f       g = f)Nr  r  r  rX  r  )r(  r  r)  rg   rK   r0  r   r   get_davis_snapshotr/  L  s\    	m//<="/"3"34H"I'++L9=,,X6<"=	
 
s   A.A??
Bc           	      v   Ub  U S;  a  US4$ [        U5      R                  S5      (       a  SOSn[        R                  S0 5      R                  U0 5      R                  U 0 5      n[	        U5      nUc  US4$ [        UR                  SS5      5      nU(       d  USX5US.4$ [	        UR                  S	5      5      n[	        UR                  S
5      5      n[	        UR                  S5      5      n	Uc  SOUnUc  SOUnU	c  SOU	n	X-  U-  X-  -   U-   n
U S:X  a  [        S[        SU
5      5      n
U
SX5U
XxU	S.4$ )aF  Apply the same quadratic calibration model used by CumulusMX.

corrected = multiplier2 * raw^2 + multiplier * raw + offset

Calibration is source-specific and currently exposed for temperature/humidity.
Davis readings still bypass PEET's 2-minute smoothing; calibration is applied
only after a source value has been selected.
Nr  r  r   r  r  F)r  rE   ro   	correctedr  r  r  r   r  r}         Y@T)r  rE   ro   r1  r  r  r  )rh   r/   r!  rg   r  r  r   r   )r  r   actual_sourcerE   r  ro   r  r  multmult2r1  s              r   apply_metric_calibrationr6  U  sS    }&AAd{M*55g>>WFF $$]B7COCO  
C
{d{3779e,-GRUVVVCGGH-.F#'',/0D377=12ENSF,3DD=CeEs"tz2V;IS	23	6IU  r   c                 D   [        [        R                  S0 5      R                  U S5      5      R                  5       nUS:X  aZ  [	        5       nUS   (       a  US   R                  U=(       d    U 5      OS nUb  US4$ [        R                  SS5      (       d  gUS	4$ US4$ )
Nr  r   r  r  rX  r  T)Nzdavis-unavailablezpeet-fallback)rh   r!  rg   r.   r/  )r  
peet_value	davis_keydesireddsr   s         r   select_metric_sourcer<  y  s    '++Ir:>>vvNOUUWG'!9;K8  !4f5d'>!$(();TBB,?**vr   c                  $    S S S S S S S S S S S S S S S.$ )N)r   r   r  r  r   r   r   r  HighGustBearingTodayr  r  r   r   r   r0  r0  r   r   blank_daily_extremesr?    s6    "!  $! ! r   r3  r   --.-r   --r   	FeelsLiker   r   r   --.--Bearingr   r   r   RainLast24HoursRainLastHourr   LowRainRateToday r   r   r  r  r   r   r   r  r>  r  r  r   r   LastDataReadz--:--:--LastDataReadDate%m/%d/%YLastDataReadEpoch
PressureMBCWOPEnabledCWOPLastSenddisabled)CWOPLastStatusCWOPLastPacketHazcamsEnabledHazcamsLastSendHazcamsLastStatus	WUEnabled
WULastSendWULastStatusc                     Ub  US::  d  US:  a  g U S-
  S-  S-  nSnSnX2-  XB-   -  [         R                  " US-  5      -   nXE-  X5-
  -  nUS-  S-  S-   $ )	Nr   r
      r   r   gQE1@gfffffm@r2  )rl   log)temp_fr}   temp_cabalphadew_cs          r   r  r    sv    8q=HsNrkQ"FAAjQZ(DHHX5E,FFEY19%EAIMRr   c                 <    SSU -  -   SUS-  -  -
  SU -  US-  -  -   $ )NgQA@gS?g     A@g{Gz?g\(\?r0  )r\  wind_mphs     r   r   r     s@    F?	H$%	' F?h$./	1r   c                     SSU -  -   SU-  -   SU -  U-  -
  SU -  U -  -
  SU-  U-  -
  SU -  U -  U-  -   SU -  U-  U-  -   S	U -  U -  U-  U-  -
  $ )
Ngn0Egbd @gIZbI$@gG?g|?g$ ?g`!T?g03K?g~>r0  )r\  r}   s     r   r   r     s    
v
	

 	! v

(	) v

&		'
 x
(
*	+ v

&
1	2 v

(8
3	4 v

&
1H
<	=
r   c                 \    U S::  a  US:  a  [        X5      $ Ub  U S:  a  [        X5      $ U $ )Nr   r   r   )r   r   )r\  r}   live_wind_mphs      r   calc_feels_like_frg    s9    |* 77 " 22Mr   c                       [        [        S5       n [        R                  " [        [
        S.U 5        S S S 5        g ! , (       d  f       g = f! [         a  n[        SU 35         S nAg S nAff = f)NrS   )r  rX  zCould not save daily extremes: )r6   	DATA_FILEr7   rX   
daily_dater   rL   r;   )rN   r   s     r   save_daily_extremesrk    sT    5)S!QIIz^DaH "!! 5/s3445s1   A #>A 
AA A 
A1A,,A1c                     [         R                  R                  [        5      (       d  g  [	        [        S5       n [
        R                  " U 5      nS S S 5        WR                  S5      [        :X  a&  [        R                  UR                  S0 5      5        g g ! , (       d  f       NN= f! [         a  n[        SU 35         S nAg S nAff = f)Nr"   r  rX  zCould not load daily extremes: )r   r   r(   ri  r6   r7   r8   rg   rj  r   r  rL   r;   )rN   r   r   s      r   load_daily_extremesrm  $  s     77>>)$$5)S!Q99Q<D " 88Fz)!!$((8R"89 * "!
  5/s3445s/   B+ BAB+ 
B($B+ +
C5CCc                  Z   [         R                  R                  [        5      (       d  g [	        [        S5       n [
        R                  " U 5      nSSS5        [        R                  " 5       nWR                  S/ 5      nU H^  u  pE [        U5      n[        U5      nSX$-
  s=::  a  [        ::  d  M1  O  M5  SUs=::  a  S::  d  MD  O  MH  [        R                  XE45        M`     g! , (       d  f       N= f! [         a     M  f = f! [         a  n[        SU 35         SnAgSnAff = f)z
Restores recent 10-minute gust samples after a parser restart.

Only samples from the last 10 minutes are kept, so old gusts naturally expire.
Nr"   samplesr      z'Could not load 10-minute gust samples: )r   r   r(   	GUST_FILEr6   r7   r8   r   rg   rk   rL   TEN_MINUTESgust_10min_samplesr3   r;   )rN   r   r  ro  r  r  r   s          r   load_gust_samplesrt  7  s     77>>)$$=)S!Q99Q<D " ((9b)HB2YT{ FK.;..13C3C3C"))2*5   "!    =7s;<<=s^   D C&6D 	C7D 4D D &
C40D 7
DD DD 
D*D%%D*c                     [        [        [        U 5        [        [        S5       n[
        R                  " U [        [        5      S.U5        SSS5        g! , (       d  f       g= f! [         a  n[        SU 35         SnAgSnAff = f)zl
Saves recent 10-minute gust samples so a quick parser restart does not reset
the displayed 10-minute gust.
rS   saved_atro  Nz'Could not save 10-minute gust samples: )
remove_old_samplesrs  rr  r6   rq  r7   rX   r   rL   r;   r  rN   r   s      r   save_gust_samplesrz  U  sr    
=-{FC)S!QII &#$67  "!!  =7s;<<=4   %A) (AA) 
A&"A) &A) )
B3BB/c                      [        [        R                  R                  [        R                  R	                  [
        5      5      S5      $ )Nzpeet_wxpanel.htmlr   r   r   r)   r1   __file__r0  r   r   serve_panelr  m  s*    rwwrwwx/HIK^__r   z/wxpanel_history_manager.htmlc                      [        [        R                  R                  [        R                  R	                  [
        5      5      S5      $ )Nzwxpanel_history_manager.htmlr~  r0  r   r   wxpanel_history_managerr  r  *    rwwrwwx/HIKijjr   z/wxpanel_climate_manager.htmlc                      [        [        R                  R                  [        R                  R	                  [
        5      5      S5      $ )Nzwxpanel_climate_manager.htmlr~  r0  r   r   wxpanel_climate_managerr  v  r  r   z/api/wxpanel/climate-normalsGETPOST)methodsc                  t   [         R                  S:X  a  [        S[        5       [        S.5      $ [         R
                  " SS9=(       d    0 n [        U R                  S5      =(       d    S5      R                  5       [        U R                  S5      =(       d    S5      R                  5       0 0 S.nU R                  S	5      =(       d    0 R                  5        H  u  p#[        U[        5      (       d  M   [        R                  " S
U-   S5        0 nS H0  n[        UR                  U5      5      nUc  M"  [!        US5      XE'   M2     U(       d  Mx  XAS	   U'   M     U R                  S5      =(       d    0 R                  5        H  u  p#[#        U5      nSUs=::  a  S::  a  O  O[        U[        5      (       d  M7  0 nS H0  n[        UR                  U5      5      nUc  M"  [!        US5      XE'   M2     U(       d  Mx  XAS   [        U5      '   M     [#        [$        R$                  " 5       5      US'   ['        U5         [)        [        [*        5      5      q[,        [*        S'   [        SUS.5      $ ! [         a     GM  f = f! [         a     GM  f = f! [         a  n[/        SU 35         S nANOS nAff = f)Nr  Tokr   r  silentstationNamerH  referenceStation)r  r  r;  monthlyr;  z2000-r3  )rD  r<  r=  r>  r   r  re   rf   rv   r  zClimate normals refresh error: r  r   )r   methodr   rO   rI   get_jsonrh   rg   stripri   rJ   rK   r   r  rL   r   rT  rj   r   r\   r  r  r  r;   )	r  cleanr    r   rp   fldrr   rs   r   s	            r   wxpanel_climate_normals_apir  z  s2   ~~T)>)@H_`aaD)/RGW[[7=2>DDF!'++.@"A"GRHNNPY[fhjEKK(.B557#d##X	#gck*51C&A}uQqzch 2 3Cgs# 8 KK	*0b7793xQ1bC!5!5x1C&A}uQqzch 2 3i Q( : diik*E.% H0n1EFj~WhHiU+,,+ "(" "(" G5#B1#!FGGGs<   7I1
J>%J 1
J ?J 
JJ
J7J22J7z/api/wxpanel/rainfall-seedc                     [         R                  S:X  a  [        S[        5       [        S.5      $ [         R
                  " SS9=(       d    0 n U R                  S5      =(       d    0 nS0 0nUR                  5        H  u  p4 [        U5      nUS:  d  US:  d  [        U[        5      (       d  M4  0 nUR                  5        Hl  u  px [        U5      n	[        U5      n
SU	s=::  a  S	::  aC  O  M-  [        R                  " U
5      (       a"  U
S
:  a  [        U
S5      U[!        U	5      '   Mh  Mj  Ml  Mn     U(       d  M  XbS   [!        U5      '   M     [        ["        R"                  " 5       5      US'   [%        U5         ['        [        [(        5      5      q[*        [(        S'   [        SUS.5      $ ! [         a     GMA  f = f! [         a     GM  f = f! [         a  n[-        SU 35         S nANOS nAff = f)Nr  Tr  r  rd   il  i  re   rf   r   r   rv   r  zRainfall seed refresh error: r  )r   r  r   r`   r_   r  rg   ri   rj   rL   rJ   rK   rk   rl   rm   rT  rh   r   rb   r  r  r  r;   )r  rd   r  rn   r   yrp   r   r   rs   rt   r   s               r   wxpanel_rainfall_seed_apir    s   ~~d,?,AK\]^^D)/RG
++g

$"EB-E	$iA t8q4xz&$'?'?"LLNLEe*ea<R<DMM!$4$4a %aCAK :@$4< + 3#&'N3q6"! &" diik*E.30n1EF*>() $.///  		    3-aS1223sB   F.%G .9G ;%G .
F=<F= 
GG
G4G//G4c                 $    SU R                   S'   U $ )N*zAccess-Control-Allow-Originr  )responses    r   add_cors_headersr    s    69H23Or   c                     U c  g U R                  5       R                  5       n [        U 5      S:w  a  g U S:X  a  g  [        U S5      $ ! [         a     g f = f)Nr   ----   )r  upperrW  rj   r<   r   s    r   hex_to_int_4r    sY    }KKM!E
5zQ5"~ s   A 
AAc                  4   [         R                  R                  [        5      (       d  g [	        [        S5       n [
        R                  " U 5      nSSS5        [        R                  " 5       nWR                  S/ 5      nU HK  u  pE [        U5      n[        U5      nSX$-
  s=::  a  [        ::  d  M1  O  M5  [        R                  XE45        MM     g! , (       d  f       N= f! [         a     Mn  f = f! [         a  n[        SU 35         SnAgSnAff = f)z
Restore rain-total samples used for rolling 24-hour rainfall after restart.
The values are RainToday totals, so midnight resets are handled in the
rolling calculator.
Nr"   ro  r   z%Could not load 24-hour rain history: )r   r   r(   RAIN_HISTORY_FILEr6   r7   r8   r   rg   rk   rL   RAIN_24HR_SECONDSrain_24hr_samplesr3   r;   )rN   r   r  ro  r  totalr   s          r   load_rain_24hr_samplesr    s     77>>+,,;#S)Q99Q<D * ((9b) IB2Ye FK4#444!(("5 ! *)    ;5aS9::;sX   C5 C6C5 	C$C5 4C5 
C!C5 $
C2.C5 1C22C5 5
D?DDc                     [        [        [        U 5        [        [        S5       n[
        R                  " U [        [        5      S.U5        SSS5        g! , (       d  f       g= f! [         a  n[        SU 35         SnAgSnAff = f)zC
Save rolling 24-hour rain history so CWOP p### survives restarts.
rS   rv  Nz%Could not save 24-hour rain history: )
rx  r  r  r6   r  r7   rX   r   rL   r;   ry  s      r   save_rain_24hr_samplesr    st    ;,.?H#S)QII &#$56  *))  ;5aS9::;r{  c                 J   U c  g [        U 5      n [        R                  X45        [	        [        [
        U5        [        [        5      S:  a  gSn[        S   S   n[        [        5      SS  H  u  pEXS-
  nUS:  a  X&-  nUnM     [        SU5      $ ! [         a     gf = f)z
Maintains and returns a rolling 24-hour rain total.

Because Peet's RainToday resets at midnight, we calculate rainfall by adding
only positive increases between stored samples. If the counter drops, that is
treated as a reset, not negative rain.
Nr   rT   r   re   )	rk   rL   r  r3   rx  r  rW  r   r   r|   r  r  previous_totalr  current_totaldeltas          r   update_rain_24hr_totalr    s     :&
 f12(*;VD
!E&q)!,N !23AB7.19NE ' 8 sE?+  s   B 
B"!B"c                 B   U c  g [        U 5      n [        R                  X45        [	        [        SU5        [        [        5      S:  a  gSn[        S   S   n[        [        5      SS  H  u  pEXS-
  nUS:  a  X&-  nUnM     [        SU5      $ ! [         a     gf = f)z
Maintains and returns rolling 1-hour rain total for Weather Underground rainin.
WU rainin expects rain over the last hour, not current rain rate.
Nr   i  rT   r   re   )rk   rL   rain_1hr_samplesr3   rx  rW  r   r   r  s          r   update_rain_1hr_totalr  1  s    
 :&
 V01'v6
q E%a(+N !12126.19NE&	 7 sE?%  s   B 
BBc                    / n[        U 5      S:  a=  [        U S   5      nUb,  US-  nSUs=::  a  S::  a  O  OUR                  SU S   U45        [        U 5      S:  a=  [        U S	   5      nUb,  US-  nSUs=::  a  S::  a  O  OUR                  S
U S	   U45        SnSnU H  u  pgnUS
:X  a  UnM  US:X  d  M  UnM     Ub  UOUnXU4$ )a  
Peet serial data uses rain units of 0.01 inches.

For Data Logger style packets:
  field 4  = long-term rain total = chunks[3]
  field 11 = today's rain total    = chunks[10]

We use today's rain total for RainToday when present.
We keep long-term rain as a fallback / debug candidate.
r   r   Nr
  r     field_4_long_term_rainr  ry   field_11_today_rain)rW  r  r3   )	chunksr@   ro   r   
today_rainlong_term_rainrE   raw_hexselecteds	            r   decode_rain_from_chunksr  S  s     J 6{a6!9%?#IEE!T!!!#;VAY"NO 6{b6":&?#IEE S !!#8&*e"LMJN",**J//"N	 #- (3zHZ//r   c                 V   U c  g [        U 5      n [        c
  U qUq[        $ U [        :  aJ  U qUqSqSq[        R                  5         [        R                  5         [        R                  5         [        $ U [        -
  nUS:  aL  [
        b)  U[
        -
  nUS:  a  X#-  S-  nSUs=::  a  S::  a  O  OUqO[        [        S-  -  qUqU qUq[        $ [
        b;  U[
        -
  nU[        :  a
  SqUq[        $ US:  a  [        US-  -  n[        U:  a  UqUq[        $ ! [         a     gf = f)a;  
Davis-ish tipping-bucket rain rate with smarter decay.

Behavior:
  - On each bucket tip: calculate rate from time between tips.
  - Between tips: do not instantly decay every packet.
  - After a short grace period, cap the displayed rate by the maximum rate
    still possible based on time since the last tip.
  - After 15 minutes without a tip, drop to 0.00.

Example with 0.01" tips:
  - 18 sec between tips = 2.00 in/hr
  - 55 sec between tips = 0.65 in/hr
  - 5 min since last tip means rate cannot still be above 0.12 in/hr
  - 15 min since last tip = 0.00 in/hr
Nr   r   g      @   r   )rk   rL   last_rain_totallast_rain_tsrain_rate_hold_valuelast_rain_tip_tsrain_tip_samplesclearrain_rate_samplesrain_total_rate_samplesRAIN_TIP_SIZE_INCHESRAIN_RATE_HOLD_SECONDS)
rain_totalr  r  dt_tipnew_rateseconds_since_tipmax_possible_rates          r   calc_rain_rate_in_per_hrr    so   ( :&

 $## O#$" !%%'##(Eqy'..Fz!Nf4 &B&+3( $8;QTZ;Z#[ !$## #"%55 66#&  L # !58IF8R S#&77'8$Ls  s   D 
D('D(c                 H   U (       d  gSnSnU  HM  u  p4[         R                  " U5      nU[         R                  " U5      -  nU[         R                  " U5      -  nMO     US:X  a  US:X  a  g[         R                  " [         R
                  " X5      5      nUS:  a  US-  nU$ )zn
Vector-average wind direction so wraparound behaves correctly.
Example: 359 and 001 average to 000, not 180.
Nr   r   h  )rl   radianssincosdegreesatan2)sample_dequesin_sumcos_sumr  r  r  avgs          r   avg_wind_direction_from_samplesr    s    
 GG"
,,w'488G$$488G$$ #
 !|1
,,tzz'3
4C
Qws
Jr   c                 ,  ^ / n[        U 5      S:  a=  [        U S   5      nUb,  US-  nSUs=::  a  S::  a  O  OUR                  SU S   U45        [        U 5      S:  a=  [        U S	   5      nUb,  US-  nSUs=::  a  S::  a  O  OUR                  S
U S	   U45        SSR                  U 5      ;   a  SR                  U 5      nUR	                  SS5      S   n[        S[        U5      S5       Vs/ s H	  oeXfS-    PM     nn[        USS 5       HC  u  p[        U	5      nUc  M  US-  nSUs=::  a  S::  d  M)  O  M-  UR                  SU 3X45        ME     U(       d  S/ 4$ SSSSSS.mUR                  U4S jS9  US   u  pn[        U5      U4$ s  snf )a  
Official Peet data units say humidity is 0.1%.

For Data Logger Mode:
  field 7 = outdoor humidity = chunks[6]

For Packet Mode:
  field 9 = outdoor humidity = chunks[8]

Your current live stream starts with "!" and is being parsed as chunked hex fields.
The previous code was using tail_chunks[1] / 22, which can get stuck on the wrong field.
This version uses the official field position first.
r  r   Nry   r   r
  data_logger_field_7r   r  packet_field_9r  rH  re   r   r   tail_after_dash_rT   )r  r  tail_after_dash_0tail_after_dash_1tail_after_dash_2c                 .   > TR                  U S   S5      $ )Nr   c   r   )cprioritys    r   r   -decode_humidity_from_chunks.<locals>.<lambda>*  s    (,,qtR"8r   )r    )	rW  r  r3   r&   splitrU  	enumerater5   rT  )r  r@   ro   r   joinedtailr*  tail_chunksr   chunkrE   r  r  s               @r   decode_humidity_from_chunksr    s    J 6{a6!9%?"HEE S !!#8&)U"KL 6{a6!9%?"HEE S !!#3VAY"FG  ||FA&q).3As4y!.DE.DA!e}.DE#KO4JCu%Cb$$$%%)9#'?&NO 5 Rx  !H OO8O9']FU<##1 Fs   /Fc                     U R                  5       n U R                  S5      n U R                  S5      n [        S[        U 5      S5       Vs/ s H	  oXS-    PM     nn[        U5      S:  a  g [	        US   5      n[	        US   5      n[	        US   5      n[	        US   5      nUb	  Ub  Ub  Uc  g US-  nU[
        -  nUS	-  S
-  n	US-  n
US-  nUS-  n[        U5      u  p[        U5      u  nnn[        X5      n[        XU5      nSUs=::  a  S::  d   g   g SU	s=::  a  S	::  d   g   g SU
s=::  a  S::  d   g   g SUs=::  a  S::  d   g   g Ub  SUs=::  a  S::  d   g   g Ub  SUs=::  a  S::  d   g   g Ub  SUs=::  a  S::  d   g   g Ub  SUs=::  a  S::  d   g   g Ub  SUs=::  a  S::  d   g   g XXUUUUUX.U4$ s  snf ! [         a     g f = f)N!|r   r   r   re   rT   ry   r     r	  rp  i      !   r
  i   r  )r  lstriprU  rW  r  WIND_CALIBRATION_FACTORr  r  r  rg  rL   )packetr*  r  raw_wind_valuedirection_rawtemp_rawpressure_rawraw_windr  	directionr   r
  r   r}   humidity_candidatesr|   rain_long_termrain_candidatesr  
feels_likes                       r   parse_packetr  0  se   9s#s#+0CK+CD+Caq5/+CDv;?%fQi0$VAY/q	*#F1I.!]%:h>NR^Rf!B&11!C'#-	"}"R',(CF(K%6Mf6U3
NO"42&tt<
 T S  !Y%#% &t"s" #h$"$ %h)=#)= *>)?C)? *@!3*+C+C ,D!1
+Ac+A ,B%qN/Jd/J 0K(JPZ\jlr  JY  Y  	Ya Ed  s   A
G F>G .AG 3A&G G G +G -G <G >G G G !G #G 5G 7G 	G G G G 1G 3
G >G 
GGc                     U (       a8  X S   S   -
  U:  a)  U R                  5         U (       a  X S   S   -
  U:  a  M'  g g g g )Nr   )popleft)r  max_age_secondsr   s      r   rx  rx  m  sC    
3a!33oE 3a!33oE,E,r   c                 N    U (       d  g [        S U  5       5      [        U 5      -  $ )Nc              3   *   #    U  H	  oS    v   M     g7frO  r0  r   samples     r   r   #avg_from_samples.<locals>.<genexpr>v  s     4|Vay|rM  )r   rW  )r  s    r   avg_from_samplesr  r  s#    4|44s<7HHHr   c                  t   [         S   b  [         S   S OS[         S   b  [         S   S OS[         S   b  [         S   S OS[         S   b  [         S   S OS[         S   b  [         S   S OS	[         S
   b  [         S
   S OS	[         S   b  [         S   S OS[         R                  S5      b  [         S   S OS[         S   [         S   b  [         S   S OS[         S   b  [         S   S OS[         S   b  [         S   S OS[         S   b  [         S   S OS[         S   b  [         S   S SS.$ S	SS.$ )Nr   .1fr@  r   r  r  r   r  rC  r   r   rA  r  r>  r  .0fr  r   r   r   rH  )r   r   r  r  r   r   r   r  r>  r  r  r   r   r   rG  )r   rg   r0  r   r   format_daily_valuesr  y  s   ESTcEdEpN?;C@v|CQR`CaCm>.9#>syO]^rOsO0D!Ec J  FLM[\oMpM|/B CCH  CIGUVfGgGs^,<=cB  zAESTcEdEpN?;C@v}ESTcEdEpN?;C@vzKYK]K]^pKqK}~.@A#F  DH ./E FCQR`CaCm>.9#>swAOP]A^Aj.7<ptM[\oMpM|/B CCH  CIKYZlKmKy~.@A#F  @FM[\oMpM|/B CCH  CJ r   c	                 0   [         R                  " S5      n	U	[        :w  a  U	q[        5       q[        5         Sn
SU S4SU S4SUS4SUS4S	US4S
US4/nUb  UR                  SUS4SUS4/5        Ub  UR                  SUS4SUS4/5        Ub  UR                  SUS45        U Hd  u  pnUc  M  [        U   c  U[        U'   Sn
M"  US:X  a  U[        U   :  a  U[        U'   Sn
MB  US:X  d  MJ  U[        U   :  d  MY  U[        U'   Sn
Mf     [        S   b  U[        S   :  a  U[        S'   [        U5      [        S'   Sn
Ub:  [        R                  S5      b  U[        R                  S5      :  a  U[        S'   Sn
U
(       a
  [        5         [        5       $ )Nr3  Fr   r<  r   r=  r  r  r   r   r  r  r   r   r   Tr   r>  r  )r   r  rj  r?  r   rk  r  r3   rj   rg   r  )r   r   r  r  r}   r  r  r   avg_windr\  changedchecksr    r   modes                  r   update_daily_extremesr    s    MM*%E

-/G 
$'	u%	z62	j%0	8V,	(E*F Xv.He,
 	
  (F351
 	
 *Iv>?"D=#&"'N3GV^s(; ;"'N3GU]u~c'::"'N3G # o&.$9X2X*.'14Y-.!3!34F!G!OS[^l^p^p  rD  _E  TE-5)*  r   c                 d    U S:  a  SOSn[        U 5      n [        U 5      nX-
  S-  nUS US U 3$ )Nr   NSr   02d05.2frY  rj   )lat
hemispherer  minutess       r   aprs_latr#    D    qcJ
c(C#hG}"Gc]75/*66r   c                 d    U S:  a  SOSn[        U 5      n [        U 5      nX-
  S-  nUS US U 3$ )Nr   EWr   03dr  r  )lonr!  r  r"  s       r   aprs_lonr*    r$  r   c                      [        [        [        U 5      5      5      n [	        U[        X 5      5      $ ! [         a    Us $ f = fr   )rj   rT  rk   rL   r   r   )r   r=  r<  defaults       r   	clamp_intr-    sD    E%,'( sC$%%  s   4 AAc                 P    [        U SSS5      nUS:  a  S[        U5      S 3$ US $ )Ni  r   -r  r(  )r-  rY  )r\  r   s     r   aprs_temp_fr1    s6    VS#q)Dax3t9S/""3Zr   c                 4    [        U SSS5      nUS:  a  gUS $ )Nr   r
  00r  )r-  )r}   r  s     r   aprs_humidityr4    s&    
Haa
(C
cz#Yr   c                 X     [        [        U 5      S-  SSS5      $ ! [         a     gf = f)Nr
  r   r/  )r-  rk   rL   r  s    r   hundredths_inchesr6    s3    u+QQ77 s    
))c                 x   U R                  S5      nU R                  S5      nU R                  S5      nU R                  S5      nU R                  S5      nU R                  S5      nU R                  S5      nU R                  S5      =(       d    U R                  S	5      nUS
;   d  US;   d  US;   a  g[        USSS5      n	U	S:X  a  Sn	[        USSS5      n
[        USSS5      n[        U5      n[        U5      n[        [	        U5      S-  SSS5      n[        US;  a  UOS5      n[        US;  a  UOU5      n[        R                  " S[        R                  " 5       5      n[        [        5      n[        [        5      n[         SU SU SU SU	S SU
S SUS SU SUS SUS SU SUS S [         3nU$ )!aT  
Builds a standard APRS weather packet for CWOP.

Included:
  - wind direction
  - 2-minute average wind speed
  - 10-minute gust
  - 2-minute average temperature
  - rain since midnight
  - humidity
  - pressure in tenths of millibars

We send r000 and p000 until hourly/24-hour rain storage is added.
Pxxx is today's rain since midnight.
r   r   rM  r   rE  r   r   WindBearingAveragerD  )Nr@  )NrA  Nr   r  r/  ry   i )NrC  z%d%H%Mz>APRS,TCPIP*:.zr|  r  r(  gr  r000pPr  r_  05d.)rg   r-  r1  r4  rk   r6  r   r  gmtimer#  CWOP_LATr*  CWOP_LONCWOP_STATION_IDCWOP_SOFTWARE_TAG)r   r\  r}   r
  r|   r  wind_avgr  bearingwind_dir
wind_speed	wind_gust	temp_parthum_partpressure_partrain_today_hundredthsrain_24hr_hundredthsr   r   r)  r  s                        r   build_cwop_packetrN    s   " \\-(F||L)H,,|,Kk*J./I||M*H<<(Dll/0KHLL4KG8|#;{l?Z!S!,H38QQ/J$3*IF#IX&HeK025q%CM-Jo<]jcde -o5	: h6I
8
C
8
C 
>+Q%qQC.*S)
Ic?
I; %
!#&
H:
M#
	   Mr   c                 X   S[          S[         S[         S3n[        R                  " [
        [        4SS9 nUR                  UR                  S5      5        [        R                  " S5        UR                  U S	-   R                  S5      5        S S S 5        g ! , (       d  f       g = f)
Nzuser z pass z vers z 1.0
   rz   asciig333333?z
)rB  CWOP_PASSCODErC  socketcreate_connectionCWOP_SERVER	CWOP_PORTsendallencoder   r  )r  loginsocks      r   send_cwop_packetr[  B  s    
 }o 6!"(	, 

 
	!	!;	":B	G4U\\'*+

3fvo--g67 
H	G	Gs   AB
B)c                  L    [         R                   " 5       n [        U [        -  -
  $ )zg
Returns seconds until the next real 5-minute clock mark.
Example send marks: :00, :05, :10, :15, etc.
)r   CWOP_SEND_INTERVAL)r   s    r   seconds_until_next_cwop_markr^  O  s     
 ))+C'9!9::r   c                  *    [         (       d*  S[        S'   S[        S'   [        R                  " S5        M6  [	        5       n U S:  a5  S[        S'   S[        U 5       S	3[        S'   [        R                  " U 5        [         (       d  M   [        [        5      n[        U5      nU(       d!  S
[        S'   [        R                  " S5        M  [        U5        [        R                  " 5       nS[        S'   [        R                  " SU5      R                  S5      [        S'   S[        S'   U[        S'   [        SU 35        [        R                  " S5        GMY  ! [         a-  nS[        S'   SU 3[        S'   [        SU 35         S nANKS nAff = f)NTFrN  rP  rQ  ry   r   zwaiting for next 5-min mark (zs)waiting for valid datar   %I:%M:%S %p0rO  sentrR  zCWOP SENT | error: zCWOP ERROR | rT   )CWOP_ENABLEDr  r   r  r^  rj   rK   rN  r[  	localtimer  r  r;   rL   )wait_secondsr   r  r   r   s        r   cwop_threadrh  X  sg    |,1N=)/9N+,JJrN 45!,0N=)1NsS_O`Naac/dN+,JJ|$|	'N+H&x0F3K/0

1V$.."C,0N=)-1]]=#-N-U-UVY-ZN>*/5N+,/5N+,L)* 	

1Y L  	',0N=)18}N+,M!%&&	's    
A E A6E 
F%#FFc                 H    U S;   a  g  [        U 5      $ ! [         a     g f = f)NNrH  rA  r@  rC  )rk   rL   r  s    r   parse_float_or_nonerk    s/    11U| s   
 
!!c                     U S;   a  g [        U 5      n [        c  U qgU [        -
  nU qUS:  a  gUS:  a  g[        US5      $ ! [         a     gf = f)z
Hazcams treats rain_min as incremental rainfall added to backend totals.
Send only rainfall since the previous Hazcams upload.
rj  r   r   rT   r   )rk   rL   last_hazcams_rain_totalrT  )r|   r  s     r   get_hazcams_rain_deltarn    sy     66:&
 &",00E( qy qy?%  s   A 
AAc                 8   0 n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n	[        U R                  S	5      5      n
[        U R                  S
5      5      nU R                  S5      n[        U5      nUb  [        US5      US'   Ub  [        US5      US'   Ub  [        US5      US'   Ub  [        US5      US'   Ub  [        US5      US'   Ub  [        US5      US'   U	b  [        U	S5      US'   U
b  [	        [        U
5      5      S-  US'   Ub  [	        [        U5      5      S-  US'   XS'   Ub8  Ub5  US::  a  Ub  US:  a  [        US5      US'   U$ US:  a  [        US5      US'   U$ )zo
Builds the Hazcams weather payload using the same field names as the
existing Hazcams/Austin station parsers.
r   r   r   rB  rM  r   r   r   rD  r8  r   rT   r  r}   r  r   wind_nowrD  rH  r  r  wind_direction_avgrain_minr   r   
wind_chillr   
heat_index)rk  rg   rn  rT  rj   )r   r  r  r}   r  r  r
  rp  rD  rH  r  rq  r|   
rain_deltas                 r   build_hazcams_payloadrv    s&   
 G%hll=&ABK"8<<#=>H#HLL1B$CDI$X\\+%>?J%hll<&@AK"8<<#=>H"8<<#>?H#HLL$ABI(i)@AN,X\\:N-OPk*J'
3J!&{A!6#Ha0
$Y2#K3
#Ha0
#Ha0
$Y2!$'n(=$>$D !%(+E2D,E(F(L$% %J :#9"!5(a-$)*a$8GL! N B$)*a$8GL!Nr   c                    [          S[         S3n[        R                  " U 5      R	                  S5      n[
        R                  R                  UUSS0SS9n[
        R                  R                  U[        S9 nUR                  nUR                  5       R                  SS	S
9nS S S 5        XV4$ ! , (       d  f       WW4$ = f)Nz/internal/stations/z/weatherr#   zContent-Typer  r  )r   r  r  rz   ignorer  )HAZCAMS_URLHAZCAMS_STATION_IDr7   r  rX  r"  r   r#  r$  HAZCAMS_TIMEOUT_SECONDSstatusr%  r&  )r  endpointr   r   r  r|  bodys          r   send_hazcams_payloadr    s    12D1EXNH::g%%g.Dnn$$!34	 % G 
		1H		IX}}%%gh%? 
J < 
J	I 4<s   :+B00
Cc                  r    [         (       d*  S[        S'   S[        S'   [        R                  " S5        M6   [	        [        5      n [        U 5      nU(       d*  S[        S'   S[        S'   [        R                  " S5        M  [        U5      u  p#[        R                  " 5       nS[        S'   [        R                  " SU5      R                  S	5      [        S
'   SU 3[        S'   [        SU SU 35        [        R                  " ["        5        GM  ! [        R                  R                   aN  nS[        S'   SUR                   3[        S'   [        SUR                   SUR                   35         S nANS nAf[          a-  nS[        S'   SU 3[        S'   [        SU 35         S nANS nAff = f)NTFrS  rP  rU  ry   r`  ra  rb  rT  z
sent HTTP zHAZCAMS SENT | HTTP  | HTTP error zHAZCAMS ERROR | HTTP r  rd  zHAZCAMS ERROR | )HAZCAMS_ENABLEDr  r   r  rK   rv  r  rf  r  r  r;   r"  r   	HTTPErrorcodereasonrL   HAZCAMS_SEND_INTERVAL)r   r  r|  r~  r   r   s         r   hazcams_threadr    s   
/4N+,2<N./JJrN	*N+H+H5G37/06N23

2/8LF.."C/3N+,04mS0Q0X0XY\0]N,-4>vh2GN./(G9=> 	

()9 ( ||%% 	?/3N+,4?x2HN./)!&&188*=>> 	*/3N+,4;A3-N./$QC())	*s,   A	D A5D F63AE<<F6	#F11F6c                 N   [        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      5      n[        U R                  S5      =(       d    U R                  S5      5      n[        U R                  S	5      5      n[        U R                  S
5      5      n	Ub  Ub  Uc  g[        [        SSS[	        [
        5      US [	        [        [        U5      5      5      US S.	n
Ub  US U
S'   Ub  US U
S'   Ub  US U
S'   Ub#  [	        [        [        U5      5      S-  5      U
S'   Ub  US U
S'   U	b  U	S U
S'   U
$ )zr
Build Weather Underground RapidFire parameters.
rainin = rolling 1-hour rain.
dailyrainin = rain since midnight.
r   r   r   r   r   r   r8  rD  rF  r   Nr   	updateraw1r  r  )	IDPASSWORDdateutcactionrealtimertfreqtempfr}   baromindewptfwindspeedmphwindgustmphr  winddirrainindailyrainin)rk  rg   WU_STATION_IDWU_STATION_KEYrh   WU_SEND_INTERVALrj   rT  )r   r\  r}   
dewpoint_fpressure_inrG  rH  rF  r  r|   r   s              r   build_wu_paramsr    s    !m!<=F"8<<#=>H$X\\2C%DEJ%hll:&>?K$X\\-%@AJ#HLL$ABI"8<<0D#E#`V_I`aH"8<<#?@H$X\\+%>?J~)[-@ "&'3<E(O,-!#&
F (-x$.s#3~#,S/}E(O 4s :;y&s^x#-c"2}Mr   c                    [         R                  R                  U 5      n[         SU 3n[         R                  R                  USS0SS9n[         R                  R                  U[        S9 nUR                  nUR                  5       R                  SSS	9R                  5       nS S S 5        XV4$ ! , (       d  f       WW4$ = f)
N?z
User-AgentzPeetWxPanel/1.0r  )r  r  rz   r#   rx  r  )r"  parse	urlencodeWU_URLr   r#  r$  WU_TIMEOUT_SECONDSr|  r%  r&  r  )r   queryr*  r   r  r|  r~  s          r   send_wu_paramsr  P  s    LL""6*EHAeW
Cnn$$01 % G 
		1C		D}}%%gh%?EEG 
E <	 
E	D 4<s   .9B22
Cc                      [         (       d*  S[        S'   S[        S'   [        R                  " S5        M6   [	        [        5      n [        U 5      nU(       d*  S[        S'   S[        S'   [        R                  " S5        M  [        U5      u  p#[        R                  " 5       nS[        S'   [        R                  " S	U5      R                  S
5      [        S'   SU SUS S  3[        S'   [	        U5      nSUS'   [        SU SUS S  SU 35        [        R                  " ["        5        GM/  ! [        R                  R                   aN  nS[        S'   SUR                   3[        S'   [        SUR                   SUR                   35         S nANS nAf[          a-  nS[        S'   SU 3[        S'   [        SU 35         S nANS nAff = f)NTFrV  rP  rX  ry   r`  r   ra  rb  rW  zHTTP z: r   z***r  zWU SENT | HTTP r  r  zWU ERROR | HTTP r  rd  zWU ERROR | )
WU_ENABLEDr  r   r  rK   r  r  rf  r  r  r;   r"  r   r  r  r  rL   r  )r   r   r|  r~  r   safe_paramsr   s          r   	wu_threadr  a  s    z*/N;'-7N>*JJrN	%N+H$X.F.2{+1I~.

1)&1LF.."C*.N;'+/==+L+S+STW+XN<(/4VHBtCRyk-JN>*v,K&+K
#OF83tCRyk[MJK 	

#$M 8 ||%% 	:*.N;'/:166(-CN>*$QVVHAahhZ899 	%*.N;'/6qc]N>*Ks#$$	%s,   A	D1 BD1 1GAFG%#GGc                     [        S[         S35        [        R                  " [        [        SS9n [        S5        SnSnSnSnSnSn U R                  S5      nU(       d  M  XR                  S	S
S9-  nSU;   Ga>  UR                  SS5      u  pUR                  5       n	U	(       d  M4  [        U	5      n
U
(       d  MH  U
u  ppnnnnnnnn[        R                  " 5       n[        R                  UU45        [        R                  UU45        [        R                  UU45        [        R                  UU45        Ub  [         R                  UU45        Ub  ["        R                  UU45        [%        [        [&        U5        [%        [        [(        U5        [%        [        [&        U5        [%        [        [&        U5        [%        [         [&        U5        [%        ["        [&        U5        [+        [        5      n[-        [        5      n[+        [        5      n[+        [         5      n[+        ["        5      nUc  UnUc  UnUc  UnUc  UnUc  Un[        (       a  [/        S [         5       5      OUnUU-
  S:  a  [1        U5        Un[3        UU5      n[5        UU5      n[7        UU5      n UU-
  S:  a  [9        U5        Un[;        SUS5      u  n!n"[;        SUS5      u  n#n$[=        SU!U"5      u  n!n%[=        SU#U$5      u  n#n&U!b  U#b  [?        U!U#5      OS n'Sn([;        SUS5      u  n)n*[;        SUS5      u  n+n,[;        SUS5      u  n-n.[;        SUS5      u  n/n0[;        SUS5      u  n1n2[A        US-  5      n3[B        U3:w  a  U3q!U-q"U1q#O8U-b  [D        c  U-O[/        [D        U-5      q"U1b  [F        c  U1O[/        [F        U15      q#[;        SUS5      u  n4n5[;        SUS5      u  n6n7[;        SUS5      u  n8n7[;        SU S5      u  n9n7U0S:X  a-  [I        5       RK                  S0 5      n:U:RK                  S U/5      n;OUn;U!b  [M        U!U#b  U#OS U+b  U+OS5      OS n<U+U14 V=s/ s H
  n=U=c  M  U=PM     n>n=U>(       a  [/        U>5      OSn?[O        U!U)U?U/U#b  [Q        U#5      OS U'U<U6U-5	      n@[        RR                  " 5       nA[I        5       nBU"U$U(U*U,U.U0U2U5S!.	nC[T        RW                  0 S"U!b  U!S# OS$_S%U#b  U#S& OS'_S(U#b'  [Y        U$5      R[                  S5      (       a  U#S# OU#S& OS'_S)U'b  U'S# OS$_S*U<b  U<S# OS$_S+U+b  U+S# OS'_S,U-b  U-S# OS'_S-[D        _S.[F        _S/U)b  U)S0 OS1_S2U)b  U)S3-  OS _S4U/b  [A        U/5      OS_S5U;b  [A        [Q        U;5      5      S6-  OS_S7U;b  [A        [Q        U;5      5      S6-  OS_S8U1b  U1S# OS'_S9U4b  U4S0 OS1_S:U6b  U6S0 OS1_U8b  U8S0 OS1U9b  U9S0 OS1WCU%U&S;.[]        U%[^        5      (       a  U%RK                  S<5      OU![]        U&[^        5      (       a  U&RK                  S<5      OU#[a        [b        RK                  S=0 5      RK                  S>S?5      5      WBRK                  S@S?5      UBRK                  SA5      UBRK                  SB5      [        Rd                  " SCWA5      Rg                  SD5      [        Rd                  " SEUA5      [A        [        R                  " 5       5      [h        SF.EW@E5        [j        (       an  [        R                  " 5       U-
  SG:  aR  [        SH5        [        SIU 35        [        SJU 35        [        SK[T        S%    SL35        [        R                  " 5       n[l        (       a  [        R                  " 5       U-
  SG:  ah  [        SM5        [        SIU 35        [        SJU 35        [        SN[T        S9    SO35        [        SP[T        S:    SQ35        [        R                  " 5       n[        R                  " 5       U-
  S:  a  [        SR[T        S"    SSWCS    ST[T        S%    SU[T        S)    SV[T        S*    SWUS# SXUS# SY[A        [Q        U5      5      S6-   SZUS# S[US0 S\[T        S9    S][T        S^    S_[T        S:    35        [        R                  " 5       nSU;   a  GM>  GMt  s  sn=f )`NzOpening z...g?rz   zReading Peet line packets...rH  r   i   rQ  rx  r  
re   c              3   *   #    U  H	  oS    v   M     g7frO  r0  r  s     r   r    reader_thread.<locals>.<genexpr>  s     ?,>&1I,>rM  rP  r   r  r}   r  r   r  r  r  r  r  r|   r   r  r  r  rX  r   r  r   r  r@  r   r  rA  WxPanelHumidityr   rB  r   r   r   r   r   r  rC  rM  r	  rD  r8  r  
Avgbearingr   r   r   r  ro   r  r  Fr  r  r  ra  rb  rK  )rE  rF  DataSourcesCalibrationRawSelectedTemperatureRawSelectedHumidityWeatherLinkLiveEnabledWeatherLinkLiveAvailableWeatherLinkLiveLastSuccessEpochWeatherLinkLiveLastErrorrI  rJ  rL  r  rT   zHUMIDITY DEBUGz
  chunks: z  candidates: z  selected humidity: %z
RAIN DEBUGz  selected RainToday: z inz  calculated RainRate: z in/hrzT z (z) | H z% | DP z | FL z | W z | 2min Avg z @ u   ° | 10min Gust z | P z | Rain z | 24hr rE  z | Rate )7r;   PORTserialSerialBAUDRATEr%  r&  r  r  r  r   wind_2min_samplesr3   rs  wind_dir_2min_samplestemp_2min_sampleshumidity_2min_samplesdewpoint_2min_samplesrx  TWO_MINUTESrr  r  r  r   rz  r  r  r  r  r<  r6  r  rj   wind_archive_peak_minutewind_archive_max_2min_avgwind_archive_max_10min_gustr/  rg   rg  r  rT  rf  r  r  rh   r/   rJ   rK   r  r!  r  r  r  HUMIDITY_DEBUG
RAIN_DEBUG)Dserbuffer
last_printlast_gust_savelast_humidity_debuglast_rain_debuglast_rain_history_savero   liner  resultr  r  r   r   r}   r  r  r|   r  r  r  r  r  rD  wind_dir_avgtemp_avghumidity_avgdewpoint_avgrecent_gustpeet_rain_ratepeet_rain_24hrpeet_rain_1hrtemp_displaysrc_temphumidity_displaysrc_humtemp_calibrationhumidity_calibrationdewpoint_displaysrc_dewpressure_displaysrc_pressurewind_displaysrc_windwind_avg_displaysrc_wind_avgdirection_displaysrc_dirgust_displaysrc_gustarchive_minuterain_today_displaysrc_rainrain_rate_display_selectedr  rain_24hr_displayrain_1hr_display	ds_valueswind_dir_avg_displayfeels_like_displayrr   wind_extreme_candidateswind_for_extremesdaily_valuesr   davis_status
source_mapsD                                                                       r   reader_threadr    sb
    
HTF#

--h
5C	
()FJNO
hhtn**WX*66fn!<<a0LDZZ\F!&)F Y_  VDTXxZYgio  rE  GVYY[F$$fd^4%%vtn5!((&))<=$$fd^4#%,,fh-?@#%,,fh-?@0+vF1;G4k6J0+vF4k6J4k6J'(9:H:;PQL'(9:H+,ABL+,ABL#(#'#' &% ?,>??+/  &+!&)!' 6j&IN3JGN1*fEM..3&v.)/&
 &:-S`%a"L((<ZWa(b%g .FmUack-l*L*5MjZjls5t22 T`Sk  qA  qM|=MN  TXG-A*hXb-c*l%9-}%]"L(-A.RZ\j-k*l)=>NPY[k)l&w%9&+v%V"L( 2.N'>9+9(,<).:+#/D]De0@kn  pI  K[  l\-+B]Be,kn  pK  MY  lZ/+?
T`+a(,@Yd,e)&#7P[#\ q"6v}j"Ya '!.044XrB	'0}}5MO`'a$'3$ )	 "3$4$@ d , 8a" 04	  4@2N&`2NQRSq2N#&`@W$; <]^0 !!+;+G&'T "* 
L .."C-/L'##(' ,")  
J !! $#8P,s!3V\$#;K;W!1# 6]a$# "  FV  FbWAXAXY`AaAa'7&<jz{~i  hl$# "@P@\&6s%;bh$# <N<Z 237`f$# |7Oc 2UY$# <L<X"23!7^b$# +,E$# %&A$# 9I9U/4[b$# @P@\.<bf$# 5F5R301XY$#  %PdPpc%0D*E&F&Lvw!$#" H\Hhc%(<"=>Dno#$#$  ,:RL#5X\%$#& <N<Z 237`g'$#( C]Ci9#>ov)$#* BSA^&7%<dk=M=Y#3C"8_f)/?MabISTdfjIkIk*:*>*>u*Eq}JTUikoJpJp';'?'?'F  wG*./D/H/HI[]_/`/d/denpu/v*w,8,<,<[%,P3?3C3CDX3Y,8,<,<\,J $mS A H H M$(MM*c$B%(%5#7E$#F G$# $L ~$))+0C"Ca"G&'
6(+,':&;<=-n\.J-K1MN&*iik#zdiikO;a?l#
6(+,&789.~k/J.K3OP/z0J/K6RS"&))+yy{Z'!+67r*]:S9T U'56 7():;< =(56 7c
 #  (~SU<5H1IC1O0P Q""-c!2 3!# '*;78 9*+<=> ?*:67
9 "YY[
y fn F 'as   b>$b>z/weather_source_manager.htmlc                      [        [        R                  R                  [        R                  R	                  [
        5      5      S5      $ )Nzweather_source_manager.htmlr~  r0  r   r   weather_source_managerr  	  s*    rwwrwwx/HIKhiir   z/api/weather-sourcesc                  Z   [         R                  S:X  a`  [        5       n [        S[        U R                  SS5      U R                  S5      U R                  S5      U R                  S0 5      S.S	.5      $ [         R                  " SS
9=(       d    0 n[        R                  " [        R                  " [        5      5      n[        [        [        5      (       a  [        O0 nUR                  UR                  5        VVs0 s H  u  pEUS;  d  M  XE_M     snn5        [        UR                  S5      [        5      (       a  US   R                  US   5        [        UR                  S5      [        5      (       a  US   R                  US   5        [        UR                  S5      [        5      (       a  S H  n[        US   R                  U5      [        5      (       d  M,  S HR  n[        US   U   R                  U5      [        5      (       d  M/  US   U   U   R                  US   U   U   5        MT     M     [        UR                  S5      [        5      (       a!  S H  nXS   ;   d  M  US   U   US   U'   M     SU;   a  [        US   5      US'   [        UR                  S5      [        5      (       ad  US   R                  5        HM  u  pvXrS   ;   d  M  [!        U5      R#                  5       S;   d  M.  [!        U5      R#                  5       US   U'   MO     [        UR                  S5      [        5      (       a  S H  nUS   R                  U5      n	[        U	[        5      (       d  M.  S Hs  nU	R                  U5      n
[        U
[        5      (       d  M+  US   U   U   nSU
;   a  [        U
S   5      US'   S H   nX;   d  M
  [%        X   5      nUc  M  XU'   M"     Mu     M     SUR'                  S0 5      S'   Uq[)        U5        [        SUSS.5      $ s  snnf )Nr  Tr  Fr  r  rX  r  )successconfigr  r  r  r  r  r  r  r  r  r  )r   r  r  )r  r  r  r  r  zjSource choices apply immediately. Restart parser only if WeatherLink Live host/port/txid settings changed.)r  r   note)r   r  r/  r   r!  rg   r  r7   r  r  r  rJ   rK   r  ri   r  rh   r.   r  
setdefaultr  )r|  incomingr  currentrq   rr   rE   r  r    r  mcdestrt   s                r   weather_sources_apir  	  s    ~~#%+#ZZU;&,jj1E&F$jj6 **Xr2	!	
 	 		 t,2HZZ

#89:F'12G'N'N#TVG
MMGMMOoODAq@n7n414Oop'++01488!"))'2D*EF'++i($//y  !34'++m,d33'F'-044V<dCC9F!'-"8"@"D"DV"LdSS}-f5f=DDW]E[\bEcdjEkl : ( (,,12D99YC1222:;M2Ns2S)*3/ Z X%%)(3E*F%G!"(,,y)400&y1779NF	**s6{/@/@/BFW/W,/K,=,=,?y!&) : (,,}-t44'F}-11&9Ggt,,5[[(!"d++m,V4V<?&*2i=&9DOBCy*273=()I	 C 6	 (" 4=Fi$Z0"v&tv  @l  m  n  nW ps   ?P'
P'
z/api/data/currentdatac                       [        [        5      $ r   )r   r  r0  r   r   current_datar	  	  s    >""r   __main__)r?   daemonz0.0.0.0NWALS_PEET_PORT5000)r  r  threaded)Tr   r  )r   )flaskr   r   r   r   r  	threadingr   r7   r   loggingrl   rS  urllib.requestr"  urllib.errorurllib.parser9   r   r  r   r   collectionsr	   __name__app	getLoggersetLevelERRORr  r  r  r  rr  RAIN_RATE_WINDOW_SECONDSr  r  r   r)   r1   r  r*   environrg   r&   PERSISTENT_ROOTr'   rW   rF   ri  rq  r  r  r   r_   rI   rO   r\   r`   rb   ru   Lockr   r   r  r   r   r   r   r   r   r   r   r   r  r  r.  r1  r9  rt  rv  r}  r  r  r  r  r  re  rB  rR  rU  rV  r]  r@  rA  CWOP_INITIAL_DELAY_SECONDSrC  r  ry  rz  r  r{  r  r  r  r  r  r  r  r  r  r  r!  r  r(  r)  r  r  r  r-  r/  r6  r<  r?  r  rj  r   r  r  rs  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rain_rate_displayrm  r  r   r   rg  rk  rm  rt  rz  router  r  r  r  r  after_requestr  r  r  r  r  r  r  r  r  r  r  rx  r  r  r  r#  r*  r-  r1  r4  r6  rN  r[  r^  rh  rk  rn  rv  r  r  r  r  r  r  r  r  r	  Threadwllr   r  histcwophazcamswurunrj   r0  r   r   <module>r*     sl	   > >     	          ( Ho   *  & &w}} 5    
 WW__RWW__X67
JJNN#$ 	ww||


~&Q"**..*CQz  _f5 MD )$N %%?@	$%CD	,-JK  
 ],LMGGLL0LM '',,}6TU -'   !#QTR ,&.P	N7'$J20S<,'T&"2h'
jbX
  
 	   !  +)    
  	Z WW\\*.JK    
 (-3_bc$(Ct\bc

 (-3_bc$)S\_`
	+ B2$ 34  ?H     B /
 ;nn	L?B!B
!H
& ]]:&
%'060$0 v0 	0
 $0 40 0 q0 V0 0 0 w0  G!0" #0$ %0( V)0* F+0, &-0. /00 g102 W304 T506 708 D90: D;0< 4=0> ?0@ A0D JE0F j1G0H I0J $K0L <M0N DO0P !%#_0d G W       "  G    G ' 7 G 7     	 	55   =<=*   3` ` *+k ,k *+k ,k )E6?C- D-B '%A 0 B 0D  
(;<;("L>  *0ZR j6=$@:z
I(=!B77&DN	8;/dD2j*D1h")%Zr)j )*j +j !E6?;=n <=n@ "## $# z


"9$
GCIIKd;AGGI>$?DJJL;t<DJJLnTBGMMO			4	8BHHJGGRZZ^^4Ev%N!OZ^G_' r   