o
    $›²j‰U  ã                   @   sô   d dl Z d dlZd dlZd dlZd dlZd dlmZmZ d dlZd dlm	Z
 ddlmZ ddlmZmZ ddlmZ ddlmZmZ d d	lmZ e  e¡Zd
ZdZdZdZdZdZdZ dZ!ddd„Z"dd„ Z#dd„ Z$dd„ Z%e
j&dd�dd„ ƒZ'dS ) é    N)ÚdatetimeÚtimezone)Ú
celery_appé   )ÚDB_PATH)Úquery_xai_apiÚSTRATEGIES_FILE)Úget_offer_count_trend_from_flat)ÚCURRENT_PRICING_PREDICATEÚSTALE_PRICING_PREDICATE)Úget_db_connectioné   é   i,  é2   iô  i † gš™™™™™ñ?i€„ é   c                 C   sü  zt tddd��}t |¡}W d  ƒ n1 sw   Y  W n ty9 } zt d|› �¡ W Y d}~dS d}~ww h d£}d}| D ]}|| dd¡› d	| d
d¡› d	� ¡ 7 }qBt	ƒ }d|v re| 
d¡ dd„ |D ƒ}	|D ]}
g |	|
< qn|D ]3}t|tƒsqw| dd¡}
| dd¡}|
|v r›|dkr›|	|
  |¡ qw|
|v rª|dkrª|	|
  |¡ qwg }|D ]#}
|	|
 d|… D ]}| d|
› d| dd¡› d| dd¡› �¡ q¹q¯|D ]#}
|	|
 d|… D ]}| d|
› d| dd¡› d| dd¡› �¡ qßqÕd |¡S )a  
    Implements tiered strategy injection (Pass 2 payload reduction).
    - Takes only 'High' confidence strategies from core categories, capped at max_per_core_category.
    - Dynamically adds specific category strategies if they match candidate keywords.
    Úrzutf-8)ÚencodingNzError reading strategies: Ú >   ÚRiskÚBuyingÚGeneralÚPricingÚTitleÚ ÚDetailed_SeasonalityÚtextbookÚSeasonalityc                 S   s   i | ]}|g “qS © r   )Ú.0Úcatr   r   ú7/var/www/agentarbitrage/keepa_deals/prime_picks_task.pyÚ
<dictcomp>4   s    z)get_tiered_strategies.<locals>.<dictcomp>Úcategoryr   Ú
confidenceÚHighz- [Category: z] IF ÚtriggerzN/Az THEN ÚadviceÚ
)Úopenr   ÚjsonÚloadÚ	ExceptionÚloggerÚerrorÚgetÚlowerÚsetÚaddÚ
isinstanceÚdictÚappendÚjoin)Ú
candidatesÚmax_per_core_categoryÚfÚ
strategiesÚeÚcore_categoriesÚcandidate_textÚcÚdynamic_categoriesÚcategorizedr   ÚsÚconfÚ	formattedr   r   r    Úget_tiered_strategies   sP   ÿ€€þ*


€.ÿ.ÿ
rC   c                 C   s   |   dt› d�¡ | jS )a0  Drop cached picks whose deals row no longer carries current pricing.

    Pass 1 decides what may ENTER the cache. This decides what may STAY in it,
    and it has to exist because two paths through `generate_prime_picks` return
    without touching the cache at all, by design (AGENTS.md 7.10, "Graceful
    Fallback"): Pass 1 finding no eligible candidates, and Pass 2 failing or
    returning nothing. A pick selected under superseded pricing logic survives
    every one of those runs, so without this it stays on the dashboard until a
    run happens to succeed - which is how ASIN 1418548839
    (`Pricing_Logic_Version` NULL) was still showing as pick #4 on 2026-09-22,
    a day after the repair sweep had skipped it.

    It runs on EVERY invocation, before Pass 1, rather than only when a pick is
    chosen, because staleness is not a property a row acquires on its own:
    bumping `PRICING_LOGIC_VERSION` re-stales the entire table at once, and that
    bump is the documented mechanism for scheduling the next repair sweep
    (AGENTS.md 7.13).

    A pick whose deals row has been deleted outright is left alone - the
    Agent's Choice INNER JOIN in `/api/deals` already makes it unshowable, and
    reaping it is the Janitor's business, not this function's.

    Returns the number of cached picks removed.
    zœ
        DELETE FROM prime_picks
        WHERE EXISTS (
            SELECT 1 FROM deals
            WHERE deals."ASIN" = prime_picks.asin
              AND z
        )
    )Úexecuter   Úrowcount)Úcursorr   r   r    Úprune_stale_priced_picksV   s   
