
    KCjU              
           d Z ddlZddlZddlmZ ddlmZ ddlmZ  e       Z	 ee
      Zdededed	ed
ef
dZ G d d      Z e       Zy)u]  
Redis-backed result cache.

Crop + district + month combinations repeat heavily in real usage —
many farmers in the same district asking about the same crop in the
same month. Caching the full analysis result avoids re-running 6 models
and an LLM call for an answer that hasn't changed.

Cache invalidation strategy: time-based TTL (REDIS_CACHE_TTL_SECONDS,
default 24h) rather than manual invalidation. This is intentional —
climate/market forecasts are inherently "as of today" data, so letting
them expire naturally is simpler and safer than trying to track every
upstream data change that should invalidate a cache key.

If Redis is down, every method here fails open (returns None / does
nothing) rather than raising — caching is a performance optimization,
not a correctness requirement, and a cache outage must never become a
full service outage.
    N)Redis)get_settings)
get_loggerdistrictcropmonthmodel_versionreturnc                     |  d| d| d| }t        j                  |j                  d            j                         dd }d| S )u   
    Deterministic cache key. model_version is included so that retraining
    and deploying new models automatically invalidates old cached results —
    no manual cache flush needed on model updates.
    :zutf-8N   z	analysis:)hashlibsha256encode	hexdigest)r   r   r   r	   rawdigests         GC:\Crop_Prediction\Backend\crop-ai-system\app\services\cache_service.pybuild_cache_keyr   !   sT     JavQugQ}o
6C^^CJJw/0::<SbAFvh    c            	       z    e Zd ZddZddZdededz  fdZddedededz  ddfd	Z	de
fd
Zededz  fd       Zy)CacheServicer
   Nc                     d | _         y N_redisselfs    r   __init__zCacheService.__init__-   s	    $(r   c                 @  K   	 t        j                  t        j                  dd      | _        | j                  j                          d {    t        j                  d       y 7 # t        $ r)}t        j                  d|        d | _        Y d }~y d }~ww xY ww)NT   )decode_responsessocket_connect_timeoutz"Redis cache connection establishedz9Could not connect to Redis at startup, caching disabled: )
r   from_urlsettings	REDIS_URLr   pingloggerinfo	Exceptionerror)r   excs     r   initzCacheService.init0   s     	..""TRSDK ++""$$$KK<= % 	LLTUXTYZ[DKK	sA   BAA) A'A) &B'A) )	B2BBBBkeyc                   K   | j                   y 	 | j                   j                  |       d {   }|y t        j                  |      S 7 # t        $ r%}t
        j                  d| d|        Y d }~y d }~ww xY ww)NzCache read failed for key=z, treating as cache miss: )r   getjsonloadsr*   r(   warning)r   r.   r   r,   s       r   r0   zCacheService.get;   sy     ;;	,,C{::c?" -  	NN7u<VWZV[\]	sH   A?A AA A?A A?A 	A<A72A?7A<<A?valuettl_secondsc                 (  K   | j                   y 	 ||nt        j                  }| j                   j                  |t	        j
                  |      |       d {    y 7 # t        $ r%}t        j                  d| d|        Y d }~y d }~ww xY ww)N)exzCache write failed for key=z, continuing without cache: )	r   r%   REDIS_CACHE_TTL_SECONDSsetr1   dumpsr*   r(   r3   )r   r.   r4   r5   ttlr,   s         r   r9   zCacheService.setG   s     ;;	a!,!8+h>^>^C++//#tzz%'8S/AAA 	aNN8=YZ]Y^_``	asA   BAA! AA! BA! !	B*B
B
BBc                    K   | j                   y	 | j                   j                          d {    y7 # t        $ r Y yw xY ww)NFT)r   r'   r*   r   s    r   health_checkzCacheService.health_checkP   sG     ;;	++""$$$ % 		s0   A6 46 A6 	AAAAc                     | j                   S )zCExposed for the rate limiter, which needs the same connection pool.r   r   s    r   
raw_clientzCacheService.raw_clientY   s     {{r   )r
   Nr   )__name__
__module____qualname__r   r-   strdictr0   intr9   boolr=   propertyr   r?    r   r   r   r   ,   sy    )	
S 
TD[ 
aS a aC$J aRV aD  EDL  r   r   )__doc__r   r1   redis.asyncior   app.core.configr   app.core.logging_configr   r%   r@   r(   rC   rE   r   r   cache_servicerH   r   r   <module>rN      si   (    ( .>	H	 c    S    QT  0 0h r   