o
    ¼£jðz  ã                   @   s®  d dl mZ d dlZd dlZd dlZd dlZd dlmZmZmZ d dl	m
Z
 d dlZd dlmZ ddlmZmZmZmZmZmZ ddlmZmZmZmZ dd	lmZmZ dd
lmZ ddl m!Z! ddl"m#Z$m%Z%m&Z&m'Z' ddl(m)Z)m*Z*m+Z+ ddl,m-Z-m.Z. ddl/m0Z0m1Z1m2Z2 d dl3m4Z4 ee5ƒZ6e
ƒ  dZ7dZ8ej9 :ej9 ;e<¡d¡Z=dZ>dZ?dZ@dZAdZBdZCdZDdd„ ZEdd„ ZFdd„ ZGdd „ ZHd!d"„ ZId*d$d%„ZJejKd&d'�d(d)„ ƒZLdS )+é    )Ú	getLoggerN)ÚdatetimeÚtimezoneÚ	timedelta)Úload_dotenv)Ú
celery_appé   )Ú create_deals_table_if_not_existsÚload_watermarkÚsave_watermarkÚ
to_db_keysÚupsert_deal_rowsÚDB_PATH)Úfetch_deals_for_dealsÚfetch_product_batchÚvalidate_asinÚfetch_current_stats_batch)ÚTokenManagerÚTokenRechargeError)ÚFUNCTION_LIST)Úget_seller_info_for_single_deal)Úload_settingsÚcalculate_all_in_costÚcalculate_profit_and_marginÚcalculate_min_listing_price)Úget_1yr_avg_sale_priceÚget_percent_discountÚ	get_trend)Úclassify_seasonalityÚget_sells_period)Ú_process_single_dealÚclean_numeric_valuesÚ_process_lightweight_update)Úget_db_connectionz3.0-ConsolidatedÚdealszheaders.jsoné   é2   Úsmart_ingestor_locki  éÈ   c                 C   s(   t dddtjd�}|t| d� }| ¡ S )zFConverts Keepa time (minutes since 2011-01-01) to ISO 8601 UTC string.éÛ  r   ©Útzinfo)Úminutes)r   r   Úutcr   Ú	isoformat)Úkeepa_minutesÚkeepa_epochÚ	dt_object© r2   ú5/var/www/agentarbitrage/keepa_deals/smart_ingestor.pyÚ_convert_keepa_time_to_iso0   s   r4   c                 C   sD   | sdS t  | ¡ tj¡}t dddtjd�}|| }t| ¡ d ƒS )zIConverts an ISO 8601 UTC string to Keepa time (minutes since 2011-01-01).r   r)   r   r*   é<   )r   ÚfromisoformatÚ
astimezoner   r-   ÚintÚtotal_seconds)Úiso_strr1   r0   Údeltar2   r2   r3   Ú_convert_iso_to_keepa_time6   s   r<   c              
   C   s˜   z+t  | ¡ tj¡}t  tj¡}||tdd� kr*t d| › d| 	¡ › �¡ | 	¡ } W n t
yE } zt d|› �¡ W Y d}~nd}~ww t| ƒ dS )z5Saves watermark with protection against future dates.é   ©Úhoursz#Clamping Extreme Future Watermark: z -> z!Error checking watermark safety: N)r   r6   r7   r   r-   Únowr   ÚloggerÚwarningr.   Ú	ExceptionÚerrorr   )Úiso_timestampÚwm_dtÚnow_utcÚer2   r2   r3   Úsave_safe_watermark?   s   €€ÿrI   c           	      C   s|  | sdS |   dg ¡}|   dg ¡}|   dg ¡}d}t|ƒdkr)|d dkr)|d }nt|ƒdkr9|d dkr9|d }|dkr?dS g }t|ƒdkrT|d dkrT| |d ¡ t|ƒdkrg|d dkrg| |d ¡ t|ƒd	krz|d	 dkrz| |d	 ¡ t|ƒd
kr�|d
 dkr�| |d
 ¡ t|ƒdkr |d dkr | |d ¡ t|ƒdkr³|d dkr³| |d ¡ t|ƒdkrÆ|d dkrÆ| |d ¡ t|ƒd	krÙ|d	 dkrÙ| |d	 ¡ t|ƒd
