# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_faculty_allocation():
    """Optimize faculty allocation to activities to maximize participation score."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("faculty_allocation")
    
    # Example data setup
    participation_scores = {
        (1, 101): 12.0,
        (2, 102): 18.5,
        (3, 103): 14.0,
        (1, 102): 10.0,
        (2, 103): 16.0
    }
    
    max_activities = {1: 2, 2: 2, 3: 1}  # Example max activities per faculty
    min_faculty = {101: 1, 102: 1, 103: 1}  # Example min faculty per activity
    
    faculty_ids = {1, 2, 3}
    activity_ids = {101, 102, 103}
    
    # CRITICAL: Validate data consistency
    assert all((i, j) in participation_scores for i in faculty_ids for j in activity_ids), "Data inconsistency in participation scores"
    
    # 2. VARIABLES
    x = model.addVars(faculty_ids, activity_ids, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    model.setObjective(gp.quicksum(participation_scores[i, j] * x[i, j] for i in faculty_ids for j in activity_ids), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Faculty availability constraint
    model.addConstrs((gp.quicksum(x[i, j] for j in activity_ids) <= max_activities[i] for i in faculty_ids), name="faculty_availability")
    
    # Activity staffing constraint
    model.addConstrs((gp.quicksum(x[i, j] for i in faculty_ids) >= min_faculty[j] for j in activity_ids), name="activity_staffing")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in faculty_ids:
            for j in activity_ids:
                if x[i, j].x > 1e-6:
                    print(f"x[{i},{j}] = {x[i, j].x:.3f}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_faculty_allocation()