
    @Wj                        S r SSKJr  SSKrSSKJrJrJr  SSS jjrSSS jjr	SSS jjr
SSS jjrSS	 jrSSS
 jjrSS jrSS jrSS jrS S jrS!S jrS"S jrS#S jrSS jrg)$z
Small, dependency-free helpers shared by every analyzer.

AliExpress payloads are inconsistent (strings for numbers, lists wrapped in
single-key dicts, missing keys), so all extraction goes through these
defensive utilities.
    )annotationsN)AnyIterableOptionalc                ~    U nUR                  S5       H%  n[        U[        5      (       a  XC;  a  Us  $ X4   nM'     U$ )z{Read a nested value using a dotted path, e.g. ``"a.b.c"``.

Returns ``default`` when any segment is missing or not a dict.
.)split
isinstancedict)datapathdefaultcurrentkeys        ڋ/Users/waleedhassanin/Documents/snowflake_co_dev_github/snow_flake_back_end_deploy/mama_care_api/ali_express_ds/pms/engine/utils/helpers.pysafe_getr      s@    
 Gzz#'4((C,>N,  N    c                J    U H  n[        X5      nUSS/ 0 4;  d  M  Us  $    U$ )z>Return the first non-empty value among several candidate keys.N )r   )r   keysr   r   values        r   first_presentr      s3    #r2r**L  Nr   c                    U c  U$ [        U [        [        45      (       a  [        U 5      $ [        R                  " S[        U 5      5      nU(       d  U$ [        UR                  5       R                  SS5      5      $ )zJCoerce AliExpress numeric strings ('27.78', '19,00', 'USD 5.99') to float.z-?\d+(?:[.,]\d+)?,r   )r
   intfloatresearchstrgroupreplace)r   r   matchs      r   to_floatr#   $   sb    }%#u&&U|II*CJ7E&&sC011r   c                >    [        [        U [        U5      5      5      $ N)r   r#   r   )r   r   s     r   to_intr&   0   s    xuW~.//r   c                2    [        [        U 5      S-   S5      $ )z9Round monetary / percentage values to two decimal places.           )roundr   )r   s    r   round2r+   4   s    u#Q''r   c                ,    [        U[        X 5      5      $ )z$Keep a score inside its valid range.)maxmin)r   lowhighs      r   clampr1   9   s    sC$%%r   c                0   U c  / $ [        U [        5      (       a  U $ [        U [        5      (       ac  [        U 5      S:X  aQ  [	        [        U R                  5       5      5      n[        U[        5      (       a  U$ [        U[        5      (       a  U/$ U /$ U /$ )zNormalize AliExpress list containers.

The API often wraps lists as ``{"some_key_dto": [...]}`` or returns a
single dict instead of a list. This flattens all of those to a plain list.
   )r
   listr   lennextitervalues)r   inners     r   as_listr:   >   s     }	%%u:?elln-.E%&&%&&ww7Nr   c                L    X:X  a	  X:X  a  S$ S$ X -
  X!-
  -  n[        US-  5      $ )zMap ``value`` to 0-100 where ``best`` earns 100 and ``worst`` earns 0.