krì|d
 dkrì| |d
 ¡ t|ƒdkrÿ|d dkrÿ| |d ¡ |�sdS |   dd¡}|dk�r|dk �rdS t|ƒ}|dk �r!dS ||d k�r*dS |dk�r<|| | }|dk �r<dS dS )z’
    Heuristic check to see if a deal is worth a heavy fetch (20 tokens).
    Returns True if potentially profitable, False if obviously bad.
    FÚcurrentÚavg90Úavg365éÿÿÿÿé   r   é   é   é   é   ÚsalesRankDrops365i°  gš™™™™™ñ?r   gš™™™™™É?T)ÚgetÚlenÚappendÚmax)	ÚstatsrJ   rK   rL   Ú	buy_priceÚsell_candidatesÚdrops365Úest_sellÚ	gross_roir2   r2   r3   Úcheck_peek_viabilityN   sJ   
&&&&&&&&&&


r^   c               
   C   sø   z`t tƒ�Q} |  ¡ }t tj¡tdd�  ¡ }d}| 	||f¡ dd„ | 
¡ D ƒ}|r?t dt|ƒ› d�¡ tjd|gd	� nt d
¡ W d  ƒ W dS W d  ƒ W dS 1 sYw   Y  W dS  ty{ } zt d|› �¡ W Y d}~dS d}~ww )z_
    Finds deals stuck in Pending (is_restricted IS NULL) for > 1 hour and re-queues them.
    r   r>   zÚ
                SELECT r.asin
                FROM user_restrictions r
                JOIN deals d ON r.asin = d.asin
                WHERE r.is_restricted IS NULL
                AND d.last_seen_utc < ?
            c                 S   ó   g | ]}|d  ‘qS )r   r2   ©Ú.0Úrowr2   r2   r3   Ú
<listcomp>¼   ó    z.requeue_stuck_restrictions.<locals>.<listcomp>z!Ghost Restriction Sweeper: Found z stuck ASINs. Re-queueing.ú4keepa_deals.sp_api_tasks.check_restriction_for_asins©Úargsz7Ghost Restriction Sweeper: No stuck restrictions found.Nz%Error in requeue_stuck_restrictions: )r#   r   Úcursorr   r@   r   r-   r   r.   ÚexecuteÚfetchallrA   ÚinforU   ÚceleryÚ	send_taskrC   rD   )Úconnrh   ÚcutoffÚqueryÚasinsrH   r2   r2   r3   Úrequeue_stuck_restrictions¤   s$   
	ç&é€ÿrr   rP   c              
      sÒ  �zI| j dk rt d| j › d�¡ W dS g }ttƒ�+}tj|_| ¡ }t	 
tj¡tdd�  ¡ }d}| |||f¡ | ¡ }W d  ƒ n1 sHw   Y  |sRW dS dd	„ |D ƒ}t d
t|ƒ› d|› �¡ |  dt|ƒ ¡ t d¡}t||ddd�\}	}
}
}|r‡|  |¡ |	r�d|	vr•t d¡ W dS g }ttƒ�}t |¡}W d  ƒ n1 s«w   Y  |	d D ]]}| d¡‰ t‡ fdd„|D ƒdƒ}|sÊq´t|ƒ}t||ƒ}|�rt |ƒ}t	 
tj¡ ¡ |d< d|d< | d¡}|du�rt!|t"t#fƒ�r|dk�rt dˆ › d|d›d�¡ | $|¡ q´|�rHttƒ�#}| ¡ }t%|||ƒ | &¡  t dt|ƒ› d �¡ W d  ƒ W dS 1 �s@w   Y  W dS W dS  t'�yh } ztj(d!|› �d"d#� W Y d}~dS d}~ww )$z«
    Finds deals that are approaching the Janitor's 72h deadline (e.g. > 48h old)
    and forces a refresh to prevent them from being deleted if they are still valid.
    é
   z(Skipping Stale Rescue: Low Refill Rate (z/min).Né0   r>   zœ
                SELECT * FROM deals
                WHERE last_seen_utc < ?
                ORDER BY last_seen_utc ASC
                LIMIT ?
            c                 S   r_   ©ÚASINr2   r`   r2   r2   r3   rc   î   rd   z&rescue_stale_deals.<locals>.<listcomp>zStale Deal Rescue: Found z deals > 48h old. Refreshing: é   ÚKEEPA_API_KEYé´   rP   ©ÚdaysÚoffersÚproductsz.Stale Deal Rescue: Keepa returned no products.Úasinc                 3   s    � | ]}|d  ˆ kr|V  qdS )rv   Nr2   ©ra   Úr©r~   r2   r3   Ú	<genexpr>  s   € z%rescue_stale_deals.<locals>.<genexpr>Úlast_seen_utcÚstale_rescueÚsourceÚProfitr   zStale Rescue: ASIN z updated but Profit is now $z.2fz'. It will be hidden from the dashboard.z*Stale Deal Rescue: Successfully refreshed ú deals.zError in rescue_stale_deals: T©Úexc_info))ÚREFILL_RATE_PER_MINUTErA   rk   r#   r   Úsqlite3ÚRowÚrow_factoryrh   r   r@   r   r-   r   r.   ri   rj   rU   Úrequest_permission_for_callÚosÚgetenvr   Úupdate_after_callrB   ÚopenÚHEADERS_PATHÚjsonÚloadrT   ÚnextÚdictr"   r!   Ú
isinstancer8   ÚfloatrV   r   ÚcommitrC   rD   )Útoken_managerÚlimitÚ
stale_rowsrn   rh   ro   rp   Ústale_asinsÚapi_keyÚ	prod_respÚ_Útokens_leftÚrows_to_upsertÚfÚheaders_listÚpÚexisting_rowÚexisting_row_dictÚprocessed_rowÚprofitrH   r2   r�   r3   Úrescue_stale_dealsÇ   sr   


ë



ÿ


$
€
(úÿ	"€ÿr«   zkeepa_deals.smart_ingestor.run)Únamec            <         sŽ  t j tjj¡} | jttd�}|j	dd�st
 d¡ d S �z™�zVt
 dt› d�¡ tƒ  tƒ  t d¡}t d¡}|sSt
 d	¡ W W | ¡ rQ| ¡  t
 d
¡ d S d S t|ƒ}| ¡ rat
 d¡ n| ¡  t|dd� t
 d¡ | d¡ t}|jdk r‡d}t
 d|› d�¡ tƒ }|d u r­t
 d¡ t tj¡t dd�  !¡ }t
 d|› �¡ t"|ƒ nPz5t #|¡ $tj¡}t tj¡}||t dd� krát
 %d|› d| !¡ › d�¡ |t dd�  !¡ }t"|ƒ W n t&yü }	 zt
 d|	› �¡ W Y d }	~	nd }	~	ww t'|ƒ}
t
 d|› d|
› d�¡ g }d}d}t
 d¡ 	 | (¡  |t)k�r,t
 %d!t)› d"�¡ nÒt*|ƒ|k�rAt
 %d#t*|ƒ› d$�¡ d }n½| d¡ d }d%}t+|ƒD ]R}z#t,||d&|d'�\}}}|d u�rf| -|¡ |�rqd(|v �rqW  n0W �qN t&�y  }	 z t
 %d)|› d*|d+ › d,|	› �¡ t. /d-|d+  ¡ W Y d }	~	�qNd }	~	ww |�r©d(|v�rªnT|d( d. �s·t
 d/¡ nGd0d1„ |d( d. D ƒ}|j0d2d3„ d d4� d}|D ]}|d5 |
k�rèt
 d6| 1d7¡› d8�¡ d } n| 2|¡ �qÏ|�rón|d+7 }t. /d+¡ �q|�st
 d9¡ W W | ¡ �r| ¡  t
 d
¡ d S d S t
 d:t*|ƒ› d;�¡ |j0d<d3„ d=� d>d1„ |D ƒ}t3ƒ ‰ i }d }z˜z]t4t5d?d�}t6j7|_8| 9¡ }d@ :dAt*|ƒ ¡}| ;dBt<› dC|› d�|¡ | =¡ }|D ]0}|dD }|dE }|dF }d} | �r‡t
 dG|dH › dI�¡ �qgˆ  >|dH ¡ t?|ƒ||dH < �qgW n t&�y´ }	 zt
 %dJ|	› �¡ W Y d }	~	nd }	~	ww W |d u�rÓz| @¡  W n0 t&�yÒ   t
