o
    cBj?                     @  s  U d Z ddlmZ ddlZddl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mZmZmZmZ ddlZeeZdZdZdZdd	d
dddZedejedejedejedejfZedZi aded< dddda ded< dUddZ!dVdd Z"dVd!d"Z#dWd$d%Z$dUd&d'Z%dUd(d)Z&dWd*d+Z'dXd.d/Z(dYd1d2Z)dZd[d5d6Z*d\d8d9Z+d]d=d>Z,d^dBdCZ-d_dEdFZ.d`dIdJZ/dKdLdadNdOZ0dbdQdRZ1dKdLdcdSdTZ2dS )dzESarvam API credit health checks for Master Panel (read-only, cached).    )annotationsN)datetime	timedeltatimezone)AnyDictListOptionalTuplez)https://api.sarvam.ai/v1/chat/completionsz
sarvam-30biX              )	exhaustedinvalid_keyunknownnot_configured	availablez\b402\bzno\s+creditszinsufficient\s+creditszcredit[s]?\s+exhaustedz3^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?)zDict[str, Dict[str, Any]]_probe_cache         
expires_atfingerprintpayloadDict[str, Any]_summary_cachereturnstrc                   C  s    t tjjdd ddS )Nr   )microsecond+00:00Z)r   nowr   utcreplace	isoformat r&   r&   9/home/aiteam/pcaa-dev/dashboard-backend/sarvam_credits.py_utc_now_iso)   s    r(   keyc                 C  sB   t | pd} t| dkr| rdS dS | d d  d| dd   S )Nr      z****r   z...)r   lenr)   r&   r&   r'   mask_key-   s   r.   c                 C  s$   t t| pdd d d S )Nr   utf-8   )hashlibsha256r   encode	hexdigestr-   r&   r&   r'   _key_fingerprint4   s   $r5   intc                	   C  s8   zt dttdttW S  ttfy   t Y S w )N<    SARVAM_CREDITS_PROBE_TTL_SECONDS)maxr6   osgetenvr   _DEFAULT_TTL	TypeError
ValueErrorr&   r&   r&   r'   _probe_ttl_seconds8   s
   r?   c                   C     t dd p	tS )NRAG_SARVAM_CHAT_URLr   )r:   r;   strip_DEFAULT_CHAT_URLr&   r&   r&   r'   	_chat_url?      rD   c                   C  r@   )NRAG_SARVAM_MODELr   )r:   r;   rB   _DEFAULT_MODELr&   r&   r&   r'   _chat_modelC   rE   rH   c                	   C  s8   zt dtdttddW S  ttfy   Y dS w )N   r7   $SARVAM_CREDITS_PROBE_TIMEOUT_SECONDS15   )r9   minr6   r:   r;   r=   r>   r&   r&   r&   r'   _probe_timeoutG   s
   rN   lineOptional[str]c                 C  sH   t |  }|sd S |ddd}|ds"d|vr"| d}|S )Nr    Tr!   +)_TS_REmatchrB   groupr$   endswith)rO   rU   rawr&   r&   r'   _parse_log_timestampN   s   
rY   	List[str]c                  C  s<   g } |  ttd dD ]}tj|r| | q| S )Nz/tmp/stt_worker_*.log)z/tmp/pca_stt-worker.logz/tmp/pca_stt_worker.log)extendsortedglobr:   pathisfileappend)pathsfixedr&   r&   r'   
_log_pathsX   s   
rc     max_lines_per_filec              
     sp  d}d}d}t tjtdd }t D ]}zt|dddd}| }W d   n1 s.w   Y  W n	 ty=   Y qw t	|| krK||  d n|}|D ]] t
 fd	d
tD s]qOt }	|d7 }|	rz!t |	dd}
|
|kr|du s}|	|kr|	}  dd }W qO ty   |du r|	}  dd }Y qOw |du r  dd }qOq|||t	t dS )zDRead recent STT log lines for credit-exhaustion signals (read-only).Nr      )hoursrr/   ignore)encodingerrorsc                 3  s    | ]}|  V  qd S )N)search).0prO   r&   r'   	<genexpr>p       z6scan_stt_logs_for_credit_exhaustion.<locals>.<genexpr>r   r!   r       )last_exhausted_atlast_exhaustion_messagerecent_exhaustion_signalslog_paths_scanned)r   r"   r   r#   r   rc   open	readlinesOSErrorr,   any_LOG_PATTERNSrY   fromisoformatr$   rB   r>   )re   rs   last_messagerecent_countcutoffr^   fhlinestailtsparsedr&   ro   r'   #scan_stt_logs_for_credit_exhaustiona   sT   

