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 extends AbstractStudentSectioningReport {
068    private StudentQuality iSQ = null;
069    private Type[] iTypes = new Type[] {
070            Type.ShortDistance, Type.AccBackToBack, Type.AccBreaksBetweenClasses, Type.AccFreeTimeOverlap
071    };
072
073    /**
074     * Constructor
075     * 
076     * @param model
077     *            student sectioning model
078     */
079    public AccommodationConflictsTable(StudentSectioningModel model) {
080        super(model);
081        iSQ = model.getStudentQuality();
082    }
083    
084    protected String rooms(SctAssignment section) {
085        if (section.getNrRooms() == 0) return "";
086        String ret = "";
087        for (RoomLocation r: section.getRooms())
088            ret += (ret.isEmpty() ? "" : ", ") + r.getName();
089        return ret;
090    }
091    
092    protected String curriculum(Student student) {
093        String curriculum = "";
094        for (AreaClassificationMajor acm: student.getAreaClassificationMajors())
095                curriculum += (curriculum.isEmpty() ? "" : ", ") + acm.toString();
096        return curriculum;
097    }
098    
099    protected String group(Student student) {
100        String group = "";
101        Set<String> groups = new TreeSet<String>();
102        for (StudentGroup g: student.getGroups())
103                groups.add(g.getReference());
104        for (String g: groups)
105                group += (group.isEmpty() ? "" : ", ") + g;
106        return group;           
107    }
108    
109    protected String advisor(Student student) {
110        String advisors = "";
111        for (Instructor instructor: student.getAdvisors())
112                advisors += (advisors.isEmpty() ? "" : ", ") + instructor.getName();
113        return advisors;
114    }
115
116    /**
117     * Create report
118     * @param assignment current assignment
119     * @return report as comma separated text file
120     */
121    @Override
122    public CSVFile createTable(Assignment<Request, Enrollment> assignment, DataProperties properties) {
123        if (iSQ == null) throw new IllegalArgumentException("Student Schedule Quality is not enabled.");
124
125        CSVFile csv = new CSVFile();
126        csv.setHeader(new CSVFile.CSVField[] {
127                new CSVFile.CSVField("__Student"),
128                new CSVFile.CSVField("External Id"), new CSVFile.CSVField("Student Name"),
129                new CSVFile.CSVField("Curriculum"), new CSVFile.CSVField("Group"), new CSVFile.CSVField("Advisor"),
130                new CSVFile.CSVField("Type"),
131                new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"), new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Room"),
132                new CSVFile.CSVField("Conflicting\nCourse"), new CSVFile.CSVField("Conflicting\nClass"), new CSVFile.CSVField("Conflicting\nMeeting Time"), new CSVFile.CSVField("Conflicting\nRoom"),
133                new CSVFile.CSVField("Penalty\nMinutes")
134                });
135        
136        List<Conflict> confs = new ArrayList<Conflict>();
137        for (Request r1 : getModel().variables()) {
138            Enrollment e1 = assignment.getValue(r1);
139            if (e1 == null || !(r1 instanceof CourseRequest))
140                continue;
141            for (StudentQuality.Type t: iTypes)
142                confs.addAll(iSQ.conflicts(t, e1));
143            for (Request r2 : r1.getStudent().getRequests()) {
144                Enrollment e2 = assignment.getValue(r2);
145                if (e2 == null || r1.getId() >= r2.getId() || !(r2 instanceof CourseRequest))
146                    continue;
147                for (StudentQuality.Type t: iTypes)
148                    confs.addAll(iSQ.conflicts(t, e1, e2));
149            }
150        }
151        Collections.sort(confs, new Comparator<Conflict>() {
152            @Override
153            public int compare(Conflict c1, Conflict c2) {
154                int cmp = (c1.getStudent().getExternalId() == null ? "" : c1.getStudent().getExternalId()).compareTo(c2.getStudent().getExternalId() == null ? "" : c2.getStudent().getExternalId());
155                if (cmp != 0) return cmp;
156                cmp = c1.getStudent().compareTo(c2.getStudent());
157                if (cmp != 0) return cmp;
158                if (c1.getType() != c2.getType())
159                    return Integer.compare(c1.getType().ordinal(), c2.getType().ordinal());
160                cmp = c1.getE1().getCourse().getName().toString().compareTo(c2.getE1().getCourse().getName());
161                if (cmp != 0) return cmp;
162                return ((Section)c1.getS1()).getName(c1.getE1().getCourse().getId()).compareTo(((Section)c2.getS1()).getName(c2.getE1().getCourse().getId()));
163            }
164        });
165        
166        for (Conflict conflict : confs) {
167            if (!matches(conflict.getR1(), conflict.getE1())) continue;
168            if (conflict.getType() == Type.AccBackToBack) {
169                boolean trueConflict = false;
170                for (int i = 0; i < Constants.DAY_CODES.length; i++) {
171                    if ((conflict.getS1().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0 || (conflict.getS2().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue;
172                    boolean inBetween = false;
173                    for (Request r: conflict.getStudent().getRequests()) {
174                        Enrollment e = r.getAssignment(assignment);
175                        if (e == null) continue;
176                        for (SctAssignment s: e.getAssignments()) {
177                            if (s.getTime() == null) continue;
178                            if ((s.getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue;
179                            if (!s.getTime().shareWeeks(conflict.getS1().getTime()) || !s.getTime().shareWeeks(conflict.getS2().getTime())) continue;
180                            if (conflict.getS1().getTime().getStartSlot() + conflict.getS1().getTime().getLength() <= s.getTime().getStartSlot() &&
181                                s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS2().getTime().getStartSlot()) {
182                                inBetween = true; break;
183                            }
184                            if (conflict.getS2().getTime().getStartSlot() + conflict.getS2().getTime().getLength() <= s.getTime().getStartSlot() &&
185                                s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS1().getTime().getStartSlot()) {
186                                inBetween = true; break;
187                            }
188                        }
189                    }
190                    if (!inBetween) { trueConflict = true; break; }
191                }
192                if (!trueConflict) continue;
193            }
194            List<CSVFile.CSVField> line = new ArrayList<CSVFile.CSVField>();
195            
196            line.add(new CSVFile.CSVField(conflict.getStudent().getId()));
197            line.add(new CSVFile.CSVField(conflict.getStudent().getExternalId()));
198            line.add(new CSVFile.CSVField(conflict.getStudent().getName()));
199            line.add(new CSVFile.CSVField(curriculum(conflict.getStudent())));
200            line.add(new CSVFile.CSVField(group(conflict.getStudent())));
201            line.add(new CSVFile.CSVField(advisor(conflict.getStudent())));
202            switch (conflict.getType()) {
203                case ShortDistance:
204                    line.add(new CSVFile.CSVField(iSQ.getDistanceMetric().getShortDistanceAccommodationReference()));
205                    break;
206                case AccBackToBack:
207                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBackToBackAccommodation()));
208                    break;
209                case AccBreaksBetweenClasses:
210                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBreakBetweenClassesAccommodation()));
211                    break;
212                case AccFreeTimeOverlap:
213                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getFreeTimeAccommodation()));
214                    break;
215                default:
216                    line.add(new CSVFile.CSVField(conflict.getType().getName()));
217                    break;
218            }
219            line.add(new CSVFile.CSVField(conflict.getE1().getCourse().getName()));
220            line.add(new CSVFile.CSVField(conflict.getS1() instanceof Section ? ((Section)conflict.getS1()).getName(conflict.getE1().getCourse().getId()) : ""));
221            line.add(new CSVFile.CSVField(conflict.getS1().getTime() == null ? "" : conflict.getS1().getTime().getLongName(isUseAmPm())));
222            line.add(new CSVFile.CSVField(rooms(conflict.getS1())));
223            line.add(new CSVFile.CSVField(conflict.getE2().isCourseRequest() ? conflict.getE2().getCourse().getName() : "Free Time"));
224            line.add(new CSVFile.CSVField(conflict.getS2() instanceof Section ? ((Section)conflict.getS2()).getName(conflict.getE2().getCourse().getId()) : ""));
225            line.add(new CSVFile.CSVField(conflict.getS2().getTime() == null ? "" : conflict.getS2().getTime().getLongName(isUseAmPm())));
226            line.add(new CSVFile.CSVField(rooms(conflict.getS2())));
227            switch (conflict.getType()) {
228                case AccFreeTimeOverlap:
229                    line.add(new CSVFile.CSVField(5 * conflict.getPenalty()));
230                    break;
231                case ShortDistance:
232                    line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getDistanceInMinutes(((Section)conflict.getS1()).getPlacement(), ((Section)conflict.getS2()).getPlacement())));
233                    break;
234                case AccBackToBack:
235                case AccBreaksBetweenClasses:
236                    TimeLocation t1 = conflict.getS1().getTime();
237                    TimeLocation t2 = conflict.getS2().getTime();
238                    if (t1.getStartSlot() + t1.getNrSlotsPerMeeting() <= t2.getStartSlot()) {
239                        int dist = t2.getStartSlot() - (t1.getStartSlot() + t1.getNrSlotsPerMeeting());
240                        line.add(new CSVFile.CSVField(5 * dist + t1.getBreakTime()));
241                    } else if (t2.getStartSlot() + t2.getNrSlotsPerMeeting() <= t1.getStartSlot()) {
242                        int dist = t1.getStartSlot() - (t2.getStartSlot() + t2.getNrSlotsPerMeeting());
243                        line.add(new CSVFile.CSVField(5 * dist + t2.getBreakTime()));
244                    } else {
245                        line.add(new CSVFile.CSVField(null));
246                    }
247                    break;
248                default:
249                    line.add(new CSVFile.CSVField(conflict.getPenalty()));
250                    break;
251            }
252            csv.addLine(line);
253        }
254        
255        return csv;
256    }
257}