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