
    HE^j_                     ~   d dl Z d dlZd dlmZmZ d dlmZmZmZ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mZ d d	lmZmZmZ d
ee   dedee   fdZdededededdf
dZdededededdf
dZdededdfdZdede fdZ!d de"e#   de$de$de%e"e#   e"e#   f   fdZ&de"e#   ddfdZ'de"e#   dededdfdZ(y)!    N)datetime	timedelta)OptionalListDictAny)settings)_logcurrent_time)get_db_connection)send_push_notification_send_batch_summary)dispatch_discord_alertsdispatch_discord_edit)send_telegram_notificationsend_telegram_batchdispatch_telegram_edit
first_seen
removed_atreturnc                    | sy 	 d}t        j                  t        j                  | |            }t        j                  t        j                  ||            }t        ||z
        }|dk  ry t	        |d      \  }}t	        |d      \  }}|dz  }	g }
|r|
j                  | d       |r|
j                  | d       |	s|
s|
j                  |	 d       d	j                  |
      S # t        t        f$ r Y y w xY w)
N%Y-%m-%d %H:%M:%Sr   iQ i  <   dhm )	timemktimestrptimeintdivmodappendjoin
ValueErrorOverflowError)r   r   fmtt1t2deltadaysremhoursminutespartss              ,/home/container/backend/scraper/processor.py_compute_listing_durationr1      s    ![[z378[[z378BG195%(	cC&
s)LLD6$LLE7!%%LLG9A'xx& s   A'C' -A9C' 'C98C9product_code	timestamppricesave_percentc                     | j                  d|f       | j                         }||d   }||z
  }t        |      dk  ryd}| j                  d|||||f       y)zWHelper to insert into price_history, calculating price_delta and preventing duplicates.z
        SELECT price FROM price_history 
        WHERE product_code = ? 
        ORDER BY timestamp DESC, id DESC LIMIT 1
    Nr   {Gz?        z
        INSERT INTO price_history (product_code, timestamp, price, save_percent, price_delta)
        VALUES (?, ?, ?, ?, ?)
    )executefetchoneabs)cursorr2   r3   r4   r5   row
last_priceprice_deltas           r0   _insert_price_historyr@   $   sw    
NN  	
 //
C
V
j({t#
NN  	5,	DF    
event_typec                 0    | j                  d||||f       y)z2Helper to insert a stock event into stock_history.zp
        INSERT INTO stock_history (product_code, event_type, timestamp, price)
        VALUES (?, ?, ?, ?)
    Nr9   )r<   r2   rB   r3   r4   s        r0   _insert_stock_eventrE   :   s!    
NN  
Iu	57rA   c                 ,    | j                  d||f       y)u  Internal-only log of a raw 'product went missing' cycle, fired once per
    removal attempt regardless of whether it later matures into a confirmed removal
    or flap-recovers. Backs get_product_volatility's tiering — decoupled from
    stock_history's public 'removed' events, which now only get written once a
    removal is confirmed past the debounce window (see the "Matured removal
    notifications" block below).zZ
        INSERT INTO removal_attempts (product_code, timestamp)
        VALUES (?, ?)
    NrD   )r<   r2   r3   s      r0   _insert_removal_attemptrG   A   s     NN  		"$rA   c                     t        j                         t        t        j                        z
  j                  d      }| j                  d||f       | j                         d   S )uD  Count raw removal attempts in the trailing window using timezone-safe local
    time cutoff. Reads removal_attempts (every attempt), not stock_history's public
    'removed' events (confirmed-only) — a flappy product must still escalate tiers
    even though most of its flaps never mature into a public stock_history row.)r-   r   zo
        SELECT COUNT(*) FROM removal_attempts
        WHERE product_code = ?
          AND timestamp >= ?
    r   )r   nowr   r	   FLAP_WINDOW_HOURSstrftimer9   r:   )r<   r2   	cutoff_tss      r0   get_product_volatilityrM   M   sZ    
 )(2L2L"MMWWXklI
NN  		"	$
 ??QrA   productsis_first_runpartial_scanc                    t               }g }g }	 |j                         }t               }d}t               }	g }