ûrG   c                 C   s¬   | sdS z$t  |  dd¡¡}t  ¡ }|jr|jd d�}|| }td| ¡ d ƒW S  tyU   zt  | d¡}t  ¡ }|| }td| ¡ d ƒW  Y S  tyT   Y Y dS w w )Nr   ÚZz+00:00)Útzinfog      ¬@z%m/%d/%y %H:%M)	r   ÚfromisoformatÚreplaceÚutcnowrI   ÚmaxÚtotal_secondsÚ
ValueErrorÚstrptime)Údate_strÚdtÚnowÚdeltar   r   r    Úget_hours_sincey   s&   ÿúrU   c                 C   s6   | r| dkrdS t  dt| ƒ¡}|rt| d¡ƒS dS )NÚ-r   z(\d+)r   )ÚreÚsearchÚstrÚintÚgroup)Ú
offers_strÚmr   r   r    Úparse_offersŒ   s
   r^   z1keepa_deals.prime_picks_task.generate_prime_picks)Únamec            ?         s	  t  d¡ �zbttƒ�f} tj| _|  ¡ }t|ƒ}|r&t  d|› dt	› d�¡ d}d}d}d|› d	|› d
�}d|› dt
› d|› d|› dt› d|› dt› dt› d|› d|› dt› dt	› d�}| |¡ ¡ }dd„ |D ƒ}	W d  ƒ n1 stw   Y  t  dt|	ƒ› d�¡ |	sŽt  d¡ W dS g }
d}d}d}|	D �]+}t| dd¡ƒ dd¡ dd¡}t| d d¡ƒ dd¡ dd¡}zt|ƒ}t|ƒ}|dkrÎ|| d! nd}W n tyÞ   d}d}Y nw t| d"¡pé| d#¡ƒ}| d$¡}|du sø|dkrûd%}nztt|ƒ dd¡ƒ}W n t�y   d%}Y nw t| d&¡ƒ}d'|d( d)  }|d*td+|d, ƒ  }|| d+|td-|ƒ   }d.}t| d/d¡ƒ ¡  ¡ }|d0k�rk|tk�rkt  d1| d2¡› d3|› d4|› d5�¡ |d*7 }d6}t|ƒ}|�s’|d7k�r’|tk�r’t  d1| d2¡› d8|› d9�¡ |d*7 }d6}n"|d:v �ršt }n|du �r²d;}|d*7 }t  d<| d2¡› d=�¡ nd;}|�sÄ|| }||d>< |
 !|¡ q˜t  d?|› d@t|	ƒ› dA|› dB|› �¡ |
