Ë
    §‘ˆjne  ã                   ó€  — 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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 j8                  d	«      Zd
„ ZdZ e«       r^	  ej@                  ejB                  ejD                  ejF                  ejH                  ejJ                  dd¬«      ZejM                  d«       nejM                  d«        G d„ de'«      Z* G d„ de*«      Z+h d£Z,ddiZ-e,j]                  e-j_                  «       «      Z0dZ1dZ2e1e,e2dhiZ3e1e-e2i iZ4e2hZ5dZ6dZ7dddd d!d"d#œZ8d$e9fd%„Z:e1fd&„Z;d'„ Z<d(„ Z=e1fd)„Z>e1fd*„Z?d+efd,„Z@d-e9d.eAfd/„ZBd0e9d1eCd2efd3„ZDdd4e1fd0e9d$e9d5ee9   d6eAd7e9f
d8„ZEd9„ ZFd=d:eAd.e9fd;„ZGd<„ ZHy# e'$ rZ(ejS                  de(› �«       Y dZ([(ŒÔdZ([(ww xY w)>é    N)ÚdatetimeÚ	timedeltaÚtimezone)Úrelativedelta)Úfetch_tenant_limitsÚget_catalogue_product_countÚget_ticket_countÚget_vector_db_sizeÚget_total_token_usage)Ú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    ú8/var/www/html/strategist-ai/app/services/infra/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   r/   zQuotaExceededError.__init__9   s   ø€ ØˆŒÜ‰Ñ˜Ÿ™Õ&r    )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ústrr/   Ú__classcell__)r1   s   @r   r*   r*   7   s   ø„ ÙIð' ÷ 'ñ 'r    r*   c                   ó   — e Zd ZdZy)ÚQuotaValidationErrorz@Exception raised when quota limits or usage cannot be validated.N)r2   r3   r4   r5   © r    r   r9   r9   =   s   „ ÙJØr    r9   >   Ú	ai_tokensÚtotal_ticketsÚai_conversationsÚstorage_vector_capacityÚai_responseséÿÿÿÿÚAivoraÚ
BrandForgeÚproduct_countuS   Weâ€™re unable to validate your service limits right now. Please try again shortly.Úv2)zproduct countrC   ÚproductsÚ	catalogueÚcatalogÚskuÚskus)Ú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)rC   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)Nr@   z-1Ú,r   Ú	unlimited)r9   ÚQUOTA_VALIDATION_MESSAGEÚ
isinstancer   r6   r   Úreplacer   ÚisdigitÚintÚ	TypeErrorÚ
ValueError)r`   Ú	raw_limitÚcleanedÚlimits       r   Ú_coerce_limitrn   q   sÔ   € Ø�B‚˜) tÒ+ØàÐÜ"Ô#;Ó<Ð<ä�)œTÔ"Ü"Ô#;Ó<Ð<ä�)œSÔ!Ø—/‘/Ó#×+Ñ+¨C°Ó4ˆØ�=‰=‹?˜kÒ)ØØ�‰Ô Ü&Ô'?Ó@Ð@Øˆ	ð=Ü�I“ˆð ˆr‚zÜ"Ô#;Ó<Ð<à€Løô ”zÐ"ò =Ü"Ô#;Ó<Ð<ð=ús   Â"C ÃC"c           
      óø  — t        | t        «      st        t        «      ‚t        j                  |t        «      }t        j                  |t        «      }|j                  | j                  «       «      }|r3t        j                  d|› dt        |«      › �«       t        t        «      ‚i |¥| ¥} |j                  |j                  «       «      }|D �ci c]  }|t        || j                  |«      «      “Œ  c}S c c}w )NzQuota mapping for z missing required keys: )re   Údictr9   rd   ÚREQUIRED_BY_PRODUCTÚgetÚCORE_REQUIRED_LIMIT_KEYSÚOPTIONAL_BY_PRODUCTÚOPTIONAL_LIMIT_DEFAULTSÚ
differenceÚkeysÚloggerÚerrorÚsortedÚunionrn   )ÚmappingÚproduct_nameÚrequiredÚoptionalÚmissingrw   Úkeys          r   Ú_validate_mapped_limitsr‚   �   sÔ   € Ü�gœtÔ$Ü"Ô#;Ó<Ð<ä"×&Ñ& |Ô5MÓN€HÜ"×&Ñ& |Ô5LÓM€Hà×!Ñ! '§,¡,£.Ó1€GÙÜ�‰Ð)¨,¨Ð7OÜ˜w›Ð(ð*ô 	+ä"Ô#;Ó<Ð<à%�Ð%˜WÐ%€GØ�>‰>˜(Ÿ-™-›/Ó*€DØAEÖF¸#ˆC”˜s G§K¡K°Ó$4Ó5Ñ5ÒFÐFùÒFs   Ã#C7c                 óÐ   — t        | t        «      syg }dD ]0  }| j                  |«      }|sŒ|j                  t	        |«      «       Œ2 dj                  |«      j                  «       S )Nr   )ÚnameÚfeatureNameÚtitleÚlabelÚdescriptionr�   ÚresourceNameÚ )re   rp   rr   Úappendr6   Újoinr   )ÚfeatureÚ
text_partsr�   Úvalues       r   Ú_extract_feature_textr�   ž   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_valuerm   r�   ÚquotaÚcap)re   rp   rr   )r�   r�   s     r   Ú_extract_capping_valuer–   ©   s9   € Ü�gœtÔ$ØàRò $ˆØ�'Š>Ø—;‘;˜sÓ#Ò#ð$ð r    c                 ó®  ‡‡— t         j                  |t        «      }t        j                  |t        «      }|j                  |j                  «       «      D �ci c]  }|d “Œ c}Š| D ]a  }t        |«      Št        |«      }|€Œt        j                  «       D ]/  \  }}|‰vrŒt        ˆfd„|D «       «      sŒ t        ||«      ‰|<    Œa Œc t        ˆfd„|D «       «      r1|j                  «       D ]  \  }}	‰j                  |«      �Œ|	‰|<   Œ ‰S |D �cg c]  }‰j                  |«      �Œ|‘Œ }
}t        j                  d|› 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-   ©rr   )r™   r�   r|   s     €r   rœ   z,_map_limits_from_features.<locals>.<genexpr>Ä   s   øè ø€ Ò
<¨Cˆ7�;‰;�sÓ 4Ô'Ñ
<ùs   ƒ z$Could not deterministically map all z quota limits. Missing: )rq   rr   rs   rt   ru   r{   rw   r�   r–   ÚFEATURE_KEYWORDSÚitemsÚanyrn   Úallrx   Úwarning)Úproduct_featuresr}   r~   r   r�   r�   rk   r`   ÚkeywordsÚdefaultr€   r›   r|   s              @@r   Ú_map_limits_from_featuresr§   ²   s^  ù€ Ü"×&Ñ& |Ô5MÓN€HÜ"×&Ñ& |Ô5LÓM€HØ$,§N¡N°8·=±=³?Ó$CÖD˜Sˆs�D‰yÒD€Gà#ò ˆÜ,¨WÓ5ˆÜ*¨7Ó3ˆ	ØÐØä%5×%;Ñ%;Ó%=ò 	Ñ!ˆK˜Ø 'Ñ)ØÜÓC¸(ÔCÕCÜ'4°[À)Ó'L�˜Ñ$Ùñ	ðô Ó
<°8Ô
<Ô<Ø$ŸN™NÓ,ò 	'‰LˆC�Ø�{‰{˜3ÓÑ'Ø&�˜’ð	'ð ˆà&ÖC�s¨'¯+©+°cÓ*:Ñ*BŠsÐC€GÐCÜ
‡N�NÐ9¸,¸ð H'Ø'. ið1ô 2àùò1 Eùò* Ds   Á
EÄEÄ+Ec                 ó   ‡— | rt        | t        «      si S t        ˆfd„| D «       d«      }|r|j                  d«      si S |d   }t	        j
                  |d¬«      }t        j                  |j                  «       «      j                  «       }dt        › d‰› d|› �}t        rM	 t        j                  |«      }|r4t        j                  d«       t        t	        j                  |«      ‰«      S 	 t%        |‰«      }	|	€#‰t&        v rt        j)                  d