g }g }| D ]3  }|j	                  d      }|s|	j                  |       |j	                  d      }|j	                  dt        j                        }	 t        |j	                  dd            }	 t        |j	                  dd            }	 t        |j	                  d	d            }	 t        |j	                  d
d            xs d}	 t        |j	                  dd            xs d}|j	                  dg       }|r6t        j                  |D ci c]  }d|v sd|v s|d   |d    c}d      nd}|j	                  di       }|j	                  d      xs i }|j	                  d      xs i }|j	                  dg       }d}t        |t              r#|j	                  d      r|j	                  d      }nyt        |t              r#|j	                  d      r|j	                  d      }nF|rDt        |t               r4t#        |      dkD  r&|d   }t        |t              r|j	                  d      }|r|j%                  d      rd|z   }|j'                  d|f       |j)                         }|dkD  rt+        d||z  z
  dz  d      n|} |ut-        d| d|        |j'                  d|||||||| ||||f       t/        |||||        t1        ||d ||       |r|j3                  |       |
j3                  |       |d!   }!|d"   }"|d#   xs d}#|d$   }$|d%   }%|j'                  d&|||| |||||f	       |"dk(  s|$d'k(  rw|%?|j'                  d(|f       t-        d)| d| d*       t5        |d+       t7        |d+       n6|$d'k7  r6|j'                  d,|f       t-        d-t        j8                   d.| d|        n|#d/z   }&|&t        j8                  k\  r|j'                  d0|f       |j'                  d1|f       |j)                         }'|'r|'d   rt;        |'d   |      }(|(r|(|d2<   t/        |||||        t1        ||d'||       t-        d3|& d4| d|        |s\|j3                  |       |
j3                  |       n9|j'                  d5|&|f       t-        d6|& d7t        j8                   d.| d|        t=        ||!z
        d8kD  st-        d9| d:|! d;| d<       |j'                  d=|| |f       t/        |||||        |r|"d/k(  s||!k  rd>nd?})|j3                  ||!||)d@       6 |sU|j'                  dA       |j?                         }*|*D ].  }|dB   }+|+|	vst-        dC|+ d|dD           tA        ||+      },|,t        jB                  k\  rt        jD                  }-n4|,t        jF                  k\  rt        jH                  }-nt        jJ                  }-|j'                  dE|||-|+f       |d%   du }.|.stM        ||+|       t        tO        jN                         |-z         }/tO        jP                  tO        jN                         |-z         }0tO        jR                  dF|0      }1t5        |+dG|/H       t7        |+dG|1I       1 |j'                  dJ       |j?                         }2tO        jT                  tO        jV                  ||            }3|2D ]  }|dB   }tY        tA        ||      d/z
  d      },|,t        jB                  k\  rt        jD                  }-dK}4n8|,t        jF                  k\  rt        jH                  }-d}4nt        jJ                  }-d}4tO        jT                  tO        jV                  |d%   |            }5|3|5z
  }6|6|-k\  s|j'                  dL|f       t1        ||dM|d%   |d!          t-        dNt        |6       dO| d|dD    dP|, dQ|4rdRn|,t        jF                  k\  rdSndT dU       t5        |dV       t7        |dV       |r;|4r?t;        |dW   |      }7|dB   |dD   |dX   |d!   |dY   dZ|dB    |d[   |7d\}8|j3                  |8        |j[                          dd]l.m/}9 |9ja                          |jc                          |sqte        |