r   api_keyc           
   
   C  s  t | pd }|sdddddt dS t }t }|dd	}|d
ddgdd}zgtj|||t d}|j}|j	p;ddd }|dkrOdddd|t dW S |dkr`ddd|pYd|t dW S |dkrqddd|pjd|t dW S |dkrddd d!|t dW S d"d#d$d%| d&| |t dW S  tj
y   d"d#d$d'dt d Y S  ty }	 ztd(|	 d"d#d$t |	dt dW  Y d}	~	S d}	~	ww ))uQ   Minimal Sarvam chat probe — detects exhausted vs valid key (not exact balance).r   skippedr   Not configuredz%No Sarvam subscription key configuredN)credits_probecredits_healthcredits_health_labelprobe_messagehttp_statuslast_checked_atzapplication/json)zapi-subscription-keyzContent-Typeuserping)rolecontentr   )modelmessages
max_tokens)headersjsontimeouti,     okr   zCredits availablez%Sarvam API accepted a minimal requesti  r   Credits exhaustedz%Sarvam returned HTTP 402 (no credits)i  r   Invalid API keyz1Sarvam returned HTTP 403 (invalid or missing key)i  rate_limitedz'Credits likely available (rate limited)u:   Sarvam returned HTTP 429 — key accepted but rate limitederrorr   zCould not verify creditszSarvam returned HTTP z: zSarvam probe timed outzSarvam credits probe failed: %s)r   rB   r(   rD   rH   requestspostrN   status_codetextTimeout	Exceptionloggerwarning)
r   r)   urlr   r   r   responsestatusbody_snippetexcr&   r&   r'   probe_sarvam_credits   s   	
	
r   force_probeboolTuple[Dict[str, Any], bool]c                C  s   t  }t }t| }t|}|s0|r0|t|dpdk r0t|dp&i }d|d< |dfS t| }d|d< || t|dt|< |dfS )z0Return probe payload and whether cache was used.r   r   resultTprobe_skippedF)r   r   )r?   timer5   r   getfloatdictr   )r)   r   ttlr"   fpcachedr   r&   r&   r'   _probe_for_key   s   
r   key_sethealthTuple[str, str]c                 C  s$   | sdS dddddd}| |dS )N)r   r   )activeActive)r   r   )r   r   )r   r   r   r   r   r   )r   r   mappingr&   r&   r'   _status_from_health   s   r   healthsc                 C  s&   dd | D }|sdS t |dd dS )Nc                 S  s   g | ]
}|r|d kr|qS )r   r&   )rm   hr&   r&   r'   
<listcomp>  s    z!_worst_health.<locals>.<listcomp>r   c                 S  s   t | dS )Nc   )_HEALTH_PRIORITYr   )r   r&   r&   r'   <lambda>  s    z_worst_health.<locals>.<lambda>r-   )rM   )r   
configuredr&   r&   r'   _worst_health  s   r   slotsList[Dict[str, Any]]c              
   C  sD   g }| D ]}| |d dtt|dpd  qd|S )N	source_id:r)   r   |)r`   r   r5   r   join)r   partsslotr&   r&   r'   _slots_fingerprint  s   .
r   Fr   List[Dict[str, str]]c                  s  t  }t }t| }|s4tdr4td|kr4|ttdp!dk r4ttd }d|d< ||d< |S t }i  i }d}g }	| D ]}
t|
d	pJd
}t|
dpS|}t|
dp\d
}t|
dped
	 }t
