Skip to content

Commit 2efab9c

Browse files
qcdyxdavidgamez
andauthored
feat: 1980 add fare product with multiple default rider categories (#1998)
* added FareProductDefaultRiderCategoriesValidator * added duplicates check * FareProductDefaultRiderCategoriesValidatorTest * fixed broken test * revised logic and test * shouldCallValidate * formatted code * fix failing test * added duplicates check * added back riderCategorySet * added tests with different fareMediaIds * reformatted code * made sure riderCategoryIds has no duplicates * revised logic --------- Co-authored-by: David Gamez <[email protected]>
1 parent 8f5f900 commit 2efab9c

File tree

3 files changed

+264
-0
lines changed

3 files changed

+264
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package org.mobilitydata.gtfsvalidator.validator;
2+
3+
import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR;
4+
5+
import java.util.*;
6+
import javax.inject.Inject;
7+
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
8+
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
9+
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
10+
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
11+
import org.mobilitydata.gtfsvalidator.table.*;
12+
13+
@GtfsValidator
14+
public class FareProductDefaultRiderCategoriesValidator extends FileValidator {
15+
GtfsFareProductTableContainer fareProductTable;
16+
GtfsRiderCategoriesTableContainer riderCategoriesTable;
17+
18+
HashMap<String, Integer> fareProductDefaultCount = new HashMap<>();
19+
Map<String, List<Integer>> fareProductRows = new HashMap<>();
20+
Map<String, List<String>> fareProductRiderCategories = new HashMap<>();
21+
22+
@Inject
23+
public FareProductDefaultRiderCategoriesValidator(
24+
GtfsFareProductTableContainer fareProductTable,
25+
GtfsRiderCategoriesTableContainer riderCategoriesTable) {
26+
this.fareProductTable = fareProductTable;
27+
this.riderCategoriesTable = riderCategoriesTable;
28+
}
29+
30+
@Override
31+
public boolean shouldCallValidate() {
32+
return riderCategoriesTable != null && !riderCategoriesTable.isMissingFile();
33+
}
34+
35+
@Override
36+
public void validate(NoticeContainer noticeContainer) {
37+
for (GtfsFareProduct fareProduct : fareProductTable.getEntities()) {
38+
String fareProductId = fareProduct.fareProductId();
39+
String riderCategoryId = fareProduct.riderCategoryId();
40+
Optional<GtfsRiderCategories> riderCategory =
41+
riderCategoriesTable.byRiderCategoryId(riderCategoryId);
42+
if (!riderCategory.isEmpty()) {
43+
if (riderCategory.get().isDefaultFareCategory().equals(GtfsRiderFareCategory.IS_DEFAULT)) {
44+
if (fareProductDefaultCount.get(fareProductId) == null) {
45+
fareProductDefaultCount.put(fareProductId, 1);
46+
fareProductRiderCategories
47+
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
48+
.add(riderCategory.get().riderCategoryId());
49+
fareProductRows
50+
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
51+
.add(fareProduct.csvRowNumber());
52+
} else if (fareProductRiderCategories.get(fareProductId) != null
53+
&& !fareProductRiderCategories.get(fareProductId).contains(riderCategoryId)) {
54+
fareProductRiderCategories
55+
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
56+
.add(riderCategory.get().riderCategoryId());
57+
fareProductDefaultCount.put(
58+
fareProductId, fareProductDefaultCount.getOrDefault(fareProductId, 1) + 1);
59+
fareProductRows
60+
.computeIfAbsent(fareProductId, k -> new ArrayList<>())
61+
.add(fareProduct.csvRowNumber());
62+
}
63+
}
64+
}
65+
}
66+
67+
for (Map.Entry<String, Integer> entry : fareProductDefaultCount.entrySet()) {
68+
if (entry.getValue() > 1) {
69+
List<Integer> rows = fareProductRows.get(entry.getKey());
70+
List<String> riderCategories = fareProductRiderCategories.get(entry.getKey());
71+
noticeContainer.addValidationNotice(
72+
new FareProductWithMultipleDefaultRiderCategoriesNotice(
73+
entry.getKey(),
74+
rows.get(0),
75+
rows.get(1),
76+
riderCategories.get(0),
77+
riderCategories.get(1)));
78+
}
79+
}
80+
}
81+
82+
/**
83+
* This notice is generated when a fare product is associated with multiple rider categories that
84+
* are marked as default.
85+
*
86+
* <p>Each fare product should have at most one default rider category.
87+
*/
88+
@GtfsValidationNotice(severity = ERROR)
89+
static class FareProductWithMultipleDefaultRiderCategoriesNotice extends ValidationNotice {
90+
91+
/** The ID of the fare product associated with the notice */
92+
private final String fareProductId;
93+
94+
/** The CSV row number of the first occurrence of the default rider category */
95+
private final int csvRowNumber1;
96+
97+
/** The CSV row number of the second occurrence of the default rider category */
98+
private final int csvRowNumber2;
99+
100+
/** The ID of the first rider category that is marked as default */
101+
private final String riderCategoryId1;
102+
103+
/** The ID of the second rider category that is marked as default */
104+
private final String riderCategoryId2;
105+
106+
public FareProductWithMultipleDefaultRiderCategoriesNotice(
107+
String fareProductId,
108+
int csvRowNumber1,
109+
int csvRowNumber2,
110+
String riderCategoryId1,
111+
String riderCategoryId2) {
112+
this.fareProductId = fareProductId;
113+
this.csvRowNumber1 = csvRowNumber1;
114+
this.csvRowNumber2 = csvRowNumber2;
115+
this.riderCategoryId1 = riderCategoryId1;
116+
this.riderCategoryId2 = riderCategoryId2;
117+
}
118+
119+
public String getFareProductId() {
120+
return fareProductId;
121+
}
122+
123+
public int getCsvRowNumber1() {
124+
return csvRowNumber1;
125+
}
126+
127+
public int getCsvRowNumber2() {
128+
return csvRowNumber2;
129+
}
130+
131+
public String getRiderCategoryId1() {
132+
return riderCategoryId1;
133+
}
134+
135+
public String getRiderCategoryId2() {
136+
return riderCategoryId2;
137+
}
138+
}
139+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package org.mobilitydata.gtfsvalidator.validator;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
import org.junit.runners.JUnit4;
11+
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
12+
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
13+
import org.mobilitydata.gtfsvalidator.table.*;
14+
15+
@RunWith(JUnit4.class)
16+
public class FareProductDefaultRiderCategoriesValidatorTest {
17+
public static GtfsFareProduct createFareProduct(
18+
int csvRowNumber, String fareId, String riderCategoryId, String fareMediaId) {
19+
return new GtfsFareProduct.Builder()
20+
.setCsvRowNumber(csvRowNumber)
21+
.setFareProductId(fareId)
22+
.setRiderCategoryId(riderCategoryId)
23+
.setFareMediaId(fareMediaId)
24+
.build();
25+
}
26+
27+
public static GtfsRiderCategories createRiderCategories(
28+
int csvRowNumber, String riderCategoryId, GtfsRiderFareCategory isDefaultFareCategory) {
29+
return new GtfsRiderCategories.Builder()
30+
.setCsvRowNumber(csvRowNumber)
31+
.setRiderCategoryId(riderCategoryId)
32+
.setIsDefaultFareCategory(isDefaultFareCategory)
33+
.build();
34+
}
35+
36+
private static List<ValidationNotice> generateNotices(
37+
List<GtfsFareProduct> fareProducts, List<GtfsRiderCategories> riderCategories) {
38+
FareProductDefaultRiderCategoriesValidator validator =
39+
new FareProductDefaultRiderCategoriesValidator(
40+
GtfsFareProductTableContainer.forEntities(fareProducts, new NoticeContainer()),
41+
GtfsRiderCategoriesTableContainer.forEntities(riderCategories, new NoticeContainer()));
42+
NoticeContainer noticeContainer = new NoticeContainer();
43+
validator.validate(noticeContainer);
44+
return noticeContainer.getValidationNotices();
45+
}
46+
47+
@Test
48+
public void testMultipleDefaultRiderCategoriesShouldTriggerNotice() {
49+
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
50+
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
51+
riderCategories.add(createRiderCategories(3, "rider2", GtfsRiderFareCategory.IS_DEFAULT));
52+
riderCategories.add(createRiderCategories(2, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));
53+
54+
List<GtfsFareProduct> fareProducts = new ArrayList<>();
55+
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
56+
fareProducts.add(createFareProduct(2, "fare1", "rider2", "fareMediaId"));
57+
assertThat(
58+
generateNotices(fareProducts, riderCategories)
59+
.contains(
60+
FareProductDefaultRiderCategoriesValidator
61+
.FareProductWithMultipleDefaultRiderCategoriesNotice.class));
62+
}
63+
64+
@Test
65+
public void testOneDefaultRiderCategoryShouldNotTriggerNotice() {
66+
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
67+
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
68+
riderCategories.add(createRiderCategories(3, "rider2", GtfsRiderFareCategory.NOT_DEFAULT));
69+
riderCategories.add(createRiderCategories(2, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));
70+
71+
List<GtfsFareProduct> fareProducts = new ArrayList<>();
72+
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
73+
fareProducts.add(createFareProduct(2, "fare1", "rider2", "fareMediaId"));
74+
assertTrue(generateNotices(fareProducts, riderCategories).isEmpty());
75+
}
76+
77+
@Test
78+
public void testDefaultRiderCategoriesWithDifferentFareProductIdsShouldNotTriggerNotice() {
79+
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
80+
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
81+
riderCategories.add(createRiderCategories(2, "rider2", GtfsRiderFareCategory.NOT_DEFAULT));
82+
riderCategories.add(createRiderCategories(3, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));
83+
84+
List<GtfsFareProduct> fareProducts = new ArrayList<>();
85+
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
86+
fareProducts.add(createFareProduct(2, "fare2", "rider1", "fareMediaId"));
87+
assertTrue(generateNotices(fareProducts, riderCategories).isEmpty());
88+
}
89+
90+
@Test
91+
public void testDefaultRiderCategoriesWithDifferentFareProductIdsShouldTriggerNotice() {
92+
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
93+
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
94+
riderCategories.add(createRiderCategories(2, "rider2", GtfsRiderFareCategory.IS_DEFAULT));
95+
riderCategories.add(createRiderCategories(3, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));
96+
97+
List<GtfsFareProduct> fareProducts = new ArrayList<>();
98+
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId"));
99+
fareProducts.add(createFareProduct(2, "fare2", "rider1", "fareMediaId"));
100+
fareProducts.add(createFareProduct(3, "fare2", "rider2", "fareMediaId"));
101+
assertThat(
102+
generateNotices(fareProducts, riderCategories)
103+
.contains(
104+
FareProductDefaultRiderCategoriesValidator
105+
.FareProductWithMultipleDefaultRiderCategoriesNotice.class));
106+
}
107+
108+
@Test
109+
public void
110+
testDefaultRiderCategoriesWithSameFareProductIdAndDifferentFareMediaIdsShouldNotTriggerNotice() {
111+
List<GtfsRiderCategories> riderCategories = new ArrayList<>();
112+
riderCategories.add(createRiderCategories(1, "rider1", GtfsRiderFareCategory.IS_DEFAULT));
113+
riderCategories.add(createRiderCategories(2, "rider2", GtfsRiderFareCategory.NOT_DEFAULT));
114+
riderCategories.add(createRiderCategories(3, "rider3", GtfsRiderFareCategory.NOT_DEFAULT));
115+
116+
List<GtfsFareProduct> fareProducts = new ArrayList<>();
117+
fareProducts.add(createFareProduct(1, "fare1", "rider1", "fareMediaId1"));
118+
fareProducts.add(createFareProduct(2, "fare1", "rider1", "fareMediaId2"));
119+
fareProducts.add(createFareProduct(3, "fare2", "rider1", "fareMediaId2"));
120+
assertTrue(generateNotices(fareProducts, riderCategories).isEmpty());
121+
}
122+
}

main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NoticeFieldsTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ public void testNoticeClassFieldNames() {
227227
"value",
228228
"duplicatedElement",
229229
"unknownElement",
230+
"fareProductId",
231+
"riderCategoryId1",
232+
"riderCategoryId2",
230233
"currencyCode");
231234
}
232235

0 commit comments

Comments
 (0)