d t        jf                         te        |dMt        jh                         tk        |
d        tk        |dM       |
rdd^l6m7}: |
D ]  } |:|d         tq        |       ||fS # t        t        f$ r d}Y w xY w# t        t        f$ r d}Y w xY w# t        t        f$ r d}Y w xY w# t        t        f$ r d}Y w xY w# t        t        f$ r d}Y w xY wc c}w # |jc                          w xY w)_zJCompare freshly-scanned product list against database and process updates.r   productCodeproductNameproductConditionwebPricer   r8   
finalPricesavePercent
ratingStarNcommentCountclassificationabF)ensure_asciimedia	heroImage	thumbnailgalleryimageAddressz//zhttps:z
                SELECT current_price, active, stability_count, pending_state, pending_removal_since
                FROM products WHERE product_code = ?
            g      ?d      zNEW PRODUCT DETECTED: z - a  
                    INSERT INTO products (
                        product_code, product_name, condition, first_seen, last_seen, removed_at,
                        original_price, current_price, save_percent, active,
                        specs, rating_star, comment_count, thumbnail_url, full_specs,
                        stability_count, pending_state, pending_removal_since
                    )
                    VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?, 1, ?, ?, ?, ?, NULL, 0, NULL, NULL)
                addedcurrent_priceactivestability_countpending_statepending_removal_sincea  
                    UPDATE products
                    SET last_seen = ?, active = 1, removed_at = NULL,
                        pending_removal_since = NULL, debounce_duration = NULL,
                        product_name = ?, original_price = ?,
                        save_percent = ?, specs = ?, rating_star = ?, comment_count = ?, thumbnail_url = ?
                    WHERE product_code = ?
                back_in_stockz
                            UPDATE products SET stability_count = 0, pending_state = NULL
                            WHERE product_code = ?
                        zFLAP RECOVERY: z) (reappeared before removal notification)	restockedz
                            UPDATE products SET pending_state = 'back_in_stock', stability_count = 1
                            WHERE product_code = ?
                        zRESTOCK PENDING (1/z):    z
                                UPDATE products SET stability_count = 0, pending_state = NULL
                                WHERE product_code = ?
                            z
                                SELECT timestamp FROM stock_history
                                WHERE product_code = ? AND event_type = 'removed'
                                ORDER BY timestamp DESC, id DESC LIMIT 1
                            _restock_durationz'PRODUCT BACK IN STOCK (confirmed after z
 cycles): z
                                UPDATE products SET stability_count = ?
                                WHERE product_code = ?
                            zRESTOCK PENDING (/r7   zPRICE CHANGE for z: z -> z INRzNUPDATE products SET current_price = ?, save_percent = ? WHERE product_code = ?
price_drop
price_hike)product	old_price	new_pricetypez
                SELECT product_code, product_name, condition, current_price, save_percent,
                       first_seen, pending_removal_since
                FROM products WHERE active = 1
            r2   zPRODUCT REMOVED (SOLD OUT): product_namea  
                        UPDATE products
                        SET active = 0, removed_at = ?,
                            stability_count = 0, pending_state = NULL,
                            pending_removal_since = COALESCE(pending_removal_since, ?),
                            debounce_duration = COALESCE(debounce_duration, ?)
                        WHERE product_code = ?
                    z%I:%M %p	cart_hold)expire_epoch)expire_timea  
            SELECT product_code, product_name, condition, current_price, save_percent,
                   first_seen, pending_removal_since, thumbnail_url
            FROM products
            WHERE pending_removal_since IS NOT NULL AND active = 0
        Tz
                    UPDATE products SET pending_removal_since = NULL, debounce_duration = NULL
                    WHERE product_code = ?
                removedzREMOVAL CONFIRMED (zs elapsed): z (volatility: z, tier: z	2 (muted)10)sold_outr   	conditionr5   z/p/thumbnail_url)rR   rS   rT   rV   rW   urlr   _listing_duration)catalog_statedispatch_watchlist_alerts)9r   r<   r   setgetaddr	   DEFAULT_CONDITIONfloatr%   	TypeErrorr!   jsondumps
isinstancedictlistlen
startswithr9   r:   roundr
   r@   rE   r#   r   r   STABILITY_THRESHOLDr1   r;   fetchallrM   TIER2_FLAP_THRESHOLDTIER2_DEBOUNCE_SECONDSTIER1_FLAP_THRESHOLDTIER1_DEBOUNCE_SECONDSTIER0_DEBOUNCE_SECONDSrG   r   	localtimerK   r   r    maxcommitbackend.scraper.catalog_stater   init_from_dbclose_dispatch_notifications
NTFY_TOPICNTFY_TOPIC_REMOVEDr   backend.notifier.discord_botr   $_dispatch_price_change_notifications);rN   rO   rP   connnew_listingsrk   r<   rI   r'   scanned_codesadded_queueremoved_queueprice_change_queuepcodenamer   original_pricerf   r5   rating_starcomment_countrZ   s
specs_jsonr^   herothumbra   r   itemr=   calculated_save_percentdb_price
was_activerh   ri   rj   	new_countsh_rowrestock_durrB   active_rowsactive_code
volatilitydebounce_secondsis_first_holdrx   expire_time_structexpire_time_strpending_rowsnow_tssuppress_alerts
pending_tselapseddurationdummy_productr   r   s;                                                              r0   process_scanned_productsr   Z   s
   D!L "MBn!"%%"$$&)+A55'Dd#55'D0(2L2LMI%!&quuZ';!<$ %aeeL!&< =#$QUU=!%<=##AEE,$:;Ct% #AEE.!$< = E UU#3R8N   )7QA3!8q33Q" &*  EE'2&E99[)/RDIIk*0bEii	2.G M$%$((>*B $ 8E4(UYY~-F %		. 9Z63w<!;KqzdD)$(HH^$<M!9!9$!? (= 8NN   //#C aoqr`reS=>3Q-QUX,XZ[&\  yE#{-dV3tf=>   D)S#~}Vm +}mM	N &fdCH_`#FD'3N# ''*&&q)/ ]
"%&7"8"=A #O 4(+,C(D%    41H*Vacpr  BF  GH ?m&F,8  ( #W& tfCv=fgh .dK@.t[A&/9 ( #W& 283O3O2PPSTXSYY\]a\bcd %4a$7	$(D(DD"NN ,  #'* #NN ,  #'	*
 &,__%6F%&).Gq	SV.W#.=HA&9$:1&$]Tkl/osTab #J9+U_`d_eehimhn!op#/ - 4 4Q 7 + 2 21 5"NN ,  #,T!24 !#4YKqA]A]@^^abfaggjkojp!qr }x/047,TF"XJd=/QUVWNNh&(?F *&$]Lcd'J!O 6CX5M\S_
*11'()1)6$.	3 g v NN  
 !//+K"!.1m37}CNH[G\]^ "8!LJ!X%B%BB+3+J+J(#x'D'DD+3+J+J(+3+J+J( NN $ s$4kBD  &))@%AT%IM$/SI'*499;9I+I'J-1^^DIIKJZ<Z-[**.--
DV*W-k;Uab.{KUdeU #Z 	  	 (T]]345C~&D 3FDAAEqIJX:::#+#B#B "&x<<<#+#B#B "'#+#B#B "'T]]37N3OQT%UVJz)G**   W $FD)SAX=Y[^_n[op*3w<.TF#cR`NaMbbpq{p|  }E  Ud  FQ  r|  @H  @]  @]  r]  kn  cf  Eh  hi  j  k &dJ7&tZ8#O8\9JCPH'*>':'*>':,/,<&)/&:'*>':!$S%8$9:),_)=-5	%M "((7i  l 	 	@""$

Wh6I6IJy(:U:UVW5y9N )!W5 !,-?@&&s	 	* %!$% 	* $ #$ 	* #"# 	* #"# 	* % $%
 Rn 	

s   Bg e5e#e;-ff+,'g 	g
g
"g
-F/g G/g ;g g Ag )Bg Fg Bg g A,g e g e  g #e84g 7e88g ;fg fg f($g 'f((g +g <g ?g  g gqueuec                 F   | syddl m} ddlm} | D ]  }|d   }|d   }|d   }|d   d	k(  rBt	        |d	|
       t        |gd	|       t        |d	|        ||d	|        ||||       nt	        |d|
       t        |gd|       t        j                  d        y)u   Sends price-drop/hike alerts queued during the scan loop. Called after
    conn.commit()/conn.close() — see the queueing comment above — so the
    per-event network I/O and time.sleep(1) no longer hold the write
    transaction open.Nr   r   )notify_price_droprr   rs   rt   ru   rp   )	ntfy_typers   )rs   )rs   rt   rq   rm   )	r   r   backend.notifier.pushr   r   r   r   r   sleep)r   r   r   eventr   rs   rt   s          r0   r   r     s    
 F7)+&	+&	=L("1	R#QCK&q,)L%aKa9	J"1	R#QCK

1 rA   r   topicc                     | sy t        |       t        j                  k\  rt        | ||       y | D ]6  }|j	                  d      }t        |||       t        j                  d       8 y )Nr   )r   listing_durationrm   )r   r	   NOTIFICATION_BATCH_THRESHOLDr   r   r   r   r   )r   r   r   r   r   s        r0   r   r     sV    
5zX:::E9e4Auu01H"1	HUJJqM rA   )F))r   r   r   r   typingr   r   r   r   backend.configr	   backend.logging_configr
   r   backend.db.connectionr   backend.notifier.ntfyr   r   r   r   r   backend.notifier.telegramr   r   r   strr1   r   r@   rE   rG   r!   rM   r   r   booltupler   r   r    rA   r0   <module>r      sd     ( , , # 5 3 M W m m(3- S XVY] 0F F FE Faf Fko F,7c 7s 7s 7[` 7ei 7
$# 
$# 
$$ 
$     T'tDz T' T'UY T'fklpqulvx|  ~B  yC  mC  gD T'n
T
 t 2	4: 	# 	c 	d 	rA   