The fundamental difficulty here is that you sort of have a multiobjective optimization problem. You have three things I think you're interested in that you can either consider objectives or "soft constraints":

Getting similar class sizes Minimizing number of levels per class Having enough students from a level in a class if there are any students in a class.

Note that I've written an optimization model for this in AMPL. Since you're using Python, there are similar optimization modeling languages for python like PuLP and pyomo that you could use. The model should not be too difficult to translate.

Here is an integer programming model and a data file that emphasizes objective number 1 while keeping the problem (integer) linear. With this objective, the optimization problem finds the same solution you gave in your example. Hopefully you can build on this and add other constraints and/or objective terms and get better solutions.

The objective is to minimize the largest class size. The variable of interest is y[i,j]. y[i,j] for i in LEVEL, j in CLASS is the number of students from level i assigned to class j. It assumes that you have input for the minimum number of students from each level in each class if any are assigned to that level.

The objective function may not be what you want, but it is a way of trying to equalize the class sizes which is linear. I also don't promise that this is the most efficient way of solving the problem. There may be a better custom algorithm for the problem, but all I had to do was express the constraints and objective and not write an algorithm. It's probably good enough for your use.

Using the solver Gurobi on neos-server.org (you could use lpsolve or another open source optimization solver), I got the solution

y := 1 1 14 1 2 9 1 3 0 2 1 6 2 2 0 2 3 18 3 1 6 3 2 16 3 3 8 ;

Model:

set LEVEL ordered; set CLASS; param maxClassSize {CLASS}; param minLevelNumberInClass {LEVEL, CLASS}; param numInLevel {LEVEL}; var z >= 0; var y{LEVEL, CLASS} integer, >= 0; var x{LEVEL, CLASS} binary; #minimize maximum class size minimize obj: z; subject to allStudentsAssigned {i in LEVEL}: sum {j in CLASS} y[i,j] = numInLevel[i]; #z is the largest of all classes sizes subject to minMaxZ {j in CLASS}: z >= sum {i in LEVEL} y[i,j]; subject to maxClassSizeCon {j in CLASS}: sum {i in LEVEL} y[i,j] <= maxClassSize[j]; #xij = 1 if any students from level i are in class j subject to defineX {i in LEVEL, j in CLASS}: y[i,j] <= min(numInLevel[i], maxClassSize[j]) * x[i,j]; #if any students from level i are assigned to class j, then there is a minimum #if x[i,j] = 1, y[i,j] >= minLevelNumberInClass[i,j] subject to minLevel {i in LEVEL, j in CLASS}: minLevelNumberInClass[i,j] * x[i,j] <= y[i,j];

Data file for your example: