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}