    001package org.cpsolver.studentsct.report;
    002
    003import java.util.ArrayList;
    004import java.util.Collections;
    005import java.util.Comparator;
    006import java.util.List;
    007import java.util.Set;
    008import java.util.TreeSet;
    009
    010import org.cpsolver.coursett.Constants;
    011import org.cpsolver.coursett.model.RoomLocation;
    012import org.cpsolver.coursett.model.TimeLocation;
    013import org.cpsolver.ifs.assignment.Assignment;
    014import org.cpsolver.ifs.util.CSVFile;
    015import org.cpsolver.ifs.util.DataProperties;
    016import org.cpsolver.studentsct.StudentSectioningModel;
    017import org.cpsolver.studentsct.extension.StudentQuality;
    018import org.cpsolver.studentsct.extension.StudentQuality.Conflict;
    019import org.cpsolver.studentsct.extension.StudentQuality.Type;
    020import org.cpsolver.studentsct.model.AreaClassificationMajor;
    021import org.cpsolver.studentsct.model.CourseRequest;
    022import org.cpsolver.studentsct.model.Enrollment;
    023import org.cpsolver.studentsct.model.Instructor;
    024import org.cpsolver.studentsct.model.Request;
    025import org.cpsolver.studentsct.model.SctAssignment;
    026import org.cpsolver.studentsct.model.Section;
    027import org.cpsolver.studentsct.model.Student;
    028import org.cpsolver.studentsct.model.StudentGroup;
    029
    030
    031/**
    032 * This class lists student accommodation conflicts in a {@link CSVFile} comma
    033 * separated text file. See {@link StudentQuality} for more
    034 * details. <br>
    035 * <br>
    036 *
    037 * Each line represent a pair if classes that are in a distance conflict and have
    038 * one or more students in common.
    039 *
    040 * <br>
    041 * <br>
    042 *
    043 * Usage: new DistanceConflictTable(model),createTable(true, true).save(aFile);
    044 *
    045 * <br>
    046 * <br>
    047 *
    048 * @version StudentSct 1.3 (Student Sectioning)<br>
    049 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    050 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    051 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    052 * <br>
    053 *          This library is free software; you can redistribute it and/or modify
    054 *          it under the terms of the GNU Lesser General Public License as
    055 *          published by the Free Software Foundation; either version 3 of the
    056 *          License, or (at your option) any later version. <br>
    057 * <br>
    058 *          This library is distributed in the hope that it will be useful, but
    059 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    060 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    061 *          Lesser General Public License for more details. <br>
    062 * <br>
    063 *          You should have received a copy of the GNU Lesser General Public
    064 *          License along with this library; if not see
    065 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    066 */
    067public class AccommodationConflictsTable implements StudentSectioningReport {
    068    private StudentSectioningModel iModel = null;
    069    private StudentQuality iSQ = null;
    070    private Type[] iTypes = new Type[] {
    071            Type.ShortDistance, Type.AccBackToBack, Type.AccBreaksBetweenClasses, Type.AccFreeTimeOverlap
    072    };
    073
    074    /**
    075     * Constructor
    076     *
    077     * @param model
    078     *            student sectioning model
    079     */
    080    public AccommodationConflictsTable(StudentSectioningModel model) {
    081        iModel = model;
    082        iSQ = iModel.getStudentQuality();
    083    }
    084
    085    /** Return student sectioning model
    086     * @return problem model
    087     **/
    088    public StudentSectioningModel getModel() {
    089        return iModel;
    090    }
    091
    092    protected String rooms(SctAssignment section) {
    093        if (section.getNrRooms() == 0) return "";
    094        String ret = "";
    095        for (RoomLocation r: section.getRooms())
    096            ret += (ret.isEmpty() ? "" : ", ") + r.getName();
    097        return ret;
    098    }
    099
    100    protected String curriculum(Student student) {
    101        String curriculum = "";
    102        for (AreaClassificationMajor acm: student.getAreaClassificationMajors())
    103                curriculum += (curriculum.isEmpty() ? "" : ", ") + acm.toString();
    104        return curriculum;
    105    }
    106
    107    protected String group(Student student) {
    108        String group = "";
    109        Set<String> groups = new TreeSet<String>();
    110        for (StudentGroup g: student.getGroups())
    111                groups.add(g.getReference());
    112        for (String g: groups)
    113                group += (group.isEmpty() ? "" : ", ") + g;
    114        return group;
    115    }
    116
    117    protected String advisor(Student student) {
    118        String advisors = "";
    119        for (Instructor instructor: student.getAdvisors())
    120                advisors += (advisors.isEmpty() ? "" : ", ") + instructor.getName();
    121        return advisors;
    122    }
    123
    124    /**
    125     * Create report
    126     *
    127     * @param assignment current assignment
    128     * @param includeLastLikeStudents
    129     *            true, if last-like students should be included (i.e.,
    130     *            {@link Student#isDummy()} is true)
    131     * @param includeRealStudents
    132     *            true, if real students should be included (i.e.,
    133     *            {@link Student#isDummy()} is false)
    134     * @param useAmPm use 12-hour format
    135     * @return report as comma separated text file
    136     */
    137    public CSVFile createTable(final Assignment<Request, Enrollment> assignment, boolean includeLastLikeStudents, boolean includeRealStudents, boolean useAmPm) {
    138        if (iSQ == null) throw new IllegalArgumentException("Student Schedule Quality is not enabled.");
    139
    140        CSVFile csv = new CSVFile();
    141        csv.setHeader(new CSVFile.CSVField[] {
    142                new CSVFile.CSVField("__Student"),
    143                new CSVFile.CSVField("External Id"), new CSVFile.CSVField("Student Name"),
    144                new CSVFile.CSVField("Curriculum"), new CSVFile.CSVField("Group"), new CSVFile.CSVField("Advisor"),
    145                new CSVFile.CSVField("Type"),
    146                new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"), new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Room"),
    147                new CSVFile.CSVField("Conflicting\nCourse"), new CSVFile.CSVField("Conflicting\nClass"), new CSVFile.CSVField("Conflicting\nMeeting Time"), new CSVFile.CSVField("Conflicting\nRoom"),
    148                new CSVFile.CSVField("Penalty\nMinutes")
    149                });
    150
    151        List<Conflict> confs = new ArrayList<Conflict>();
    152        for (Request r1 : getModel().variables()) {
    153            Enrollment e1 = assignment.getValue(r1);
    154            if (e1 == null || !(r1 instanceof CourseRequest))
    155                continue;
    156            for (StudentQuality.Type t: iTypes)
    157                confs.addAll(iSQ.conflicts(t, e1));
    158            for (Request r2 : r1.getStudent().getRequests()) {
    159                Enrollment e2 = assignment.getValue(r2);
    160                if (e2 == null || r1.getId() >= r2.getId() || !(r2 instanceof CourseRequest))
    161                    continue;
    162                for (StudentQuality.Type t: iTypes)
    163                    confs.addAll(iSQ.conflicts(t, e1, e2));
    164            }
    165        }
    166        Collections.sort(confs, new Comparator<Conflict>() {
    167            @Override
    168            public int compare(Conflict c1, Conflict c2) {
    169                int cmp = (c1.getStudent().getExternalId() == null ? "" : c1.getStudent().getExternalId()).compareTo(c2.getStudent().getExternalId() == null ? "" : c2.getStudent().getExternalId());
    170                if (cmp != 0) return cmp;
    171                cmp = c1.getStudent().compareTo(c2.getStudent());
    172                if (cmp != 0) return cmp;
    173                if (c1.getType() != c2.getType())
    174                    return Integer.compare(c1.getType().ordinal(), c2.getType().ordinal());
    175                cmp = c1.getE1().getCourse().getName().toString().compareTo(c2.getE1().getCourse().getName());
    176                if (cmp != 0) return cmp;
    177                return ((Section)c1.getS1()).getName(c1.getE1().getCourse().getId()).compareTo(((Section)c2.getS1()).getName(c2.getE1().getCourse().getId()));
    178            }
    179        });
    180
    181        for (Conflict conflict : confs) {
    182            if (conflict.getStudent().isDummy() && !includeLastLikeStudents) continue;
    183            if (!conflict.getStudent().isDummy() && !includeRealStudents) continue;
    184            if (conflict.getType() == Type.AccBackToBack) {
    185                boolean trueConflict = false;
    186                for (int i = 0; i < Constants.DAY_CODES.length; i++) {
    187                    if ((conflict.getS1().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0 || (conflict.getS2().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue;
    188                    boolean inBetween = false;
    189                    for (Request r: conflict.getStudent().getRequests()) {
    190                        Enrollment e = r.getAssignment(assignment);
    191                        if (e == null) continue;
    192                        for (SctAssignment s: e.getAssignments()) {
    193                            if (s.getTime() == null) continue;
    194                            if ((s.getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue;
    195                            if (!s.getTime().shareWeeks(conflict.getS1().getTime()) || !s.getTime().shareWeeks(conflict.getS2().getTime())) continue;
    196                            if (conflict.getS1().getTime().getStartSlot() + conflict.getS1().getTime().getLength() <= s.getTime().getStartSlot() &&
    197                                s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS2().getTime().getStartSlot()) {
    198                                inBetween = true; break;
    199                            }
    200                            if (conflict.getS2().getTime().getStartSlot() + conflict.getS2().getTime().getLength() <= s.getTime().getStartSlot() &&
    201                                s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS1().getTime().getStartSlot()) {
    202                                inBetween = true; break;
    203                            }
    204                        }
    205                    }
    206                    if (!inBetween) { trueConflict = true; break; }
    207                }
    208                if (!trueConflict) continue;
    209            }
    210            List<CSVFile.CSVField> line = new ArrayList<CSVFile.CSVField>();
    211
    212            line.add(new CSVFile.CSVField(conflict.getStudent().getId()));
    213            line.add(new CSVFile.CSVField(conflict.getStudent().getExternalId()));
    214            line.add(new CSVFile.CSVField(conflict.getStudent().getName()));
    215            line.add(new CSVFile.CSVField(curriculum(conflict.getStudent())));
    216            line.add(new CSVFile.CSVField(group(conflict.getStudent())));
    217            line.add(new CSVFile.CSVField(advisor(conflict.getStudent())));
    218            switch (conflict.getType()) {
    219                case ShortDistance:
    220                    line.add(new CSVFile.CSVField(iSQ.getDistanceMetric().getShortDistanceAccommodationReference()));
    221                    break;
    222                case AccBackToBack:
    223                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBackToBackAccommodation()));
    224                    break;
    225                case AccBreaksBetweenClasses:
    226                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBreakBetweenClassesAccommodation()));
    227                    break;
    228                case AccFreeTimeOverlap:
    229                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getFreeTimeAccommodation()));
    230                    break;
    231                default:
    232                    line.add(new CSVFile.CSVField(conflict.getType().getName()));
    233                    break;
    234            }
    235            line.add(new CSVFile.CSVField(conflict.getE1().getCourse().getName()));
    236            line.add(new CSVFile.CSVField(conflict.getS1() instanceof Section ? ((Section)conflict.getS1()).getName(conflict.getE1().getCourse().getId()) : ""));
    237            line.add(new CSVFile.CSVField(conflict.getS1().getTime() == null ? "" : conflict.getS1().getTime().getLongName(useAmPm)));
    238            line.add(new CSVFile.CSVField(rooms(conflict.getS1())));
    239            line.add(new CSVFile.CSVField(conflict.getE2().isCourseRequest() ? conflict.getE2().getCourse().getName() : "Free Time"));
    240            line.add(new CSVFile.CSVField(conflict.getS2() instanceof Section ? ((Section)conflict.getS2()).getName(conflict.getE2().getCourse().getId()) : ""));
    241            line.add(new CSVFile.CSVField(conflict.getS2().getTime() == null ? "" : conflict.getS2().getTime().getLongName(useAmPm)));
    242            line.add(new CSVFile.CSVField(rooms(conflict.getS2())));
    243            switch (conflict.getType()) {
    244                case AccFreeTimeOverlap:
    245                    line.add(new CSVFile.CSVField(5 * conflict.getPenalty()));
    246                    break;
    247                case ShortDistance:
    248                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getDistanceInMinutes(((Section)conflict.getS1()).getPlacement(), ((Section)conflict.getS2()).getPlacement())));
    249                    break;
    250                case AccBackToBack:
    251                case AccBreaksBetweenClasses:
    252                    TimeLocation t1 = conflict.getS1().getTime();
    253                    TimeLocation t2 = conflict.getS2().getTime();
    254                    if (t1.getStartSlot() + t1.getNrSlotsPerMeeting() <= t2.getStartSlot()) {
    255                        int dist = t2.getStartSlot() - (t1.getStartSlot() + t1.getNrSlotsPerMeeting());
    256                        line.add(new CSVFile.CSVField(5 * dist + t1.getBreakTime()));
    257                    } else if (t2.getStartSlot() + t2.getNrSlotsPerMeeting() <= t1.getStartSlot()) {
    258                        int dist = t1.getStartSlot() - (t2.getStartSlot() + t2.getNrSlotsPerMeeting());
    259                        line.add(new CSVFile.CSVField(5 * dist + t2.getBreakTime()));
    260                    } else {
    261                        line.add(new CSVFile.CSVField(null));
    262                    }
    263                    break;
    264                default:
    265                    line.add(new CSVFile.CSVField(conflict.getPenalty()));
    266                    break;
    267            }
    268            csv.addLine(line);
    269        }
    270
    271        return csv;
    272    }
    273
    274    @Override
    275    public CSVFile create(Assignment<Request, Enrollment> assignment, DataProperties properties) {
    276        return createTable(assignment, properties.getPropertyBoolean("lastlike", false), properties.getPropertyBoolean("real", true), properties.getPropertyBoolean("useAmPm", true));
    277    }
    278}