Backend Engineer¶
Ad Selection¶

Select which ads should be run on your website to maximize revenue, taking into account the associated cost and user experience.
You are a Backend Engineer tasked with optimizing the placement and selection of advertisements on a popular web platform. Your goal is to maximize the click-through rate (CTR) and revenue generated from ads while ensuring a good user experience. This includes not overwhelming users with too many ads and ensuring that the ads shown are relevant to the users. You have access to historical data on ad performance and user engagement. Additionally, you must decide whether to display high-revenue but low-CTR ads or low-revenue but high-CTR ads on specific pages, adding complexity to your optimization.
Objective: Maximize revenue
Constraints:
- Overall CTR Constraint: Ensure the average CTR remains above 2.5%.
- Page CTR Constraint: If an ad with a CTR below 2.0% is selected, it must be accompanied by at least one ad with a CTR above 3.0% on the same page.
- User Experience Constraint: No more than 3 ads per page.
- Relevance Constraint: At least 70% of the ads shown must match user interests.
- Budget Constraint: The total cost of ads displayed should not exceed $500 per day.
You can find the data in the enclosed table, the columns are: Page ID,Ad ID,CTR (%),Revenue per click ($),User Interest Match (Yes/No),Cost per ad ($)
Page ID,Ad ID,CTR (%),Revenue per click ($),User Interest Match (Yes/No),Cost per ad ($)
1,1,2.5,0.50,Yes,20
1,2,3.3,0.55,No,15
1,3,1.7,0.60,Yes,25
1,4,2.0,0.48,No,18
1,5,3.4,0.62,Yes,26
1,6,2.3,0.42,Yes,12
1,7,2.1,0.54,No,20
1,8,2.7,0.65,Yes,28
1,9,3.1,0.53,No,24
1,10,1.8,0.57,Yes,22
2,1,2.2,0.45,Yes,30
2,2,2.9,0.50,No,10
2,3,3.2,0.55,Yes,22
2,4,2.6,0.65,Yes,28
2,5,3.0,0.53,No,24
2,6,1.6,0.50,Yes,16
2,7,3.5,0.60,Yes,30
2,8,2.4,0.47,No,18
2,9,2.8,0.68,Yes,32
2,10,1.9,0.40,No,14
3,1,3.3,0.52,No,20
3,2,1.9,0.57,Yes,15
3,3,3.1,0.50,Yes,16
3,4,2.8,0.68,No,30
3,5,2.9,0.38,Yes,10
3,6,3.0,0.51,No,12
3,7,1.8,0.62,Yes,25
3,8,2.0,0.55,No,18
3,9,2.7,0.63,Yes,27
3,10,2.3,0.63,No,27
4,1,3.2,0.40,Yes,12
4,2,2.4,0.55,No,28
4,3,2.6,0.60,Yes,30
4,4,3.4,0.51,Yes,14
4,5,1.6,0.70,No,32
4,6,2.5,0.45,Yes,20
4,7,3.0,0.52,No,16
4,8,1.9,0.61,Yes,24
4,9,2.8,0.50,No,22
4,10,2.3,0.56,Yes,26
🔢 Problem Definition
Sets
\(P\): Set of pages \(p\).
\(A\): Set of ads \(a\).
Parameters
\(\text{CTR}_{p,a}\): Click-Through Rate of ad \(a\) on page \(p\) (in percentage).
\(\text{Revenue}_{a}\): Revenue generated per click from ad \(a\) (in dollars).
\(\text{InterestMatch}_{a}\): Binary parameter indicating if ad \(a\) matches user interests (1 if Yes, 0 if No).
\(\text{Cost}_{a}\): Cost per ad \(a\) (in dollars).
\(\text{MaxAdsPerPage} = 3\): Maximum number of ads per page.
\(\text{MinCTR} = 2.5\): Minimum average CTR across all selected ads.
\(\text{BudgetLimit} = 500\): Maximum budget for the cost of selected ads.
Decision Variables
\(x_{p,a}\): Binary decision variable, 1 if ad \(a\) is selected for page \(p\), 0 otherwise.
Objective
Maximize the total revenue across all pages:
Constraints
Overall CTR Constraint:
Page CTR Constraint: For each page \(p\):
User Experience Constraint:
Relevance Constraint:
Budget Constraint:
import pandas as pd
from gurobipy import Model, GRB, quicksum
# Load the data from the provided CSV file
file_path = 'path_to_your_file/ad_selection.csv'
ad_data = pd.read_csv(file_path)
# Clean the data by removing rows with missing values
cleaned_ad_data = ad_data.dropna()
# Extract relevant data from the cleaned DataFrame
pages = cleaned_ad_data['Page ID'].unique()
ads = cleaned_ad_data['Ad ID'].unique()
ctr = cleaned_ad_data.set_index(['Page ID', 'Ad ID'])['CTR (%)'].to_dict()
revenue = cleaned_ad_data.set_index(['Page ID', 'Ad ID'])['Revenue per click ($)'].to_dict()
interest_match = cleaned_ad_data.set_index(['Page ID', 'Ad ID'])['User Interest Match (Yes/No)'].apply(lambda x: 1 if x == 'Yes' else 0).to_dict()
cost = cleaned_ad_data.set_index(['Page ID', 'Ad ID'])['Cost per ad ($)'].to_dict()
# Create a new Gurobi model
model = Model("Ad_Selection")
# Decision variables
x = model.addVars(pages, ads, vtype=GRB.BINARY, name="x")
# Objective: Maximize revenue
model.setObjective(quicksum(revenue[p, a] * ctr[p, a] * x[p, a] for p in pages for a in ads), GRB.MAXIMIZE)
# Constraint 1: Overall CTR Constraint (Reformulated to avoid division by expression)
model.addConstr(
quicksum(ctr[p, a] * x[p, a] for p in pages for a in ads) >= 2.5 * quicksum(x[p, a] for p in pages for a in ads),
"Overall_CTR"
)
# Constraint 2: Page CTR Constraint
for p in pages:
model.addConstrs((x[p, a] == 1) >> (quicksum(x[p, a2] for a2 in ads if ctr[p, a2] > 3.0) >= 1)
for a in ads if ctr[p, a] < 2.0)
# Constraint 3: User Experience Constraint (Max 3 ads per page)
model.addConstrs((quicksum(x[p, a] for a in ads) <= 3 for p in pages), "MaxAdsPerPage")
# Constraint 4: Relevance Constraint (At least 70% ads should match user interest)
model.addConstr(
quicksum(interest_match[p, a] * x[p, a] for p in pages for a in ads) >= 0.7 * quicksum(x[p, a] for p in pages for a in ads),
"Relevance"
)
# Constraint 5: Budget Constraint
model.addConstr(
quicksum(cost[p, a] * x[p, a] for p in pages for a in ads) <= 500,
"Budget"
)
# Optimize the model
model.optimize()
# Extract the results
solution = {(p, a): x[p, a].x for p in pages for a in ads if x[p, a].x > 0.5}
# Calculate total revenue and average CTR
total_revenue = sum(revenue[p, a] * ctr[p, a] for (p, a) in solution.keys())
average_ctr = sum(ctr[p, a] for (p, a) in solution.keys()) / len(solution)
# Output the results
print(f"Total Revenue: ${total_revenue:.2f}")
print(f"Average CTR: {average_ctr:.2f}%")
print("Selected ads per page:")
for (p, a) in solution.keys():
print(f"Page {p}, Ad {a}")
A/B Testing¶

