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