o
    mDWj–  ã                   @  sÆ   d Z ddlmZ ddlZddlmZmZmZ d;d<dd„Zd;d=dd„Z	d>d?dd„Z
d@dAdd„ZdBdd„ZdCdDdd „ZdEd"d#„ZdFd&d'„ZdGd)d*„ZdHd+d,„ZdId/d0„ZdJd3d4„ZdKd7d8„ZdGd9d:„ZdS )Lzâ
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                 C  s:   | }|  d¡D ]}t|tƒr||vr|  S || }q|S )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)r   r   r	   ÚcurrentÚkey© r   út/var/www/snowflake_co_dev_github/snow_flake_back_end_deploy/mama_care_api/ali_express_ds/pms/engine/utils/helpers.pyÚsafe_get   s   
r   r   ÚkeysúIterable[str]c                 C  s0   |D ]}t | |ƒ}|ddg i fvr|  S q|S )z>Return the first non-empty value among several candidate keys.NÚ )r   )r   r   r	   r   Úvaluer   r   r   Úfirst_present   s   
ÿr   ç        r   Úfloatc                 C  sN   | du r|S t | ttfƒrt| ƒS t 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	   Úmatchr   r   r   Úto_float$   s   r"   r   c                 C  s   t t| t|ƒƒƒS ©N)r   r"   r   )r   r	   r   r   r   Úto_int0   s   r$   c                 C  s   t t| ƒd dƒS )z9Round monetary / percentage values to two decimal places.r   é   )Úroundr   )r   r   r   r   Úround24   s   r'   ç      Y@ÚlowÚhighc                 C  s   t |t|| ƒƒS )z$Keep a score inside its valid range.)ÚmaxÚmin)r   r)   r*   r   r   r   Úclamp9   s   r-   Úlistc                 C  sj   | du rg S t | tƒr| S t | tƒr2t| ƒdkr/tt|  ¡ ƒƒ}t |tƒr'|S t |tƒr/|gS | gS | gS )zÄNormalize 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   r.   r   ÚlenÚnextÚiterÚvalues)r   Úinnerr   r   r   Úas_list>   s   



r5   ÚbestÚworstc                 C  s4   ||kr| |kr
dS dS ||  ||  }t |d ƒS )z‘Map ``value`` to 0-100 where ``best`` earns 100 and ``worst`` earns 0.

    Works in both directions (best < worst means "lower is better").
    r(   r   ©r-   )r   r6   r7   Úratior   r   r   Úlinear_scoreT   s   r:   Úfull_score_atc                 C  s   |dkrdS t | | d ƒS )z8Map ``value`` to 0-100, saturating at ``full_score_at``.r   r   r(   r8   ©r   r;   r   r   r   Úratio_score_   s   r=   c                 C  s€   t | tƒsi S | }tdƒD ]0}d}| ¡ D ]\}}| d¡r)t |tƒr)|}d} nqt | d¡tƒr8|d }d}|s= |S q|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   r   r   r   Úunwrap_responsef   s&   
€þrG   Úrawú#tuple[Optional[int], Optional[int]]c                 C  sŒ   | du rdS t | ttfƒrt| ƒ}||fS dd„ t dt| ƒ¡D ƒ}|s&dS t|ƒdkr4|d |d fS t|d |d ƒt|d |d ƒfS )z½Parse 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                 S  s   g | ]}t |ƒ‘qS r   )r   )Ú.0Únr   r   r   Ú
<listcomp>�   s    z'parse_delivery_days.<locals>.<listcomp>z\d+r/   r   )	r   r   r   r   Úfindallr   r0   r,   r+   )rH   ÚdaysÚnumbersr   r   r   Úparse_delivery_days„   s   $rP   rB   ú%'list[tuple[Optional[float], float]]'c                 C  sF   dd„ | D ƒ}t dd„ |D ƒƒ}|dkrdS tt dd„ |D ƒƒ| ƒ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                 S  s(   g | ]\}}|d ur|dkr||f‘qS )Nr   r   ©rJ   ÚsÚwr   r   r   rL   ž   s   ( z$weighted_average.<locals>.<listcomp>c                 s  s   � | ]\}}|V  qd S r#   r   )rJ   rE   rT   r   r   r   Ú	<genexpr>Ÿ   s   € z#weighted_average.<locals>.<genexpr>r   r   c                 s  s   � | ]	\}}|| V  qd S r#   r   rR   r   r   r   rU   ¢   s   € )Úsumr-   )rB   Ú	availableÚtotal_weightr   r   r   Úweighted_average—   s
   rY   Úanchorsú'list[tuple[float, float]]'c                 C  s˜   |sdS | |d d krt |d d ƒS t||dd… ƒD ]&\\}}\}}| |krC||kr5| | ||  nd}t ||||   ƒ  S qt |d d ƒS )zòLinear 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   r/   Ng      ð?éÿÿÿÿ)r-   Úzip)r   rZ   Úx1Úy1Úx2Úy2r9   r   r   r   Úpiecewise_score¥   s   "þrb   c                 C  s(   |dks| dkr
dS t | | d d ƒ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(   r8   r<   r   r   r   Úsaturating_score·   s   rc   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*   r   r
   r   )r   r   r
   r.   )r   r   r6   r   r7   r   r
   r   )r   r   r;   r   r
   r   )r   r   r
   r   )rH   r   r
   rI   )rB   rQ   r
   r   )r   r   rZ   r[   r
   r   )Ú__doc__Ú
__future__r   r   Útypingr   r   r   r   r   r"   r$   r'   r-   r5   r:   r=   rG   rP   rY   rb   rc   r   r   r   r   Ú<module>   s$    	