j%dKd dL� Y n w n|d u�rñz| @¡  W w  t&�yð   t
j%dKd dL� Y w w w tA}!|jdMk �r
d+}!t
 dN|j› dO|!› dP�¡ n+|jdk �r d}!t
 dQ|j› dO|!› dR�¡ n|jdSk �r5d-}!t
 dT|j› dO|!› dR�¡ tBtCƒ�}"tD E|"¡}#W d   ƒ n	1 �sJw   Y  d}$t+dt*|ƒ|!ƒD �]}%| (¡  ||%|%|! … }&dUd1„ |&D ƒ}'‡ fdVd1„|'D ƒ}(‡ fdWd1„|'D ƒ})i }*g }+|(�rÑ| dXt*|(ƒ ¡ tF||(dYddZ�\},}}}|�r¥| -|¡ |,�rÑd[|,v �rÑ|,d[ D ]}-tG|- 1d\¡ƒ�rÄ|+ 2|-d7 ¡ �q±t
 d]|- 1d7¡› �¡ �q±|+�rt+dt*|+ƒtHƒD ]?}.|+|.|.tH … }/| dt*|/ƒ ¡ tI||/dYd+dd^�\}0}}}|�r| -|¡ |0�rd[|0v �r|0d[ D ]	}-|-|*|-d7 < �q�qÜ|)�rR| dt*|)ƒ ¡ tF||)d_ddZ�\}1}}}|�r<| -|¡ |1�rRd[|1v �rR|1d[ D ]	}-|-|*|-d7 < �qHg }2|&D ]h}|d7 }3|3|*v�rc�qV|*|3 }4|4 J|¡ d }5|3ˆ v �r�tK||3 |4ƒ}5|5�r�tL|5ƒ}5t tj¡ !¡ |5d`< da|5db< n%tM|4||ƒ}6tN|4|6|ƒ}5|5�rµtL|5ƒ}5tO|5ƒ}5t tj¡ !¡ |5d`< dc|5db< |5�r½|2 2|5¡ �qV|2�rNt
 ddt*|2ƒ› dedfd1„ |2d dM… D ƒ› dg�¡ zVt4t5d?d��D}7|7 9¡ }8tP|8|2|#ƒ |7 Q¡  |$t*|2ƒ7 }$dhd1„ |2D ƒ}9|9�rtjRdi|9gdj� |&dk }:tS|:d5 ƒ};tT|;ƒ t
 dl|;› �¡ W d   ƒ n	1 �s(w   Y  W �qY t&�yM }	 zt
jdm|	› �d dL� W Y d }	~	�qYd }	~	ww |&dk }:tS|:d5 ƒ};tT|;ƒ t
 dn|;› �¡ �qYt
 dot*|ƒ› dp|$› d�¡ W n. tU�y£ }	 z!t
 %dq|	› dr�¡ W Y d }	~	W | ¡ �r�| ¡  t
 d
¡ d S d S d }	~	ww W | ¡ �rµ| ¡  t
 d
¡ d S d S | ¡ �rÆ| ¡  t
 d
¡ w w )sN)ÚtimeoutF)ÚblockingzD--- Task: smart_ingestor is already running. Skipping execution. ---z+--- Task: smart_ingestor started (Version: z) ---rx   Ú	XAI_TOKENz KEEPA_API_KEY not set. Aborting.z+--- Task: smart_ingestor lock released. ---z6Skipping sync (Recharge active, estimated tokens low).rP   )rœ   zStep 1: Initializing Sync...r%   r&   z-Low Refill Rate. Reducing NEW_DEALS limit to Ú.zYCRITICAL: Watermark not found. Assuming fresh start/reset required but not handling here.r=   r>   zWatermark defaulted to z-CRITICAL: Extreme Future Watermark Detected (z > z1). Resetting to 24h ago to restore functionality.zError checking watermark date: zLoaded watermark: z (Keepa time: ú)r   z4Step 2: Paginating through deals to find new ones...Tz/Safety Limit Reached: Stopped pagination after z pages.zNew Deal Limit Reached: Found r‡   rw   é   )Ú	sort_typer›   r$   zFetch failed on page z
 (Attempt r   z): é   Údrz!No more deals found (empty list).c                 S   s   g | ]}t | d ¡ƒr|‘qS r�   )r   rT   ©ra   Údr2   r2   r3   rc   ›  s    zrun.<locals>.<listcomp>c                 S   ó   | d S ©NÚ
