Ë
    F()j²N  ã            	       óL  — 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 d dl	m
Z
mZmZmZ d dlmZmZ d dlmZ d dlm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mZ  e j<                  d
«      Zd„ Z dZ! e «       r^	  ejD                  ejF                  ejH                  ejJ                  ejL                  ejN                  dd¬«      Z!ejQ                  d«       nejQ                  d«        G d„ de)«      Z, G d„ de,«      Z-h d£Z.ddiZ/e.ja                  e/jc                  «       «      Z2dZ3dZ4dddddd œZ5d!e6fd"„Z7d#„ Z8d$„ Z9d%„ Z:d&„ Z;d'„ Z<d(efd)„Z=d*e6d+e>fd,„Z?d-e6d.e@d/efd0„ZAd8d-e6d!e6d1ee6   d2e>fd3„ZBd4„ ZCd9d5e>d+e6fd6„ZDd7„ ZEy# e)$ rZ*ejW                  de*› �«       Y dZ*[*Œ´dZ*[*ww xY w):é    N)ÚdatetimeÚ	timedeltaÚtimezone)Úrelativedelta)Úfetch_tenant_limitsÚget_ticket_countÚget_vector_db_sizeÚget_total_token_usage)Úget_ai_response_message_countÚget_conversation_thread_count)Úclient)Úsettings)ÚQUOTA_MAPPING_PROMPT)ÚOptionalÚquotasc                  ó¢   — t        t        dd«      xs dj                  «       j                  «       } | dv ryt	        t        t        dd«      «      S )NÚENVÚ >   ÚdevÚtestÚlocalÚdevelopmentFÚQUOTA_REDIS_CACHE_ENABLEDT)Úgetattrr   ÚstripÚlowerÚbool)Úenv_names    ú2/var/www/html/strategist-ai/app/services/quotas.pyÚ_quota_redis_enabledr       sH   € Üœ %¨Ó,Ò2°×9Ñ9Ó;×AÑAÓC€HØÐ:Ñ:ØÜ”œÐ"=¸tÓDÓEÐEó    Té   )ÚhostÚportÚusernameÚpasswordÚsslÚdecode_responsesÚsocket_timeoutz1Redis client for quotas initialized successfully.z#Failed to initialize Redis client: z0Redis quota cache disabled for this environment.c                   ó(   ‡ — e Zd ZdZdefˆ fd„Zˆ xZS )ÚQuotaExceededErrorz?Exception raised when a tenant has exceeded its resource quota.Úmessagec                 óF   •— || _         t        ‰| �	  | j                   «       y ©N)r,   ÚsuperÚ__init__)Úselfr,   Ú	__class__s     €r   r0   zQuotaExceededError.__init__4   s   ø€ ØˆŒÜ‰Ñ˜Ÿ™Õ&r!   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ústrr0   Ú__classcell__)r2   s   @r   r+   r+   2   s   ø„ ÙIð' ÷ 'ñ 'r!   r+   c                   ó   — e Zd ZdZy)ÚQuotaValidationErrorz@Exception raised when quota limits or usage cannot be validated.N)r3   r4   r5   r6   © r!   r   r:   r:   8   s   „ ÙJØr!   r:   >   Ú	ai_tokensÚtotal_ticketsÚai_conversationsÚstorage_vector_capacityÚai_responseséÿÿÿÿuS   Weâ€™re unable to validate your service limits right now. Please try again shortly.Úv2)ÚtokenÚtokensÚ	characterÚ
characters)ÚticketÚticketsÚsupportÚhelpdeskzcustomer queryzcustomer queries)ÚstorageÚvectorÚ	knowledgeÚkbÚdatabaseÚfileÚfilesÚindexing)ÚconversationÚconversationsÚthreadÚthreadszchat sessionzchat sessions)ÚresponseÚ	responseszassistant messagezassistant messageszai replyz
ai replies©r<   r=   r?   r>   r@   Úmetric_typec                 óÊ  — |dk(  s|dk(  ry|€t        t        «      ‚t        |t        «      rt        t        «      ‚t        |t        «      rU|j                  «       j                  dd«      }|j                  «       dk(  ry|j                  «       st        t        «      ‚|}	 t        |«      }|dk  rt        t        «      ‚|S # t        t        f$ r t        t        «      ‚w xY w)NrA   z-1Ú,r   Ú	unlimited)r:   ÚQUOTA_VALIDATION_MESSAGEÚ
isinstancer   r7   r   Úreplacer   ÚisdigitÚintÚ	TypeErrorÚ
ValueError)rZ   Ú	raw_limitÚcleanedÚlimits       r   Ú_coerce_limitrh   R   sÔ   € Ø�B‚˜) tÒ+ØàÐÜ"Ô#;Ó<Ð<ä�)œTÔ"Ü"Ô#;Ó<Ð<ä�)œSÔ!Ø—/‘/Ó#×+Ñ+¨C°Ó4ˆØ�=‰=‹?˜kÒ)ØØ�‰Ô Ü&Ô'?Ó@Ð@Øˆ	ð=Ü�I“ˆð ˆr‚zÜ"Ô#;Ó<Ð<à€Løô ”zÐ"ò =Ü"Ô#;Ó<Ð<ð=ús   Â"C ÃC"c           
      ód  — t        | t        «      st        t        «      ‚t        j                  | j                  «       «      }|r0t        j                  dt        |«      › �«       t        t        «      ‚i t        ¥| ¥} t        D �ci c]  }|t        || j                  |«      «      “Œ  c}S c c}w )Nz%Quota mapping missing required keys: )r_   Údictr:   r^   ÚCORE_REQUIRED_LIMIT_KEYSÚ
