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}