Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cb872fd
added FareProductDefaultRiderCategoriesValidator
qcdyx Feb 28, 2025
0b5c01d
added duplicates check
qcdyx Mar 3, 2025
2813f1f
FareProductDefaultRiderCategoriesValidatorTest
qcdyx Mar 3, 2025
cf993b3
fixed broken test
qcdyx Mar 3, 2025
98ec936
Merge branch 'master' into 1980-add-fare_product_with_multiple_defaul…
qcdyx Mar 3, 2025
fce4ba6
Merge branch 'master' into 1980-add-fare_product_with_multiple_defaul…
qcdyx Mar 3, 2025
1006cd3
revised logic and test
qcdyx Mar 4, 2025
1611fb5
shouldCallValidate
qcdyx Mar 4, 2025
eccb0e6
Merge branch 'master' into 1980-add-fare_product_with_multiple_defaul…
qcdyx Mar 4, 2025
2364cca
formatted code
qcdyx Mar 4, 2025
ea5580d
Merge branch '1980-add-fare_product_with_multiple_default_rider_categ…
qcdyx Mar 4, 2025
1a49d5d
Merge branch 'master' into 1980-add-fare_product_with_multiple_defaul…
qcdyx Mar 4, 2025
51b5cbd
Merge branch 'master' into 1980-add-fare_product_with_multiple_defaul…
davidgamez Mar 4, 2025
647b579
fix failing test
davidgamez Mar 4, 2025
1ba8f75
Merge branch 'master' into 1980-add-fare_product_with_multiple_defaul…
qcdyx Mar 4, 2025
fbbef92
Merge branch 'master' into 1980-add-fare_product_with_multiple_defaul…
qcdyx Mar 4, 2025
70e150d
added duplicates check
qcdyx Mar 4, 2025
75d12b9
Merge branch '1980-add-fare_product_with_multiple_default_rider_categ…
qcdyx Mar 4, 2025
35eeefc
added back riderCategorySet
qcdyx Mar 5, 2025
96519a7
added tests with different fareMediaIds
qcdyx Mar 5, 2025
0b00a10
reformatted code
qcdyx Mar 5, 2025
42ac760
made sure riderCategoryIds has no duplicates
qcdyx Mar 5, 2025
0c25cb1
revised logic
qcdyx Mar 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package org.mobilitydata.gtfsvalidator.validator;

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

import java.util.*;
import javax.inject.Inject;
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.*;

