o
    d6itU                  	   @   s   d Z ddlZddlZddlZddlmZmZ ddlmZmZm	Z	 ddl
mZ ddlZeeZdZdZdZG d	d
 d
ZdadefddZddedededefddZdefddZdS )a  
Puja API Cache Manager

This module handles fetching, caching, and refreshing puja information from the backend API.
Cache is stored in a JSON file and refreshed weekly or on-demand.

TOKEN OPTIMIZATION:
- Extracts lightweight index (names, IDs, categories only)
- Uses simple keyword matching first
- Optionally uses LLM for complex queries (with minimal context)
- Fetches full details only for matched pujas
    N)datetime	timedelta)DictListOptional)Pathzdata/puja_api_cache.json   z/https://karishyewebbackend.divami.com/all-pujasc                
   @   s   e Zd ZdZefdefddZdee fddZ	dedd	fd
dZ
dee fddZd#dedee fddZdedee fddZdee dedee fddZdedee dee fddZd$dedededee fddZd#d ee dedefd!d"Zd	S )%PujaAPICachez9Manages caching of puja information from the backend API.
cache_filec                 C   s2   t t }|jjj| _| j| | _td dS )zInitialize the cache manager.z[PUJA CACHE] Cache initializedN)r   __file__resolveparentproject_root
cache_pathloggerdebug)selfr
   current_file r   </var/www/html/karishye-ai-python/app/utils/puja_api_cache.py__init__    s   zPujaAPICache.__init__returnc              
   C   s   zP| j  std W dS | j jddd}t|}W d   n1 s&w   Y  t|	dd}|t
td }t |krItd	 W dS td
 |W S  tyn } ztdt|j  W Y d}~dS d}~ww )z/Load cache from disk if it exists and is valid.z![PUJA CACHE] Cache file not foundNrutf-8encoding	cached_atz
2000-01-01)daysz[PUJA CACHE] Cache expiredz[PUJA CACHE] Valid cache foundz"[PUJA CACHE] Error loading cache: )r   existsr   r   openjsonloadr   fromisoformatgetr   CACHE_DURATION_DAYSnow	Exceptionerrortype__name__)r   f
cache_datacached_timeexpiry_timeer   r   r   _load_cache)   s&   



zPujaAPICache._load_cachedataNc              
   C   s   z;| j jjddd t  |d}| j jddd}tj||ddd	 W d
   n1 s/w   Y  t	
d W d
S  tyY } zt	dt|j  W Y d
}~d
S d
}~ww )zSave cache to disk.T)parentsexist_ok)r   r0   wr   r      F)indentensure_asciiNz%[PUJA CACHE] Cache saved successfullyz![PUJA CACHE] Error saving cache: )r   r   mkdirr   r%   	isoformatr   r    dumpr   r   r&   r'   r(   r)   )r   r0   r+   r*   r.   r   r   r   _save_cacheB   s   
$zPujaAPICache._save_cachec              
      s   z=t d tjdd4 I dH }|tI dH }|  | }W d  I dH  n1 I dH s2w   Y  t d |W S  tj	y] } zt 
dt|j  W Y d}~dS d}~w tyz } zt 
dt|j  W Y d}~dS d}~ww )zFetch fresh data from the API.)[PUJA CACHE] Fetching fresh data from APIg      >@)timeoutNz/[PUJA CACHE] Successfully fetched data from APIz[PUJA CACHE] HTTP error: z&[PUJA CACHE] Error fetching from API: )r   r   httpxAsyncClientr#   API_URLraise_for_statusr    info	HTTPErrorr'   r(   r)   r&   )r   clientresponser0   r.   r   r   r   _fetch_from_apiU   s&   

(
zPujaAPICache._fetch_from_apiFforce_refreshc                    s   |s|   }|rtd |dS td |  I dH }|r(| | |S td |   }|r=td |dS td dS )z
        Get puja data from cache or API.
        
        Args:
            force_refresh: If True, bypass cache and fetch fresh data
        
        Returns:
            Dictionary containing puja information or None if failed
        z&[PUJA CACHE] Returning data from cacher0   r;   Nz=[PUJA CACHE] API fetch failed, trying stale cache as fallbackz3[PUJA CACHE] Returning stale cache data as fallbackz[PUJA CACHE] No cache available)r/   r   r   r#   rE   r:   warningr'   )r   rF   r+   api_datar   r   r   get_puja_datai   s$   







zPujaAPICache.get_puja_data	puja_datac           	      C   s   g }t |tr|ddkr|dg }ng S |D ]A}|dd}|dg D ]2}|dg D ])}|di }||d	|d|d
||d|dpPddd d q/q'q|S )z
        Extract a lightweight index of all pujas (just names, IDs, categories).
        This is sent to LLM for initial matching to save tokens.
        
        Returns:
            List of lightweight puja summaries
        statussuccessr0   name subCategoriespujasubcategorymappingspujasidurl_namedescriptionNd   )rR   rM   rS   categorysubcategorydescription_preview
