compute plan grouping
This commit is contained in:
parent
3896636f9b
commit
19b36b9a2c
7 changed files with 284 additions and 93 deletions
|
@ -5,22 +5,33 @@ 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):
|
||||
"""Generate comprehensive price list grouped by compute plan groups and service levels"""
|
||||
# Fetch all active compute plans with related data
|
||||
compute_plans = (
|
||||
ComputePlan.objects.filter(active=True)
|
||||
.select_related("cloud_provider")
|
||||
.select_related("cloud_provider", "group")
|
||||
.prefetch_related("prices")
|
||||
.order_by("cloud_provider__name")
|
||||
.order_by("group__order", "group__name", "cloud_provider__name")
|
||||
)
|
||||
|
||||
# Apply natural sorting for compute plan names
|
||||
compute_plans = sorted(
|
||||
compute_plans, key=lambda x: (x.cloud_provider.name, natural_sort_key(x.name))
|
||||
compute_plans,
|
||||
key=lambda x: (
|
||||
x.group.order if x.group else 999, # No group plans at the end
|
||||
x.group.name if x.group else "ZZZ",
|
||||
x.cloud_provider.name,
|
||||
natural_sort_key(x.name),
|
||||
),
|
||||
)
|
||||
|
||||
# Fetch all appcat price configurations
|
||||
appcat_prices = (
|
||||
VSHNAppCatPrice.objects.all()
|
||||
.select_related("service", "discount_model")
|
||||
|
@ -28,13 +39,15 @@ def pricelist(request):
|
|||
.order_by("service__name")
|
||||
)
|
||||
|
||||
pricing_data_by_service_level = defaultdict(list)
|
||||
pricing_data_by_group_and_service_level = defaultdict(lambda: defaultdict(list))
|
||||
processed_combinations = set()
|
||||
|
||||
# Generate pricing combinations for each compute plan and service
|
||||
for plan in compute_plans:
|
||||
plan_currencies = set(plan.prices.values_list("currency", flat=True))
|
||||
|
||||
for appcat_price in appcat_prices:
|
||||
# Determine units based on variable unit type
|
||||
if appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.RAM:
|
||||
units = int(plan.ram)
|
||||
elif appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.CPU:
|
||||
|
@ -57,6 +70,7 @@ def pricelist(request):
|
|||
).values_list("currency", flat=True)
|
||||
)
|
||||
|
||||
# Find currencies that exist across all pricing components
|
||||
matching_currencies = plan_currencies.intersection(
|
||||
base_fee_currencies
|
||||
).intersection(unit_rate_currencies)
|
||||
|
@ -73,23 +87,25 @@ def pricelist(request):
|
|||
currency,
|
||||
)
|
||||
|
||||
# Skip if combination already processed
|
||||
if combination_key in processed_combinations:
|
||||
continue
|
||||
|
||||
processed_combinations.add(combination_key)
|
||||
|
||||
# Get pricing components
|
||||
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:
|
||||
|
||||
# Skip if any pricing component is missing
|
||||
if any(
|
||||
price is None
|
||||
for price in [compute_plan_price, base_fee, unit_rate]
|
||||
):
|
||||
continue
|
||||
|
||||
# Calculate replica enforcement based on service level
|
||||
if service_level == VSHNAppCatPrice.ServiceLevel.GUARANTEED:
|
||||
replica_enforce = appcat_price.ha_replica_min
|
||||
else:
|
||||
|
@ -98,6 +114,7 @@ def pricelist(request):
|
|||
total_units = units * replica_enforce
|
||||
standard_sla_price = base_fee + (total_units * unit_rate)
|
||||
|
||||
# Apply discount if available
|
||||
discount_breakdown = None
|
||||
if (
|
||||
appcat_price.discount_model
|
||||
|
@ -131,11 +148,23 @@ def pricelist(request):
|
|||
service_level
|
||||
]
|
||||
|
||||
pricing_data_by_service_level[service_level_display].append(
|
||||
group_name = plan.group.name if plan.group else "No Group"
|
||||
|
||||
# Add pricing data to the grouped structure
|
||||
pricing_data_by_group_and_service_level[group_name][
|
||||
service_level_display
|
||||
].append(
|
||||
{
|
||||
"cloud_provider": plan.cloud_provider.name,
|
||||
"service": appcat_price.service.name,
|
||||
"compute_plan": plan.name,
|
||||
"compute_plan_group": group_name,
|
||||
"compute_plan_group_description": (
|
||||
plan.group.description if plan.group else ""
|
||||
),
|
||||
"compute_plan_group_node_label": (
|
||||
plan.group.node_label if plan.group else ""
|
||||
),
|
||||
"vcpus": plan.vcpus,
|
||||
"ram": plan.ram,
|
||||
"cpu_mem_ratio": plan.cpu_mem_ratio,
|
||||
|
@ -173,5 +202,26 @@ def pricelist(request):
|
|||
}
|
||||
)
|
||||
|
||||
context = {"pricing_data_by_service_level": dict(pricing_data_by_service_level)}
|
||||
# Order groups correctly, placing "No Group" last
|
||||
ordered_groups_intermediate = {}
|
||||
all_group_names = list(pricing_data_by_group_and_service_level.keys())
|
||||
|
||||
if "No Group" in all_group_names:
|
||||
all_group_names.remove("No Group")
|
||||
all_group_names.append("No Group")
|
||||
|
||||
for group_name_key in all_group_names:
|
||||
ordered_groups_intermediate[group_name_key] = (
|
||||
pricing_data_by_group_and_service_level[group_name_key]
|
||||
)
|
||||
|
||||
# Convert defaultdicts to regular dicts for the template
|
||||
final_context_data = {}
|
||||
for group_key, service_levels_dict in ordered_groups_intermediate.items():
|
||||
final_context_data[group_key] = {
|
||||
sl_key: list(plans_list)
|
||||
for sl_key, plans_list in service_levels_dict.items()
|
||||
}
|
||||
|
||||
context = {"pricing_data_by_group_and_service_level": final_context_data}
|
||||
return render(request, "services/pricelist.html", context)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue