from django.shortcuts import render import re from collections import defaultdict from hub.services.models import ComputePlan, VSHNAppCatPrice def natural_sort_key(name): """Extract numeric part from compute plan name for natural sorting""" match = re.search(r"compute-std-(\d+)", name) return int(match.group(1)) if match else 0 def pricelist(request): # Get all active compute plans with their pricing data compute_plans = ( ComputePlan.objects.filter(active=True) .select_related("cloud_provider") .prefetch_related("prices") .order_by("cloud_provider__name") ) # Sort compute plans naturally by provider and plan number compute_plans = sorted( compute_plans, key=lambda x: (x.cloud_provider.name, natural_sort_key(x.name)) ) # Get all VSHNAppCat pricing configurations appcat_prices = ( VSHNAppCatPrice.objects.all() .select_related("service", "discount_model") .prefetch_related("base_fees", "unit_rates", "discount_model__tiers") .order_by("service__name") ) pricing_data_by_service_level = defaultdict(list) # Track processed combinations to avoid duplicates processed_combinations = set() for plan in compute_plans: # Get all currencies available for this compute plan plan_currencies = set(plan.prices.values_list("currency", flat=True)) for appcat_price in appcat_prices: # Determine units based on variable unit type (RAM or CPU) if appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.RAM: units = int(plan.ram) elif appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.CPU: units = int(plan.vcpus) else: continue # Get currencies available for base fees and unit rates base_fee_currencies = set( appcat_price.base_fees.values_list("currency", flat=True) ) # Get all distinct service levels for this pricing config service_levels = appcat_price.unit_rates.values_list( "service_level", flat=True ).distinct() for service_level in service_levels: # Get currencies available for this specific service level unit_rate_currencies = set( appcat_price.unit_rates.filter( service_level=service_level ).values_list("currency", flat=True) ) # Find currencies that exist in ALL three places: plan, base fee, and unit rate matching_currencies = plan_currencies.intersection( base_fee_currencies ).intersection(unit_rate_currencies) # Skip if no common currencies found if not matching_currencies: continue # Process each matching currency for currency in matching_currencies: # Create unique combination key to prevent duplicates combination_key = ( plan.cloud_provider.name, plan.name, appcat_price.service.name, service_level, currency, ) # Skip if this combination was already processed if combination_key in processed_combinations: continue processed_combinations.add(combination_key) # Get pricing data for this currency - skip if any is missing compute_plan_price = plan.get_price(currency) if compute_plan_price is None: continue base_fee = appcat_price.get_base_fee(currency) if base_fee is None: continue unit_rate = appcat_price.get_unit_rate(currency, service_level) if unit_rate is None: continue # Calculate replica enforcement based on service level if service_level == VSHNAppCatPrice.ServiceLevel.GUARANTEED: replica_enforce = appcat_price.ha_replica_min else: replica_enforce = 1 total_units = units * replica_enforce # Apply discount model if available and active if ( appcat_price.discount_model and appcat_price.discount_model.active ): discounted_price = ( appcat_price.discount_model.calculate_discount( unit_rate, total_units ) ) sla_price = base_fee + discounted_price else: # Standard pricing without discount sla_price = base_fee + (total_units * unit_rate) # Calculate final price (compute + SLA) final_price = compute_plan_price + sla_price # Get human-readable service level name service_level_display = dict(VSHNAppCatPrice.ServiceLevel.choices)[ service_level ] # Add row to the appropriate service level group pricing_data_by_service_level[service_level_display].append( { "cloud_provider": plan.cloud_provider.name, "service": appcat_price.service.name, "compute_plan": plan.name, "vcpus": plan.vcpus, "ram": plan.ram, "cpu_mem_ratio": plan.cpu_mem_ratio, "term": plan.get_term_display(), "currency": currency, "compute_plan_price": compute_plan_price, "variable_unit": appcat_price.get_variable_unit_display(), "units": units, "replica_enforce": replica_enforce, "service_level": service_level_display, "sla_base": base_fee, "sla_per_unit": unit_rate, "sla_price": sla_price, "final_price": final_price, "discount_model": ( appcat_price.discount_model.name if appcat_price.discount_model else None ), "has_discount": bool( appcat_price.discount_model and appcat_price.discount_model.active ), } ) context = {"pricing_data_by_service_level": dict(pricing_data_by_service_level)} return render(request, "services/pricelist.html", context)