isinstancedictr#   append)	r   rJ   index
categoriesrV   category_namesub_categorymappingpujar   r   r   _extract_puja_index   s(   
z PujaAPICache._extract_puja_index
puja_indexqueryc              
      s   zAddl m} ddd |dd D }d| d	| d
}||I dH }|j }ddl}||}	t	dt
|	 d |	W S  tya }
 ztdt|
j  g W  Y d}
~
S d}
~
ww )a-  
        Use LLM to intelligently match user query to puja IDs.
        Sends only lightweight index to save tokens.
        
        Args:
            puja_index: Lightweight index of pujas
            query: User's search query
        
        Returns:
            List of matched puja IDs
        r   )general_agent
c                 S   sB   g | ]}d |d  d|d  d|d  d|d dd	  d
	qS )zID: rR   z	 | Name: rM   z | Category: rV   z | Preview: rX   N2   ...r   ).0pr   r   r   
<listcomp>   s    4z1PujaAPICache._llm_match_pujas.<locals>.<listcomp>NrU   zgGiven this list of pujas and a user query, identify the most relevant puja IDs (up to 3).

PUJA INDEX:
z

USER QUERY: u  

Return ONLY a JSON array of puja IDs (integers) that best match the query.
Consider semantic meaning, not just keywords.
Examples:
- "marriage" or "wedding" → find "Marriage Engagement", "Vivaha", or wedding-related pujas
- "cost of wedding" → find "Vivaaham" or "Marriage" pujas
- "baby naming" → find "Naamakaranam"
- "new home" → find "Gruhapravesham"
- "obstacle removal" → find "Ganapati Homam"
- "engagement" → find "Marriage Engagement" or engagement-related ceremonies

IMPORTANT: Look for partial word matches too. "marriage" should match "Marriage Engagement".

Return format: [4, 15, 23]
If no good match, return: []
z[LLM MATCH] Matched z pujasz[LLM MATCH] Error: )app.agents.agentsrf   joinrunoutputstripr    loadsr   r   lenr&   r'   r(   r)   )r   rd   re   rf   
index_textpromptresultmatched_ids_textr    matched_idsr.   r   r   r   _llm_match_pujas   s,   



zPujaAPICache._llm_match_pujaspuja_idsc              	   C   s   g }t |tr|ddkr|dg }ng S |D ]9}|dd}|dg D ]*}|dg D ]!}|di }	|	d	|v rPi |	||ddd
}
||
 q/q'q|S )a  
        Get full detailed information for specific puja IDs.
        
        Args:
            puja_data: Full API data
            puja_ids: List of puja IDs to fetch details for
        
        Returns:
            List of full puja objects with all details
        rK   rL   r0   rM   rN   rO   rP   rQ   rR   )rV   rW   rY   )r   rJ   rz   detailed_pujasr^   rV   r_   r`   ra   rb   puja_with_contextr   r   r   _get_detailed_puja_info   s*   

z$PujaAPICache._get_detailed_puja_infouse_llminclude_pricingc                    s2  |   I dH }|sdS | |}|sdS tdt| d| d|  |  g d}t fdd	|D r=  d
 g }|D ]k}|dpId }	|dpRd }
|dp[d }|dpdd }  }d}|D ]}t|dk rxqo||	v s||
v s||v s||v r|d7 }qodd |D }|sqAt	dt|d }||kr|
|d qAtdt| d |r| ||dd }|r| j||d}tdt| d |S |r|std | ||I dH }|r| ||}|r| j||d}tdt| d |S td d | d!S )"a  
        Token-optimized search with optional LLM enhancement.
        
        WORKFLOW:
        1. Extract lightweight index (names, IDs, categories only) - saves tokens
        2. Try simple keyword matching first (fast, free)
        3. If use_llm=True and no matches, use LLM with minimal context
        4. Fetch full details only for matched pujas
        
        Args:
            query: Search query (puja name, category, pricing info, etc.)
            use_llm: If True, use LLM for intelligent semantic matching when keyword search fails
            include_pricing: If True, include pricing details in response; if False, only spiritual/ritual details
        
        Returns:
            Formatted string with matching puja information
        Nz`I apologize, but I'm unable to fetch the puja information at the moment. Please try again later.zSI apologize, but the puja information format is unexpected. Please try again later.z[SEARCH] Searching z pujas | LLM: z | Include Pricing: )marriageweddingvivahvivahashaadikalyanam
engagementc                 3   s    | ]}| v V  qd S )Nr   )rj   synquery_lowerr   r   	<genexpr>2  s    z+PujaAPICache.search_puja.<locals>.<genexpr>z# marriage engagement vivah kalyanamrM   rN   rX   rV   rS   r         c                 S   s   g | ]
}t |d kr|qS )r   )rs   )rj   r3   r   r   r   rl   L  s    z,PujaAPICache.search_puja.<locals>.<listcomp>r4   rR   z [SEARCH] Keyword matching found z matches   )r   z[SEARCH] Returning z results from keyword matchingz%[SEARCH] Trying LLM semantic matchingz results from LLM matchingz[SEARCH] No matches foundz/I couldn't find specific information matching 'zh' in our current puja catalog. Could you try rephrasing your question or ask about a specific puja name?)rI   rc   r   r   rs   loweranyr#   splitmaxr\   r}   _format_puja_resultsry   )r   re   r~   r   rJ   rd   marriage_synonymssimple_matchesrb   	puja_name	puja_descrV   rS   query_wordsmatch_countwordmeaningful_words	thresholdresultsformatted_resultsllm_matchesr   r   r   search_puja  sf   
 
 