‰› d�«       i S |	r_t        j)                  d«       t        rB	 t        j+                  |dt	        j
                  |	«      «       t        j)                  d«       |	S |	S t        j)                  d«       	 t	        j
                  |d¬«      }
t-        j.                  |
¬«      }t0        j2                  j4                  j7                  t8        j:                  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        jC                  d|› �d¬«       tE        tF        «      ‚d}~ww xY w)aº  
    Maps one product's plan features to internal quota categories, using an LLM
    where the keywords cannot settle it. Caches in Redis for 10 minutes.

    The product is a parameter because each one measures something different:
    Aivora counts tokens and conversations, BrandForge counts products. Reading
    Aivora's numbers for a BrandForge tenant is how a customer who never bought
    Aivora ends up unable to build anything.
    c              3   óJ   •K  — | ]  }|j                  d «      ‰k(  r|–— Œ y­w)ÚproductNameNrž   )r™   Úpr}   s     €r   rœ   z$get_mapped_limits.<locals>.<genexpr>Ü   s+   øè ø€ ò B˜qØŸE™E -Ó0°LÒ@ô ñ Bùs   ƒ #NÚfeaturesT©Ú	sort_keysz
quota_map:ú:z#Found quota mapping in Redis cache.z Error reading from Redis cache: úNo z% limits found in the plan; unmetered.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)$re   ÚlistÚnextrr   ÚjsonÚdumpsÚhashlibÚmd5ÚencodeÚ	hexdigestÚQUOTA_CACHE_VERSIONÚredis_clientrx   Údebugr‚   ÚloadsÚ	Exceptionr£   r§   ÚUNMETERED_IS_ALLOWEDÚinfoÚsetexr   ÚformatÚopenai_clientÚchatÚcompletionsÚcreater   Ú	LLM_MODELÚchoicesr+   r¶   ry   r9   rd   )r¤   r}   Úproduct_dataÚproduct_plan_featuresÚfeatures_jsonÚfeatures_hashÚ	cache_keyÚ
cached_valÚeÚdeterministic_mappingÚfeatures_list_strÚpromptr^   r|   s    `            r   Úget_mapped_limitsrß   Ï   s?  ø€ ñ ¤:Ð.>ÄÔ#EØˆ	äó BÐ$4ô BØCGóI€Lá˜|×/Ñ/°
Ô;Øˆ	à(¨Ñ4Ðô —J‘JÐ4ÀÔE€MÜ—K‘K × 4Ñ 4Ó 6Ó7×AÑAÓC€Mð Ô0Ð1°°<°.ÀÀ-ÀÐQ€Iåð	CÜ%×)Ñ)¨)Ó4ˆJÙÜ—‘ÐBÔCÜ.¬t¯z©z¸*Ó/EØ/;ó=ð =ð ô 6Ð6KØ6BóDÐàÐ$¨Ô9MÑ)Mô 	�‰�c˜,˜Ð'LÐMÔNØˆ	ÙÜ�‰ÐbÔcÝðaÜ×"Ñ" 9¨c´4·:±:Ð>SÓ3TÔUÜ—‘Ð]Ô^ð %Ð$Ð$Ð$ô ‡K�KÐEÔFð=Ü ŸJ™JÐ'<ÀQÔGÐÜ%×,Ñ,Ð;LÔMˆä ×%Ñ%×1Ñ1×8Ñ8Ü×$Ñ$Ø%°&Ñ9Ð:Ø# ]Ð3ð 9ó 
ˆô *Ü�J‰J�x×'Ñ'¨Ñ*×2Ñ2×:Ñ:Ó;¸\óKˆä�‰ÐCÄDÇJÁJÈwÓDWÐCXÐYÔZõ ðEÜ×"Ñ" 9¨c´4·:±:¸gÓ3FÔGÜ—‘ÐSÔTð ˆˆwˆøô_ ò 	CÜ�N‰NÐ=¸a¸SÐA×BÒBûð	Cûô& ò aÜ—‘Ð![Ð\]Ð[^Ð_×`Ð`Ø$Ð$ûðaûô2 ò EÜ—‘Ð!?À¸sÐC×DÐDàˆûðEûô ò =Ü�‰Ð5°a°SÐ9ÀDˆÔIÜ"Ô#;Ó<Ð<ûð=úsz   Â#A
J9 Ä>?K( ÆCM É6?L Ê5M Ê7M Ê9	K%ËK Ë K%Ë(	LË1LÌLÌ	MÌ L>Ì8M Ì>MÍM Í	M=Í)M8Í8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   Úutcrf   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ö   8  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è   rf   rÇ   r   ré   rï   rÂ   rÃ   rÀ   rÁ   rÄ   rÅ   rr   rÉ   Úfromisoformatrx   rÌ   Ú	isoformatÚsetrÊ   ry   )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	ræ   Úuser_idÚrequested_amountr}   c                 ó€  — t         j                  d|› d| › d|› d|› d�	«       t        | «      \  }}|sP|t        v r t         j                  d| › d|› d|› d	�«       y
t         j	                  d| › d�«       t        t        «      ‚t        ||«      }|sP|t        v r t         j                  d|› d| › d|› d�«       y
t         j	                  d| › d�«       t        t        «      ‚|j                  |«      }|€$|t        v rt         j                  |› d|› d�«       y
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  r§d}
n¤|dk(  r%dd lm}  || |	¬«      }|t        d|«      z   |kD  r}d}
nz|d!k(  r%dd"lm}  || |	¬«      }|t        d|«      z   |kD  rSd}
nP|d#k(  r$t'        | «      }t        d|«      }|r2||z   |kD  r*d}
n't         j	                  d$|› �«       t        t        «      ‚|
rYt         j+                  d)|› d*| › d+|› d,|› d�	«       t-        | ||«       d-d.d/d0d1d2|› d3�d4œ}|j                  |d5«      }t/        |«      ‚t         j                  d6|› d*| › d+|› d7|› d8|	› d�«       y
# t
        $ r ‚ t(        $ r4}t         j	                  d%|› d&| › d'|› �d¬(«       t        t        «      ‚d
}~ww xY w)9a  
    Checks the current usage against the monthly (or overall) limits.

    product_name selects whose numbers apply. Catalogue work passes BrandForge,
    which caps products and meters nothing else; the chat passes Aivora, which
    meters tokens, conversations and tickets.
    z	Checking z quota for tenant: z (User: z, Product: ú)zNo plan features for z; z is unmetered under rü   Nz#No quota features found for tenant z6. Blocking request because limits cannot be validated.r°   z limits on z	's plan; z is unmetered.z&No quota mapping generated for tenant z does not meter z; allowing.r@   z
Quota for z is unlimited.Fr   r;   )Ú
start_dateTr<   r>   i   r=   )Úget_conversation_thread_countr?   )Úget_ai_response_message_countrC   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.zYour plan allows zc products and this would take you past that. Remove some products or upgrade your plan to add more.)r;   r<   r>   r=   r?   rC   uL   Youâ€™ve hit your resource quota. Upgrade or wait for the reset to continue.zQuota check passed for ú/z, Start Date: )rx   rÌ   r   rË   ry   r9   rd   rß   rr   rn   rÈ   r	  r   rô   r	   r
   Ú#app.services.integrations.firestorer  r  r   rÊ   r£   Úsend_quota_notificationr*   )r÷   r`   r
  r  r}   r¤   rù   Úlimitsrm   r  Ú	quota_hitÚusageÚusage_bytesÚlimit_bytesr  r  ÚaddingrÛ   Úmsg_mapÚerr_msgs                       r   Úcheck_quotar  …  s  € ô ‡K�K�)˜K˜=Ð(;¸I¸;ð GØ!˜ +¨l¨^¸1ð>ô ?ô ':¸)Ó&DÑ#Ð�mÙØÔ/Ñ/ô �K‰KÐ/°	¨{¸"¸[¸Mð J+Ø+7¨.¸ð;ô <àÜ�‰Ð:¸9¸+ÐE{Ð|Ô}Ü"Ô#;Ó<Ð<äÐ/°Ó>€FÙØÔ/Ñ/ô �K‰K˜#˜l˜^¨;°y°kÀØ&˜- ~ð7ô 8àÜ�‰Ð=¸i¸[ÐH~Ðô  	AÜ"Ô#;Ó<Ð<ð �J‰J�{Ó#€EØ€}˜Ô)=Ñ=ô 	�‰�|�nÐ$4°[°MÀÐMÔNØÜ˜+ uÓ-€EØ�‚{Ü�‰�z + ¨nÐ=Ô>Øô & iÐ1AÀ=ÓQ€Jð €IØ€Eð1=Ø˜+Ò%Ü)¨)À
Ô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àÐ.Ò.õñ 2°)È
ÔSˆEØ”s˜1Ð.Ó/Ñ/°%Ò7ÀT¹à˜NÒ*õñ 2°)È
ÔSˆEØ”s˜1Ð.Ó/Ñ/°%Ò7ÀT¹à˜OÒ+ô 0°	Ó:ˆEô ˜Ð,Ó-ˆFÙ˜% &™.¨5Ò0¸d±)ô �L‰LÐ;¸K¸=ÐIÔJÜ&Ô'?Ó@Ð@ñ Ü�‰Ð,¨[¨M¸ÀIÀ;ÈiÐX]ÐW^Ð^gÐhmÐgnÐnoÐpÔqÜ 	¨;¸Ô@ð
 gð Mð (]Ø lØmð  1°°ð 8>ð ?ñ