lastUpdater2   ©Úxr2   r2   r3   Ú<lambda>   ó    zrun.<locals>.<lambda>)ÚkeyÚreverserº   zStop Trigger: Deal r~   z <= Watermark.zNo new deals found.zFound z? new deals. Sorting by Oldest First for incremental processing.c                 S   r¸   r¹   r2   r»   r2   r2   r3   r½   ·  r¾   )r¿   c                 S   r_   r�   r2   r¶   r2   r2   r3   rc   º  rd   r5   ú,Ú?zSELECT * FROM z WHERE ASIN IN (ÚList_atÚ1yr_Avgr†   zASIN rv   z6: Detected as ZOMBIE/BAD DATA. Forcing heavy re-fetch.z Failed to check existing ASINs: z/Failed to close existing-ASIN check connection.rˆ   rs   zCritically Low Refill Rate (z#/min). Reducing SCAN_BATCH_SIZE to z  (Max potential cost 22 tokens).zLow Refill Rate (z to prevent Deficit Lockout.é   zMid Refill Rate (c                 S   r_   r�   r2   r¶   r2   r2   r3   rc     rd   c                    s   g | ]}|ˆ vr|‘qS r2   r2   ©ra   Úa©Úexisting_asins_setr2   r3   rc   	  ó    c                    s   g | ]}|ˆ v r|‘qS r2   r2   rÆ   rÈ   r2   r3   rc   
  rÊ   rN   im  rz   r}   rX   zPeek Rejected: ASIN )r{   Úhistoryr|   ry   rƒ   Úsmart_ingestor_lightr…   Úsmart_ingestorz
Upserting z deals to DB. ASINs: c                 S   s   g | ]}|  d ¡‘qS ru   )rT   r   r2   r2   r3   rc   Z  s    z...c                 S   s   g | ]
}d |v r|d  ‘qS ru   r2   r`   r2   r2   r3   rc   h  s    re   rf   rM   zWatermark ratcheted to z Chunk processing/upsert failed: z'Watermark advanced (no deals saved) to zTask Complete: Processed z scanned, upserted z--- Task Paused: z$. Releasing lock to free worker. ---)VÚredisÚRedisÚfrom_urlrl   ÚconfÚ
broker_urlÚlockÚLOCK_KEYÚLOCK_TIMEOUTÚacquirerA   rk   ÚSMART_INGESTOR_VERSIONrr   r	   r�   r�   rD   ÚlockedÚreleaser   Úshould_skip_syncÚsync_tokensr«   rŽ   ÚMAX_NEW_DEALS_PER_RUNrŠ   r
   r   r@   r   r-   r   r.   r   r6   r7   rB   rC   r<   Úemit_heartbeatÚMAX_PAGES_PER_RUNrU   Úranger   r‘   ÚtimeÚsleepÚsortrT   rV   Úsetr#   r   r‹   rŒ   r�   rh   Újoinri   Ú
TABLE_NAMErj   Úaddr—   ÚcloseÚSCAN_BATCH_SIZEr’   r“   r”   r•   r   r^   ÚCOMMIT_BATCH_SIZEr   Úupdater"   r!   r   r    r   r   rš   rm   r4   rI   r   )<Úredis_clientrÓ   rŸ   Úxai_api_keyr›   Úcurrent_max_dealsÚwatermark_isorF   rG   rH   Úwatermark_keepa_timeÚall_new_dealsÚpageÚhit_new_deal_limitÚdeal_responseÚmax_page_retriesÚattemptr¡   r¢   Údeals_on_pageÚfound_older_dealÚdealÚ	asin_listÚexisting_rows_mapÚ
conn_checkÚc_checkÚplaceholdersÚrowsr€   Úlist_atÚyr_avgrª   Ú	is_zombieÚcurrent_batch_sizer¤   ÚheadersÚtotal_upsertedÚiÚchunk_dealsÚchunk_asinsÚchunk_new_asinsÚchunk_existing_asinsÚchunk_productsÚnew_candidatesÚ	peek_respr¦   ÚjÚ	sub_batchr    Úprod_resp_lightr£   r~   Úproduct_datar©   Úseller_data_cachern   rh   Ú	new_asinsÚlast_deal_in_chunkÚ
new_wm_isor2   rÈ   r3   Úrun(  s  



  Lþ � �¶




€€ÿ




€  €þ

È:
 
Vþ �¬ê€ÿ€
ÿýÿý
ÿ€

€
€.ê€"€ÿ 
þ€ûþ
þþr  )rP   )MÚloggingr   r�   r”   r‹   rà   r   r   r   Údotenvr   rÎ   Úworkerr   rl   Údb_utilsr	   r
   r   r   r   r   Ú	keepa_apir   r   r   r   r›   r   r   Úfield_mappingsr   Úseller_infor   Úbusiness_calculationsr   Úbusiness_load_settingsr   r   r   Únew_analyticsr   r   r   Úseasonality_classifierr   r   Ú
processingr    r!   r"   Úkeepa_deals.db_utilsr#   Ú__name__rA   r×   rå   Úpathrä   ÚdirnameÚ__file__r“   ÚMAX_ASINS_PER_BATCHrè   ré   rÔ   rÕ   rÞ   rÜ   r4   r<   rI   r^   rr   r«   Útaskr  r2   r2   r2   r3   Ú<module>   sN     	V
#
a