|}|r| vrt||d\}}| |< |||< |p| }n|s |dddddt dd |||  |i }d}|r|||kr|| }tt||dd\}}|	|||t|t|||||d|d|d|d|d|ddd qAdd | D }t|dk}d}t|dkrd}dd  |	D }t|}ttd!d" |	D |\}}t fd#d"| D  t
d
i }i d$td%d" | D d&|d'|d(t|d)|	d*dd+d,d-|d.|d|d|dd|dd|dd|dptt d| o|| d/d0d||}|d1kr|d2rtd3d" |	D d}|r|dd4krd5|d6< n|rd7|d6< nd8|d6< || |t|d9at|S ):z
    Probe each unique Sarvam key used across backend, STT, and RAG.

    slots items: source_id, source_label, env_hint, key
    r   r   r   r   Tr   cache_ttl_secondsFr   r   source_labelenv_hintr)   r   r   r   r   zKey not set for this slotN)r   r   r   r   r   r   r   r   r   r   r   r   )r   r   r   r   
key_maskedsame_key_asr   status_labelr   r   r   r   r   r   c                 S  s8   h | ]}t |d pd rt |d pd qS )r)   r   r   r   rB   rm   sr&   r&   r'   	<setcomp>e  s   8 z)get_sarvam_keys_health.<locals>.<setcomp>r   ur   Different Sarvam keys are configured for Dashboard, STT, and/or MCube AI — each row below is checked separately.c                 S  s   g | ]	}| d p
dqS )r   r   r   r   r&   r&   r'   r   n  s    z*get_sarvam_keys_health.<locals>.<listcomp>c                 s  s    | ]}| d V  qdS )r   Nr   r   r&   r&   r'   rp   p  rq   z)get_sarvam_keys_health.<locals>.<genexpr>c                 3  s@    | ]}t |d pd r tt |d pd V  qdS r)   r   N)r   r   rB   r5   r   probe_by_fpr&   r'   rp   s  s   > r   c                 s  s&    | ]}t |d pd V  qdS r   r   r   r&   r&   r'   rp   x  s   $ keys_in_synckeys_mismatch_warningunique_key_count	key_slotscredits_balancecredits_balance_noteuA   Exact balance is only available on the Sarvam Dashboard → Usager   r   dashboard_urlz!https://dashboard.sarvam.ai/usager   rs   c                 s  s"    | ]}| d dkr|V  qdS )r   sttNr   r   r&   r&   r'   rp     s     r   zISTT worker key reports exhausted credits (matches recent STT log errors).credits_health_warningu`   STT logs reported credit errors earlier — likely resolved if you topped up or updated the key.un   STT logs recently reported credit errors — Dashboard, STT, and RAG use different keys; check each row below.r   )r?   r   r   r   r   r   r   r   r   rB   r5   r   
setdefaultr(   r   r   r`   r.   r,   r   rz   next)r   r   r   r"   r   r   logsfirst_source_by_fpany_probe_ranpublic_slotsr   r   r   r   r)   key_fpprobe
from_cachesame_asslot_statusslot_status_labelunique_keysr   r   slot_healthsaggregate_healthr   r   aggregate_prober   
stt_healthr&   r   r'   get_sarvam_keys_health  s   
	



r   Nonec                   C  s   dddda i adS )z:Clear cached probe results (e.g. after Sarvam key change).r   r   Nr   )r   r   r&   r&   r&   r'   invalidate_credit_cache  s   r   c                C  s(   t | pd }tddd|dg|dS )z'Backward-compatible single-key wrapper.r   backendzDashboard / backendu2   dashboard-backend/.env → SARVAM_SUBSCRIPTION_KEY)r   r   r   r)   r   )r   rB   r   )r   r   r)   r&   r&   r'   get_sarvam_credit_status  s   r   )r   r   )r)   r   r   r   )r   r6   )rO   r   r   rP   )r   rZ   )rd   )re   r6   r   r   )r   r   r   r   )r)   r   r   r   r   r   )r   r   r   r   r   r   )r   rZ   r   r   )r   r   r   r   )r   r   r   r   r   r   )r   r   )r   r   r   r   r   r   )3__doc__
__future__r   r]   r1   loggingr:   rer   r   r   r   typingr   r   r   r	   r
   r   	getLogger__name__r   rC   rG   r<   r   compile
IGNORECASEr{   rT   r   __annotations__r   r(   r.   r5   r?   rD   rH   rN   rY   rc   r   r   r   r   r   r   r   r   r   r&   r&   r&   r'   <module>   sf    










	
)
_



 