zPujaAPICache.search_pujarQ   c                 C   s  |sdS g }|dd D ]N}| dd}| dd}| dd	}| d
d	}| dd	}	| dg }
d| d}|rC|d| d7 }|ra|dkrat|dkrW|dd d n|}|d| d7 }|rk|d| d7 }|	rw|d|	  d7 }|
rVt|
dd dD ]\}}| dd}| dd}| dd}|rdnd	}| dg }|r|dt| d | d!7 }|D ]}| d"i }| d#d$}|d%| d7 }q|d&7 }|d'| d(7 }|d)| d7 }|rQ| d*d}| d+}| d,}| d-}|d.7 }|t|ttfrd/|d0dnd1| d7 }|dus#|dus#|durQ|d27 }|dur5|d3|d0d7 }|durC|d4|d0d7 }|durQ|d5|d0d7 }|d7 } || qd6d| d7 S )8a@  
        Format puja data into a readable string.
        
        Args:
            pujas: List of puja dictionaries to format
            include_pricing: If True, include pricing information; if False, only spiritual/ritual details
        
        Returns:
            Formatted string with puja information
        zNo results found.Nr   rM   zUnknown PujarT   zNo description availablerV   rN   regionlanguage
pujamodelsz**z**
z
Category: rg   i,  ri   z
Description: zRegion: z
Language: r   r   durationHrszN/AnoOfPujarisisPopularModelFu    ⭐ (Popular)
proceduresz
**Sacred Rituals (z	 rituals)z:**
	proceduretitleUnknownu     • z
**Service Details:**
u     • Duration: z hours
u     • Number of Pujaris: modelSellingPrice
pujariCostsamagriCost
serviceFeez
**Pricing Information:**
u     • Total Price: ₹,u     • Total Price: z
**Price Breakdown:**
u.     • Guru Dakshina (goes 100% to pujari): ₹u0     • Samagri (pure organic puja materials): ₹u     • Service Fee: ₹z>
============================================================
z<============================================================)	r#   rs   r   	enumeraterZ   intfloatr\   rn   )r   rQ   r   result_partsrb   rM   rT   rV   r   r   puja_models	puja_infodescidxmodeldurationnum_pujaris
is_popularpopular_tagr   	proc_dataprocr   pricepujari_costsamagri_costservice_feer   r   r   r   q  sn    


.


z!PujaAPICache._format_puja_results)FFF)r)   
__module____qualname____doc__
CACHE_FILEstrr   r   r   r/   r:   rE   boolrI   r   rc   r   ry   r}   r   r   r   r   r   r   r	      s    	$"; $ cr	   r   c                   C   s   t du rt a t S )z(Get or create the global cache instance.N)_cache_instancer	   r   r   r   r   get_cache_instance  s   r   Fre   r~   r   c                    s   t  }|j| ||dI dH S )a  
    Convenience function to search puja information with optional LLM enhancement.
    
    TOKEN OPTIMIZATION:
    - By default (use_llm=False): Uses fast keyword matching, zero LLM tokens
    - When use_llm=True: Falls back to LLM for semantic matching if keyword search fails
    - LLM only sees lightweight index (names, IDs, categories), not full data
    - Full details fetched only for matched pujas
    
    SPIRITUAL-FIRST APPROACH:
    - include_pricing=False (DEFAULT): Returns ONLY spiritual/ritual details
    - include_pricing=True: Returns spiritual details + pricing breakdown
    
    Args:
        query: Search query for puja information
        use_llm: Enable LLM-based semantic matching for complex queries (default: False)
        include_pricing: Include pricing information in response (default: False)
    
    Returns:
        Formatted puja information
    
    Examples:
        # Spiritual details only (no pricing)
        await get_puja_info("Ganapati Homam")
        
        # With pricing information
        await get_puja_info("Ganapati Homam", include_pricing=True)
        
        # Complex semantic search with LLM fallback and pricing
        await get_puja_info("puja for new baby", use_llm=True, include_pricing=True)
    )r~   r   N)r   r   )re   r~   r   cacher   r   r   get_puja_info  s    r   c                     s"   t  } | jddI dH }|duS )zv
    Force refresh the puja cache from API.
    
    Returns:
        True if refresh successful, False otherwise
    T)rF   N)r   rI   )r   r0   r   r   r   refresh_cache  s   r   r   )r   r    loggingosr   r   typingr   r   r   pathlibr   r=   	getLoggerr)   r   r   r$   r?   r	   r   r   r   r   r   r   r   r   r   r   <module>   s(    
   9$