Choose from a pool of A/B variants and allocate different user segments to them to minimize user disruption while upholding statistical significance.
You are a backend engineer at a software company preparing to conduct A/B tests on a new feature. The goal is to determine the optimal selection of three out of ten possible test variants (V1 to V10) and allocate different user segments to these chosen variants.
Objective:
Minimize the total user disruption
- Selection constraint: Choose three out of ten possible variants.
- Minimum users for statistical significance: Each chosen variant must have at least 100 users in each user segment.
- Total users: Make all users from each segment are allocated to the variants that end up being chosen.
- Balance constraint: Within each user segment, the difference of allocation between any two variants cannot be more than 300
Data:
The user segments and disruptions can be found in ab_testing.csv and has the following columns: User Segment,Total Users,Disruption in V1,Disruption in V2,Disruption in V3,Disruption in V4,Disruption in V5,Disruption in V6,Disruption in V7,Disruption in V8,Disruption in V9,Disruption in V10
User Segment,Total Users,Disruption in V1,Disruption in V2,Disruption in V3,Disruption in V4,Disruption in V5,Disruption in V6,Disruption in V7,Disruption in V8,Disruption in V9,Disruption in V10
New Users,1000,3,4,2,5,3,4,2,3,5,4
Regular Users,1500,2,1,3,2,1,2,3,1,2,3
Power Users,500,1,2,1,3,2,1,1,2,3,2
🔢 Problem Definition
Let’s define the problem as follows:
Sets
S: Set of user segments, where S = {New Users, Regular Users, Power Users}.
V: Set of variants, where V = {V1, V2, …, V10}.
Parameters
U_s: Total number of users in segment s ∈ S.
D_{s,v}: Disruption for user segment s with variant v.
Decision Variables
x_v: Binary variable indicating whether variant v is selected (x_v = 1) or not (x_v = 0).
y_{s,v}: Number of users in segment s allocated to variant v.
Objective
Minimize the total disruption:
Constraints
Selection Constraint: Choose exactly 3 out of 10 possible variants:
\[\sum_{v \in V} x_v = 3\]Minimum Users Constraint: Each chosen variant must have at least 100 users in each user segment:
\[y_{s,v} \geq 100 \times x_v, \quad \forall s \in S, v \in V\]Total Users Constraint: Make sure all users from each segment are allocated:
\[\sum_{v \in V} y_{s,v} = U_s, \quad \forall s \in S\]Balance Constraint: Within each user segment, the difference of allocation between any two chosen variants cannot be more than 300:
\[y_{s,v_1} - y_{s,v_2} \leq 300, \quad \forall s \in S, v_1 \in V, v_2 \in V\]\[y_{s,v_2} - y_{s,v_1} \leq 300, \quad \forall s \in S, v_1 \in V, v_2 \in V\]
from gurobipy import Model, GRB, quicksum
import pandas as pd
# Load the data from the CSV file
file_path = '/mnt/data/ab_testing.csv'
data = pd.read_csv(file_path)
# Extract relevant data
segments = data['User Segment'].tolist()
variants = [f"V{i}" for i in range(1, 11)]
users = data['Total Users'].tolist()
disruption = data.iloc[:, 2:].values
# Create a new model
model = Model("A/B Testing Variant Selection")
# Decision variables
x = model.addVars(variants, vtype=GRB.BINARY, name="x")
y = model.addVars(segments, variants, vtype=GRB.CONTINUOUS, name="y")
# Objective: Minimize total disruption
model.setObjective(quicksum(disruption[s, v] * y[segments[s], variants[v]]
for s in range(len(segments))
for v in range(len(variants))), GRB.MINIMIZE)
# Constraint 1: Select exactly 3 variants
model.addConstr(quicksum(x[v] for v in variants) == 3, name="SelectThreeVariants")
# Constraint 2: Minimum users for each chosen variant
for s in range(len(segments)):
for v in range(len(variants)):
model.addConstr(y[segments[s], variants[v]] >= 100 * x[variants[v]],
name=f"MinUsers_{segments[s]}_{variants[v]}")
# Constraint 3: Allocate all users in each segment
for s in range(len(segments)):
model.addConstr(quicksum(y[segments[s], variants[v]] for v in range(len(variants))) == users[s],
name=f"AllocateAllUsers_{segments[s]}")
# Constraint 4: Balance constraint between any two variants
for s in range(len(segments)):
for v1 in range(len(variants)):
for v2 in range(v1 + 1, len(variants)):
model.addConstr(y[segments[s], variants[v1]] - y[segments[s], variants[v2]] <= 300,
name=f"Balance_{segments[s]}_{variants[v1]}_{variants[v2]}")
model.addConstr(y[segments[s], variants[v2]] - y[segments[s], variants[v1]] <= 300,
name=f"Balance_{segments[s]}_{variants[v2]}_{variants[v1]}")
# Optimize the model
model.optimize()
# Extract the results
selected_variants = [v for v in variants if x[v].x > 0.5]
allocation = {segments[s]: {variants[v]: y[segments[s], variants[v]].x for v in range(len(variants))} for s in range(len(segments))}
Backend API Routing¶