Works in both directions (best < worst means "lower is better").
      Y@r(   r1   )r   bestworstratios       r   linear_scorerA   T   s5    
 }u.3.]u|,Er   c                0    US::  a  g[        X-  S-  5      $ )z8Map ``value`` to 0-100, saturating at ``full_score_at``.r   r(   r<   r=   r   full_score_ats     r   ratio_scorerE   _   s     %'5011r   c                b   [        U [        5      (       d  0 $ U n[        S5       H  nSnUR                  5        H8  u  pEUR	                  S5      (       d  M  [        U[        5      (       d  M4  UnSn  O   [        UR                  S5      [        5      (       a  US   nSnU(       a  M    U$    U$ )a  Peel AliExpress API envelopes off a payload.

Real API responses arrive wrapped, e.g.::

    {"aliexpress_ds_product_wholesale_get_response": {"result": {...}}}
    {"aliexpress_ds_freight_query_response": {"result": {...}}}

This walks down through any ``*_response`` wrapper and any ``result``
container until the actual data object is reached. Payloads that are
already unwrapped pass through unchanged.
   F	_responseTresult)r
   r   rangeitemsendswithget)r   r   _	descendedr   r   s         r   unwrap_responserP   f   s     dD!!	G1X	!--/JC||K((Zt-D-D 		 *
 gkk(+T22h'GIyN  Nr   c                b   U c  g[        U [        [        45      (       a  [        U 5      nX4$ [        R                  " S[        U 5      5       Vs/ s H  n[        U5      PM     nnU(       d  g[        U5      S:X  a
  US   US   4$ [        US   US   5      [        US   US   5      4$ s  snf )zParse AliExpress delivery time strings.

Accepts values like ``"12-20"``, ``"15"``, ``"7 days"`` or integers.
Returns ``(min_days, max_days)`` or ``(None, None)`` when unknown.
)NNz\d+r3   r   )	r
   r   r   r   findallr   r5   r.   r-   )rawdaysnnumberss       r   parse_delivery_daysrW      s     {#U|$$3xz!zz&#c(;<;!s1v;G<
7|qqz71:%%wqz71:&GAJ
(CCC =s   B,c                    U  VVs/ s H  u  pUc  M
  US:  d  M  X4PM     nnn[        S U 5       5      nUS::  a  g[        [        S U 5       5      U-  5      $ s  snnf )a  Weighted average of (score, weight) pairs on a 0-100 scale.

Missing metrics are passed as ``None`` scores and are *excluded*; the
remaining weights are renormalized so unavailable data never drags the
result toward zero. Returns 0.0 when nothing is available.
r   c              3  *   #    U  H	  u  pUv   M     g 7fr%    ).0rN   ws      r   	<genexpr>#weighted_average.<locals>.<genexpr>   s     /YTQqYs   r(   c              3  .   #    U  H  u  pX-  v   M     g 7fr%   rZ   )r[   sr\   s      r   r]   r^      s     1ytqQUys   )sumr1   )rK   r`   r\   	availabletotal_weights        r   weighted_averagerd      sd     %*EEDAQq1u!EIE/Y//Lq1y11L@AA	 Fs   	AAAc                    U(       d  gXS   S   ::  a  [        US   S   5      $ [        XSS 5       H3  u  u  p#u  pEX::  d  M  XB:w  a	  X-
  XB-
  -  OSn[        X6XS-
  -  -   5      s  $    [        US   S   5      $ )zLinear interpolation over (value, score) anchor points.

Produces smooth, gradually increasing scores instead of hard cliffs.
Anchors must be sorted by value; values outside the range clamp to the
first / last anchor's score.
r(   r   r3   Ng      ?)r1   zip)r   anchorsx1y1x2y2r@   s          r   piecewise_scorerm      s     
1WQZ]##!'12;7(2;02UZBG,cErw//00 8 Q  r   c                B    US::  d  U S::  a  g[        X-  S-  S-  5      $ )zSquare-root saturation curve on a 0-100 scale.

Rewards early progress: 10% of the saturation point already earns ~32
points instead of 10. Used for volume metrics (sales, reviews) so that
moderate history is not punished as if it were zero.
r   r(   g      ?r<   r=   rC   s     r   saturating_scorero      s-     UaZ5(S0E9::r   r%   )r   r   r   r   r   r   returnr   )r   r   r   zIterable[str]r   r   rp   r   )r(   )r   r   r   r   rp   r   )r   )r   r   r   r   rp   r   )r   r   rp   r   )r(   r<   )r   r   r/   r   r0   r   rp   r   )r   r   rp   r4   )r   r   r>   r   r?   r   rp   r   )r   r   rD   r   rp   r   )r   r   rp   r   )rS   r   rp   z#tuple[Optional[int], Optional[int]])rK   z%'list[tuple[Optional[float], float]]'rp   r   )r   r   rh   z'list[tuple[float, float]]'rp   r   )__doc__
__future__r   r   typingr   r   r   r   r   r#   r&   r+   r1   r:   rA   rE   rP   rW   rd   rm   ro   rZ   r   r   <module>rt      s`    # 	 * *
	20(
&
, 2<D&B!$	;r   