differenceÚkeysÚloggerÚerrorÚsortedÚOPTIONAL_LIMIT_DEFAULTSÚREQUIRED_LIMIT_KEYSrh   Úget)ÚmappingÚmissingÚkeys      r   Ú_validate_mapped_limitsrw   n   s�   € Ü�gœtÔ$Ü"Ô#;Ó<Ð<ä&×1Ñ1°'·,±,³.ÓA€GÙÜ�‰Ð<¼VÀG»_Ð<MÐNÔOÜ"Ô#;Ó<Ð<à4Ô(Ð4¨GÐ4€GÜATÖU¸#ˆC”˜s G§K¡K°Ó$4Ó5Ñ5ÒUÐUùÒUs   Â#B-c                 óÐ   — t        | t        «      syg }dD ]0  }| j                  |«      }|sŒ|j                  t	        |«      «       Œ2 dj                  |«      j                  «       S )Nr   )ÚnameÚfeatureNameÚtitleÚlabelÚdescriptionrv   ÚresourceNameÚ )r_   rj   rs   Úappendr7   Újoinr   )ÚfeatureÚ
text_partsrv   Úvalues       r   Ú_extract_feature_textr…   z   s`   € Ü�gœtÔ$Øà€JØ^ò *ˆØ—‘˜CÓ ˆÚØ×Ñœc %›jÕ)ð*ð �8‰8�JÓ×%Ñ%Ó'Ð'r!   c                 ób   — t        | t        «      sy dD ]  }|| v sŒ| j                  |«      c S  y )N)ÚcappingValueÚcapping_valuerg   r„   ÚquotaÚcap)r_   rj   rs   )r‚   rv   s     r   Ú_extract_capping_valuer‹   …   s9   € Ü�gœtÔ$ØàRò $ˆØ�'Š>Ø—;‘;˜sÓ#Ò#ð$ð r!   c                 ó  ‡‡	— t         D �ci c]  }|d “Œ c}Š	| D ]\  }t        |«      Št        |«      }|€Œt        j	                  «       D ]*  \  }}t        ˆfd„|D «       «      sŒt        ||«      ‰	|<    Œ\ Œ^ t        ˆ	fd„t        D «       «      r5t        j	                  «       D ]  \  }}‰	j                  |«      �Œ|‰	|<   Œ ‰	S t        D �cg c]  }‰	j                  |«      �Œ|‘Œ }}t        j                  d|› �«       y c c}w c c}w )Nc              3   ó&   •K  — | ]  }|‰v –— Œ
 y ­wr.   r;   )Ú.0ÚkeywordÚfeature_texts     €r   ú	<genexpr>z,_map_limits_from_features.<locals>.<genexpr>˜   s   øè ø€ ÒC¨w�7˜lÔ*ÑCùs   ƒc              3   óD   •K  — | ]  }‰j                  |«      d u–— Œ y ­wr.   ©rs   )rŽ   rv   rt   s     €r   r‘   z,_map_limits_from_features.<locals>.<genexpr>œ   s   øè ø€ Ò
L¨Cˆ7�;‰;�sÓ 4Ô'Ñ
Lùs   ƒ z;Could not deterministically map all quota limits. Missing: )rr   r…   r‹   ÚFEATURE_KEYWORDSÚitemsÚanyrh   Úallrk   rq   rs   rn   Úwarning)
Úaivora_featuresrv   r‚   re   rZ   ÚkeywordsÚdefaultru   r�   rt   s
           @@r   Ú_map_limits_from_featuresrœ   Ž   s  ù€ Ü$7Ö8˜Sˆs�D‰yÒ8€Gà"ò 	ˆÜ,¨WÓ5ˆÜ*¨7Ó3ˆ	ØÐØä%5×%;Ñ%;Ó%=ò 	Ñ!ˆK˜ÜÓC¸(ÔCÕCÜ'4°[À)Ó'L�˜Ñ$Ùñ	ð	ô Ó
LÔ3KÔ
LÔLÜ3×9Ñ9Ó;ò 	'‰LˆC�Ø�{‰{˜3ÓÑ'Ø&�˜’ð	'ð ˆä6ÖS�s¸'¿+¹+ÀcÓ:JÑ:RŠsÐS€GÐSÜ
‡N�NÐPÐQXÐPYÐZÔ[Øùò+ 9ùò& Ts   ‹
DÃD	Ã%D	c                 ó¤  — | rt        | t        «      si S t        d„ | D «       d«      }|r|j                  d«      si S |d   }t	        j
                  |d¬«      }t        j                  |j                  «       «      j                  «       }dt        › d|› �}t        rL	 t        j                  |«      }|r3t        j                  d«       t        t	        j                  |«      «      S 	 t%        |«      }|r_t        j'                  d
«       t        rB	 t        j)                  |dt	        j
                  |«      «       t        j'                  d«       |S |S t        j'                  d«       	 t	        j
                  |d¬«      }	t+        j,                  |	¬«      }
t.        j0                  j2                  j5                  t6        j8                  d|
dœgddi¬«      }t        t	        j                  |j:                  d   j<                  j>                  «      «      }t        j'                  dt	        j
                  |«      › �«       t        rB	 t        j)                  |dt	        j
                  |«      «       t        j'                  d«       |S |S # t         $ r#}t        j#                  d	|› �«       Y d}~�ŒÊd}~ww xY w# t         $ r#}t        j#                  d|› �«       Y d}~|S d}~ww xY w# t         $ r#}t        j#                  d|› �«       Y d}~|S d}~ww xY w# t         $ r.}t        jA                  d|› �d¬«       tC        tD        «      ‚d}~ww xY w)z‡
    Intelligently maps Aivora features to internal quota categories using an LLM.
    Caches the results in Redis for 10 minutes.
    c              3   óJ   K  — | ]  }|j                  d «      dk(  sŒ|–— Œ y­w)ÚproductNameÚAivoraNr“   )rŽ   Úps     r   r‘   z$get_mapped_limits.<locals>.<genexpr>¯   s    è ø€ ÒX˜a°q·u±u¸]Ó7KÈxÓ7WœÑXùs   ‚#œ#NÚfeaturesT©Ú	sort_keysz
quota_map:ú:z#Found quota mapping in Redis cache.z Error reading from Redis cache: zLSuccessfully extracted quota limits deterministically from product features.iX  z?Stored deterministic quota mapping in Redis cache (10 min TTL).z:Error writing deterministic quota mapping to Redis cache: z3Quota mapping cache miss. Triggering LLM mapping...é   )Úindent)Úfeatures_listÚuser)ÚroleÚcontentÚtypeÚjson_object)ÚmodelÚmessagesÚresponse_formatr   z-Successfully extracted quota limits via LLM: z5Stored new quota mapping in Redis cache (10 min TTL).zError writing to Redis cache: zFailed to map quotas via LLM: ©Úexc_info)#r_   ÚlistÚnextrs   ÚjsonÚdumpsÚhashlibÚmd5ÚencodeÚ	hexdigestÚQUOTA_CACHE_VERSIONÚredis_clientrn   Údebugrw   ÚloadsÚ	Exceptionr˜   rœ   ÚinfoÚsetexr   ÚformatÚopenai_clientÚchatÚcompletionsÚcreater   Ú	LLM_MODELÚchoicesr,   r«   ro   r:   r^   )Úproduct_featuresÚaivora_datar™   Úfeatures_jsonÚfeatures_hashÚ	cache_keyÚ
cached_valÚeÚdeterministic_mappingÚfeatures_list_strÚpromptrW   rt   s                r   Úget_mapped_limitsrÓ   ¦   së  € ñ
 ¤:Ð.>ÄÔ#EØˆ	ô ÑXÐ#3ÔXÐZ^Ó_€KÙ˜kŸo™o¨jÔ9Øˆ	à! *Ñ-€Oô —J‘J˜¸$Ô?€MÜ—K‘K × 4Ñ 4Ó 6Ó7×AÑAÓC€MØÔ0Ð1°°=°/ÐB€Iåð	CÜ%×)Ñ)¨)Ó4ˆJÙÜ—‘ÐBÔCÜ.¬t¯z©z¸*Ó/EÓFÐFð ô 6°oÓFÐÙÜ�‰ÐbÔcÝðaÜ×"Ñ" 9¨c´4·:±:Ð>SÓ3TÔUÜ—‘Ð]Ô^ð %Ð$Ð$Ð$ô ‡K�KÐEÔFð=Ü ŸJ™J ¸qÔAÐÜ%×,Ñ,Ð;LÔMˆä ×%Ñ%×1Ñ1×8Ñ8Ü×$Ñ$Ø%°&Ñ9Ð:Ø# ]Ð3ð 9ó 
ˆô *¬$¯*©*°X×5EÑ5EÀaÑ5H×5PÑ5P×5XÑ5XÓ*YÓZˆÜ�‰ÐCÄDÇJÁJÈwÓDWÐCXÐYÔZõ ðEÜ×"Ñ" 9¨c´4·:±:¸gÓ3FÔGÜ—‘ÐSÔTð ˆˆwˆøôK ò 	CÜ�N‰NÐ=¸a¸SÐA×BÒBûð	Cûô ò aÜ—‘Ð![Ð\]Ð[^Ð_×`Ð`Ø$Ð$ûðaûô0 ò EÜ—‘Ð!?À¸sÐC×DÐDàˆûðEûô ò =Ü�‰Ð5°a°SÐ9ÀDˆÔIÜ"Ô#;Ó<Ð<ûð=úsz   ÂA	J Ä?J: Å*CL É?K) ÊL Ê	L Ê	J7ÊJ2Ê2J7Ê:	K&ËK!Ë!K&Ë)	LË2LÌ
L ÌLÌL Ì	MÌ!)M
Í
MÚbaselinec                 ó`  — | r;| j                   �/| j                  t        j                  «      j	                  d¬«      } t        j                  «       }|j                  | j                  z
  dz  |j                  z   | j                  z
  }| t        |¬«      z   }||kD  r| t        |dz
  ¬«      z   S |S )zPCalculates the start of the current monthly cycle relative to the baseline date.N©Útzinfoé   )Úmonthsé   )
r×   Ú
astimezoner   Úutcr`   r   ÚutcnowÚyearÚmonthr   )rÔ   ÚnowÚmonths_diffÚ	candidates       r   Úcalculate_anniversary_startrã   ê   s›   € ñ �H—O‘OÐ/Ø×&Ñ&¤x§|¡|Ó4×<Ñ<ÀDÐ<ÓIˆä
�/‰/Ó
€Cà—8‘8˜hŸm™mÑ+¨rÑ1°C·I±IÑ=ÀÇÁÑN€Kð œ=°Ô<Ñ<€Ið �3‚Øœ-¨{¸Q©Ô?Ñ?Ð?àÐr!   ÚtextÚreturnc                 ó8   — | syt        dt        | «      dz  «      S )zTConservative local estimate used for quota preflight before provider usage is known.rÚ   é   )ÚmaxÚlen)rä   s    r   Úestimate_text_tokensrê   ý   s   € áØÜˆq”#�d“)˜q‘.Ó!Ð!r!   Ú	tenant_idÚcurrent_featuresÚdb_updated_atc                 ó˜  — |r;|j                   �/|j                  t        j                  «      j	                  d¬«      }t
        s#|xs t        j                  «       }t        |«      S d| › �}t        j                  «       }t        j                  t        j                  |d¬«      j                  «       «      j                  «       }	 t
        j                  |«      }|�r$t        j                   |«      }t        j"                  |d   «      }|j                  d«      }	|j                  d«      r$t        j"                  |j                  d«      «      nd}
d	}|	|k7  rt$        j'                  d
| › d�«       |}d}n)|
r'|r%||
kD  r t$        j'                  d| › d|› d�«       |}d}|rÛ|j)                  «       ||r|j)                  «       nd|j)                  «       dœ}t
        j+                  |t        j                  |«      «       n|t$        j'                  d| › �«       |xs |}|j)                  «       ||r|j)                  «       nd|j)                  «       dœ}t
        j+                  |t        j                  |«      «       t        |«      S # t,        $ r6}t$        j/                  d| › d|› �«       |xs |}t        |«      cY d}~S d}~ww xY w)a#  
    Retrieves the start date for the current monthly quota cycle.
    Logic:
    1. Baseline is db_updated_at.
    2. If productFeatures content changes OR db_updated_at changes, we reset the baseline to 'now'.
    3. The current start_date is the monthly anniversary of that baseline.
    NrÖ   zquota_state_v3:Tr£   Úbaseline_daterÌ   rí   Fz"Quota feature content changed for z. Resetting baseline to NOW.z!Plan updatedAt changed in DB for z. Resetting baseline to ú.)rï   rÌ   rí   Ú
updated_atz'Initializing quota baseline for tenant z(Error managing quota state in Redis for ú: )r×   rÛ   r   rÜ   r`   r¼   r   rÝ   rã   r·   r¸   rµ   r¶   r¹   rº   rs   r¾   Úfromisoformatrn   rÀ   Ú	isoformatÚsetr¿   ro   )rë   rì   rí   rÔ   Ú	state_keyrà   rÌ   Úcached_stateÚstateÚold_hashÚold_db_updatedÚreset_requiredÚ	new_staterÏ   s                 r   Úget_quota_start_daterý     sw  € ñ ˜×-Ñ-Ð9Ø%×0Ñ0´·±Ó>×FÑFÈdÐFÓSˆåØ Ò5¤H§O¡OÓ$5ˆÜ*¨8Ó4Ð4à! ) Ð-€IÜ
�/‰/Ó
€Cô —K‘K¤§
¡
Ð+;ÀtÔ L× SÑ SÓ UÓV×`Ñ`Ób€Mð/5Ü#×'Ñ'¨	Ó2ˆÚÜ—J‘J˜|Ó,ˆEÜ×-Ñ-¨e°OÑ.DÓEˆHØ—y‘y Ó1ˆHØSX×S\ÑS\Ð]lÔSmœX×3Ñ3°E·I±I¸oÓ4NÔOÐswˆNà"ˆNà˜=Ò(Ü—‘Ð@ÀÀÐKgÐhÔiØ�Ø!%‘á¡M°mÀnÒ6TÜ—‘Ð?À	¸{ÐJbÐcpÐbqÐqrÐsÔtØ(�Ø!%�áð &.×%7Ñ%7Ó%9Ø%2ÙBO ]×%<Ñ%<Ô%>ÐUYØ"%§-¡-£/ñ	�	ô × Ñ  ¬D¯J©J°yÓ,AÕBô �K‰KÐAÀ)ÀÐMÔNØ$Ò+¨ˆHà!)×!3Ñ!3Ó!5Ø!.Ù>K ×!8Ñ!8Ô!:ÐQUØ!Ÿm™m›oñ	ˆIô ×Ñ˜Y¬¯
©
°9Ó(=Ô>ô +¨8Ó4Ð4øäò 5Ü�‰Ð?À	¸{È"ÈQÈCÐPÔQØ Ò' CˆÜ*¨8Ó4Õ4ûð5ús   ÃGJ
 Ê
	K	Ê+KÊ>K	ËK	Úuser_idÚrequested_amountc                 ó$  — t         j                  d|› d| › d|› d�«       t        | «      \  }}|s(t         j                  d| › d�«       t	        t
        «      ‚t        |«      }|s(t         j                  d| › d�«       t	        t
        «      ‚|j                  |«      }t        ||«      }|dk(  rt         j                  d	|› d
�«       yt        | ||«      }d}	d}
	 |dk(  r"t        | |¬«      }
|
t        d|«      z   |kD  rÎd}	nË|dk(  r"t        | |¬«      }
|
t        d|«      z   |kD  r§d}	n¤|dk(  r*t        | «      }|dz  dz  }|}
|t        d|«      z   |kD  rxd}	nu|dk(  r"t        | |¬«      }
|
t        d|«      z   |kD  rQd}	nN|dk(  r"t!        | |¬«      }
|
t        d|«      z   |kD  r*d}	n't         j                  d|› �«       t	        t
        «      ‚|	rTt         j%                  d|› d| › d|