j"dCdD„ d6dE� |
ddF… }t  dGt|ƒ› dH�¡ t#|ƒ}g } |D ].}!|  !|! d2¡|! dId¡dd!… |! d$¡|! d&¡|! dJ¡|! d¡|! d/¡|! dK¡dLœ¡ �qúdM|› dNt$j%| dOdP�› dQ�}"dRdSdTœdU|"dTœgdVd.dWdXœ}#ddl&}$t|"ƒ}%t  dY|%› dZ�¡ |$ &¡ }&t'|#ƒ}'|$ &¡ |& }(t  d[|(d\›d]�¡ g ‰ d.})|'�r{d^|'v �r‹t  (d_|' d^d`¡› �¡ d6})�n$da|'v �r­|'da �r­|'da d  dbi ¡}*|* dcd¡ ¡ }+t  dd|+› �¡ t)j*de|+t)j+df�},|,�rÃ|, ,d*¡ ¡ }-n%|+ ¡ }-|- -dg¡}.|- .dh¡}/|.dik�rè|/dik�rè|/|.k�rè|-|.|/d* … }-z¯t$ /|-¡}0i }1g ‰ t0|0t1ƒ�rp|0 djg ¡}2|0 dkg ¡}3t0|2t2ƒ�r+t|2ƒdk�r+t0|2d tƒ�r+dld„ |2D ƒ‰ ˆ D ]}4dm|1|4< �q"n&|2D ]#}5t0|5t1ƒ�rOdn|5v �rOt|5dn ƒ}4ˆ  !|4¡ t|5 dod¡ƒ|1|4< �q-|3D ]}5t0|5t1ƒ�rndn|5v �rnt|5 dod¡ƒ|1t|5dn ƒ< �qS| D ]#}6t|6 d2¡ƒ}4|4ˆ v }7|1 |4dp¡}8t  dq|4› dr|7› ds|8› dt�¡ �qrW n t$j3�y¬   t  (du|+› �¡ d6})Y nw d6})|)�rºt  dv¡ W dS ‡ fdwd„|D ƒ}9|9�sÎt  dx¡ W dS t  dyt|9ƒ› dz�¡ tt4 5¡ ƒ}:t6 7t8j9¡ :¡ };g }<t;|9d*d{�D ]\}=}!|< !t|! d2¡ƒ|=t|! d>d¡ƒ|;|:f¡ �qïttƒ�O} |  ¡ }| d|¡ z | d}¡ | <d~|<¡ | d¡ t  d€t|<ƒ› d�|:› d�¡ W n t=�yS }> z| d‚¡ t  (dƒ|>› �¡ ‚ d}>~>ww W d  ƒ W dS 1 �saw   Y  W dS  t=�y† }> zt j(d„|>› �d6d…� W Y d}>~>dS d}>~>ww )†zž
    Executes the async Two-Pass Prime Picks pipeline.
    Pass 1: Smart Floor SQL/math filtering & scoring
    Pass 2: xAI Mastermind batched evaluation
    z#Starting Async Prime Picks PipelinezEvicted z: cached Prime Pick(s) whose pricing is no longer current (z).z:CAST(REPLACE(REPLACE("Profit", '$', ''), ',', '') AS REAL)z?CAST(REPLACE(REPLACE("All_in_Cost", '$', ''), ',', '') AS REAL)z;CAST(REPLACE(REPLACE("List_at", '$', ''), ',', '') AS REAL)z((z	 * 1.0 / z) * 100)z;
                SELECT * FROM deals
                WHERE z >= z
                AND z > 0
                AND z <= zE
                AND CAST(REPLACE("Deal_Trust", '%', '') AS REAL) >= z?
                AND "List_at" IS NOT NULL
                AND z®
                AND "1yr_Avg" IS NOT NULL
                AND "1yr_Avg" NOT IN ('-', 'N/A', '', '0', '0.00', '$0.00')
                AND "1yr_Avg" != 0
                AND z
            c                 S   ó   g | ]}t |ƒ‘qS r   )r3   )r   Úrowr   r   r    Ú
