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 * @author Tomáš Müller 049 * @version StudentSct 1.3 (Student Sectioning)<br> 050 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 051 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 052 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 053 * <br> 054 * This library is free software; you can redistribute it and/or modify 055 * it under the terms of the GNU Lesser General Public License as 056 * published by the Free Software Foundation; either version 3 of the 057 * License, or (at your option) any later version. <br> 058 * <br> 059 * This library is distributed in the hope that it will be useful, but 060 * WITHOUT ANY WARRANTY; without even the implied warranty of 061 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 062 * Lesser General Public License for more details. <br> 063 * <br> 064 * You should have received a copy of the GNU Lesser General Public 065 * License along with this library; if not see 066 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 067 */ 068public class AccommodationConflictsTable extends AbstractStudentSectioningReport { 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 super(model); 082 iSQ = model.getStudentQuality(); 083 } 084 085 protected String rooms(SctAssignment section) { 086 if (section.getNrRooms() == 0) return ""; 087 String ret = ""; 088 for (RoomLocation r: section.getRooms()) 089 ret += (ret.isEmpty() ? "" : ", ") + r.getName(); 090 return ret; 091 } 092 093 protected String curriculum(Student student) { 094 String curriculum = ""; 095 for (AreaClassificationMajor acm: student.getAreaClassificationMajors()) 096 curriculum += (curriculum.isEmpty() ? "" : ", ") + acm.toString(); 097 return curriculum; 098 } 099 100 protected String group(Student student) { 101 String group = ""; 102 Set<String> groups = new TreeSet<String>(); 103 for (StudentGroup g: student.getGroups()) 104 groups.add(g.getReference()); 105 for (String g: groups) 106 group += (group.isEmpty() ? "" : ", ") + g; 107 return group; 108 } 109 110 protected String advisor(Student student) { 111 String advisors = ""; 112 for (Instructor instructor: student.getAdvisors()) 113 advisors += (advisors.isEmpty() ? "" : ", ") + instructor.getName(); 114 return advisors; 115 } 116 117 /** 118 * Create report 119 * @param assignment current assignment 120 * @return report as comma separated text file 121 */ 122 @Override 123 public CSVFile createTable(Assignment<Request, Enrollment> assignment, DataProperties properties) { 124 if (iSQ == null) throw new IllegalArgumentException("Student Schedule Quality is not enabled."); 125 126 CSVFile csv = new CSVFile(); 127 csv.setHeader(new CSVFile.CSVField[] { 128 new CSVFile.CSVField("__Student"), 129 new CSVFile.CSVField("External Id"), new CSVFile.CSVField("Student Name"), 130 new CSVFile.CSVField("Curriculum"), new CSVFile.CSVField("Group"), new CSVFile.CSVField("Advisor"), 131 new CSVFile.CSVField("Type"), 132 new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"), new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Room"), 133 new CSVFile.CSVField("Conflicting\nCourse"), new CSVFile.CSVField("Conflicting\nClass"), new CSVFile.CSVField("Conflicting\nMeeting Time"), new CSVFile.CSVField("Conflicting\nRoom"), 134 new CSVFile.CSVField("Penalty\nMinutes") 135 }); 136 137 List<Conflict> confs = new ArrayList<Conflict>(); 138 for (Request r1 : getModel().variables()) { 139 Enrollment e1 = assignment.getValue(r1); 140 if (e1 == null || !(r1 instanceof CourseRequest)) 141 continue; 142 for (StudentQuality.Type t: iTypes) 143 confs.addAll(iSQ.conflicts(t, e1)); 144 for (Request r2 : r1.getStudent().getRequests()) { 145 Enrollment e2 = assignment.getValue(r2); 146 if (e2 == null || r1.getId() >= r2.getId() || !(r2 instanceof CourseRequest)) 147 continue; 148 for (StudentQuality.Type t: iTypes) 149 confs.addAll(iSQ.conflicts(t, e1, e2)); 150 } 151 } 152 Collections.sort(confs, new Comparator<Conflict>() { 153 @Override 154 public int compare(Conflict c1, Conflict c2) { 155 int cmp = (c1.getStudent().getExternalId() == null ? "" : c1.getStudent().getExternalId()).compareTo(c2.getStudent().getExternalId() == null ? "" : c2.getStudent().getExternalId()); 156 if (cmp != 0) return cmp; 157 cmp = c1.getStudent().compareTo(c2.getStudent()); 158 if (cmp != 0) return cmp; 159 if (c1.getType() != c2.getType()) 160 return Integer.compare(c1.getType().ordinal(), c2.getType().ordinal()); 161 cmp = c1.getE1().getCourse().getName().toString().compareTo(c2.getE1().getCourse().getName()); 162 if (cmp != 0) return cmp; 163 return ((Section)c1.getS1()).getName(c1.getE1().getCourse().getId()).compareTo(((Section)c2.getS1()).getName(c2.getE1().getCourse().getId())); 164 } 165 }); 166 167 for (Conflict conflict : confs) { 168 if (!matches(conflict.getR1(), conflict.getE1())) continue; 169 if (conflict.getType() == Type.AccBackToBack) { 170 boolean trueConflict = false; 171 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 172 if ((conflict.getS1().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0 || (conflict.getS2().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue; 173 boolean inBetween = false; 174 for (Request r: conflict.getStudent().getRequests()) { 175 Enrollment e = r.getAssignment(assignment); 176 if (e == null) continue; 177 for (SctAssignment s: e.getAssignments()) { 178 if (s.getTime() == null) continue; 179 if ((s.getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue; 180 if (!s.getTime().shareWeeks(conflict.getS1().getTime()) || !s.getTime().shareWeeks(conflict.getS2().getTime())) continue; 181 if (conflict.getS1().getTime().getStartSlot() + conflict.getS1().getTime().getLength() <= s.getTime().getStartSlot() && 182 s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS2().getTime().getStartSlot()) { 183 inBetween = true; break; 184 } 185 if (conflict.getS2().getTime().getStartSlot() + conflict.getS2().getTime().getLength() <= s.getTime().getStartSlot() && 186 s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS1().getTime().getStartSlot()) { 187 inBetween = true; break; 188 } 189 } 190 } 191 if (!inBetween) { trueConflict = true; break; } 192 } 193 if (!trueConflict) continue; 194 } 195 List<CSVFile.CSVField> line = new ArrayList<CSVFile.CSVField>(); 196 197 line.add(new CSVFile.CSVField(conflict.getStudent().getId())); 198 line.add(new CSVFile.CSVField(conflict.getStudent().getExternalId())); 199 line.add(new CSVFile.CSVField(conflict.getStudent().getName())); 200 line.add(new CSVFile.CSVField(curriculum(conflict.getStudent()))); 201 line.add(new CSVFile.CSVField(group(conflict.getStudent()))); 202 line.add(new CSVFile.CSVField(advisor(conflict.getStudent()))); 203 switch (conflict.getType()) { 204 case ShortDistance: 205 line.add(new CSVFile.CSVField(iSQ.getDistanceMetric().getShortDistanceAccommodationReference())); 206 break; 207 case AccBackToBack: 208 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBackToBackAccommodation())); 209 break; 210 case AccBreaksBetweenClasses: 211 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBreakBetweenClassesAccommodation())); 212 break; 213 case AccFreeTimeOverlap: 214 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getFreeTimeAccommodation())); 215 break; 216 default: 217 line.add(new CSVFile.CSVField(conflict.getType().getName())); 218 break; 219 } 220 line.add(new CSVFile.CSVField(conflict.getE1().getCourse().getName())); 221 line.add(new CSVFile.CSVField(conflict.getS1() instanceof Section ? ((Section)conflict.getS1()).getName(conflict.getE1().getCourse().getId()) : "")); 222 line.add(new CSVFile.CSVField(conflict.getS1().getTime() == null ? "" : conflict.getS1().getTime().getLongName(isUseAmPm()))); 223 line.add(new CSVFile.CSVField(rooms(conflict.getS1()))); 224 line.add(new CSVFile.CSVField(conflict.getE2().isCourseRequest() ? conflict.getE2().getCourse().getName() : "Free Time")); 225 line.add(new CSVFile.CSVField(conflict.getS2() instanceof Section ? ((Section)conflict.getS2()).getName(conflict.getE2().getCourse().getId()) : "")); 226 line.add(new CSVFile.CSVField(conflict.getS2().getTime() == null ? "" : conflict.getS2().getTime().getLongName(isUseAmPm()))); 227 line.add(new CSVFile.CSVField(rooms(conflict.getS2()))); 228 switch (conflict.getType()) { 229 case AccFreeTimeOverlap: 230 line.add(new CSVFile.CSVField(5 * conflict.getPenalty())); 231 break; 232 case ShortDistance: 233 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getDistanceInMinutes(((Section)conflict.getS1()).getPlacement(), ((Section)conflict.getS2()).getPlacement()))); 234 break; 235 case AccBackToBack: 236 case AccBreaksBetweenClasses: 237 TimeLocation t1 = conflict.getS1().getTime(); 238 TimeLocation t2 = conflict.getS2().getTime(); 239 if (t1.getStartSlot() + t1.getNrSlotsPerMeeting() <= t2.getStartSlot()) { 240 int dist = t2.getStartSlot() - (t1.getStartSlot() + t1.getNrSlotsPerMeeting()); 241 line.add(new CSVFile.CSVField(5 * dist + t1.getBreakTime())); 242 } else if (t2.getStartSlot() + t2.getNrSlotsPerMeeting() <= t1.getStartSlot()) { 243 int dist = t1.getStartSlot() - (t2.getStartSlot() + t2.getNrSlotsPerMeeting()); 244 line.add(new CSVFile.CSVField(5 * dist + t2.getBreakTime())); 245 } else { 246 line.add(new CSVFile.CSVField(null)); 247 } 248 break; 249 default: 250 line.add(new CSVFile.CSVField(conflict.getPenalty())); 251 break; 252 } 253 csv.addLine(line); 254 } 255 256 return csv; 257 } 258}