› d|› d�	«       t'        | ||«       dd d!d"d#d$œ}|j                  |d%«      }t)        |«      ‚t         j                  d&|› d| › d|
› d'|› d(|› d�«       y# t        $ r ‚ t"        $ r4}t         j                  d|› d| › d|› �d¬«       t	        t
        «      ‚d}~ww xY w))zK
    Checks the current usage against the monthly (or overall) limits.
    z	Checking z quota for tenant: z (User: ú)z#No quota features found for tenant z6. Blocking request because limits cannot be validated.z&No quota mapping generated for tenant rA   z
Quota for z is unlimited.NFr   r<   )Ú
start_dateTr=   r?   i   r>   r@   z Unknown quota metric requested: z$Could not calculate quota usage for z in tenant rò   r±   zQuota exceeded for z
 (Tenant: z	, Usage: z	, Limit: uK   Weâ€™re unable to process your request right now. Please try again shortly.um   We couldnâ€™t create your support request at the moment. Please contact our team through the Contact Us page.us   That information isnâ€™t available at the moment. Please get in touch with our support team for further assistance.zJThis service is currently unavailable. Please try again in a little while.uO   Youâ€™ve hit your AI response quota. Upgrade or wait for the reset to continue.rY   uL   Youâ€™ve hit your resource quota. Upgrade or wait for the reset to continue.zQuota check passed for ú/z, Start Date: )rn   rÀ   r   ro   r:   r^   rÓ   rs   rh   r½   rý   r
   rè   r   r	   r   r   r¿   r˜   Úsend_quota_notificationr+   )rë   rZ   rþ   rÿ   rÉ   rí   Úlimitsrg   r  Ú	quota_hitÚusageÚusage_bytesÚlimit_bytesrÏ   Úmsg_mapÚerr_msgs                   r   Úcheck_quotar  J  s  € ô ‡K�K�)˜K˜=Ð(;¸I¸;ÀhÈwÈiÐWXÐYÔZô ':¸)Ó&DÑ#Ð�mÙÜ�‰Ð:¸9¸+ÐE{Ð|Ô}Ü"Ô#;Ó<Ð<ô Ð/Ó0€FÙÜ�‰Ð=¸i¸[ÐH~Ðô  	AÜ"Ô#;Ó<Ð<ð �J‰J�{Ó#€EÜ˜+ uÓ-€EØ�‚{Ü�‰�z + ¨nÐ=Ô>Øô & iÐ1AÀ=ÓQ€Jð €IØ€Eð=Ø˜+Ò%Ü)¨)À
ÔKˆEØ”s˜1Ð.Ó/Ñ/°%Ò7ÀT¹à˜OÒ+Ü$ Y¸:ÔFˆEØ”s˜1Ð.Ó/Ñ/°%Ò7ÀT¹àÐ5Ò5ä,¨YÓ7ˆKØ $™,¨Ñ-ˆKØˆEØœS Ð$4Ó5Ñ5¸ÒCÐQUÁYàÐ.Ò.Ü1°)È
ÔSˆEØ”s˜1Ð.Ó/Ñ/°%Ò7ÀT¹à˜NÒ*Ü1°)È
ÔSˆEØ”s˜1Ð.Ó/Ñ/°%Ò7ÀT¹ô �L‰LÐ;¸K¸=ÐIÔJÜ&Ô'?Ó@Ð@ñ Ü�‰Ð,¨[¨M¸ÀIÀ;ÈiÐX]ÐW^Ð^gÐhmÐgnÐnoÐpÔqÜ 	¨;¸Ô@ð
 gð Mð (]Ø lØmñ