<listcomp>Ë   ó    z(generate_prime_picks.<locals>.<listcomp>NzPass 1: Found z! candidates matching Smart Floor.zNo candidates found. Exiting.r   ÚProfitÚ0ú$r   ú,ÚAll_in_Costéd   Úlast_seen_utcÚ
Deal_foundÚSales_Rank_Currenti@B ÚOffersé   g    €„>Aé�   r   g      à?g{®Gáz”?gü©ñÒMbP?Fr   z
year-roundz[Pass 1 Filter] Dropped ASIN=ÚASINz (seasonality=z, sales_rank=u   ) â€” year-round + high rankTÚrisingz (trend=rising, sales_rank=u#   ) â€” rising offers + weak velocity)ÚfallingÚflatg      ð?z)[Pass 1 Filter] No trend signal for ASIN=u%    â€” passing through with no modifierÚ_scorez+[Pass 1 Filter] Offer-trend filter dropped z of z candidates; z3 had no trend signal; Year-round high rank dropped c                 S   s   |   dd¡S )Nrt   r   )r.   )Úxr   r   r    Ú<lambda>  s    z&generate_prime_picks.<locals>.<lambda>)ÚkeyÚreverser   zPass 1: Top z( candidates selected for xAI evaluation.r   ÚSales_Rank_Drops_last_180_daysÚPercent_Down)rp   r   rl   rm   ry   rd   r   rz   at  
        You are the xAI Mastermind evaluating the top candidate deals.

        **Evaluation Strategy:**
        You MUST evaluate candidates holistically against ALL strategies present in the provided text rules. However, recognize that not every deal will be a "perfect" match for every strategy (e.g. not everything needs to be seasonal).

        Strategies:
        z"

        **Candidates:**
        é   )ÚindentuØ  

        **Instructions:**
        Evaluate the items (ASINs) and determine if they represent solid arbitrage opportunities based on the strategies. Filter out any deals that violate key risk management rules or are obviously poor choices, but allow good standard deals to pass.
        You MUST return a JSON object containing two keys: "selected" and "rejected". The "selected" key should contain a list of objects with the ASIN and a brief 1-sentence reason. The "rejected" key should contain a list of objects with the ASIN and a brief 1-sentence reason.
        Example:
        {
          "selected": [{"asin": "0123456789", "reason": "High ROI and stable BSR trend."}],
          "rejected": [{"asin": "B01ABCD123", "reason": "Sales rank is too erratic indicating high risk."}]
        }
        You MUST return ONLY the JSON object. No markdown formatting, no other explanations.
        IMPORTANT â€” TEXTBOOK COUNTERFEIT RISK CORRECTION: Some strategies you have been given may suggest categorical rejection of textbooks (medical, engineering, AP, college subjects, etc.) due to counterfeit risk. This is overcautious and incorrect. Do NOT reject textbook candidates based on category alone or subject area alone.

        Counterfeit risk is genuinely HIGH only when ALL FOUR of the following conditions are met simultaneously:

        1. Sales Rank is under 100,000 (high current demand)
        2. Price is significantly below historical market average
        3. Condition is New or Like-New
        4. Seller has limited feedback or appears to be moving multiple new-condition copies at suspiciously low prices

        One, two, or three of these factors alone is NOT a counterfeit signal. Only all four together is the red flag pattern.

        Books with Sales Rank above 500,000 are very unlikely to be counterfeit regardless of category. Older editions of textbooks are also unlikely to be counterfeit. Apply the four-factor test specifically â€” do not reject on category.

        This correction takes priority over any general strategies suggesting textbook avoidance.

        IMPORTANT â€” SEASONAL HIGH-RANK CORRECTION: Some candidates will have a high current sales rank (over 1,000,000) but be flagged in `Detailed_Seasonality` as a seasonal title (e.g., "Medical School", "Christmas", "Summer Reading", "Spring Gardening", or any season-specific category). Do NOT reject seasonal candidates solely based on current high sales rank.

        A book that ranks 1.5M in the off-season may rank 200K-400K during its season, with strong ROI for buyers willing to hold inventory. The ROI on seasonal flips often exceeds high-velocity flips because the off-season buy price is depressed. The seller plans inventory by holding books at their own prep warehouse and shipping to Amazon 2-3 months before the selling season to minimize FBA aged-inventory surcharges (which begin at 181 days and escalate steeply at 271+ days).

        Apply the high-rank rejection rule strictly only when the candidate is classified as "Year-round" or has no clear seasonal classification. For seasonal candidates, prioritize offer-trend stability and discount depth over current rank.

        This correction takes priority over general velocity-based rejection rules.

        Úsystemz'You are a precise JSON-only output bot.)ÚroleÚcontentÚuserzgrok-4-fast-reasoninggš™™™™™É?)ÚmessagesÚmodelÚstreamÚtemperaturez'Pass 2: Querying xAI API (prompt size: z
 chars)...zPass 2: xAI API call took z.2fz	 seconds.r-   zxAI API returned an error: zUnknown ErrorÚchoicesÚmessager   zPass 2 Raw Response: z```(?:json)?\s*(.*?)\s*```)ÚflagsÚ{Ú}éÿÿÿÿÚselectedÚrejectedc                 S   r`   r   )rY   )r   ru   r   r   r    rb   �  rc   z(No reason provided (legacy array format)ÚasinÚreasonzSee raw responsez[Pass 2 Reasoning] ASIN=z
 Selected=z	 Reason="ú"z$Failed to parse xAI output as JSON: zIPass 2 failed. Preserving previous valid run instead of overriding cache.c                    s"   g | ]}t | d ¡ƒˆ v r|‘qS )rp   )rY   r.   )r   Úd©Úselected_asinsr   r    rb   °  s   " z:Pass 2 returned empty list. Preserving previous valid run.zPass 2 complete. Selected z Prime Picks.)ÚstartzBEGIN TRANSACTIONzDELETE FROM prime_picksz’
                    INSERT INTO prime_picks (asin, rank, score, generated_at, run_id)
                    VALUES (?, ?, ?, ?, ?)
                ÚCOMMITzSuccessfully saved z" Prime Picks to database (run_id: ÚROLLBACKz"Failed to save Prime Picks to DB: z$Error in generate_prime_picks task: )Úexc_info)>r,   Úinfor   r   Úsqlite3ÚRowÚrow_factoryrF   rG   r
   ÚPASS_1_MIN_PROFITÚPASS_1_MIN_ROIÚPASS_1_MAX_ROIÚPASS_1_MIN_DEAL_TRUSTÚPASS_1_MAX_LIST_ATrD   ÚfetchallÚlenrY   r.   rK   ÚfloatrO   rU   r^   ÚminrM   r/   ÚstripÚPASS_1_YEAR_ROUND_VELOCITY_CAPr	   Ú PASS_1_OFFER_TREND_VELOCITY_GATEÚ#PASS_1_OFFER_TREND_BONUS_MULTIPLIERr4   ÚsortrC   r)   ÚdumpsÚtimer   r-   rW   rX   ÚDOTALLr[   ÚfindÚrfindÚloadsr2   r3   ÚlistÚJSONDecodeErrorÚuuidÚuuid4r   rS   r   ÚutcÚ	isoformatÚ	enumerateÚexecutemanyr+   )?ÚconnrF   ÚprunedÚsanitized_profitÚsanitized_costÚsanitized_list_atÚroi_exprÚqueryÚ	deal_rowsÚ
deals_listÚfiltered_dealsÚdropped_candidatesÚno_trend_candidatesÚdropped_year_round_high_rankÚdealÚ
profit_strÚcost_strÚprofitÚcostÚroiÚhours_sinceÚ
sales_rankÚoffersÚbase_half_lifeÚfinal_half_lifeÚscoreÚdrop_candidateÚseasonalityÚtrendÚoffer_trend_modifierÚtop_20Ústrategies_textÚcandidates_for_air�   ÚpromptÚpayloadrª   Úprompt_sizeÚ
start_timeÚresponse_dataÚlatencyÚ	ai_failedr†   r   Ú
json_matchÚcontent_cleanÚ	start_idxÚend_idxÚparsedÚreasoning_mapÚselected_listÚrejected_listr�   Úitemr=   Úis_selectedrŽ   Úfinal_dealsÚrun_idÚgenerated_atÚrecords_to_insertÚrank_idxr:   r   r‘   r    Úgenerate_prime_picks“   sÄ  

ÿÿþþýüüûûúø	÷	÷óÓ/

  þ
ÿ$


€&øù
ö0þù



*ÿ
€€"ûþ


û


ý
 
€ýÿ(ò"€ÿrí   )r   )(Úloggingr˜   r)   rW   r±   r   r   ÚosÚworkerr   ÚceleryÚdb_utilsr   Úava_advisorr   r   Únew_analyticsr	   Úpricing_versionr
   r   Úkeepa_deals.db_utilsr   Ú	getLoggerÚ__name__r,   r›   rœ   r�   rž   rŸ   r¦   r§   r¥   rC   rG   rU   r^   Útaskrí   r   r   r   r    Ú<module>   s8    

;#