@GtfsValidator
public class FareProductDefaultRiderCategoriesValidator extends FileValidator {
GtfsFareProductTableContainer fareProductTable;
GtfsRiderCategoriesTableContainer riderCategoriesTable;

HashMap<String, Integer> fareProductDefaultCount = new HashMap<>();
Map<String, List<Integer>> fareProductRows = new HashMap<>();
Map<String, List<String>> fareProductRiderCategories = new HashMap<>();

@Inject
public FareProductDefaultRiderCategoriesValidator(
GtfsFareProductTableContainer fareProductTable,
GtfsRiderCategoriesTableContainer riderCategoriesTable) {
this.fareProductTable = fareProductTable;
this.riderCategoriesTable = riderCategoriesTable;
}

@Override
public boolean shouldCallValidate() {
return riderCategoriesTable != null && !riderCategoriesTable.isMissingFile();
}

@Override
public void validate(NoticeContainer noticeContainer) {
for (GtfsFareProduct fareProduct : fareProductTable.getEntities()) {
String fareProductId = fareProduct.fareProductId();
String riderCategoryId = fareProduct.riderCategoryId();
Optional<GtfsRiderCategories> riderCategory =
riderCategoriesTable.byRiderCategoryId(riderCategoryId);
if (!riderCategory.isEmpty()) {
if (riderCategory.get().isDefaultFareCategory().equals(GtfsRiderFareCategory.IS_DEFAULT)) {
if (fareProductDefaultCount.get(fareProductId) == null) {
fareProductDefaultCount.put(fareProductId, 1);
fareProductRiderCategories
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
.add(riderCategory.get().riderCategoryId());
fareProductRows
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
.add(fareProduct.csvRowNumber());
} else if (fareProductRiderCategories.get(fareProductId) != null
&& !fareProductRiderCategories.get(fareProductId).contains(riderCategoryId)) {
fareProductRiderCategories
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
.add(riderCategory.get().riderCategoryId());
fareProductDefaultCount.put(
fareProductId, fareProductDefaultCount.getOrDefault(fareProductId, 1) + 1);
fareProductRows
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
.add(fareProduct.csvRowNumber());
}
}
}
}

for (Map.Entry<String, Integer> entry : fareProductDefaultCount.entrySet()) {
if (entry.getValue() > 1) {
List<Integer> rows = fareProductRows.get(entry.getKey());
List<String> riderCategories = fareProductRiderCategories.get(entry.getKey());
noticeContainer.addValidationNotice(
new FareProductWithMultipleDefaultRiderCategoriesNotice(
entry.getKey(),
rows.get(0),
rows.get(1),
riderCategories.get(0),
riderCategories.get(1)));
}
}
}

/**
* This notice is generated when a fare product is associated with multiple rider categories that
* are marked as default.
*
* <p>Each fare product should have at most one default rider category.
*/
@GtfsValidationNotice(severity = ERROR)
static class FareProductWithMultipleDefaultRiderCategoriesNotice extends ValidationNotice {

/** The ID of the fare product associated with the notice */
private final String fareProductId;

/** The CSV row number of the first occurrence of the default rider category */
private final int csvRowNumber1;

/** The CSV row number of the second occurrence of the default rider category */
private final int csvRowNumber2;

/** The ID of the first rider category that is marked as default */
private final String riderCategoryId1;

/** The ID of the second rider category that is marked as default */
private final String riderCategoryId2;

public FareProductWithMultipleDefaultRiderCategoriesNotice(
String fareProductId,
int csvRowNumber1,
int csvRowNumber2,
String riderCategoryId1,
String riderCategoryId2) {
this.fareProductId = fareProductId;
this.csvRowNumber1 = csvRowNumber1;
this.csvRowNumber2 = csvRowNumber2;
this.riderCategoryId1 = riderCategoryId1;
this.riderCategoryId2 = riderCategoryId2;
}

public String getFareProductId() {
return fareProductId;
}

public int getCsvRowNumber1() {
return csvRowNumber1;
}

public int getCsvRowNumber2() {
return csvRowNumber2;
}

public String getRiderCategoryId1() {
return riderCategoryId1;
}

public String getRiderCategoryId2() {
return riderCategoryId2;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.mobilitydata.gtfsvalidator.validator;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.*;

@RunWith(JUnit4.class)
public class FareProductDefaultRiderCategoriesValidatorTest {
public static GtfsFareProduct createFareProduct(
int csvRowNumber, String fareId, String riderCategoryId, String fareMediaId) {
return new GtfsFareProduct.Builder()
.setCsvRowNumber(csvRowNumber)
.setFareProductId(fareId)
.setRiderCategoryId(riderCategoryId)
.setFareMediaId(fareMediaId)
.build();
}

public static GtfsRiderCategories createRiderCategories(
int csvRowNumber, String riderCategoryId, GtfsRiderFareCategory isDefaultFareCategory) {
return new GtfsRiderCategories.Builder()
.setCsvRowNumber(csvRowNumber)
.setRiderCategoryId(riderCategoryId)
.setIsDefaultFareCategory(isDefaultFareCategory)
.build();
}

private static List<ValidationNotice> generateNotices(
List<GtfsFareProduct> fareProducts, List<GtfsRiderCategories> riderCategories) {
FareProductDefaultRiderCategoriesValidator validator =
new FareProductDefaultRiderCategoriesValidator(
GtfsFareProductTableContainer.forEntities(fareProducts, new NoticeContainer()),
GtfsRiderCategoriesTableContainer.forEntities(riderCategories, new NoticeContainer()));
NoticeContainer noticeContainer = new NoticeContainer();
validator.validate(noticeContainer);
return noticeContainer.getValidationNotices();
}

@Test
public void testMultipleDefaultRiderCategoriesShouldTriggerNotice() {
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
riderCategories.add(createRiderCategories(3, "rider2", GtfsRiderFareCategory.IS_DEFAULT));
riderCategories.add(createRiderCategories(2, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));

List<GtfsFareProduct> fareProducts = new ArrayList<>();
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
fareProducts.add(createFareProduct(2, "fare1", "rider2", "fareMediaId"));
assertThat(
generateNotices(fareProducts, riderCategories)
.contains(
FareProductDefaultRiderCategoriesValidator
.FareProductWithMultipleDefaultRiderCategoriesNotice.class));
}

@Test
public void testOneDefaultRiderCategoryShouldNotTriggerNotice() {
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
riderCategories.add(createRiderCategories(3, "rider2", GtfsRiderFareCategory.NOT_DEFAULT));
riderCategories.add(createRiderCategories(2, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));

List<GtfsFareProduct> fareProducts = new ArrayList<>();
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
fareProducts.add(createFareProduct(2, "fare1", "rider2", "fareMediaId"));
assertTrue(generateNotices(fareProducts, riderCategories).isEmpty());
}

@Test
public void testDefaultRiderCategoriesWithDifferentFareProductIdsShouldNotTriggerNotice() {
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
riderCategories.add(createRiderCategories(2, "rider2", GtfsRiderFareCategory.NOT_DEFAULT));
riderCategories.add(createRiderCategories(3, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));

List<GtfsFareProduct> fareProducts = new ArrayList<>();
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
fareProducts.add(createFareProduct(2, "fare2", "rider1", "fareMediaId"));
assertTrue(generateNotices(fareProducts, riderCategories).isEmpty());
}

@Test
public void testDefaultRiderCategoriesWithDifferentFareProductIdsShouldTriggerNotice() {
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
riderCategories.add(createRiderCategories(2, "rider2", GtfsRiderFareCategory.IS_DEFAULT));
riderCategories.add(createRiderCategories(3, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));

List<GtfsFareProduct> fareProducts = new ArrayList<>();
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
fareProducts.add(createFareProduct(2, "fare2", "rider1", "fareMediaId"));
fareProducts.add(createFareProduct(3, "fare2", "rider2", "fareMediaId"));
assertThat(
generateNotices(fareProducts, riderCategories)
.contains(
FareProductDefaultRiderCategoriesValidator
.FareProductWithMultipleDefaultRiderCategoriesNotice.class));
}

@Test
public void
testDefaultRiderCategoriesWithSameFareProductIdAndDifferentFareMediaIdsShouldNotTriggerNotice() {
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
riderCategories.add(createRiderCategories(2, "rider2", GtfsRiderFareCategory.NOT_DEFAULT));
riderCategories.add(createRiderCategories(3, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));

List<GtfsFareProduct> fareProducts = new ArrayList<>();
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId1"));
fareProducts.add(createFareProduct(2, "fare1", "rider1", "fareMediaId2"));
fareProducts.add(createFareProduct(3, "fare2", "rider1", "fareMediaId2"));
assertTrue(generateNotices(fareProducts, riderCategories).isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ public void testNoticeClassFieldNames() {
"value",
"duplicatedElement",
"unknownElement",
"fareProductId",
"riderCategoryId1",
"riderCategoryId2",
"currencyCode");
}

Expand Down
Loading