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}