001package org.cpsolver.studentsct.model; 002 003import java.text.DecimalFormat; 004import java.util.HashSet; 005import java.util.Iterator; 006import java.util.Set; 007 008import org.cpsolver.ifs.assignment.Assignment; 009import org.cpsolver.ifs.model.Value; 010import org.cpsolver.ifs.util.ToolBox; 011import org.cpsolver.studentsct.StudentSectioningModel; 012import org.cpsolver.studentsct.extension.DistanceConflict; 013import org.cpsolver.studentsct.extension.StudentQuality; 014import org.cpsolver.studentsct.extension.TimeOverlapsCounter; 015import org.cpsolver.studentsct.reservation.Reservation; 016 017 018/** 019 * Representation of an enrollment of a student into a course. A student needs 020 * to be enrolled in a section of each subpart of a selected configuration. When 021 * parent-child relation is defined among sections, if a student is enrolled in 022 * a section that has a parent section defined, he/she has be enrolled in the 023 * parent section as well. Also, the selected sections cannot overlap in time. <br> 024 * <br> 025 * 026 * @author Tomáš Müller 027 * @version StudentSct 1.3 (Student Sectioning)<br> 028 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 029 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 030 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 031 * <br> 032 * This library is free software; you can redistribute it and/or modify 033 * it under the terms of the GNU Lesser General Public License as 034 * published by the Free Software Foundation; either version 3 of the 035 * License, or (at your option) any later version. <br> 036 * <br> 037 * This library is distributed in the hope that it will be useful, but 038 * WITHOUT ANY WARRANTY; without even the implied warranty of 039 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 040 * Lesser General Public License for more details. <br> 041 * <br> 042 * You should have received a copy of the GNU Lesser General Public 043 * License along with this library; if not see 044 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 045 */ 046 047public class Enrollment extends Value<Request, Enrollment> { 048 private static DecimalFormat sDF = new DecimalFormat("0.000"); 049 private Request iRequest = null; 050 private Config iConfig = null; 051 private Course iCourse = null; 052 private Set<? extends SctAssignment> iAssignments = null; 053 private Double iCachedPenalty = null; 054 private int iPriority = 0; 055 private boolean iNoReservationPenalty = false; 056 private Reservation iReservation = null; 057 private Long iTimeStamp = null; 058 private String iApproval = null; 059 060 /** 061 * Constructor 062 * 063 * @param request 064 * course / free time request 065 * @param priority 066 * zero for the course, one for the first alternative, two for the second alternative 067 * @param noReservationPenalty 068 * when true +1 is added to priority (prefer enrollments with reservations) 069 * @param course 070 * selected course 071 * @param config 072 * selected configuration 073 * @param assignments 074 * valid list of sections 075 * @param reservation used reservation 076 */ 077 public Enrollment(Request request, int priority, boolean noReservationPenalty, Course course, Config config, Set<? extends SctAssignment> assignments, Reservation reservation) { 078 super(request); 079 iRequest = request; 080 iConfig = config; 081 iAssignments = assignments; 082 iPriority = priority; 083 iCourse = course; 084 iNoReservationPenalty = noReservationPenalty; 085 if (iConfig != null && iCourse == null) 086 for (Course c: ((CourseRequest)iRequest).getCourses()) { 087 if (c.getOffering().getConfigs().contains(iConfig)) { 088 iCourse = c; 089 break; 090 } 091 } 092 iReservation = reservation; 093 } 094 095 /** 096 * Constructor 097 * 098 * @param request 099 * course / free time request 100 * @param priority 101 * zero for the course, one for the first alternative, two for the second alternative 102 * @param course 103 * selected course 104 * @param config 105 * selected configuration 106 * @param assignments 107 * valid list of sections 108 * @param reservation used reservation 109 */ 110 public Enrollment(Request request, int priority, Course course, Config config, Set<? extends SctAssignment> assignments, Reservation reservation) { 111 this(request, priority, false, course, config, assignments, reservation); 112 } 113 114 /** 115 * Constructor 116 * 117 * @param request 118 * course / free time request 119 * @param priority 120 * zero for the course, one for the first alternative, two for the second alternative 121 * @param config 122 * selected configuration 123 * @param assignments 124 * valid list of sections 125 * @param assignment current assignment (to guess the reservation) 126 */ 127 public Enrollment(Request request, int priority, Config config, Set<? extends SctAssignment> assignments, Assignment<Request, Enrollment> assignment) { 128 this(request, priority, null, config, assignments, null); 129 if (assignments != null && assignment != null) 130 guessReservation(assignment, true); 131 } 132 133 /** 134 * Guess the reservation based on the enrollment 135 * @param assignment current assignment 136 * @param onlyAvailable use only reservation that have some space left in them 137 */ 138 public void guessReservation(Assignment<Request, Enrollment> assignment, boolean onlyAvailable) { 139 if (iCourse != null) { 140 Reservation best = null; 141 for (Reservation reservation: ((CourseRequest)iRequest).getReservations(iCourse)) { 142 if (reservation.isIncluded(this)) { 143 if (onlyAvailable && reservation.getContext(assignment).getReservedAvailableSpace(assignment, iConfig, iRequest) < iRequest.getWeight() && !reservation.canBatchAssignOverLimit()) 144 continue; 145 if (best == null || best.getPriority() > reservation.getPriority()) { 146 best = reservation; 147 } else if (best.getPriority() == reservation.getPriority() && 148 best.getContext(assignment).getReservedAvailableSpace(assignment, iConfig, iRequest) < reservation.getContext(assignment).getReservedAvailableSpace(assignment, iConfig, iRequest)) { 149 best = reservation; 150 } 151 } 152 } 153 iReservation = best; 154 } 155 } 156 157 /** Student 158 * @return student 159 **/ 160 public Student getStudent() { 161 return iRequest.getStudent(); 162 } 163 164 /** Request 165 * @return request 166 **/ 167 public Request getRequest() { 168 return iRequest; 169 } 170 171 /** True if the request is course request 172 * @return true if the request if course request 173 **/ 174 public boolean isCourseRequest() { 175 return iConfig != null; 176 } 177 178 /** Offering of the course request 179 * @return offering of the course request 180 **/ 181 public Offering getOffering() { 182 return (iConfig == null ? null : iConfig.getOffering()); 183 } 184 185 /** Config of the course request 186 * @return config of the course request 187 **/ 188 public Config getConfig() { 189 return iConfig; 190 } 191 192 /** Course of the course request 193 * @return course of the course request 194 **/ 195 public Course getCourse() { 196 return iCourse; 197 } 198 199 /** List of assignments (selected sections) 200 * @return assignments (selected sections) 201 **/ 202 @SuppressWarnings("unchecked") 203 public Set<SctAssignment> getAssignments() { 204 return (Set<SctAssignment>) iAssignments; 205 } 206 207 /** List of sections (only for course request) 208 * @return selected sections 209 **/ 210 @SuppressWarnings("unchecked") 211 public Set<Section> getSections() { 212 if (isCourseRequest()) 213 return (Set<Section>) iAssignments; 214 return new HashSet<Section>(); 215 } 216 217 /** True when this enrollment is overlapping with the given enrollment 218 * @param enrl other enrollment 219 * @return true if there is an overlap 220 **/ 221 public boolean isOverlapping(Enrollment enrl) { 222 if (enrl == null || isAllowOverlap() || enrl.isAllowOverlap()) 223 return false; 224 for (SctAssignment a : getAssignments()) { 225 if (a.isOverlapping(enrl.getAssignments())) 226 return true; 227 } 228 return false; 229 } 230 231 /** Percent of sections that are wait-listed 232 * @return percent of sections that are wait-listed 233 **/ 234 public double percentWaitlisted() { 235 if (!isCourseRequest()) 236 return 0.0; 237 CourseRequest courseRequest = (CourseRequest) getRequest(); 238 int nrWaitlisted = 0; 239 for (Section section : getSections()) { 240 if (courseRequest.isWaitlisted(section)) 241 nrWaitlisted++; 242 } 243 return ((double) nrWaitlisted) / getAssignments().size(); 244 } 245 246 /** Percent of sections that are selected 247 * @return percent of sections that are selected 248 **/ 249 public double percentSelected() { 250 if (!isCourseRequest()) 251 return 0.0; 252 CourseRequest courseRequest = (CourseRequest) getRequest(); 253 int nrSelected = 0; 254 for (Section section : getSections()) { 255 if (courseRequest.isSelected(section)) 256 nrSelected++; 257 } 258 return ((double) nrSelected) / getAssignments().size(); 259 } 260 261 /** Percent of sections that are selected 262 * @return percent of sections that are selected 263 **/ 264 public double percentSelectedSameSection() { 265 if (!isCourseRequest() || getStudent().isDummy()) return (getRequest().hasSelection() ? 1.0 : 0.0); 266 CourseRequest courseRequest = (CourseRequest) getRequest(); 267 int nrSelected = 0; 268 Set<Long> nrMatching = new HashSet<Long>(); 269 sections: for (Section section : getSections()) { 270 for (Choice choice: courseRequest.getSelectedChoices()) { 271 if (choice.getSubpartId() != null) nrMatching.add(choice.getSubpartId()); 272 if (choice.sameSection(section)) { 273 nrSelected ++; continue sections; 274 } 275 } 276 } 277 return (nrMatching.isEmpty() ? 1.0 : ((double) nrSelected) / nrMatching.size()); 278 } 279 280 /** Percent of sections that have the same configuration 281 * @return percent of sections that are selected 282 **/ 283 public double percentSelectedSameConfig() { 284 if (!isCourseRequest() || getStudent().isDummy() || getConfig() == null) return (getRequest().hasSelection() ? 1.0 : 0.0); 285 CourseRequest courseRequest = (CourseRequest) getRequest(); 286 boolean hasConfigSelection = false; 287 for (Choice choice: courseRequest.getSelectedChoices()) { 288 if (choice.getConfigId() != null) { 289 hasConfigSelection = true; 290 if (choice.getConfigId().equals(getConfig().getId())) return 1.0; 291 } 292 } 293 return (hasConfigSelection ? 0.0 : 1.0); 294 } 295 296 /** Percent of sections that are initial 297 * @return percent of sections that of the initial enrollment 298 **/ 299 public double percentInitial() { 300 if (!isCourseRequest()) 301 return 0.0; 302 if (getRequest().getInitialAssignment() == null) 303 return 0.0; 304 Enrollment inital = getRequest().getInitialAssignment(); 305 int nrInitial = 0; 306 for (Section section : getSections()) { 307 if (inital.getAssignments().contains(section)) 308 nrInitial++; 309 } 310 return ((double) nrInitial) / getAssignments().size(); 311 } 312 313 /** Percent of sections that have same time as the initial assignment 314 * @return percent of sections that have same time as the initial assignment 315 **/ 316 public double percentSameTime() { 317 if (!isCourseRequest()) 318 return 0.0; 319 Enrollment ie = getRequest().getInitialAssignment(); 320 if (ie != null) { 321 int nrInitial = 0; 322 sections: for (Section section : getSections()) { 323 for (Section initial: ie.getSections()) { 324 if (section.sameInstructionalType(initial) && section.sameTime(initial)) { 325 nrInitial ++; 326 continue sections; 327 } 328 } 329 } 330 return ((double) nrInitial) / getAssignments().size(); 331 } 332 Set<Choice> selected = ((CourseRequest)getRequest()).getSelectedChoices(); 333 if (!selected.isEmpty()) { 334 int nrInitial = 0; 335 sections: for (Section section : getSections()) { 336 for (Choice choice: selected) { 337 if (choice.sameOffering(section) && choice.sameInstructionalType(section) && choice.sameTime(section)) { 338 nrInitial ++; 339 continue sections; 340 } 341 342 } 343 } 344 return ((double) nrInitial) / getAssignments().size(); 345 } 346 return 0.0; 347 } 348 349 /** True if all the sections are wait-listed 350 * @return all the sections are wait-listed 351 **/ 352 public boolean isWaitlisted() { 353 if (!isCourseRequest()) 354 return false; 355 CourseRequest courseRequest = (CourseRequest) getRequest(); 356 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 357 Section section = (Section) i.next(); 358 if (!courseRequest.isWaitlisted(section)) 359 return false; 360 } 361 return true; 362 } 363 364 /** True if all the sections are selected 365 * @return all the sections are selected 366 **/ 367 public boolean isSelected() { 368 if (!isCourseRequest()) 369 return false; 370 CourseRequest courseRequest = (CourseRequest) getRequest(); 371 for (Section section : getSections()) { 372 if (!courseRequest.isSelected(section)) 373 return false; 374 } 375 return true; 376 } 377 378 public boolean isRequired() { 379 if (!isCourseRequest()) 380 return false; 381 CourseRequest courseRequest = (CourseRequest) getRequest(); 382 for (Section section : getSections()) { 383 if (!courseRequest.isRequired(section)) 384 return false; 385 } 386 return true; 387 } 388 389 /** 390 * Enrollment penalty -- sum of section penalties (see 391 * {@link Section#getPenalty()}) 392 * @return online penalty 393 */ 394 public double getPenalty() { 395 if (iCachedPenalty == null) { 396 double penalty = 0.0; 397 if (isCourseRequest()) { 398 for (Section section : getSections()) { 399 penalty += section.getPenalty(); 400 } 401 } 402 iCachedPenalty = Double.valueOf(penalty / getAssignments().size()); 403 } 404 return iCachedPenalty.doubleValue(); 405 } 406 407 /** Enrollment value */ 408 @Override 409 public double toDouble(Assignment<Request, Enrollment> assignment) { 410 return toDouble(assignment, true); 411 } 412 413 /** Enrollment value 414 * @param assignment current assignment 415 * @param precise if false, distance conflicts and time overlaps are ignored (i.e., much faster, but less precise computation) 416 * @return enrollment penalty 417 **/ 418 public double toDouble(Assignment<Request, Enrollment> assignment, boolean precise) { 419 if (precise) { 420 StudentSectioningModel model = (StudentSectioningModel)variable().getModel(); 421 if (model.getStudentQuality() != null) 422 return - getRequest().getWeight() * model.getStudentWeights().getWeight(assignment, this, studentQualityConflicts(assignment)); 423 else 424 return - getRequest().getWeight() * model.getStudentWeights().getWeight(assignment, this, distanceConflicts(assignment), timeOverlappingConflicts(assignment)); 425 } else { 426 Double value = (assignment == null ? null : variable().getContext(assignment).getLastWeight()); 427 if (value != null) return - value; 428 return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this); 429 } 430 } 431 432 /** Enrollment name */ 433 @Override 434 public String getName() { 435 if (getRequest() instanceof CourseRequest) { 436 Course course = null; 437 CourseRequest courseRequest = (CourseRequest) getRequest(); 438 for (Course c : courseRequest.getCourses()) { 439 if (c.getOffering().getConfigs().contains(getConfig())) { 440 course = c; 441 break; 442 } 443 } 444 String ret = (course == null ? getConfig() == null ? "" : getConfig().getName() : course.getName()); 445 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 446 Section assignment = (Section) i.next(); 447 ret += "\n " + assignment.getLongName(true) + (i.hasNext() ? "," : ""); 448 } 449 return ret; 450 } else if (getRequest() instanceof FreeTimeRequest) { 451 return "Free Time " + ((FreeTimeRequest) getRequest()).getTime().getLongName(true); 452 } else { 453 String ret = ""; 454 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 455 SctAssignment assignment = i.next(); 456 ret += assignment.toString() + (i.hasNext() ? "," : ""); 457 if (i.hasNext()) 458 ret += "\n "; 459 } 460 return ret; 461 } 462 } 463 464 public String toString(Assignment<Request, Enrollment> a) { 465 if (getAssignments().isEmpty()) return "not assigned"; 466 Set<DistanceConflict.Conflict> dc = distanceConflicts(a); 467 Set<TimeOverlapsCounter.Conflict> toc = timeOverlappingConflicts(a); 468 int share = 0; 469 if (toc != null) 470 for (TimeOverlapsCounter.Conflict c: toc) 471 share += c.getShare(); 472 String ret = toDouble(a) + "/" + sDF.format(getRequest().getBound()) 473 + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty())) 474 + (dc == null || dc.isEmpty() ? "" : "/dc:" + dc.size()) 475 + (share <= 0 ? "" : "/toc:" + share); 476 if (getRequest() instanceof CourseRequest) { 477 double sameGroup = 0.0; int groupCount = 0; 478 for (RequestGroup g: ((CourseRequest)getRequest()).getRequestGroups()) { 479 if (g.getCourse().equals(getCourse())) { 480 sameGroup += g.getEnrollmentSpread(a, this, 1.0, 0.0); 481 groupCount ++; 482 } 483 } 484 if (groupCount > 0) 485 ret += "/g:" + sDF.format(sameGroup / groupCount); 486 } 487 if (getRequest() instanceof CourseRequest) { 488 ret += " "; 489 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 490 SctAssignment assignment = i.next(); 491 ret += assignment + (i.hasNext() ? ", " : ""); 492 } 493 } 494 if (getReservation() != null) ret = "(r) " + ret; 495 return ret; 496 } 497 498 @Override 499 public String toString() { 500 if (getAssignments().isEmpty()) return "not assigned"; 501 String ret = sDF.format(getRequest().getBound()) + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty())); 502 if (getRequest() instanceof CourseRequest) { 503 ret += " "; 504 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 505 SctAssignment assignment = i.next(); 506 ret += assignment + (i.hasNext() ? ", " : ""); 507 } 508 } 509 if (getReservation() != null) ret = "(r) " + ret; 510 if (getRequest().isMPP()) ret += " [i" + sDF.format(100.0 * percentInitial()) + "/t" + sDF.format(100.0 * percentSameTime()) + "]"; 511 if (variable().getStudent().getExternalId() != null) 512 ret = "[" + variable().getStudent().getExternalId() + "] " + ret; 513 return ret; 514 } 515 516 @Override 517 public boolean equals(Object o) { 518 if (o == null || !(o instanceof Enrollment)) 519 return false; 520 Enrollment e = (Enrollment) o; 521 if (!ToolBox.equals(getCourse(), e.getCourse())) 522 return false; 523 if (!ToolBox.equals(getConfig(), e.getConfig())) 524 return false; 525 if (!ToolBox.equals(getRequest(), e.getRequest())) 526 return false; 527 if (!ToolBox.equals(getAssignments(), e.getAssignments())) 528 return false; 529 if (!ToolBox.equals(getReservation(), e.getReservation())) 530 return false; 531 return true; 532 } 533 534 /** Distance conflicts, in which this enrollment is involved. 535 * @param assignment current assignment 536 * @return distance conflicts 537 **/ 538 public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) { 539 if (!isCourseRequest()) 540 return null; 541 if (getRequest().getModel() instanceof StudentSectioningModel) { 542 DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict(); 543 if (dc == null) return null; 544 return dc.allConflicts(assignment, this); 545 } else 546 return null; 547 } 548 549 /** Time overlapping conflicts, in which this enrollment is involved. 550 * @param assignment current assignment 551 * @return time overlapping conflicts 552 **/ 553 public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) { 554 if (getRequest().getModel() instanceof StudentSectioningModel) { 555 TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps(); 556 if (toc == null) 557 return null; 558 return toc.allConflicts(assignment, this); 559 } else 560 return null; 561 } 562 563 public Set<StudentQuality.Conflict> studentQualityConflicts(Assignment<Request, Enrollment> assignment) { 564 if (!isCourseRequest()) 565 return null; 566 if (getRequest().getModel() instanceof StudentSectioningModel) { 567 StudentQuality sq = ((StudentSectioningModel) getRequest().getModel()).getStudentQuality(); 568 if (sq == null) return null; 569 return sq.allConflicts(assignment, this); 570 } else 571 return null; 572 } 573 574 /** 575 * Return enrollment priority 576 * @return zero for the course, one for the first alternative, two for the second alternative 577 */ 578 public int getPriority() { 579 return iPriority + (iNoReservationPenalty ? 1 : 0); 580 } 581 582 /** 583 * Return enrollment priority, ignoring priority bump provided by reservations 584 * @return zero for the course, one for the first alternative, two for the second alternative 585 */ 586 public int getTruePriority() { 587 return iPriority; 588 } 589 590 /** 591 * Return adjusted enrollment priority, including priority bump provided by reservations 592 * (but ensuring that getting the course without a reservation is still better than getting an alternative) 593 * @return zero for the course, two for the first alternative, four for the second alternative; plus one when the no reservation penalty applies 594 */ 595 public int getAdjustedPriority() { 596 return 2 * iPriority + (iNoReservationPenalty ? 1 : 0); 597 } 598 599 /** 600 * Return total number of slots of all sections in the enrollment. 601 * @return number of slots used 602 */ 603 public int getNrSlots() { 604 int ret = 0; 605 for (SctAssignment a: getAssignments()) { 606 if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings(); 607 } 608 return ret; 609 } 610 611 /** 612 * Return reservation used for this enrollment 613 * @return used reservation 614 */ 615 public Reservation getReservation() { return iReservation; } 616 617 /** 618 * Set reservation for this enrollment 619 * @param reservation used reservation 620 */ 621 public void setReservation(Reservation reservation) { iReservation = reservation; } 622 623 /** 624 * Time stamp of the enrollment 625 * @return enrollment time stamp 626 */ 627 public Long getTimeStamp() { 628 return iTimeStamp; 629 } 630 631 /** 632 * Time stamp of the enrollment 633 * @param timeStamp enrollment time stamp 634 */ 635 public void setTimeStamp(Long timeStamp) { 636 iTimeStamp = timeStamp; 637 } 638 639 /** 640 * Approval of the enrollment (only used by the online student sectioning) 641 * @return consent approval 642 */ 643 public String getApproval() { 644 return iApproval; 645 } 646 647 /** 648 * Approval of the enrollment (only used by the online student sectioning) 649 * @param approval consent approval 650 */ 651 public void setApproval(String approval) { 652 iApproval = approval; 653 } 654 655 /** 656 * True if this enrollment can overlap with other enrollments of the student. 657 * @return can overlap with other enrollments of the student 658 */ 659 public boolean isAllowOverlap() { 660 return (getReservation() != null && getReservation().isAllowOverlap()); 661 } 662 663 /** 664 * Enrollment limit, i.e., the number of students that would be able to get into the offering using this enrollment (if all the sections are empty) 665 * @return enrollment limit 666 */ 667 public int getLimit() { 668 if (!isCourseRequest()) return -1; // free time requests have no limit 669 Integer limit = null; 670 for (Section section: getSections()) 671 if (section.getLimit() >= 0) { 672 if (limit == null) 673 limit = section.getLimit(); 674 else 675 limit = Math.min(limit, section.getLimit()); 676 } 677 return (limit == null ? -1 : limit); 678 } 679 680 /** 681 * Credit of this enrollment (using either {@link Course#getCreditValue()} or {@link Subpart#getCreditValue()} when course credit is not present) 682 * @return credit of this enrollment 683 */ 684 public float getCredit() { 685 if (getCourse() == null) return 0f; 686 if (getAssignments().isEmpty()) return 0f; 687 if (getCourse().hasCreditValue()) return getCourse().getCreditValue(); 688 float subpartCredit = 0f; 689 for (Subpart subpart: getConfig().getSubparts()) { 690 if (subpart.hasCreditValue()) subpartCredit += subpart.getCreditValue(); 691 } 692 return subpartCredit; 693 } 694 695}