Skip to content

Commit

Permalink
feat: pickup drop odd window validator
Browse files Browse the repository at this point in the history
  • Loading branch information
cka-y committed Dec 24, 2024
1 parent 6185a8b commit a78887d
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.report.model.FeedMetadata;
import org.mobilitydata.gtfsvalidator.report.model.NoticeView;
import org.mobilitydata.gtfsvalidator.report.model.ReportSummary;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunnerConfig;
import org.mobilitydata.gtfsvalidator.util.VersionInfo;
Expand Down Expand Up @@ -56,9 +61,30 @@ public void generateReport(
context.setVariable("config", config);
context.setVariable("date", date);
context.setVariable("is_different_date", is_different_date);
context.setVariable(
"uniqueFieldsByCode",
getUniqueFieldsForCodes(
summary.getNoticesMap().values().stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))));

try (FileWriter writer = new FileWriter(reportPath.toFile())) {
templateEngine.process("report.html", context, writer);
}
}

private Map<String, List<String>> getUniqueFieldsForCodes(
Map<String, List<NoticeView>> noticesByCode) {
return noticesByCode.entrySet().stream()
.collect(
Collectors.toMap(
Map.Entry::getKey, // Notice code
entry -> {
// Find the notice with the most fields
return entry.getValue().stream()
.max(Comparator.comparingInt(notice -> notice.getFields().size()))
.map(NoticeView::getFields) // Extract fields from that notice
.orElse(List.of()); // Default to an empty list if no notices
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,53 @@
*/
package org.mobilitydata.gtfsvalidator.validator;

import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR;

import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.GtfsStopTime;

import org.mobilitydata.gtfsvalidator.type.GtfsTime;

/**
* TODO
* Validates the pickup and drop off windows in stop_times.txt.
*
* <p>Generated notice: TODO
* <p>Generated notice: {@link ForbiddenArrivalOrDepartureTimeNotice}, {@link
* MissingPickupOrDropOffWindowNotice}, {@link InvalidPickupDropOffWindowNotice}
*/
@GtfsValidator
public class PickupDropOffWindowValidator
extends SingleEntityValidator<GtfsStopTime> {
public class PickupDropOffWindowValidator extends SingleEntityValidator<GtfsStopTime> {

@Override
public void validate(GtfsStopTime stopTime, NoticeContainer noticeContainer) {
// TODO: Implement this validator
if (stopTime.hasArrivalTime() || stopTime.hasDepartureTime()) {
// forbidden_arrival_or_departure_time
noticeContainer.addValidationNotice(
new ForbiddenArrivalOrDepartureTimeNotice(
stopTime.csvRowNumber(),
stopTime.arrivalTime(),
stopTime.departureTime(),
stopTime.startPickupDropOffWindow(),
stopTime.endPickupDropOffWindow()));
}
if (!stopTime.hasStartPickupDropOffWindow() || !stopTime.hasEndPickupDropOffWindow()) {
noticeContainer.addValidationNotice(
new MissingPickupOrDropOffWindowNotice(
stopTime.csvRowNumber(),
stopTime.startPickupDropOffWindow(),
stopTime.endPickupDropOffWindow()));
}
if (stopTime.hasStartPickupDropOffWindow() && stopTime.hasEndPickupDropOffWindow()) {
if (stopTime.startPickupDropOffWindow().isAfter(stopTime.endPickupDropOffWindow())
|| stopTime.startPickupDropOffWindow().equals(stopTime.endPickupDropOffWindow())) {
noticeContainer.addValidationNotice(
new InvalidPickupDropOffWindowNotice(
stopTime.csvRowNumber(),
stopTime.startPickupDropOffWindow(),
stopTime.endPickupDropOffWindow()));
}
}
}

@Override
Expand All @@ -41,4 +71,85 @@ public boolean shouldCallValidate(ColumnInspector header) {
return header.hasColumn(GtfsStopTime.START_PICKUP_DROP_OFF_WINDOW_FIELD_NAME)
|| header.hasColumn(GtfsStopTime.END_PICKUP_DROP_OFF_WINDOW_FIELD_NAME);
}

/**
* Arrival and departure times are forbidden in stop_times.txt when pickup and drop off windows
* are provided.
*/
@GtfsValidationNotice(severity = ERROR)
public static class ForbiddenArrivalOrDepartureTimeNotice extends ValidationNotice {

/** The row of the faulty record. */
private final int csvRowNumber;

/** The arrival time of the faulty record. */
private final GtfsTime arrivalTime;

/** The departure time of the faulty record. */
private final GtfsTime departureTime;

/** The start pickup drop off window of the faulty record. */
private final GtfsTime startPickupDropOffWindow;

/** The end pickup drop off window of the faulty record. */
private final GtfsTime endPickupDropOffWindow;

public ForbiddenArrivalOrDepartureTimeNotice(
int csvRowNumber,
GtfsTime arrivalTime,
GtfsTime departureTime,
GtfsTime startPickupDropOffWindow,
GtfsTime endPickupDropOffWindow) {
this.csvRowNumber = csvRowNumber;
this.arrivalTime = arrivalTime;
this.departureTime = departureTime;
this.startPickupDropOffWindow = startPickupDropOffWindow;
this.endPickupDropOffWindow = endPickupDropOffWindow;
}
}

/** Start or end pickup drop off window is missing in stop_times.txt. */
@GtfsValidationNotice(severity = ERROR)
public static class MissingPickupOrDropOffWindowNotice extends ValidationNotice {
/** The row of the faulty record. */
private final int csvRowNumber;

/** The start pickup drop off window of the faulty record. */
private final GtfsTime startPickupDropOffWindow;

/** The end pickup drop off window of the faulty record. */
private final GtfsTime endPickupDropOffWindow;

public MissingPickupOrDropOffWindowNotice(
int csvRowNumber, GtfsTime startPickupDropOffWindow, GtfsTime endPickupDropOffWindow) {
this.csvRowNumber = csvRowNumber;
this.startPickupDropOffWindow = startPickupDropOffWindow;
this.endPickupDropOffWindow = endPickupDropOffWindow;
}
}

/**
* Start or end pickup drop off window is invalid in stop_times.txt.
*
* <p>The value of `end_pickup_drop_off_window` must be strictly greater than the value of
* `start_pickup_drop_off_window`.
*/
@GtfsValidationNotice(severity = ERROR)
public static class InvalidPickupDropOffWindowNotice extends ValidationNotice {
/** The row of the faulty record. */
private final int csvRowNumber;

/** The start pickup drop off window of the faulty record. */
private final GtfsTime startPickupDropOffWindow;

/** The end pickup drop off window of the faulty record. */
private final GtfsTime endPickupDropOffWindow;

public InvalidPickupDropOffWindowNotice(
int csvRowNumber, GtfsTime startPickupDropOffWindow, GtfsTime endPickupDropOffWindow) {
this.csvRowNumber = csvRowNumber;
this.startPickupDropOffWindow = startPickupDropOffWindow;
this.endPickupDropOffWindow = endPickupDropOffWindow;
}
}
}
12 changes: 6 additions & 6 deletions main/src/main/resources/report.html
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,9 @@ <h3 th:text="${noticesByCode.key}" />
<table>
<thead>
<tr>
<th:block th:each="field: ${noticesByCode.value[0].fields}">
<th:block th:each="field: ${uniqueFieldsByCode[noticesByCode.key]}">
<th>
<span th:text="${field}"></span>
<span th:text="${field}"></span>
<a href="#" class="tooltip" onclick="event.preventDefault();"><span>(?)</span>
<span class="tooltiptext" th:text="${noticesByCode.value[0].getCommentForField(field)}"></span>
</a>
Expand All @@ -379,10 +379,10 @@ <h3 th:text="${noticesByCode.key}" />
</tr>
</thead>
<tbody>
<tr th:each="notice, iStat : ${noticesByCode.value}" th:if="${iStat.count <= 50}">
<th:block th:each="field: ${notice.fields}">
<td th:text="${notice.getValueForField(field)}" />
</th:block>
<tr th:each="notice: ${noticesByCode.value}">
<th:block th:each="field: ${uniqueFieldsByCode[noticesByCode.key]}">
<td th:text="${notice.getFields().contains(field) ? notice.getValueForField(field) : 'N/A'}" />
</th:block>
</tr>
</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public void testNoticeClassFieldNames() {
"departureTime1",
"distanceKm",
"endFieldName",
"endPickupDropOffWindow",
"endValue",
"entityCount",
"entityId",
Expand Down Expand Up @@ -182,6 +183,7 @@ public void testNoticeClassFieldNames() {
"specifiedField",
"speedKph",
"startFieldName",
"startPickupDropOffWindow",
"startValue",
"stopCsvRowNumber",
"stopDesc",
Expand Down

0 comments on commit a78887d

Please sign in to comment.