ˆð —+‘+˜kÐ+yÓzˆÜ  Ó)Ð)ä
‡K�KÐ)¨+¨°jÀÀÈ9ÐUZÐT[Ð[\Ð]bÐ\cÐcqÐr|Ðq}Ð}~Ðõ  Aøô9  ò ØÜò =Ü�‰Ð;¸K¸=ÈÐT]ÐS^Ð^`ÐabÐ`cÐdÐosˆÔtÜ"Ô#;Ó<Ð<ûð=ús   ÅD!K7 Ë7L=Ì	/L8Ì8L=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_URLrx   rÌ   Ústatus_coderÊ   ry   )ÚpayloadrJ   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_KEYrx   r£   rh   ÚtimeÚSYSTEM_SERVICE_NAMEÚjwtrÄ   )r*  rì   r'  rJ   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.)r;   r<   r>   r=   r?   )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: )rx   rÈ   rr   rÌ   ÚasyncioÚget_event_loopÚ
is_runningÚcreate_taskr)  ÚrunrÊ   ry   )	r÷   r`   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')i  )IÚloggingrÀ   rÂ   Úredisr   r   r   Údateutil.relativedeltar   Úapp.services.infra.databaser   r   r	   r
   r   Úapp.core.llm_clientr   rÏ   Úapp.core.configr   Úapp.core.promptsr   r"  r9  r5  r3  Útypingr   Ú	getLoggerrx   r   rÇ   ÚRedisÚ
REDIS_HOSTÚ
REDIS_PORTÚ
REDIS_USERÚREDIS_PASSWORDÚ	REDIS_SSLrÌ   rÊ   rÛ   ry   r*   r9   rs   ru   r{   rw   ÚREQUIRED_LIMIT_KEYSÚAIVORAÚ
BRANDFORGErq   rt   rË   rd   rÆ   rŸ   r6   rn   r‚   r�   r–   r§   rß   rï   rh   rö   r¾   r	  r  r)  r!  r  r:   r    r   ú<module>rR     s‹  ðÛ Û Û Û ß 2Ñ 2Ý 0÷õ õ 8Ý $Ý 1Û Û Û 
Û Ý à	ˆ×	Ñ	˜8Ó	$€òFð €ÙÔð@Ø"�u—{‘{Ø×$Ñ$Ø×$Ñ$Ø×(Ñ(Ø×,Ñ,Ø×"Ñ"Ø!Øô
ˆð 	�‰ÐGÕHð ‡K�KÐBÔCô'˜ô 'ô	Ð-ô 	òÐ ð �BðÐ ð /×4Ñ4Ð5L×5QÑ5QÓ5SÓTÐ ð 
€Ø€
ð Ð$Ø�Ð!ðÐ ð
 Ð#Ø�ðÐ ð #�|Ð àpÐ ØÐ ð
0à?ØgØpØoØrñ
Ð ð˜só ð8 39ó Gò"	(òð >Dó ð: 6<ó T=ðl¨(ó ð&"˜sð " só "ðE5 Cð E5¸4ð E5ÐPXó E5ðN LPØ()¸vñAA˜3ð AA¨Sð AA¸8ÀC¹=ð AAØ"%ðAAØ9<óAAòFDñ ¨#ð ¸ó ó$ Døðq ò @Ø�‰Ð:¸1¸#Ð>×?Ñ?ûð@ús   Á6AF ÆF=ÆF8Æ8F=