o
     /jF                     @  s  d Z ddlmZ ddlmZ ddlmZ ddlmZ ddl	Z	ddl
Z
ee jd Ze r8e	jdee ddlZed ddlmZ ddlmZ ddlZddlZdd	lm Z m!Z! ddl"m#Z$ ed
Z%e%d Z&dZ'e%d Z(e%d Z)dZ*dZ+dZ,e%d Z-e-d Z.e-d Z/e-d Z0e-d Z1e-d Z2dZ3dZ4dZ5dZ6dZ7dZ8dZ9dZ:eG dd dZ;eG d d! d!Z<dd$d%Z=dd)d*Z>dd.d/Z?dd1d2Z@dd4d5ZAdd7d8ZBdd<d=ZCddAdBZDddEdFZEddJdKZFddOdPZGdddUdVZHddWdXZIdd[d\ZJdddbdcZKddfdgZLddhdiZMddodpZNddrdsZOddudvZPddxdyZQdd|d}ZRdddZSddddZTdddZUddddZVdddZWdddZXdddZYeZdkrTeY  dS dS )a  
Station-by-station regression analysis for NOAA and STOFS NAVD water levels.

Input is a single table with at least:
    station_id, time, noaa_navd_m, stofs_navd_m

Outputs are written under OUTPUT_DIR:
    figures/raw/{station_id}_raw_regression.png
    figures/detrended/{station_id}_detrended_regression.png
    station_regression_summary.csv
    processing_log.csv
    station_regression_report.html
    )annotations)	dataclass)escape)PathNz.python_depsZAgg)ReadNCget_statz/sciclone/schism10/sgao08z2STOFS_single/regression/noaa_stofs_hourly_navd.csvTz.STOFS_single/v1.1/obs_navd_hourly_2000_2025.ncz0STOFS_single/v1.1/stofs_navd_hourly_2000_2025.ncZwater_level_navd_m
station_idtimezSTOFS_single/regressionzfigures/rawzfigures/detrendedzstation_regression_summary.csvzprocessing_log.csvzstation_regression_report.htmlZnoaa_navd_mZstofs_navd_mx         i,  c                   @  sN   e Zd ZU ded< ded< ded< ded< ded< ded< ded< d	ed
< dS )TrendResultfloatslope_m_per_yearslope_mm_per_yearintercept_mslope_ci_low_mm_per_yearslope_ci_high_mm_per_yearp_value	r_squared
np.ndarrayfittedN__name__
__module____qualname____annotations__ r   r   R/sciclone/schism10/sgao08/STOFS_single/regression/noaa_stofs_station_regression.pyr   N   s   
 r   c                   @  s6   e Zd ZU ded< ded< ded< ded< ded< dS )	PairRegressionResultr   slope	interceptr   r   r   r   Nr   r   r   r   r   r   Z   s   
 r   returnNonec                   C  s.   t jddd tjddd tjddd d S )NT)parentsexist_ok)
OUTPUT_DIRmkdirRAW_FIG_DIRDETRENDED_FIG_DIRr   r   r   r   ensure_output_dirsc   s   r*   valueobjectstrc                 C  s    t |  }ddd |D S )N c                 s  s(    | ]}|  s|d v r|ndV  qdS ))-_.r0   N)isalnum).0Zchr   r   r   	<genexpr>k   s   & z"safe_station_id.<locals>.<genexpr>)r-   stripjoin)r+   textr   r   r   safe_station_idi   s   r8   
input_filer   pd.DataFramec                 C  s  |   std|  dt| }tttth}t|	|j
}|r(td| |ttttg  }|t tj |t< tj|t dd|t< tj|t dd|t< tj|t dd|t< ||t d|t  @   }|jrwtd|jttgdd	ttg  ttg}|S )
NzInput file not found: z,. Edit INPUT_FILE at the top of this script.z(Input file is missing required columns: coerceerrorsr.   z*No rows remain after station/time parsing.F)Zas_index)existsFileNotFoundErrorpdZread_csvSTATION_COLTIME_COLNOAA_COL	STOFS_COLsorted
differencecolumns
ValueErrorcopyastyper-   r5   to_datetime
to_numericnenotnaemptygroupbymeansort_values)r9   dfrequiredmissingr   r   r   
read_inputn   s,   

"
rV   rS   c                 C  s   |   }|t jdj |d< |tdgjtdft	dftdft	dfd
 }||d  |d  @ |d tk@ |d	 tk@    }|tdgS )
NMmonthrQ   countnoaa_monthly_mstofs_monthly_mnoaa_valid_hoursstofs_valid_hoursr[   r\   r]   r^   )rI   rB   dt	to_periodto_timestamprP   rA   aggrC   rD   reset_indexrN   MIN_VALID_HOURS_PER_MONTHrR   )rS   Zworkmonthlyr   r   r   hourly_to_common_monthly   s.   



rf   pd.DatetimeIndexc                 C  s   t | dd}tj| d d  td}|dr9|ddddd }tj|dd	d }t
|tj|d	d
 S ddlm} |||d}t
tdd |D S )Nunitsr.   dtypezseconds since    z+0000T)Zutcs)Zunitr   )num2date)rh   c                 S  s   g | ]}|  qS r   )Z	isoformat)r3   dater   r   r   
<listcomp>   s    z+netcdf_time_to_datetime.<locals>.<listcomp>)getattrnpasarrayr   
startswithreplacer5   r@   rK   Ztz_localizeZDatetimeIndexZto_timedeltanetCDF4rm   )Ztime_varrh   valuesZorigin_textoriginrm   datesr   r   r   netcdf_time_to_datetime   s   
ry   	list[str]c                 C  sN   ddl m} | d d  }|jjdv r |jdkr dd ||D S dd |D S )Nr   )chartostring>   US   c                 S     g | ]}t | qS r   r-   r5   r3   itemr   r   r   ro          z-decode_netcdf_station_ids.<locals>.<listcomp>c                 S  r   r   r   r   r   r   r   ro      r   )ru   r{   rj   kindndim)Zstation_id_varr{   rv   r   r   r   decode_netcdf_station_ids   s
   r   noaa_ncstofs_nctuple[pd.DataFrame, list[str]]c                   s  |   r|  std|  d| g }tt| dd}tt|dd}ztttfD ] }||jvr<td| d|  ||jvrKtd| d| q+t	|jt }t	|jt }t
