A specialty coffee roasting company has received shipments of two premium green coffee beans: Ethiopia Yirgacheffe (8,000 kg) and Colombia Supremo (12,000 kg). The company needs to allocate these beans to two different product lines to maximize its weekly profit:
1.  **Single-Origin Bags (SO):** High-end bags of whole beans sold to specialty cafes.
2.  **House Blend (HB):** A signature blend sold ground to retail partners.

**Economic & Quality Data:**
The profitability of each product line depends on the cost of the beans and the processing involved. Furthermore, each product line must meet a minimum average quality standard, measured by the coffee's "cupping score" (a 0-100 scale).

| Bean Origin | Available (kg) | Cupping Score |
|:-----------:|:--------------:|:-------------:|
| Ethiopia    | 8,000          | 92            |
| Colombia    | 12,000         | 87            |

| Product Line      | Revenue per kg | Processing Cost per kg | Minimum Avg. Cupping Score |
|:------------------|:--------------:|:----------------------:|:--------------------------:|
| Single-Origin (SO)| $40.00         | $5.00                  | 90                         |
| House Blend (HB)  | $28.00         | $8.50                  | 88                         |

**Operational Constraints:**
- The company's primary roaster, used for the House Blend, has a maximum weekly processing capacity of 10,000 kg.
- Due to a contract with a key client, the company must produce at least 2,000 kg of Single-Origin bags per week.

The goal is to determine the optimal quantity (in kg) of each type of green bean to allocate to each product line to maximize total weekly profit, while respecting all inventory, quality, and operational constraints.

Problem type: LP (Toy: 4 variables, 6 constraints, 12 nonzeros)

Mathematical model:
Set:
1. Beans
   The set of green coffee bean origins, \( B = \{\text{Ethiopia}, \text{Colombia}\} \)
2. Processes
   The set of product lines, \( P = \{\text{SO}, \text{HB}\} \)

Parameter:
1. Inventory (kg)
   \( I_b \): Available inventory for each bean origin b.
   \( I_{\text{Ethiopia}} = 8000 \), \( I_{\text{Colombia}} = 12000 \)
2. CuppingScore
   \( s_b \): The quality score for each bean origin b.
   \( s_{\text{Ethiopia}} = 92 \), \( s_{\text{Colombia}} = 87 \)
3. RevenuePerKg
   \( R_p \): Revenue per kg for each product line p.
   \( R_{\text{SO}} = 40 \), \( R_{\text{HB}} = 28 \)
4. ProcessingCostPerKg
   \( C_p \): Processing cost per kg for each product line p.
   \( C_{\text{SO}} = 5 \), \( C_{\text{HB}} = 8.5 \)
5. QualityThreshold
   \( T_p \): Minimum required average cupping score for product line p.
   \( T_{\text{SO}} = 90 \), \( T_{\text{HB}} = 88 \)
6. OperationalLimits
   Roaster capacity (for HB): \( \text{Cap}_{\text{HB}} = 10000 \) kg
   Minimum SO production: \( \text{Min}_{\text{SO}} = 2000 \) kg

Decision variable:
1. Allocation
   Continuous variable \( x_{bp} \geq 0 \) for all \( b \in B, p \in P \), representing the kilograms of bean b allocated to product line p.

Objective:
Maximize total weekly profit.
max: \( \sum_{b \in B} \sum_{p \in P} (R_p - C_p) \cdot x_{bp} \)

Constraint:
1. Inventory limits
   The total amount of each bean type allocated cannot exceed its available inventory.
   \( \sum_{p \in P} x_{bp} \leq I_b, \quad \forall b \in B \)
2. Quality requirements (linearized)
   The weighted average cupping score for each product line must meet its threshold. This is linearized from \( \frac{\sum_b s_b \cdot x_{bp}}{\sum_b x_{bp}} \geq T_p \).
   \( \sum_{b \in B} (s_b - T_p) \cdot x_{bp} \geq 0, \quad \forall p \in P \)
3. Operational constraints
   The total amount allocated to the House Blend cannot exceed the roaster's capacity.
   \( \sum_{b \in B} x_{b, \text{HB}} \leq \text{Cap}_{\text{HB}} \)
   The total amount allocated to Single-Origin bags must meet the minimum contractual obligation.
   \( \sum_{b \in B} x_{b, \text{SO}} \geq \text{Min}_{\text{SO}} \)

Type:
Continuous, Linear, Linear
LP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_coffee_blending(
    beans=['Ethiopia', 'Colombia'],
    processes=['SO', 'HB'],
    inventory={'Ethiopia': 8000, 'Colombia': 12000},
    cupping_score={'Ethiopia': 92, 'Colombia': 87},
    revenue={'SO': 40, 'HB': 28},
    proc_cost={'SO': 5, 'HB': 8.5},
    quality_threshold={'SO': 90, 'HB': 88},
    roaster_capacity_hb=10000,
    min_production_so=2000
):
    """
    Solves the specialty coffee blending and allocation problem to maximize profit.
    """

    # Create a new model
    model = gp.Model("CoffeeBlending")

    # --- Decision Variables ---
    # x[b, p]: kg of bean 'b' allocated to process 'p'
    x = model.addVars(beans, processes, lb=0, name="allocation")

    # --- Objective Function ---
    # Maximize total profit: (Revenue - Processing Cost) * Quantity
    profit = gp.quicksum((revenue[p] - proc_cost[p]) * x[b, p]
                         for b in beans for p in processes)
    model.setObjective(profit, GRB.MAXIMIZE)

    # --- Constraints ---
    # 1. Inventory limits for each bean type
    model.addConstrs((x.sum(b, '*') <= inventory[b] for b in beans),
                     name="inventory")

    # 2. Quality requirements for each product line (linearized)
    for p in processes:
        model.addConstr(gp.quicksum(
            (cupping_score[b] - quality_threshold[p]) * x[b, p]
            for b in beans) >= 0,
                        name=f"quality_{p}")

    # 3. Operational constraints
    model.addConstr(x.sum('*', 'HB') <= roaster_capacity_hb,
                    name="roaster_capacity")
    model.addConstr(x.sum('*', 'SO') >= min_production_so,
                    name="min_so_production")

    # --- Solve the model ---
    model.optimize()

    # --- Return results ---
    if model.status == GRB.OPTIMAL:
        return {"status": "optimal", "obj": model.ObjVal}
    else:
        return {"status": f"{model.status}"}


if __name__ == "__main__":
    result = solve_coffee_blending()
    print(result)

Solution status: OPTIMAL
Optimal objective value: 545000