Route the network from end-users to first and second-degree internal servers, minimizing latency while maintaining low cost and a reliability SLA.
You are a Backend Engineer tasked with optimizing the configuration and routing rules of an API gateway to ensure efficient handling of requests, reduce latency, and improve the reliability of backend services. The API gateway routes incoming requests to different backend services, each with varying processing times, reliability, and costs. Your goal is to distribute the requests in such a way that minimizes the total latency while meeting the reliability requirements and keeping the cost within budget.
You have five backend services (Service A, Service B, Service C, Service D, and Service E) and need to determine the percentage of requests to route to each service. Each service has a known average processing time, reliability rate, and cost per request. After the initial routing to these backend services, the requests are then routed to a second layer of gateways (F, G, H, I, J, K, L, M, N), with specific routes between the initial services and the internal gateways. Each route has an associated additional processing time, reliability rate, and cost per request.
Objective: Minimize the total latency of the API gateway system.
Constraints:
- Backend Reliability constraint: The weighted reliability of the Backend Services must be at least 99.5%.
- Internal Reliability constraint: The weighted reliability of the Internal Gateways must be at least 99.5%.
- Backend Routing constraint: The sum of the routing percentages to the Backend Services must equal 100% and no route may take up more than 40%.
- Internal Routing constraint: For each of the routes starting at the same Backend Service, the sum of the routing percentages must equal 100% and no route may take up more than 75%. This means we need to have separate constraints for routes starting at A,B,C,etc.
- Cost constraint: The average cost across all requests must not exceed $0.40.
- Non-negativity constraint: The routing percentages must be non-negative.
- Routing constraints between layers: Specific backend services can only route to certain gateways as defined in the data.
Data:
The Backend Services can be found in network_routing_backend.csv and has the following columns: Service,Average Processing Time (ms),Reliability (percentage),Cost per Request ($)
The Routing from Backend Services to Internal Gateways can be found in network_routing_internal.csv and has the following columns: Backend Service,Internal Gateway,Additional Processing Time (ms),Additional Reliability (percentage)
Service,Average Processing Time (ms),Reliability (percentage),Cost per Request ($)
A,200,99.9,0.30
B,150,99.6,0.25
C,100,99.0,0.10
D,120,99.7,0.15
E,180,99.8,0.20
Backend Service,Internal Gateway,Additional Processing Time (ms),Additional Reliability (percentage)
A,F,50,99.8
A,G,60,99.7
A,H,55,99.9
B,H,40,99.6
B,I,45,99.7
C,J,50,99.8
C,K,30,99.5
C,L,35,99.6
D,M,25,99.7
D,N,20,99.8
E,F,60,99.9
E,G,55,99.8
🔢 Problem Definition
Decision Variables:
\(x_i\): Percentage of requests routed to Backend Service \(i\) where \(i \in \{A, B, C, D, E\}\).
\(y_{ij}\): Percentage of requests routed from Backend Service \(i\) to Internal Gateway \(j\) where \(j \in \{F, G, H, I, J, K, L, M, N\}\) and \(i \in \{A, B, C, D, E\}\).
Parameters:
\(T_i\): Average processing time of Backend Service \(i\) (ms).
\(R_i\): Reliability of Backend Service \(i\) (%).
\(C_i\): Cost per request for Backend Service \(i\) ($).
\(T_{ij}\): Additional processing time for routing from Backend Service \(i\) to Internal Gateway \(j\) (ms).
\(R_{ij}\): Additional reliability for routing from Backend Service \(i\) to Internal Gateway \(j\) (%).
\(C_{ij}\): Additional cost per request for routing from Backend Service \(i\) to Internal Gateway \(j\) ($).
Objective:
Minimize the total latency of the system:
Constraints:
Backend Reliability Constraint:
\[\sum_{i \in \{A, B, C, D, E\}} x_i \cdot R_i \geq 0.995\]Internal Reliability Constraint:
\[\sum_{i \in \{A, B, C, D, E\}} \sum_{j \in \text{Gateways reachable from } i} y_{ij} \cdot R_{ij} \geq 0.995\]Backend Routing Constraint:
\[\sum_{i \in \{A, B, C, D, E\}} x_i = 1, \quad 0 \leq x_i \leq 0.4\]Internal Routing Constraint:
\[\sum_{j \in \text{Gateways reachable from } i} y_{ij} = 1, \quad 0 \leq y_{ij} \leq 0.75 \quad \forall i \in \{A, B, C, D, E\}\]Cost Constraint:
\[\sum_{i \in \{A, B, C, D, E\}} \left( x_i \cdot C_i + \sum_{j \in \text{Gateways reachable from } i} y_{ij} \cdot C_{ij} \right) \leq 0.4\]Non-Negativity Constraint:
\[x_i \geq 0, \quad y_{ij} \geq 0\]
from gurobipy import Model, GRB, quicksum
import pandas as pd
# Load the data from the CSV files
backend_data_path = '/mnt/data/network_routing_backend.csv'
internal_data_path = '/mnt/data/network_routing_internal.csv'
backend_df = pd.read_csv(backend_data_path)
internal_df = pd.read_csv(internal_data_path)
# Extract relevant data from the dataframes
services = backend_df['Service'].tolist()
gateways = internal_df['Internal Gateway'].unique().tolist()
processing_times = dict(zip(backend_df['Service'], backend_df['Average Processing Time (ms)']))
reliabilities = dict(zip(backend_df['Service'], backend_df['Reliability (percentage)']))
costs = dict(zip(backend_df['Service'], backend_df['Cost per Request ($)']))
internal_routing = {}
for _, row in internal_df.iterrows():
internal_routing[(row['Backend Service'], row['Internal Gateway'])] = {
'processing_time': row['Additional Processing Time (ms)'],
'reliability': row['Additional Reliability (percentage)']
}
# Create the Gurobi model
model = Model('API_Gateway_Optimization')
# Decision variables
x = model.addVars(services, vtype=GRB.CONTINUOUS, name="x")
y = model.addVars(internal_routing.keys(), vtype=GRB.CONTINUOUS, name="y")
# Objective: Minimize total latency
model.setObjective(
quicksum(x[i] * processing_times[i] for i in services) +
quicksum(y[i, j] * internal_routing[i, j]['processing_time'] for i, j in internal_routing.keys()),
GRB.MINIMIZE
)
# Constraints
# 1. Backend Reliability Constraint
model.addConstr(
quicksum(x[i] * reliabilities[i] for i in services) >= 99.5,
name="Backend_Reliability"
)
# 2. Internal Reliability Constraint
model.addConstr(
quicksum(y[i, j] * internal_routing[i, j]['reliability'] for i, j in internal_routing.keys()) >= 99.5,
name="Internal_Reliability"
)
# 3. Backend Routing Constraint
model.addConstr(quicksum(x[i] for i in services) == 1, name="Backend_Routing_Total")
for i in services:
model.addConstr(x[i] <= 0.4, name=f"Backend_Routing_Limit_{i}")
# 4. Internal Routing Constraints
for i in services:
model.addConstr(quicksum(y[i, j] for j in gateways if (i, j) in y) == 1, name=f"Internal_Routing_Total_{i}")
for j in gateways:
if (i, j) in y:
model.addConstr(y[i, j] <= 0.75, name=f"Internal_Routing_Limit_{i}_{j}")
# 5. Cost Constraint
model.addConstr(
quicksum(x[i] * costs[i] for i in services) +
quicksum(y[i, j] * 0.0 for i, j in internal_routing.keys()) <= 0.4, # Cost on routing is considered zero here for simplicity
name="Cost_Constraint"
)
# Optimize the model
model.optimize()
# Collect the results
routing_results = {i: x[i].X for i in services}
internal_routing_results = {(i, j): y[i, j].X for i, j in internal_routing.keys()}
E-Commerce Pricing¶