|t
|ksjt|j|jsntdt|jt }t|jt }	dd	 t|	D   fd
d|D }
|
std|d }td|i}|jt }|jt }t|D ]v\}}| vrq | }tj|d d |f tjt}tj|d d |f tjt}| }||t< ||t< |djtdftdftdftdfd }||d  |d  @ |d t k@ |d t k@   }||t!< |"| qW |#  |#  n	|#  |#  w |sItjt!dddgd|
fS tj$|dd}t!dddddg}|| %t!dg|
fS )Nz@CSV input is missing and paired NetCDF files are not available: , rk   )ZfmtzMissing z in z-NOAA and STOFS NetCDF time axes do not match.c                 S  s   i | ]\}}||qS r   r   )r3   idxr   r   r   r   
<dictcomp>   s    z3paired_netcdf_to_common_monthly.<locals>.<dictcomp>c                   s   g | ]}| v r|qS r   r   )r3   r   Zstofs_indexr   r   ro      r   z3paired_netcdf_to_common_monthly.<locals>.<listcomp>z8No common station_id values between paired NetCDF files.rW   rX   rQ   rY   rZ   r[   r\   r]   r^   rG   T)Zignore_index)&r>   r?   r   r-   NETCDF_TIME_VARNETCDF_STATION_ID_VARNETCDF_WATER_VARZ	variablesrH   ry   lenrq   Zarray_equalrv   r   	enumerater`   ra   r@   	DataFrameZmaZfillednanrJ   r   rI   rC   rD   rP   rb   rc   rN   rd   rA   appendcloseconcatrR   )r   r   rowsZnoaa_dsZstofs_dsZvar_nameZ
noaa_timesZstofs_timesZnoaa_idsZ	stofs_idsZstation_idsZmonth_indexbaseZnoaa_varZ	stofs_varZnoaa_idxr   Z	stofs_idxZnoaa_valuesZstofs_valuesZstation_framere   Zmonthly_allrG   r   r   r   paired_netcdf_to_common_monthly   s   

 

""






r   rx   pd.Series | pd.DatetimeIndexr   c                 C  s.   t | }| }|| t jdd jtdS )Ng{Gv@)Zdaysri   )r@   rK   minZ	Timedeltato_numpyr   )rx   r_   startr   r   r   years_since_start  s   
r   	pd.Seriesrv   c              
   C  s  t j|ddjtd}t| }t|t|@ }| dk r#td|| }|| }t	
|t	|jddtid}tj|tjtd}|t	|||< |jd	d
}	t|jd }
t|
|
d t|jd t|	d d t|	d d t|jd t|j|dS )Nr;   r<   ri      z,Need at least 3 valid points for time trend.ZHACZmaxlags)Zcov_typeZcov_kwds皙?alphark   g     @@r   rk   r   rk   rk   )r   r   r   r   r   r   r   r   )r@   rL   r   r   r   rq   isfinitesumrH   smOLSadd_constantfitTREND_HAC_MAXLAGS	full_liker   predictZconf_intparamsr   pvaluesrsquared)rx   rv   yxmaskx_validy_validmodelr   Zcir    r   r   r   fit_time_trend  s2   r   x_valuespd.Series | np.ndarrayy_valuesc           	      C  s   t jt | ddjtd}t jt |ddjtd}t|t|@ }| dk r0td|| }|| }t	
|t	| }tj|tjtd}|t	|||< tt|jd t|jd t|jd t|j|dS )	Nr;   r<   ri   r   z1Need at least 3 valid points for pair regression.rk   r   )r    r!   r   r   r   )r@   rL   Seriesr   r   rq   r   r   rH   r   r   r   r   r   r   r   r   r   r   r   )	r   r   r   r   r   r   r   r   r   r   r   r   fit_pair_regression*  s"   r   noaastofsdict[str, float]c                 C  s   t jt | ddjtd}t jt |ddjtd}t|t|@ }| dk r7tjtjtjtjdS t	|| || }t|j
t|jt|jt|jdS )Nr;   r<   ri   r~   )	pearson_rbias_mrmse_mmae_m)r@   rL   r   r   r   rq   r   r   r   r   RZMEZRMSDZMAE)r   r   Zobsmodr   metricsr   r   r   pair_metrics@  s   r   r   r   digitsintc                 C  s&   | d u s	t | sdS | d| dS )NNAr1   frq   r   )r+   r   r   r   r   format_floatU  s   r   c                 C  s.   | d u s	t | sdS | dk r| dS | dS )Nr   g-C6?z.2ez.4fr   )r+   r   r   r   format_p[  s
   r   axplt.Axesc                 C  s8   | j tjdd | j td | jddd d S )Nr~   )r   z%YT      ?r   )ZxaxisZset_major_locatormdatesZYearLocatorZset_major_formatterZDateFormattergrid)r   r   r   r   set_month_axisc  s   r   Q?abpad_fractiontuple[float, float]c                 C  s   t t j| tdt j|tdg}|t | }|jdkrdS tt |}tt |}t ||r=tt	|d d}n|| | }|| || fS )Nri   r   )g            ?g?)
rq   Zconcatenaterr   r   r   sizer   maxZiscloseabs)r   r   r   rv   lohiZpadr   r   r   equal_limitsi  s   "
r   re   r   c                 C  sb  d|  d|d   dd|d  ddt| ddd	t|d
  dt|d  dt|d  dt|d  dt|d  
dd	t|d  dt|d  dt|d  dt|d  dt|d  
dt|d  dt|d  ddt|d  dt|d   d!t|d"  d#t|d$  d%t|d&  d't|d(  d)t|d*  d+	g}d,|S )-Nstation_id: zperiod: rX   %Y-%m to common months: r.   zNOAA trend:z  raw_noaa_trend_mm_per_yearz mm/yr [!raw_noaa_trend_ci_low_mm_per_yearr   "raw_noaa_trend_ci_high_mm_per_yearz], p=raw_noaa_trend_p_valuez, R2=raw_noaa_trend_r_squaredzSTOFS trend:raw_stofs_trend_mm_per_year"raw_stofs_trend_ci_low_mm_per_year#raw_stofs_trend_ci_high_mm_per_yearraw_stofs_trend_p_valueraw_stofs_trend_r_squaredzdifference trend:  raw_difference_trend_mm_per_yearz
 mm/yr, p=raw_difference_trend_p_valuez(STOFS = intercept + slope x NOAA: slope=raw_pair_slope, intercept=raw_pair_intercept_mz m, R2=raw_pair_r_squaredz
Pearson r=raw_pearson_rz, bias=
raw_bias_mz	 m, RMSE=
raw_rmse_mz m, MAE=	raw_mae_m m
)r   r   r   r   r   r6   r   re   r   linesr   r   r   stats_text_raww  s\   "
















(r   c                 C  s   d|  dt | ddt|d  ddt|d  dd	t|d
  dt|d  ddt|d  dt|d  dt|d  ddt|d  dg
}d|S )Nr   r   r.   zNOAA detrended anomaly SD: detrended_noaa_sd_mr   zSTOFS detrended anomaly SD: detrended_stofs_sd_mzregression: slope=detrended_pair_sloper   detrended_pair_intercept_mzPearson r: detrended_pearson_rzR2: detrended_pair_r_squaredzRMSE: detrended_rmse_mzMAE: detrended_mae_mr   )r   r   r6   r   r   r   r   stats_text_detrended  s    


r	  
noaa_trendstofs_trend
diff_trendpair_regout_pngc              
   C  sz  |d }|d j td}	|d j td}
|
|	 }tjddddd\}}|j|  d	d
d |d }|j||	dddd |j||
dddd |j||jddddd |j||jddddd |d |d |j	dd t
| |d }|j||dddd |j||jddddd |jd d!d"d# |d$ |d% |j	dd t
| |d& }|j|	|
d
d'd(d)d*d+ t|	|
\}}|j||g||gd,d-d.d t|	}|j|	| |j| ddd/d ||| ||| |jd0d1d2 |d3 |d4 |d5 |jdd6d7 |j	dd |d8 }|d9 |d: |jd;d<t| ||d=d>d?d@|jdA |j|tdB t| d S )CNrX   r[   ri   r\   r~      	   TZfigsizeZconstrained_layoutz Raw Monthly Mean Regression   fontsizer   r   #1f77b4g?zNOAA monthly meancolorlwlabel#d62728zSTOFS monthly mean       @--z
NOAA trendr  r  Zlsr  zSTOFS trendzA. Monthly Mean Time SerieszWater level (m NAVD88)   r   rk   #2ca02czSTOFS - NOAAblackzdifference trend        0.45皙?r  r  zB. Difference Time SerieszSTOFS - NOAA (m)r   ?#4c78a8nonecommon monthsrl   r   r  Z
edgecolorsr  0.35333333?1:1 line
regressionequalboxZ
adjustablezC. STOFS vs NOAAzNOAA monthly mean (m NAVD88)zSTOFS monthly mean (m NAVD88)r   r   r   offzD. Station Statistics{Gz?\(\?topleft	monospaceg      !@ZvaZhaZfamilyr  	transformZdpi)r   r   pltsubplotssuptitleplotr   	set_title
set_ylabellegendr   axhlinescatterr   rq   argsortset_xlimset_ylim
set_aspect
set_xlabelr   axisr7   r   	transAxessavefigFIG_DPIr   )r   re   r
  r  r  r  r   r  rx   r   r   difffigaxesr   r   r   orderr   r   r   plot_raw_station  sh   











rS  -tuple[pd.DataFrame, TrendResult, TrendResult]c                 C  s   |   }|d jj|d< |dd d}|dd d}|d | |d< |d | |d< t|d |d }t|d |d }|d |j |d< |d |j |d	< |||fS )
NrX   Zcalendar_monthr[   rQ   r\   Znoaa_anomaly_mZstofs_anomaly_mnoaa_detrended_anomaly_mstofs_detrended_anomaly_m)rI   r_   rX   rP   r;  r   r   )re   detZ	noaa_climZ
stofs_climZnoaa_anom_trendZstofs_anom_trendr   r   r   compute_detrended_anomalies  s   
rX  rW  c              
   C  sn  |d }|d j td}|d j td}|| }tt|}	tjddddd\}
}|
j|  d	d
d |d }|j||dddd |j||dddd |jdddd |	d |
d |jdd t| |d }|j||dddd |j|	dddd |	d!d"d# |jdddd |	d$ |
d% |jdd t| |d& }|j||d
d'd(d)d*d+ t||\}}|j||g||gd,d-d.d t|}|j|| |j| dd/d0d ||| ||| |jd1d2d3 |	d4 |d5 |
d6 |jdd7d8 |jdd |d9 }|d: |	d; |jd<d=t| ||d>d?d@dA|jdB |
j|tdC t|
 d S )DNrX   rU  ri   rV  r~   r  Tr  z% Detrended Monthly Anomaly Regressionr  r  r  r  r   zNOAA detrended anomalyr  r  zSTOFS detrended anomalyr$  r%  r&  r'  zA. Detrended Monthly AnomalieszDetrended anomaly (m)r   r!  r"  zdetrended STOFS - NOAAr#  g?r  zmean=z.3fr   r  zB. Detrended DifferencezDifference (m)r   r(  r)  r*  r+  r,  r-  r.  r/  r  r0  r1  r2  r3  zC. Detrended STOFS vs NOAAzNOAA detrended anomaly (m)zSTOFS detrended anomaly (m)r   r   r   r4  zD. Detrended Statisticsr5  r6  r7  r8  r9  r  r:  r<  )r   r   rq   Znanmeanr=  r>  r?  r@  rD  rA  rB  rC  r   rE  r   rF  r   rG  rH  rI  rJ  r   rK  r7   r	  rL  rM  rN  r   )r   rW  r  r   r  rx   r   r   rO  Z	diff_meanrP  rQ  r   r   r   rR  r   r   r   plot_detrended_station  sh   










rY  dict[str, object]c                 C  s,  | djdd}t| }t| d }t| d }|d }|d }|| }t|d |}t|d |}	t|d |}
t||}t||}i d| d	|d  	d
d|d 
 	d
dtt|d|t d|t d|jd|jd|jd|jd|jd|	jd|	jd|	jd|	jd|	jd|
j|
j|
j|
j|
j|j|j|j|j|d |d |d |d d}t| |||	|
||| t|\}}}|d }|d  }t||}t||}|ttj|d!d"ttj|d!d"|j|j|j|j|d |d |d d#	 t| |||| |S )$NrX   T)Zdropz_raw_regression.pngz_detrended_regression.pngr[   r\   r   first_common_monthr   last_common_monthcommon_months
raw_figuredetrended_figurer   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )Z'raw_difference_trend_ci_low_mm_per_yearZ(raw_difference_trend_ci_high_mm_per_yearr   Zraw_difference_trend_r_squaredr   r   Zraw_pair_p_valuer   r   r   r   r   rU  rV  rk   )Zddof)	r  r  r  r  Zdetrended_pair_p_valuer  r  r  r  )rR   rc   r8   r(   r)   r   r   r   r   strftimer   r   r   Zrelative_tor&   Zas_posixr   r   r   r   r   r    r!   rS  rX  updater   rq   ZnanstdrY  )r   re   Zstation_safeZraw_pngZdet_pngr   r   rO  r
  r  r  Zraw_pair_regZraw_pair_metricsr   rW  r0   Zdet_noaaZ	det_stofsZdet_pair_regZdet_pair_metricsr   r   r   analyze_stationP  s   

	
 

rb  log_rowslist[dict[str, object]]c                 C  s(   g d}t | j|djtdd d S )Nr   statusreasonr]  first_month
last_monthr   Findex)r@   r   Zreindexto_csvPROCESSING_LOG_CSV)rc  rG   r   r   r   write_processing_log  s    rn  summary_rowsc                 C  s,   t | }|js|d}|jtdd |S )Nr   Frj  )r@   r   rO   rR   rl  SUMMARY_CSV)ro  summaryr   r   r   write_summary  s
   

rr  Fis_pboolc              	   C  sP   zt | }W n ttfy   Y dS w t|sdS t|r"t|S t||S )Nr.   )r   	TypeErrorrH   rq   r   r   r   r   )r+   r   rs  valr   r   r   html_number  s   
rw  c              	   C  s<   zt | }W n ttfy   Y dS w t|sdS |dS )Nr.   ZNaNz.12gr   ru  rH   rq   r   )r+   rv  r   r   r   html_sort_value  s   
ry  c                 C  s    dt |  dt| ||d dS )Nz<td data-sort=''>)r   rs  </td>)ry  rw  )r+   r   rs  r   r   r   html_num_cell  s    r|  r    r   tuple[str, str]c              	   C  sb   z
t | }t |}W n ttfy   Y dS w t|r!t|s#dS |dkr)dS |dkr/dS dS )N)znot significantnot-significantr   r   )zsignificant increasezsignificant-increase)zsignificant decreasezsignificant-decreaserx  )r    r   Z	slope_valZp_valr   r   r   trend_label  s   r  rq  c                 C  s  t | }tdd |D }tdd |D }g }|  D ]\}}tt|dd}tt|dd}	t|d|d\}
}|dg d	tt|d
d dtt|dd dtt|dd dt	|ddd t	|d t	|d t	|d t	|ddd d| dt|
 dt	|d t	|d t	|d t	|d t	|d t	|d t	|d t	|d  t	|d! d"| d#| d$|	 d#|	 d% qg }|D ]Y}t|d&d}d'd(d)d*|d+}|d	tt|d
d d,| dt| d-tt|d.d dt	|ddd d/tt|d0d dtt|d1d d2 qd|}| j
rud3}ntt| d   d4tt| d   d5}d6| d7| d8| d9| d:t d;t d<ttj d=ttj d>d| d?| d@}tj|dAdB d S )CNc                 s  "    | ]}| d dkrdV  qdS rf  skippedrk   Ngetr3   rowr   r   r   r4          z$write_html_report.<locals>.<genexpr>c                 s  r  rf  failedrk   Nr  r  r   r   r   r4     r  r^  r.   r_  r   r   z<tr><td>r   z	</td><td>r[  r\  r{  r]  r   )r   r   r   T)rs  z<td><span class='tag rz  z</span></td>r   r   r   r   r   r  r  r  r  z<td><a href='z'><img src='z-' alt='raw regression'></a></td><td><a href='z+' alt='detrended regression'></a></td></tr>rf  successZinsufficientr  )r  r  r  r~  z</td><td><span class='tag z</span></td><td>rg  z<td>rh  ri  z
</td></tr>zNo successful station rowsr   z, common monthly NOAA/STOFS NAVD water levelsa  <!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>NOAA-STOFS Station Regression Report</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 18px; color: #20242a; background: #f7f8f4; }
    h1 { font-size: 22px; margin: 0 0 8px; }
    h2 { font-size: 17px; margin: 18px 0 8px; }
    .meta { color: #555; margin-bottom: 12px; line-height: 1.4; }
    input { width: min(520px, 95vw); padding: 8px 10px; border: 1px solid #9aa3a8; border-radius: 4px; margin: 8px 0 14px; background: white; }
    table { border-collapse: collapse; width: 100%; background: white; font-size: 13px; }
    th, td { border-bottom: 1px solid #d8ddd9; padding: 7px 8px; text-align: left; vertical-align: middle; white-space: nowrap; }
    th { position: sticky; top: 0; background: #e8eee9; cursor: pointer; z-index: 1; }
    tbody tr:hover { background: #f1f6f4; }
    img { width: 168px; height: 110px; object-fit: cover; border: 1px solid #ccd2ce; }
    a { color: #145a7a; }
    .tag { display: inline-block; min-width: 118px; text-align: center; padding: 3px 6px; border-radius: 999px; font-size: 12px; }
    .significant-increase { background: #d7efe0; color: #155c32; }
    .significant-decrease { background: #f3d9d4; color: #8c241c; }
    .not-significant { background: #e6e8eb; color: #3d444b; }
    .insufficient { background: #fff2c7; color: #6a4a00; }
    .success { background: #d7efe0; color: #155c32; }
    .failed { background: #f3d9d4; color: #8c241c; }
    .table-wrap { overflow-x: auto; }
    .links a { margin-right: 12px; }
  </style>
</head>
<body>
  <h1>NOAA-STOFS Station Regression Report</h1>
  <div class="meta">
    zO; raw monthly mean and detrended anomaly regressions.
    Successful stations: z; skipped: z
; failed: z;
    minimum common months: z,; minimum valid hours per source-month:
    z).
    <div class="links">
      <a href="z!">summary CSV</a>
      <a href="a\  ">processing log CSV</a>
    </div>
  </div>
  <input id="search" type="search" placeholder="Search station id, metric value, or trend label">
  <h2>Station Summary</h2>
  <div class="table-wrap">
  <table id="stationTable" class="sortable">
    <thead>
      <tr>
        <th>station_id</th>
        <th>first</th>
        <th>last</th>
        <th>months</th>
        <th>NOAA trend<br>mm/yr</th>
        <th>STOFS trend<br>mm/yr</th>
        <th>diff trend<br>mm/yr</th>
        <th>diff p</th>
        <th>label</th>
        <th>raw slope</th>
        <th>raw R2</th>
        <th>raw r</th>
        <th>raw bias<br>m</th>
        <th>raw RMSE<br>m</th>
        <th>detr slope</th>
        <th>detr R2</th>
        <th>detr r</th>
        <th>detr RMSE<br>m</th>
        <th>raw fig</th>
        <th>detr fig</th>
      </tr>
    </thead>
    <tbody>
      at  
    </tbody>
  </table>
  </div>
  <h2>Processing Log</h2>
  <div class="table-wrap">
    <table id="logTable" class="sortable">
      <thead>
        <tr>
          <th>station_id</th>
          <th>status</th>
          <th>reason</th>
          <th>common months</th>
          <th>first</th>
          <th>last</th>
        </tr>
      </thead>
      <tbody>
        ax  
      </tbody>
    </table>
  </div>
  <script>
    function cellValue(row, index) {
      const cell = row.children[index];
      if (!cell) return '';
      if (cell.dataset.sort !== undefined) {
        const numeric = Number(cell.dataset.sort);
        return Number.isFinite(numeric) ? numeric : cell.innerText.toLowerCase();
      }
      return cell.innerText.toLowerCase();
    }
    document.querySelectorAll('table.sortable').forEach(table => {
      const tbody = table.tBodies[0];
      table.querySelectorAll('th').forEach((th, index) => {
        th.addEventListener('click', () => {
          const rows = Array.from(tbody.rows);
          const ascending = th.dataset.asc !== 'true';
          table.querySelectorAll('th').forEach(h => h.dataset.asc = '');
          th.dataset.asc = ascending ? 'true' : 'false';
          rows.sort((a, b) => {
            const av = cellValue(a, index);
            const bv = cellValue(b, index);
            if (Number.isFinite(av) && Number.isFinite(bv)) return ascending ? av - bv : bv - av;
            return ascending ? String(av).localeCompare(String(bv)) : String(bv).localeCompare(String(av));
          });
          rows.forEach(row => tbody.appendChild(row));
        });
      });
    });
    const search = document.getElementById('search');
    const stationBody = document.getElementById('stationTable').tBodies[0];
    search.addEventListener('input', () => {
      const q = search.value.toLowerCase();
      Array.from(stationBody.rows).forEach(row => {
        row.style.display = row.innerText.toLowerCase().includes(q) ? '' : 'none';
      });
    });
  </script>
</body>
</html>
zutf-8)encoding)r   r   Ziterrowsr   r-   r  r  r   r6   r|  rO   r   r   MIN_COMMON_MONTHSrd   rp  namerm  REPORT_HTMLZ
write_text)rq  rc  Zok_countZskipped_countZfailed_countr   r0   r  Zraw_linkZdet_linkr  Zlabel_classZlog_table_rowsZlog_rowrf  Zstatus_classZlog_htmlZperiod_texthtmlr   r   r   write_html_report  s   
		



 !!!"#%&FX r  c                  C  s  t   z;t rtt} t| }t| t  }n$tr-t	dt
 dt  tt
t\}}ntt} t| }t| t  }W n( tyf } zddt|ddddg}t| tg  tt |  d }~ww g }g }t|D ]}||t |k  }tt|}|r|d  d	nd}	|r|d  d	nd}
|tk r||d
d| dt ||	|
d qoz|t|| ||dd||	|
d W qo ty } z||d| dtjdd  ||	|
d W Y d }~qod }~ww t|}t| t|| t	dt   t	dt!  t	dt"  t	dt#d   t	dt|  t	dt$dd |D   t	dt$dd |D   d S )Nz0Input CSV not found, using paired NetCDF files: r   __all__r  r   r.   re  rX   r   r  zonly z common months; need r  z; rk   )limitzWrote summary: zWrote processing log: zWrote HTML report: zWrote figures under: ZfigureszSuccessful stations: zSkipped stations: c                 s       | ]}|d  dkrdV  qdS r  r   r  r   r   r   r4         zrun_analysis.<locals>.<genexpr>zFailed stations: c                 s  r  r  r   r  r   r   r   r4     r  )%r*   
INPUT_FILEr>   rV   rf   rE   rA   uniqueUSE_NETCDF_IF_CSV_MISSINGprintNOAA_NC_FILESTOFS_NC_FILEr   	Exceptionr-   rn  rr  r  r@   r   rI   r   r   r   r`  r   r  r   rb  	traceback
format_excr5   rp  rm  r  r&   r   )Zhourlyre   Zall_station_idsexcrc  ro  r   Zstation_monthlyr]  rh  ri  rq  r   r   r   run_analysis  s   



 r  __main__)r"   r#   )r+   r,   r"   r-   )r9   r   r"   r:   )rS   r:   r"   r:   )r"   rg   )r"   rz   )r   r   r   r   r"   r   )rx   r   r"   r   )rx   r   rv   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   r"   r#   )r   )r   r   r   r   r   r   r"   r   )r   r-   re   r:   r   r   r"   r-   )r   r-   re   r:   r
  r   r  r   r  r   r  r   r   r   r  r   r"   r#   )re   r:   r"   rT  )r   r-   rW  r:   r  r   r   r   r  r   r"   r#   )r   r-   re   r:   r"   rZ  )rc  rd  r"   r#   )ro  rd  r"   r:   )r   F)r+   r,   r   r   rs  rt  r"   r-   )r    r,   r   r,   r"   r}  )rq  r:   rc  rd  r"   r#   )[__doc__Z
__future__r   Zdataclassesr   r  r   Zpathlibr   sysr  __file__ZresolveparentZ
LOCAL_DEPSr>   pathinsertr-   Z
matplotlibZuseZmatplotlib.datesrx   r   Zmatplotlib.pyplotZpyplotr=  Znumpyrq   Zpandasr@   Zpylibr   r   Zstatsmodels.apiZapir   ZROOTr  r  r  r  r   r   r   r&   r(   r)   rp  rm  r  rA   rB   rC   rD   r  rN  r   rd   r   r   r*   r8   rV   rf   ry   r   r   r   r   r   r   r   r   r   r   r   r	  rS  rX  rY  rb  rn  rr  rw  ry  r|  r  r  r  r   r   r   r   r   <module>   s   







	
I






,

F

C
Y





 
J
Z
