# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_machine_repair():
    """Optimize technician assignments to minimize total value points of machine repairs."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("machine_repair")
    
    # Data from the database
    machines = [(1, 15), (2, 25), (3, 10)]  # (machine_id, value_points)
    repair_assignments = [(1, 101, True), (2, 102, True), (3, 103, False), (1, 103, True), (2, 101, False)]
    constraint_bounds = [(1, 3), (2, 2), (3, 1)]  # (technician_id, max_repairs)
    
    # Extracting data for optimization
    machine_ids = [m[0] for m in machines]
    value_points = {m[0]: m[1] for m in machines}
    technician_ids = list(set([r[0] for r in repair_assignments]))
    max_repairs = {c[0]: c[1] for c in constraint_bounds}
    
    # Validate array lengths
    assert len(machine_ids) == len(value_points), "Array length mismatch for machines"
    assert len(technician_ids) == len(max_repairs), "Array length mismatch for technicians"
    
    # 2. VARIABLES
    x = model.addVars(technician_ids, machine_ids, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(gp.quicksum(value_points[j] * x[i, j] for i in technician_ids for j in machine_ids), GRB.MINIMIZE)
    
    # 4. CONSTRAINTS
    
    # Each repair must be assigned to exactly one technician
    model.addConstrs((gp.quicksum(x[i, j] for i in technician_ids) == 1 for j in machine_ids), name="assign_once")
    
    # Technician capacity constraints
    model.addConstrs((gp.quicksum(x[i, j] for j in machine_ids) <= max_repairs[i] for i in technician_ids), name="capacity")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in technician_ids:
            for j in machine_ids:
                if x[i, j].x > 1e-6:
                    print(f"Technician {i} assigned to machine {j}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_machine_repair()