from dateutil.relativedelta import relativedelta from flask import session, current_app, flash from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.sql.expression import text from common.extensions import db, cache_manager from common.models.entitlements import PartnerServiceLicenseTier, License, LicenseUsage, LicensePeriod, PeriodStatus from common.models.user import Partner, PartnerTenant from common.services.entitlements import LicensePeriodServices from common.utils.database import Database from common.utils.eveai_exceptions import EveAINoManagementPartnerService, EveAINoActiveLicense, \ EveAIStorageQuotaExceeded, EveAIEmbeddingQuotaExceeded, EveAIInteractionQuotaExceeded, EveAILicensePeriodsExceeded, \ EveAIException from common.utils.model_logging_utils import set_logging_information, update_logging_information from datetime import datetime as dt, timezone as tz from common.utils.security_utils import current_user_has_role class LicenseUsageServices: @staticmethod def check_storage_and_embedding_quota(tenant_id: int, file_size_mb: float) -> None: """ Check if a tenant can add a new document without exceeding storage and embedding quotas Args: tenant_id: ID of the tenant file_size_mb: Size of the file in MB Raises: EveAIStorageQuotaExceeded: If storage quota would be exceeded EveAIEmbeddingQuotaExceeded: If embedding quota would be exceeded EveAINoActiveLicense: If no active license is found EveAIException: For other errors """ # Get active license period license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) # Early return if both overruns are allowed - no need to check usage at all if license_period.additional_storage_allowed and license_period.additional_embedding_allowed: return # Check storage quota only if overruns are not allowed if not license_period.additional_storage_allowed: LicenseUsageServices._validate_storage_quota(license_period, file_size_mb) # Check embedding quota only if overruns are not allowed if not license_period.additional_embedding_allowed: LicenseUsageServices._validate_embedding_quota(license_period, file_size_mb) @staticmethod def check_embedding_quota(tenant_id: int, file_size_mb: float) -> None: """ Check if a tenant can re-embed a document without exceeding embedding quota Args: tenant_id: ID of the tenant file_size_mb: Size of the file in MB Raises: EveAIEmbeddingQuotaExceeded: If embedding quota would be exceeded EveAINoActiveLicense: If no active license is found EveAIException: For other errors """ # Get active license period license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) # Early return if both overruns are allowed - no need to check usage at all if license_period.additional_embedding_allowed: return # Check embedding quota LicenseUsageServices._validate_embedding_quota(license_period, file_size_mb) @staticmethod def check_interaction_quota(tenant_id: int) -> None: """ Check if a tenant can execute a specialist without exceeding interaction quota. As it is impossible to estimate the number of interaction tokens, we only check if the interaction quota are exceeded. So we might have a limited overrun. Args: tenant_id: ID of the tenant Raises: EveAIInteractionQuotaExceeded: If interaction quota would be exceeded EveAINoActiveLicense: If no active license is found EveAIException: For other errors """ # Get active license period license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) # Early return if both overruns are allowed - no need to check usage at all if license_period.additional_interaction_allowed: return # Convert tokens to M tokens and check interaction quota LicenseUsageServices._validate_interaction_quota(license_period) @staticmethod def _validate_storage_quota(license_period: LicensePeriod, additional_mb: float) -> None: """Check storage quota and raise exception if exceeded""" current_storage = license_period.license_usage.storage_mb_used or 0 projected_storage = current_storage + additional_mb max_storage = license_period.max_storage_mb # Hard limit check (we only get here if overruns are NOT allowed) if projected_storage > max_storage: raise EveAIStorageQuotaExceeded( current_usage=current_storage, limit=max_storage, additional=additional_mb ) @staticmethod def _validate_embedding_quota(license_period: LicensePeriod, additional_mb: float) -> None: """Check embedding quota and raise exception if exceeded""" current_embedding = license_period.license_usage.embedding_mb_used or 0 projected_embedding = current_embedding + additional_mb max_embedding = license_period.included_embedding_mb # Hard limit check (we only get here if overruns are NOT allowed) if projected_embedding > max_embedding: raise EveAIEmbeddingQuotaExceeded( current_usage=current_embedding, limit=max_embedding, additional=additional_mb ) @staticmethod def _validate_interaction_quota(license_period) -> None: """Check interaction quota and raise exception if exceeded (tokens in millions). We might have an overrun!""" current_tokens = license_period.license_usage.interaction_total_tokens_used / 1_000_000 or 0 max_tokens = license_period.included_interaction_tokens # Hard limit check (we only get here if overruns are NOT allowed) if current_tokens > max_tokens: raise EveAIInteractionQuotaExceeded( current_usage=current_tokens, limit=max_tokens )