
    Qj                        d Z ddlmZ ddlZddlmZmZmZ d-d.dZd-d/dZ	d0d1dZ
d2d3dZd4dZd5d6dZd7dZd8dZd9d!Zd:d"Zd;d%Zd<d(Zd=d+Zd9d,ZdS )>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Optionaldatar   pathstrdefaultreturnc                    | }|                     d          D ]'}t          |t                    r||vr|c S ||         }(|S )zRead 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)r   r   r
   currentkeys        8mama_care_api/ali_express_ds/pms/engine/utils/helpers.pysafe_getr      sW    
 Gzz#  '4(( 	Cw,>,>NNN#,N    r   keysIterable[str]c                H    |D ]}t          | |          }|ddg i fvr|c S |S )z>Return the first non-empty value among several candidate keys.N )r   )r   r   r
   r   values        r   first_presentr      sG      s##r2r***LLL +Nr           r   floatc                   | |S t          | t          t          f          rt          |           S t          j        dt          |                     }|s|S t          |                                                    dd                    S )zJCoerce AliExpress numeric strings ('27.78', '19,00', 'USD 5.99') to float.Nz-?\d+(?:[.,]\d+)?,r   )r   intr   researchr	   groupreplace)r   r
   matchs      r   to_floatr&   $   sy    }%#u&& U||I*CJJ77E &&sC00111r   r    c                V    t          t          | t          |                              S N)r    r&   r   )r   r
   s     r   to_intr)   0   s     xuW~~..///r   c                B    t          t          |           dz   d          S )z9Round monetary / percentage values to two decimal places.r      )roundr   )r   s    r   round2r-   4   s    u#Q'''r         Y@lowhighc                >    t          |t          ||                     S )z$Keep a score inside its valid range.)maxmin)r   r/   r0   s      r   clampr4   9   s    sCe$$%%%r   listc                N   | g S t          | t                    r| S t          | t                    rst          |           dk    r]t	          t          |                                                     }t          |t                    r|S t          |t                    r|gS | gS | gS )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.
    N   )r   r5   r   lennextitervalues)r   inners     r   as_listr=   >   s     }	% % u::??ellnn--..E%&& %&& ww7Nr   bestworstc                \    ||k    r
| |k    rdndS || z
  ||z
  z  }t          |dz            S )zMap ``value`` to 0-100 where ``best`` earns 100 and ``worst`` earns 0.

    Works in both directions (best < worst means "lower is better").
    r.   r   r4   )r   r>   r?   ratios       r   linear_scorerC   T   sC    
 u}}uu3.U]ut|,Er   full_score_atc                <    |dk    rdS t          | |z  dz            S )z8Map ``value`` to 0-100, saturating at ``full_score_at``.r   r   r.   rA   r   rD   s     r   ratio_scorerG   _   s*    s%-'50111r   c                `   t          | t                    si S | }t          d          D ]}d}|                                D ]5\  }}|                    d          rt          |t                    r|}d} n6t          |                    d          t                    r
|d         }d}|s n|S )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_responserR   f   s     dD!! 	G1XX  	!--// 	 	JC||K(( Zt-D-D  	gkk(++T22 	h'GI 	E	Nr   raw#tuple[Optional[int], Optional[int]]c                ~   | dS t          | t          t          f          rt          |           }||fS d t          j        dt          |                     D             }|sdS t          |          dk    r|d         |d         fS t          |d         |d                   t          |d         |d                   fS )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.
    N)NNc                ,    g | ]}t          |          S  )r    ).0ns     r   
<listcomp>z'parse_delivery_days.<locals>.<listcomp>   s    <<<!s1vv<<<r   z\d+r7   r   )	r   r    r   r!   findallr	   r8   r3   r2   )rS   daysnumberss      r   parse_delivery_daysr^      s     {z#U|$$ 3xxTz<<rz&#c((;;<<<G z
7||qqz71:%%wqz71:&&GAJ
(C(CCCr   rM   %'list[tuple[Optional[float], float]]'c                    d | D             }t          d |D                       }|dk    rdS t          t          d |D                       |z            S )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.
    c                ,    g | ]\  }}||dk    ||fS )Nr   rW   rX   sws      r   rZ   z$weighted_average.<locals>.<listcomp>   s)    EEEDAqQ]q1uu!Quuur   c              3      K   | ]	\  }}|V  
d S r(   rW   )rX   rP   rd   s      r   	<genexpr>z#weighted_average.<locals>.<genexpr>   s&      //TQq//////r   r   r   c              3  &   K   | ]\  }}||z  V  d S r(   rW   rb   s      r   rf   z#weighted_average.<locals>.<genexpr>   s*      11tq!QU111111r   )sumr4   )rM   	availabletotal_weights      r   weighted_averagerk      sn     FEEEEEI//Y/////Lqs11y11111L@AAAr   anchors'list[tuple[float, float]]'c                H   |sdS | |d         d         k    rt          |d         d                   S t          ||dd                   D ]>\  \  }}\  }}| |k    r-||k    r| |z
  ||z
  z  nd}t          ||||z
  z  z             c S ?t          |d         d                   S )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   r7   Ng      ?)r4   zip)r   rl   x1y1x2y2rB   s          r   piecewise_scoreru      s      s
1WQZ]###!'7122;77 1 1R(2rB;;02bURZBG,,cEerBw//00000  Q   r   c                N    |dk    s| dk    rdS t          | |z  dz  dz            S )a  Square-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.   rA   rF   s     r   saturating_scorerw      s9     UaZZs5=(S0E9:::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/   r   r0   r   r   r   )r   r   r   r5   )r   r   r>   r   r?   r   r   r   )r   r   rD   r   r   r   )r   r   r   r   )rS   r   r   rT   )rM   r_   r   r   )r   r   rl   rm   r   r   )__doc__
__future__r   r!   typingr   r   r   r   r   r&   r)   r-   r4   r=   rC   rG   rR   r^   rk   ru   rw   rW   r   r   <module>r{      s    # " " " " " 				 * * * * * * * * * *
 
 
 
 
    	2 	2 	2 	2 	20 0 0 0 0( ( ( (
& & & & &
   ,       2 2 2 2   <D D D D&B B B B! ! ! !$	; 	; 	; 	; 	; 	;r   