    001package org.cpsolver.studentsct.report;
    002
    003import java.text.DecimalFormat;
    004import java.util.Comparator;
    005import java.util.TreeSet;
    006
    007import org.cpsolver.ifs.assignment.Assignment;
    008import org.cpsolver.ifs.util.CSVFile;
    009import org.cpsolver.ifs.util.DataProperties;
    010import org.cpsolver.studentsct.StudentSectioningModel;
    011import org.cpsolver.studentsct.model.Config;
    012import org.cpsolver.studentsct.model.Enrollment;
    013import org.cpsolver.studentsct.model.Offering;
    014import org.cpsolver.studentsct.model.Request;
    015import org.cpsolver.studentsct.model.Section;
    016import org.cpsolver.studentsct.model.Subpart;
    017
    018
    019/**
    020 * This class lists all unbalanced sections. Each line includes the class, its meeting time,
    021 * number of enrolled students, desired section size, and the limit. The Target column show
    022 * the ideal number of students the section (if all the sections were filled equally) and the
    023 * Disbalance shows the % between the target and the current enrollment.
    024 *
    025 * <br>
    026 * <br>
    027 *
    028 * Usage: new UnbalancedSectionsTable(model),createTable(true, true).save(aFile);
    029 *
    030 * <br>
    031 * <br>
    032 *
    033 * @author  Tomáš Müller
    034 * @version StudentSct 1.3 (Student Sectioning)<br>
    035 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    036 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    037 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    038 * <br>
    039 *          This library is free software; you can redistribute it and/or modify
    040 *          it under the terms of the GNU Lesser General Public License as
    041 *          published by the Free Software Foundation; either version 3 of the
    042 *          License, or (at your option) any later version. <br>
    043 * <br>
    044 *          This library is distributed in the hope that it will be useful, but
    045 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    046 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    047 *          Lesser General Public License for more details. <br>
    048 * <br>
    049 *          You should have received a copy of the GNU Lesser General Public
    050 *          License along with this library; if not see
    051 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    052 */
    053public class UnbalancedSectionsTable extends AbstractStudentSectioningReport {
    054    private static DecimalFormat sDF1 = new DecimalFormat("0.####");
    055    private static DecimalFormat sDF2 = new DecimalFormat("0.0000");
    056
    057    /**
    058     * Constructor
    059     *
    060     * @param model
    061     *            student sectioning model
    062     */
    063    public UnbalancedSectionsTable(StudentSectioningModel model) {
    064        super(model);
    065    }
    066
    067    /**
    068     * Create report
    069     *
    070     * @param assignment current assignment
    071     * @return report as comma separated text file
    072     */
    073    @Override
    074    public CSVFile createTable(Assignment<Request, Enrollment> assignment, DataProperties properties) {
    075        CSVFile csv = new CSVFile();
    076        csv.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"),
    077                new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Enrollment"),
    078                new CSVFile.CSVField("Target"), new CSVFile.CSVField("Limit"), new CSVFile.CSVField("Disbalance [%]") });
    079
    080        TreeSet<Offering> offerings = new TreeSet<Offering>(new Comparator<Offering>() {
    081            @Override
    082            public int compare(Offering o1, Offering o2) {
    083                int cmp = o1.getName().compareToIgnoreCase(o2.getName());
    084                if (cmp != 0) return cmp;
    085                return o1.getId() < o2.getId() ? -1 : o2.getId() == o2.getId() ? 0 : 1;
    086            }
    087        });
    088        offerings.addAll(getModel().getOfferings());
    089
    090        Offering last = null;
    091        for (Offering offering: offerings) {
    092            if (offering.isDummy()) continue;
    093            for (Config config: offering.getConfigs()) {
    094                double configEnrl = 0;
    095                for (Enrollment e: config.getEnrollments(assignment)) {
    096                    if (!matches(e.getRequest(), e)) continue;
    097                    configEnrl += e.getRequest().getWeight();
    098                }
    099                for (Subpart subpart: config.getSubparts()) {
    100                    if (subpart.getSections().size() <= 1) continue;
    101                    if (subpart.getLimit() > 0) {
    102                        // sections have limits -> desired size is section limit x (total enrollment / total limit)
    103                        double ratio = configEnrl / subpart.getLimit();
    104                        for (Section section: subpart.getSections()) {
    105                            double enrl = 0.0;
    106                            for (Enrollment e: section.getEnrollments(assignment)) {
    107                                if (!matches(e.getRequest(), e)) continue;
    108                                enrl += e.getRequest().getWeight();
    109                            }
    110                            double desired = ratio * section.getLimit();
    111                            if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * section.getLimit())) {
    112                                if (last != null && !offering.equals(last)) csv.addLine();
    113                                csv.addLine(new CSVFile.CSVField[] {
    114                                        new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()),
    115                                        new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()),
    116                                        new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(isUseAmPm()) + " - " + section.getTime().getEndTimeHeader(isUseAmPm())),
    117                                        new CSVFile.CSVField(sDF1.format(enrl)),
    118                                        new CSVFile.CSVField(sDF2.format(desired)),
    119                                        new CSVFile.CSVField(sDF1.format(section.getLimit())),
    120                                        new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / section.getLimit()))))
    121                                });
    122                                last = offering;
    123                            }
    124                        }
    125                    } else {
    126                        // unlimited sections -> desired size is total enrollment / number of sections
    127                        for (Section section: subpart.getSections()) {
    128                            double enrl = 0.0;
    129                            for (Enrollment e: section.getEnrollments(assignment)) {
    130                                if (!matches(e.getRequest(), e)) continue;
    131                                enrl += e.getRequest().getWeight();
    132                            }
    133                            double desired = configEnrl / subpart.getSections().size();
    134                            if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * desired)) {
    135                                if (last != null && !offering.equals(last)) csv.addLine();
    136                                csv.addLine(new CSVFile.CSVField[] {
    137                                        new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()),
    138                                        new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()),
    139                                        new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(isUseAmPm()) + " - " + section.getTime().getEndTimeHeader(isUseAmPm())),
    140                                        new CSVFile.CSVField(sDF1.format(enrl)),
    141                                        new CSVFile.CSVField(sDF2.format(desired)),
    142                                        new CSVFile.CSVField(""),
    143                                        new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / desired))))
    144                                });
    145                                last = offering;
    146                            }
    147                        }
    148                    }
    149                }
    150            }
    151        }
    152        return csv;
    153    }
    154}