ˆð —+‘+˜kÐ+yÓzˆÜ  Ó)Ð)ä
‡K�KÐ)¨+¨°jÀÀÈ9ÐUZÐT[Ð[\Ð]bÐ\cÐcqÐr|Ðq}Ð}~Ðõ  Aøô-  ò ØÜò =Ü�‰Ð;¸K¸=ÈÐT]ÐS^Ð^`ÐabÐ`cÐdÐosˆÔtÜ"Ô#;Ó<Ð<ûð=ús   ÃC2I	 É	JÉ/J
Ê
Jc              ƒ   óº  K  — t        «       }d|› �ddœ}t        j                  d¬«      4 ƒd {  –—† }	 |j                  t        j
                  | |¬«      ƒ d {  –—† }t        j                  d|j                  › �«       d d d «      ƒd {  –—†  y 7 Œb7 Œ9# t        $ r"}t        j                  d|› �«       Y d }~Œ;d }~ww xY w7 Œ7# 1 ƒd {  –—†7  sw Y   y xY w­w)	NzBearer zapplication/json)ÚAuthorizationzContent-Typeg      $@)Útimeout)rµ   ÚheaderszQuota notification API result: z#Failed to send quota notification: )Úgenerate_system_tokenÚhttpxÚAsyncClientÚpostr   ÚNOTIFICATION_API_URLrn   rÀ   Ústatus_coder¿   ro   )ÚpayloadrC   r  r   ÚrrÏ   s         r   Ú
_do_notifyr  �  sÖ   è ø€ ä!Ó#€Eð # 5 'Ð*Ø*ñ€Gô
 × Ñ ¨Ô.÷ Dð D°&ð	DØ—k‘k¤(×"?Ñ"?ÀgÐW^�kÓ_×_ˆAÜ�K‰KÐ9¸!¿-¹-¸ÐIÔJ÷D÷ Dñ Døà_ùäò 	DÜ�L‰LÐ>¸q¸cÐB×CÑCûð	Dúð	Dø÷ D÷ Dñ Düsy   ‚-C¯B°C³Cµ%BÁBÁ&BÂCÂCÂCÂBÂ	CÂB<Â7CÂ<CÃCÃCÃCÃCÃCÃCÚexpires_in_secc                 ó  — t         j                  st        j                  d«       yt	        t        j
                  «       «      }dt         j                  ||| z   dœ}t        j                  |t         j                  d¬«      }|S )zR
    Generates a system JWT for authenticating with the notification service.
    z5SYSTEM_JWT_KEY not configured. Notification may fail.r   Úsystem)r¬   ÚserviceÚiatÚexpÚHS256)Ú	algorithm)	r   ÚSYSTEM_JWT_KEYrn   r˜   rb   ÚtimeÚSYSTEM_SERVICE_NAMEÚjwtr¹   )r  rà   r  rC   s       r   r  r  ­  sj   € ô ×"Ò"Ü�‰ÐNÔOØä
Œd�i‰i‹kÓ
€CàÜ×/Ñ/ØØ�^Ñ#ñ	€Gô �J‰J�w¤× 7Ñ 7À7ÔK€EØ€Lr!   c                 óÔ  — |st         j                  d«       y ddddddœ}|j                  |d«      \  }}|d	|› d
�d|gdœ}t         j                  d|› d|› �«       	 t	        j
                  «       }|j                  «       r|j                  t        |«      «       y t	        j                  t        |«      «       y # t        $ r"}t         j                  d|› �«       Y d }~y d }~ww xY w)Nz5No user_id provided for notification alert. Skipping.)u    Alert ðŸš¨ Aivora AI Token LimituK   AI token quota approaching limit â€” chatbot responses will be paused soon.)u$   Alert ðŸš¨ Aivora Ticket Count Limitu;   Ticket count limit reached â€” new submissions are on hold.)u   Alert ðŸš¨ Aivora DB Storageu@   Vector DB Storage at capacity â€” new training uploads disabled.)u*   Alert ðŸš¨ Aivora Conversation Count LimituB   Conversation limit has exhausted â€” Live chat temporarily paused.)u#   Alert ðŸš¨ Aivora AI Response Limitz8Aivora AI response limit hit. Upgrade or wait for reset.rY   )zQuota Limit hitz"A resource limit has been reached.z<p>z</p>T)r{   r}   ÚisSelectedAllÚuserIdsz"Triggering quota notification for z	 to user z'Error orchestrating notification task: )rn   r½   rs   rÀ   ÚasyncioÚget_event_loopÚ
is_runningÚcreate_taskr  Úrunr¿   ro   )	rë   rZ   rþ   r
  r{   Údescr  ÚlooprÏ   s	            r   r  r  ¿  sõ   € ÙÜ�‰ÐLÔMØð Ið Að $Hð QØ{ñ€Gð —+‘+˜kÐ+dÓe�K€Eˆ4ð Ø˜T˜F $Ð'ØØ�9ñ	€Gô ‡K�KÐ4°[°MÀÈ7È)ÐTÔUðDÜ×%Ñ%Ó'ˆØ�?‰?ÔØ×ÑœZ¨Ó0Õ1ä�K‰Kœ
 7Ó+Õ,øÜò DÜ�‰Ð>¸q¸cÐB×CÑCûðDús   Á>B< ÂB< Â<	C'ÃC"Ã"C')NrÚ   )i  )FÚloggingrµ   r·   Úredisr   r   r   Údateutil.relativedeltar   Úapp.services.databaser   r   r	   r
   Úapp.services.firestore_servicer   r   Úapp.core.llm_clientr   rÃ   Úapp.core.configr   Úapp.core.promptsr   r  r)  r%  r#  Útypingr   Ú	getLoggerrn   r    r¼   ÚRedisÚ
REDIS_HOSTÚ
REDIS_PORTÚ
REDIS_USERÚREDIS_PASSWORDÚ	REDIS_SSLrÀ   r¿   rÏ   ro   r+   r:   rk   rq   Úunionrm   rr   r^   r»   r”   r7   rh   rw   r…   r‹   rœ   rÓ   rã   rb   rê   r³   rý   r  r  r  r  r;   r!   r   ú<module>rA     s$  ðÛ Û Û Û ß 2Ñ 2Ý 0÷ó ÷ hÝ 7Ý $Ý 1Û Û Û 
Û Ý à	ˆ×	Ñ	˜8Ó	$€òFð €ÙÔð@Ø"�u—{‘{Ø×$Ñ$Ø×$Ñ$Ø×(Ñ(Ø×,Ñ,Ø×"Ñ"Ø!Øô
ˆð 	�‰ÐGÕHð ‡K�KÐBÔCô'˜ô 'ô	Ð-ô 	òÐ ð �BðÐ ð /×4Ñ4Ð5L×5QÑ5QÓ5SÓTÐ àpÐ ØÐ ð @ØgØpØoØrñÐ ð˜só ò8
Vò	(òòò0B=ðH¨(ó ð&"˜sð " só "ðE5 Cð E5¸4ð E5ÐPXó E5ñNQA˜3ð QA¨Sð QA¸8ÀC¹=ð QAÐcfó QAòfDñ ¨#ð ¸ó ó$ Døðe ò @Ø�‰Ð:¸1¸#Ð>×?Ñ?ûð@ús   Á<AF  Æ F#ÆFÆF#