Determine the optimal sales price for the items on an E-commerce platform.
You are a backend engineer at an e-commerce company tasked with optimizing pricing strategies for a range of electronic products. Your goal is to maximize the total revenue while considering the supply, demand, customer behavior and shipping costs.
Objective: Maximize total revenue
Constraints:
- Stock Availability: The total quantity of each product sold cannot exceed the available stock.
- Demand Elasticity: The demand for each product decreases as the price increases. Use the demand elasticity equation for this.
- Shipping Costs: Shipping costs are deducted from the revenue and vary based on the product size and weight. Use the shipping costs equation for this.
- Minimum Price Constraint: Prices must be at least 80% of the base price to ensure profitability.
Equations:
- Demand elasticity: Q = Q0 * (1 - E * ((P-P0)/P0)) with Q being the demand, Q0 base demand, P0 base price, P new price and E the price elasticity
- Shipping costs: SC = Base Shipping Cost + ((2×Size (kg))+(1.5×Weight (kg)) * units sold)
The data is enclosed. The headers are: Product ID,Product Name,Stock Quantity,Base Demand,Base Price,Size (kg),Weight (kg),Base Shipping Cost,Price Elasticity
Product ID,Product Name,Stock Quantity,Base Demand,Base Price,Size (kg),Weight (kg),Base Shipping Cost,Price Elasticity
P001,Smartphone A,500,300,700,0.2,0.5,5,1.5
P002,Laptop B,200,150,1200,1.5,2.5,10,1.2
P003,Tablet C,300,250,400,0.5,1.0,6,1.8
P004,Smartwatch D,100,100,150,0.1,0.2,3,2.0
P005,Camera E,100,80,500,0.7,1.2,7,1.6
P006,Headphones F,400,200,100,0.3,0.4,4,2.5
P007,Speaker G,250,120,250,1.0,1.5,8,1.4
P008,Monitor H,180,110,300,3.0,4.0,12,1.3
P009,Keyboard I,300,270,50,0.6,0.7,5,2.2
P010,Mouse J,450,320,30,0.4,0.5,3,2.0
🔢 Problem Definition
In this problem, we are tasked with optimizing pricing strategies for a range of electronic products to maximize the total revenue. The optimization problem can be defined as follows:
Decision Variables
\(P_i\): Price of product \(i\)
Parameters
\(S_i\): Stock Quantity of product \(i\)
\(Q0_i\): Base Demand for product \(i\)
\(P0_i\): Base Price for product \(i\)
\(W_i\): Weight (kg) of product \(i\)
\(Z_i\): Size (kg) of product \(i\)
\(BSC_i\): Base Shipping Cost for product \(i\)
\(E_i\): Price Elasticity for product \(i\)
Objective Function
The objective is to maximize the total revenue:
Where \(Q_i\) (the demand for product \(i\)) is given by the demand elasticity equation:
Constraints
Stock Availability: The total quantity of each product sold cannot exceed the available stock.
\[Q_i \leq S_i \quad \forall i\]Minimum Price Constraint: Prices must be at least 80% of the base price.
\[P_i \geq 0.8 \times P0_i \quad \forall i\]Non-negative Demand: Ensure that demand remains non-negative.
\[Q_i \geq 0 \quad \forall i\]
import pandas as pd
from gurobipy import Model, GRB, quicksum
# Load the data
file_path = '/mnt/data/ecommerce.csv'
data = pd.read_csv(file_path)
# Initialize the model
model = Model("E-commerce Pricing Optimization")
# Create decision variables for the prices
prices = {}
demand = {}
for i, row in data.iterrows():
product_id = row['Product ID']
base_price = row['Base Price']
stock = row['Stock Quantity']
base_demand = row['Base Demand']
elasticity = row['Price Elasticity']
# Price variable
prices[product_id] = model.addVar(vtype=GRB.CONTINUOUS, name=f"Price_{product_id}", lb=0.8 * base_price)
# Demand variable based on the elasticity formula
demand[product_id] = model.addVar(vtype=GRB.CONTINUOUS, name=f"Demand_{product_id}")
# Add the demand elasticity constraints and non-negative demand
for i, row in data.iterrows():
product_id = row['Product ID']
base_price = row['Base Price']
base_demand = row['Base Demand']
elasticity = row['Price Elasticity']
model.addConstr(
demand[product_id] == base_demand * (1 - elasticity * ((prices[product_id] - base_price) / base_price)))
model.addConstr(demand[product_id] >= 0) # Non-negative demand constraint
# Add stock constraints
for i, row in data.iterrows():
product_id = row['Product ID']
stock = row['Stock Quantity']
model.addConstr(demand[product_id] <= stock)
# Objective function
revenue = quicksum(prices[prod_id] * demand[prod_id] -
(row['Base Shipping Cost'] + (2 * row['Size (kg)'] + 1.5 * row['Weight (kg)']) * demand[prod_id])
for prod_id, row in data.set_index('Product ID').iterrows())
model.setObjective(revenue, GRB.MAXIMIZE)
# Optimize the model
model.optimize()
# Collecting results
results = {
"Product ID": [],
"Optimal Price": [],
"Expected Demand": []
}
for prod_id in prices:
results["Product ID"].append(prod_id)
results["Optimal Price"].append(prices[prod_id].X)
results["Expected Demand"].append(demand[prod_id].X)
# Convert the results to a DataFrame for better readability
results_df = pd.DataFrame(results)