Ë
    KµCjU  ã            
       ó˜   — 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   € ð ˆJ�a˜�v˜Q˜u˜g Q } oÐ
6€CÜ�^‰^˜CŸJ™J wÓ/Ó0×:Ñ:Ó<¸S¸bÐA€FØ�v�hÐÐó    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�   è ø€ ð	ÜŸ.™.Ü×"Ñ"°TÐRSôˆDŒKð —+‘+×"Ñ"Ó$×$Ð$Ü�K‰KÐ<Õ=ð %ùäò 	Ü�L‰LÐTÐUXÐTYÐZÔ[ØˆD�K‰Kûð	ü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“?Ð"ð -ùô ò 	Ü�N‰NÐ7¸°uÐ<VÐWZÐV[Ð\Ô]Üûð	ü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Ø—+‘+—/‘/ #¤t§z¡z°%Ó'8¸S�/ÓA×AÒAùÜò 	aÜ�N‰NÐ8¸¸Ð=YÐZ]Ð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ð 
 T¨D¡[ó 
ña˜Sð a¨ð a¸CÀ$¹Jð aÐRVó að Dó ð ð˜E D™Lò ó ñ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   