# Complete DOCPLEX implementation

from docplex.mp.model import Model

def optimize_scientist_allocation():
    # 1. MODEL & DATA SETUP
    mdl = Model(name="scientist_allocation")

    # Data from the database
    projects = [(1, 15.0), (2, 25.0), (3, 35.0)]
    assignments = [(101, 1), (101, 2), (102, 2), (102, 3), (103, 3)]
    constraint_bounds = {'scientist': 2, 'project': 1}

    # Extracting data
    project_ids = [p[0] for p in projects]
    hours = {p[0]: p[1] for p in projects}
    scientist_ids = list(set(a[0] for a in assignments))

    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(project_ids) == len(hours), "Array length mismatch"
    safe_range_projects = range(len(project_ids))
    safe_range_scientists = range(len(scientist_ids))

    # 2. VARIABLES
    x = {(s, p): mdl.binary_var(name=f"x_{s}_{p}") for s in scientist_ids for p in project_ids}

    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(hours[p] * x[s, p] for s in scientist_ids for p in project_ids)
    mdl.maximize(objective)

    # 4. CONSTRAINTS

    # Minimum Projects per Scientist
    for s in scientist_ids:
        mdl.add_constraint(mdl.sum(x[s, p] for p in project_ids) >= constraint_bounds['scientist'], ctname=f"min_projects_{s}")

    # Minimum Scientists per Project
    for p in project_ids:
        mdl.add_constraint(mdl.sum(x[s, p] for s in scientist_ids) >= constraint_bounds['project'], ctname=f"min_scientists_{p}")

    # 5. SOLVING & RESULTS
    solution = mdl.solve()

    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for s in scientist_ids:
            for p in project_ids:
                value = solution.get_value(x[s, p])
                if value > 1e-6:
                    print(f"x[{s},{p}] = {value:.3f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")

    return mdl

# Run the optimization
optimize_scientist_allocation()