
    ?Qj                        d Z ddlZddlZddlmZmZmZ ddlmZmZm	Z	 ddl
mZmZmZ dedefdZd	edefd
Zd	edefdZdd	edee         deeee	f                  fdZdedededeeee	f                  fdZd	edefdZdS )a
  
Params storage layer.

FIXED 2026-07-02: the original version of this file (inherited verbatim
from the old, never-actually-live td-params-server) looked for
    {PARAMS_DATA_DIR}/{symbol}/{date}.json
but real published data on Corsair is actually laid out as
    {PARAMS_DATA_DIR}/{TD_EA_CODE}/{symbol}/{date}.json
(confirmed against data/params/TrendDegree1/USDJPY/2026-06-26.json,
which predates and survived this fix). The old code would have silently
returned UNKNOWN_SYMBOL for every real request - never caught before
because nothing had actually gone live against it yet.

FIXED 2026-07-04: two related bugs found once a real EA actually polled
this in production, both now fixed together:

  1. The current-week check used `start <= today < end` (strict less-
     than on the end date) - excluding the week's own final day from
     counting as "current." A week published through Friday would stop
     being served starting ON that same Friday, hours before it
     actually ended. Now `start <= today <= end` (inclusive).

  2. Trading weeks are now uniformly Saturday-Friday across ALL
     instruments (see Lightning's week_utils.py) so that 24/7
     instruments (crypto) get the same real weekend gap to run
     walk-forward and publish that 5-day instruments always had. But
     that gap still needs bridging: a new week's params might not be
     published the instant the old week ends. Rather than fail
     NOT_AVAILABLE during that gap, this now explicitly falls back to
     the most recent PAST published week if no current week exists -
     "keep trading last week's params until this week's are ready" -
     with MAX_PARAMS_AGE_DAYS as the only thing that cuts this fallback
     off (refuses to serve something dangerously stale as if it were
     still usable).

Selection priority (in order):
    1. A week containing today (start <= today <= end) - the ideal case.
    2. The most recent PAST week (end < today) - explicit stale-but-
       known fallback, gated by MAX_PARAMS_AGE_DAYS.
    3. The nearest UPCOMING week (start > today) - kept for the edge
       case of publishing ahead of a week's start; rarely hit under the
       new Sat-Fri model since publishing typically happens on or after
       the Saturday a week begins, not before.
    4. None -> NOT_AVAILABLE.

UPDATED 2026-07-10: field_mapping.py's published doc shape changed -
instrument scaling fields (point, contract_size, price_decimals) and
dd_calibration's contents changed, but trade_week_start/trade_week_end
remain top-level (unchanged) since the EA's own freshness gate reads
them directly from there. No change was needed in this module after
all - noted here in case a stale comment elsewhere suggests otherwise.
    N)datedatetime	timedelta)OptionalDictAny)PARAMS_DATA_DIRMAX_PARAMS_AGE_DAYS
TD_EA_CODEsreturnc                 P    t          j        | d                                          S )Nz%Y-%m-%d)r   strptimer   )r   s    $/wd/license_services/params_store.py_parse_iso_dater   <   s!    Q
++00222    canonical_symbolc                     d                     d | D                       }t          j                             t          t          |          S )N c              3   J   K   | ]}|                                 s|d v |V  dS )z._-N)isalnum).0cs     r   	<genexpr>z_symbol_dir.<locals>.<genexpr>B   s3      LL!))++Le1LLr   )joinospathr	   r   )r   safes     r   _symbol_dirr   @   s;    77LL.LLLLLD7<<T:::r   c                 |   t          |           }t          j                            |          sg S g }t          j        |          D ]}|                    d          st          j                            ||          }	 t          |dd          5 }t          j	        |          }d d d            n# 1 swxY w Y   t          |d                   }t          |d                   }n## t          $ r t          d| d           Y w xY w|                    |||f           |                    d	 
           |S )Nz.jsonrutf-8encodingtrade_week_starttrade_week_endz([params_store] WARNING: could not parse z
, skippingc                     | d         S )Nr    )ts    r   <lambda>z&list_published_weeks.<locals>.<lambda>X   s
    qt r   )key)r   r   r   isdirlistdirendswithr   openjsonloadr   	Exceptionprintappendsort)	r   dresultsfnamefpathfhdocstartends	            r   list_published_weeksr>   F   s   $%%A7== 	GA , ,~~g&& 	Q&&	eS7333 $rimm$ $ $ $ $ $ $ $ $ $ $ $ $ $ $#C(:$;<<E!#&6"788CC 	 	 	NUNNNOOOH	 	sE*++++LL^^L$$$Ns6    C)B3'C)3B7	7C):B7	;-C))D	D	todayc                 l   t          j                    t          |           }|sd S |D ])\  }}}|cxk    r|k    rn t          ||          c S *fd|D             }|r!|d         \  }}}t          ||          }||S fd|D             }|r|d         \  }}}t          ||          S d S )Nc                 ,    g | ]}|d          k     |S )   r(   r   wr?   s     r   
<listcomp>z2get_current_or_upcoming_params.<locals>.<listcomp>k   s"    ---!!uAr   c                 ,    g | ]}|d          k    |S )r   r(   rC   s     r   rE   z2get_current_or_upcoming_params.<locals>.<listcomp>w   s"    111aAaD5LLLLLr   r   )r   r?   r>   _load_and_sanity_check)	r   r?   weeksr<   r=   r9   pastresultupcomings	    `       r   get_current_or_upcoming_paramsrM   \   s   }
 !122E t # = =sEE    S     )%e<<<<< ! .---u---D  HsE'sE::M 21115111H 9$QKsE%eS%8884r   r9   r&   c                     ||z
  t          t                    k    rt          d|  d           d S t          | dd          5 }t	          j        |          cd d d            S # 1 swxY w Y   d S )N)daysz[params_store] WARNING: zB is stale beyond MAX_PARAMS_AGE_DAYS, refusing to serve as currentr!   r"   r#   )r   r
   r3   r/   r0   r1   )r9   r&   r?   r:   s       r   rH   rH      s    )1D"E"E"EEErrrrssst	eS7	+	+	+ ry}}                 s   A%%A),A)c                 Z    t           j                            t          |                     S N)r   r   r,   r   )r   s    r   symbol_has_any_datarR      s    7==%566777r   rQ   )__doc__r0   r   r   r   r   typingr   r   r   configr	   r
   r   strr   r   listr>   rM   rH   boolrR   r(   r   r   <module>rY      s  3 3h  				 . . . . . . . . . . & & & & & & & & & & C C C C C C C C C C3s 3t 3 3 3 3;# ;# ; ; ; ;3 4    ,   S  $  [cdhilnqiqdr[s        F# t D XVZ[^`c[cVdMe    8# 8$ 8 8 8 8 8 8r   