From e1459222e04ad7fee2cf3671a365b1773aedc243 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 21 Feb 2023 14:38:00 -0500 Subject: [PATCH 01/28] add initial framework for displaying mobility data --- .../components/validation/GtfsValidationViewer.js | 11 +++++++++++ .../validation/MobilityDataValidationResult.js | 10 ++++++++++ lib/types/index.js | 2 ++ 3 files changed, 23 insertions(+) create mode 100644 lib/manager/components/validation/MobilityDataValidationResult.js diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js index ffbf19c60..0621dea82 100644 --- a/lib/manager/components/validation/GtfsValidationViewer.js +++ b/lib/manager/components/validation/GtfsValidationViewer.js @@ -30,6 +30,7 @@ import type {Props as FeedVersionViewerProps} from '../version/FeedVersionViewer import type {ValidationResult} from '../../../types' import ValidationErrorItem from './ValidationErrorItem' +import MobilityDataValidationResult from './MobilityDataValidationResult' const DEFAULT_LIMIT = 10 @@ -269,6 +270,16 @@ export default class GtfsValidationViewer extends Component { {this.messages('title')} {validationContent} + Mobility Data + + {version.mobilityDataResult && version.mobilityDataResult.notices.map(notice => ( + + )) } + + This is the no validation errors message that should only appear if there are none. + + + ) } diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js new file mode 100644 index 000000000..f493dc75d --- /dev/null +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -0,0 +1,10 @@ +import React from 'react' +import { ListGroupItem } from 'react-bootstrap' + +const MobilityDataValidationResult = (props) => { + const {notice} = props + + return

{notice.code}

+} + +export default MobilityDataValidationResult \ No newline at end of file diff --git a/lib/types/index.js b/lib/types/index.js index 7ef9b4ca1..0dc090cb3 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -542,6 +542,8 @@ export type FeedVersion = FeedVersionSummary & { nextVersionId: ?string, noteCount: number, notes: Array, + // TODO: Type + mobilityDataResult: ?any, previousVersionId: ?string, processedByExternalPublisher: ?number, sentToExternalPublisher: ?number, From a46ca8f3f2810b3e9b1031158bdf8e8c4d8e9cf9 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 24 Feb 2023 11:56:18 -0500 Subject: [PATCH 02/28] refactor(MobilityDataValidationResult): show rules from rules.json --- .../validation/GtfsValidationViewer.js | 16 ++++++++-------- .../validation/MobilityDataValidationResult.js | 6 ++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js index 0621dea82..baa06c491 100644 --- a/lib/manager/components/validation/GtfsValidationViewer.js +++ b/lib/manager/components/validation/GtfsValidationViewer.js @@ -270,15 +270,15 @@ export default class GtfsValidationViewer extends Component { {this.messages('title')} {validationContent} - Mobility Data - - {version.mobilityDataResult && version.mobilityDataResult.notices.map(notice => ( + Mobility Data Validation Issues + + {version.mobilityDataResult && version.mobilityDataResult.notices.map(notice => ( - )) } - - This is the no validation errors message that should only appear if there are none. - - + ))} + {!version.mobilityDataResult || version.mobilityDataResult.notices.length === 0 && + This is the no validation errors message that should only appear if there are none. + } + ) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index f493dc75d..9dbf72c5c 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -1,10 +1,12 @@ import React from 'react' import { ListGroupItem } from 'react-bootstrap' +import rules from './rules.json' const MobilityDataValidationResult = (props) => { - const {notice} = props + const { notice } = props + const rule = rules.find(rd => rd.rule === notice.code) - return

{notice.code}

+ return

{notice.code}

{rule.description}

} export default MobilityDataValidationResult \ No newline at end of file From cf5fdad68a7b8e37d64f1f76fc90a5de2e1067c7 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 24 Feb 2023 11:58:04 -0500 Subject: [PATCH 03/28] refactor: lint --- lib/manager/components/validation/GtfsValidationViewer.js | 2 +- .../components/validation/MobilityDataValidationResult.js | 8 ++++---- lib/types/index.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js index baa06c491..ea840267a 100644 --- a/lib/manager/components/validation/GtfsValidationViewer.js +++ b/lib/manager/components/validation/GtfsValidationViewer.js @@ -275,7 +275,7 @@ export default class GtfsValidationViewer extends Component { {version.mobilityDataResult && version.mobilityDataResult.notices.map(notice => ( ))} - {!version.mobilityDataResult || version.mobilityDataResult.notices.length === 0 && + {(!version.mobilityDataResult || version.mobilityDataResult.notices.length === 0) && This is the no validation errors message that should only appear if there are none. } diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 9dbf72c5c..f778d97fb 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -3,10 +3,10 @@ import { ListGroupItem } from 'react-bootstrap' import rules from './rules.json' const MobilityDataValidationResult = (props) => { - const { notice } = props - const rule = rules.find(rd => rd.rule === notice.code) + const { notice } = props + const rule = rules.find(rd => rd.rule === notice.code) - return

{notice.code}

{rule.description}

+ return

{notice.code}

{rule.description}

} -export default MobilityDataValidationResult \ No newline at end of file +export default MobilityDataValidationResult diff --git a/lib/types/index.js b/lib/types/index.js index 0dc090cb3..7c1affa49 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -539,11 +539,11 @@ export type FeedVersion = FeedVersionSummary & { fileTimestamp: number, isCreating?: boolean, isochrones?: any, + mobilityDataResult: ?any, nextVersionId: ?string, noteCount: number, notes: Array, // TODO: Type - mobilityDataResult: ?any, previousVersionId: ?string, processedByExternalPublisher: ?number, sentToExternalPublisher: ?number, From e526aae8da671c3082c18c8cfb59402c5fae3ff2 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 24 Feb 2023 15:52:17 -0500 Subject: [PATCH 04/28] refactor(MobilityDataValidationResult): draw tables --- .../MobilityDataValidationResult.js | 98 ++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index f778d97fb..e96259a0e 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -1,12 +1,104 @@ +/* eslint-disable no-fallthrough */ import React from 'react' -import { ListGroupItem } from 'react-bootstrap' +import { ListGroupItem, Table } from 'react-bootstrap' + import rules from './rules.json' +// from https://stackoverflow.com/a/4149671 +function unCamelCase (s) { + return s + .split(/(?=[A-Z])/) + .join(' ') + .toLowerCase() +} + +const NoticeTable = ({ notices, headerOverides = {} }) => { + if (notices.length === 0) return null + + const headers = Object.keys(notices[0]) + + return ( + + + + {headers.map((header) => ( + + ))} + + + + {notices.map((notice) => ( + + {headers.map((header, index) => { + const FieldWrapper = + header === 'fieldValue' ? 'pre' : React.Fragment + + return ( + + ) + })} + + ))} + +
+ {headerOverides[header] || unCamelCase(header)} +
+ {notice[header]} +
+ ) +} + +// eslint-disable-next-line complexity +const renderNoticeDetail = (notice) => { + switch (notice.code) { + case 'too_many_rows': + notice.csvRowNumber = notice.rowNumber + case 'fare_transfer_rule_duration_limit_type_without_duration_limit': + case 'fare_transfer_rule_duration_limit_without_type': + case 'fare_transfer_rule_missing_transfer_count': + case 'fare_transfer_rule_with_forbidden_transfer_count': + notice.filename = 'fare_transfer_rules.txt' + case 'empty_file': + case 'emtpy_row': + case 'missing_recommended_field': + case 'missing_timepoint_column': + case 'missing_required_file': + case 'missing_recommended_file': + case 'unknown_file': + return ( +
    + {notice.sampleNotices.map((notice) => ( +
  • + {notice.filename} + {notice.csvRowNumber && `: row ${notice.csvRowNumber}`} +
  • + ))} +
+ ) + default: + return ( +
+ View List + +
+ ) + } +} + const MobilityDataValidationResult = (props) => { const { notice } = props - const rule = rules.find(rd => rd.rule === notice.code) + const rule = rules.find((rd) => rd.rule === notice.code) - return

{notice.code}

{rule.description}

+ if (!rule) return null + return ( + +

+ {notice.code.replaceAll('_', ' ').toLowerCase()} +

+

{rule.description}

+ {renderNoticeDetail(notice)} +
+ ) } export default MobilityDataValidationResult From c07756cdf440a58df3b97deefd7f94bba6f6c918 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 1 Mar 2023 11:34:53 -0500 Subject: [PATCH 05/28] refactor(MobilityDataValidationResult): new validator matches old validator style --- .../validation/GtfsValidationViewer.js | 6 +- .../MobilityDataValidationResult.js | 64 ++- lib/manager/components/validation/rules.json | 429 ++++++++++++++++++ lib/manager/util/version.js | 7 + 4 files changed, 486 insertions(+), 20 deletions(-) create mode 100644 lib/manager/components/validation/rules.json diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js index ea840267a..a9761e9f4 100644 --- a/lib/manager/components/validation/GtfsValidationViewer.js +++ b/lib/manager/components/validation/GtfsValidationViewer.js @@ -267,7 +267,9 @@ export default class GtfsValidationViewer extends Component { } - {this.messages('title')} + + {this.messages('title')} + {validationContent} Mobility Data Validation Issues @@ -276,7 +278,7 @@ export default class GtfsValidationViewer extends Component { ))} {(!version.mobilityDataResult || version.mobilityDataResult.notices.length === 0) && - This is the no validation errors message that should only appear if there are none. + The MobilityData validator has not produced any errors! This may be because the validator is still running. } diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index e96259a0e..0e82890e9 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -1,7 +1,14 @@ /* eslint-disable no-fallthrough */ -import React from 'react' +import React, { useState } from 'react' +import Icon from '@conveyal/woonerf/components/icon' import { ListGroupItem, Table } from 'react-bootstrap' +import toSentenceCase from '../../../common/util/to-sentence-case' +import { + mobilityDataValidationErrorMapping, + validationErrorIconLookup +} from '../../util/version' + import rules from './rules.json' // from https://stackoverflow.com/a/4149671 @@ -12,18 +19,18 @@ function unCamelCase (s) { .toLowerCase() } -const NoticeTable = ({ notices, headerOverides = {} }) => { +const NoticeTable = ({ headerOverides = {}, notices }) => { if (notices.length === 0) return null const headers = Object.keys(notices[0]) return ( - +
{headers.map((header) => ( - ))} @@ -33,7 +40,7 @@ const NoticeTable = ({ notices, headerOverides = {} }) => { {headers.map((header, index) => { const FieldWrapper = - header === 'fieldValue' ? 'pre' : React.Fragment + header === 'fieldValue' ? 'pre' : React.Fragment return ( {headers.map((header, index) => { const FieldWrapper = - header === 'fieldValue' ? 'pre' : React.Fragment + (header === 'fieldValue' || header === 'message') ? 'pre' : React.Fragment return (
- {headerOverides[header] || unCamelCase(header)} + + {headerOverides[header] || toSentenceCase(unCamelCase(header))}
@@ -66,7 +73,7 @@ const renderNoticeDetail = (notice) => { case 'missing_recommended_file': case 'unknown_file': return ( -
    +
      {notice.sampleNotices.map((notice) => (
    • {notice.filename} @@ -77,10 +84,7 @@ const renderNoticeDetail = (notice) => { ) default: return ( -
      - View List - -
      + ) } } @@ -88,15 +92,39 @@ const renderNoticeDetail = (notice) => { const MobilityDataValidationResult = (props) => { const { notice } = props const rule = rules.find((rd) => rd.rule === notice.code) - if (!rule) return null + + const errorClass = `gtfs-error-${mobilityDataValidationErrorMapping[notice.severity]}` + const [expanded, setExpanded] = useState(notice.totalNotices < 2) + return ( - -

      - {notice.code.replaceAll('_', ' ').toLowerCase()} -

      -

      {rule.description}

      - {renderNoticeDetail(notice)} + +
      + {/* TODO: animations */} +

      setExpanded(!expanded)} style={{cursor: 'pointer'}}> + + + + {toSentenceCase(notice.code.replaceAll('_', ' ').toLowerCase())} + + {' '} + — {notice.totalNotices} case{notice.totalNotices > 1 ? 's' : ''}{' '} + found + + + + +

      + {expanded &&

      {rule.description}

      } +
      + {expanded && renderNoticeDetail(notice)}
      ) } diff --git a/lib/manager/components/validation/rules.json b/lib/manager/components/validation/rules.json new file mode 100644 index 000000000..2b3250dac --- /dev/null +++ b/lib/manager/components/validation/rules.json @@ -0,0 +1,429 @@ +[ + { + "rule": "block_trips_with_overlapping_stop_times", + "description": "Trips with the same block id have overlapping stop times." + }, + { + "rule": "csv_parsing_failed", + "description": "Parsing of a CSV file failed. One common case of the problem is when a cell value contains more than 4096 characters." + }, + { + "rule": "decreasing_shape_distance", + "description": "When sorted by `shape.shape_pt_sequence`, two consecutive shape points must not have decreasing values for `shape_dist_traveled`. " + }, + { + "rule": "decreasing_or_equal_stop_time_distance", + "description": "When sorted by `stop_times.stop_sequence`, two consecutive entries in `stop_times.txt` should have increasing distance, based on the field `shape_dist_traveled`. If the values are equal, this is considered as an error. " + }, + { + "rule": "duplicated_column", + "description": "The input file CSV header has the same column name repeated." + }, + { + "rule": "duplicate_key", + "description": "The values of the given key and rows are duplicates." + }, + { + "rule": "empty_column_name", + "description": "A column name has not been provided. Such columns are skipped by the validator." + }, + { + "rule": "empty_file", + "description": "Empty csv file found in the archive: file does not have any headers, or is a required file and does not have any data. The GTFS specification requires the first line of each file to contain field names and required files must have data." + }, + { + "rule": "equal_shape_distance_diff_coordinates", + "description": "When sorted by shape.shape_pt_sequence, the values for shape_dist_traveled must increase along a shape. Two consecutive points with equal values for shape_dist_traveled and different coordinates indicate an error." + }, + { + "rule": "fare_transfer_rule_duration_limit_type_without_duration_limit", + "description": "A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit_type` field but no `duration_limit` specified." + }, + { + "rule": "fare_transfer_rule_duration_limit_without_type", + "description": "A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit` field but no `duration_limit_type` specified." + }, + { + "rule": "fare_transfer_rule_invalid_transfer_count", + "description": "A row from GTFS file `fare_transfer_rules.txt` has a defined `transfer_count` with an invalid value." + }, + { + "rule": "fare_transfer_rule_missing_transfer_count", + "description": "A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` equal to `to_leg_group_id`, but has no `transfer_count` specified. Per the spec, `transfer_count` is required if the two leg group ids are equal." + }, + { + "rule": "fare_transfer_rule_with_forbidden_transfer_count", + "description": "A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` not equal to `to_leg_group_id`, but has `transfer_count` specified. Per the spec, `transfer_count` is forbidden if the two leg group ids are not equal." + }, + { + "rule": "foreign_key_violation", + "description": "A foreign key references the primary key of another file. A foreign key violation means that the foreign key referenced from a given row (the child file) cannot be found in the corresponding file (the parent file). The Foreign keys are defined in the specification under \"Type\" for each file." + }, + { + "rule": "inconsistent_agency_timezone", + "description": "Agencies from GTFS `agency.txt` have been found to have different timezones." + }, + { + "rule": "invalid_color", + "description": "Value of field with type `color` is not valid. A color must be encoded as a six-digit hexadecimal number. The leading \"#\" is not included." + }, + { + "rule": "invalid_currency", + "description": "Value of field with type `currency` is not valid. Currency code must follow ISO 4217" + }, + { + "rule": "invalid_currency_amount", + "description": "A currency amount field has a value that does not match the format (e.g. expected number of decimal places) of its corresponding currency code field. The number of decimal places is specified by ISO 4217." + }, + { + "rule": "invalid_date", + "description": "Value of field with type `date` is not valid. Dates must have the YYYYMMDD format." + }, + { + "rule": "invalid_email", + "description": "Value of field with type `email` is not valid. Definitions for valid emails are quite vague. We perform strict validation using the Apache Commons EmailValidator." + }, + { + "rule": "invalid_float", + "description": "Value of field with type `float` is not valid. " + }, + { + "rule": "invalid_integer", + "description": "Value of field with type `integer` is not valid. " + }, + { + "rule": "invalid_language_code", + "description": "Value of field with type `language` is not valid. Language codes must follow IETF BCP 47." + }, + { + "rule": "invalid_phone_number", + "description": "Value of field with type `phone number` is not valid. This rule uses the [PhoneNumberUtil](https://www.javadoc.io/doc/com.googlecode.libphonenumber/libphonenumber/8.4.1/com/google/i18n/phonenumbers/PhoneNumberUtil.html) class to validate a phone number based on a country code. If no country code is provided in the parameters used to run the validator, this notice won't be emitted. " + }, + { + "rule": "invalid_row_length", + "description": "A row in the input file has a different number of values than specified by the CSV header." + }, + { + "rule": "invalid_time", + "description": "Value of field with type `time` is not valid. Time must be in the `H:MM:SS`, `HH:MM:SS` or `HHH:MM:SS` format." + }, + { + "rule": "invalid_timezone", + "description": "Value of field with type `timezone` is not valid.Timezones are defined at www.iana.org. Timezone names never contain the space character but may contain an underscore. Refer to Wikipedia for a list of valid values." + }, + { + "rule": "invalid_url", + "description": "Value of field with type `url` is not valid. Definitions for valid URLs are quite vague. We perform strict validation using the Apache Commons UrlValidator." + }, + { + "rule": "location_without_parent_station", + "description": "A location that must have `parent_station` field does not have it. The following location types must have `parent_station`: entrance, generic node, boarding_area." + }, + { + "rule": "location_with_unexpected_stop_time", + "description": "Referenced locations (using `stop_times.stop_id`) must be stops/platforms, i.e. their `stops.location_type` value must be 0 or empty." + }, + { + "rule": "missing_calendar_and_calendar_date_files", + "description": "Both files calendar_dates.txt and calendar.txt are missing from the GTFS archive. At least one of the files must be provided." + }, + { + "rule": "missing_level_id", + "description": "GTFS file `levels.txt` is required for elevator (`pathway_mode=5`). A row from `stops.txt` linked to an elevator pathway has no value for `stops.level_id`." + }, + { + "rule": "missing_required_column", + "description": "A required column is missing in the input file." + }, + { + "rule": "missing_required_field", + "description": "The given field has no value in some input row, even though values are required." + }, + { + "rule": "missing_required_file", + "description": "A required file is missing. If this notice is triggered for every core file, it might be a problem with the input. To create a zip file from the GTFS `.txt` files: select all the `.txt` files, right-click, and compress. Do not compress the folder containing the files. " + }, + { + "rule": "missing_stop_name", + "description": "`stops.stop_name` is required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)." + }, + { + "rule": "missing_trip_edge", + "description": "First and last stop of a trip must define both `arrival_time` and `departure_time` fields." + }, + { + "rule": "new_line_in_value", + "description": "A value in CSV file has a new line or carriage return." + }, + { + "rule": "number_out_of_range", + "description": "The values in the given column of the input rows are out of range." + }, + { + "rule": "overlapping_frequency", + "description": "Trip frequencies must not overlap in time" + }, + { + "rule": "pathway_to_platform_with_boarding_areas", + "description": "A pathway has an endpoint that is a platform which has boarding areas. A platform that has boarding" + }, + { + "rule": "pathway_to_wrong_location_type", + "description": "A pathway has an endpoint that is a station. Pathways endpoints must be platforms (stops)," + }, + { + "rule": "pathway_unreachable_location", + "description": "A location belongs to a station that has pathways and is not reachable at least in one direction:" + }, + { + "rule": "point_near_origin", + "description": "A point is too close to origin `(0, 0)`." + }, + { + "rule": "point_near_pole", + "description": "A point is too close to the North or South Pole." + }, + { + "rule": "route_both_short_and_long_name_missing", + "description": "Both short_name and long_name are missing for a route." + }, + { + "rule": "start_and_end_range_equal", + "description": "The fields `frequencies.start_date` and `frequencies.end_date` have been found equal in `frequencies.txt`. The GTFS spec is currently unclear how this case should be handled (e.g., is it a trip that circulates once?). It is recommended to use a trip not defined via frequencies.txt for this case." + }, + { + "rule": "start_and_end_range_out_of_order", + "description": "Date or time fields have been found out of order in `calendar.txt`, `feed_info.txt` and `stop_times.txt`." + }, + { + "rule": "station_with_parent_station", + "description": "Field `parent_station` must be empty when `location_type` is 1." + }, + { + "rule": "stop_time_timepoint_without_times", + "description": "Any records with `stop_times.timepoint` set to 1 must define a value for `stop_times.arrival_time` and `stop_times.departure_time` fields." + }, + { + "rule": "stop_time_with_arrival_before_previous_departure_time", + "description": "For a given `trip_id`, the `arrival_time` of (n+1)-th stoptime in sequence must not precede the `departure_time` of n-th stoptime in sequence in `stop_times.txt`." + }, + { + "rule": "stop_time_with_only_arrival_or_departure_time", + "description": "Missing `stop_time.arrival_time` or `stop_time.departure_time`" + }, + { + "rule": "stop_without_location", + "description": "`stop_lat` and/or `stop_lon` are required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)." + }, + { + "rule": "stop_without_zone_id", + "description": "If `fare_rules.txt` is provided, and `fare_rules.txt` uses at least one column among `origin_id`, `destination_id`, and `contains_id`, then all stops and platforms (location_type = 0) must have `stops.zone_id` assigned. " + }, + { + "rule": "too_many_rows", + "description": "A CSV file has too many rows. Feeds with too large files cannot be processed in a reasonable time by GTFS consumers." + }, + { + "rule": "transfer_with_invalid_stop_location_type", + "description": "A `from_stop_id` or `to_stop_id` field from GTFS file `transfers.txt` references a stop that has a `location_type` other than 0 or 1 (aka Stop/Platform or Station)." + }, + { + "rule": "transfer_with_invalid_trip_and_route", + "description": "A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a route that does not match its `trips.txt` `route_id`." + }, + { + "rule": "transfer_with_invalid_trip_and_stop", + "description": "A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a stop that is not included in the referenced trip's stop-times." + }, + { + "rule": "transfer_with_suspicious_mid_trip_in_seat", + "description": "A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` with an in-seat transfer type references a stop that is not in the expected position in the trip's stop-times. For in-seat transfers, we expect the stop to be the last stop-time in the trip sequence for `from_stop_id` and the first stop-time for `to_stop_id`. If you are intentionally using this feature to model mid-trip transfers, you can ignore this warning, but be aware that this functionality is still considered to be partially experimental in some interpretations of the spec." + }, + { + "rule": "translation_foreign_key_violation", + "description": "An entity with the given `record_id` and `record_sub_id` cannot be found in the referenced table." + }, + { + "rule": "translation_unexpected_value", + "description": "A field in a translations row has value but must be empty." + }, + { + "rule": "wrong_parent_location_type", + "description": "Value of field `location_type` of parent found in field `parent_station` is invalid." + }, + { + "rule": "attribution_without_role", + "description": "At least one of the fields `is_producer`, `is_operator`, or `is_authority` should be set to 1." + }, + { + "rule": "duplicate_route_name", + "description": "All routes of the same `route_type` with the same `agency_id` should have unique combinations of `route_short_name` and `route_long_name`." + }, + { + "rule": "empty_row", + "description": "A row in the input file has only spaces." + }, + { + "rule": "equal_shape_distance_same_coordinates", + "description": "When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and the same coordinates indicate a duplicative shape point." + }, + { + "rule": "fast_travel_between_consecutive_stops", + "description": "A transit vehicle moves too fast between two consecutive stops. The speed threshold depends on route type." + }, + { + "rule": "fast_travel_between_far_stops", + "description": "A transit vehicle moves too fast between far consecutive stops (more than in 10 km apart). " + }, + { + "rule": "feed_expiration_date7_days", + "description": "The dataset expiration date defined in `feed_info.txt` is in seven days or less. At any time, the published GTFS dataset should be valid for at least the next 7 days." + }, + { + "rule": "feed_expiration_date30_days", + "description": "At any time, the GTFS dataset should cover at least the next 30 days of service, and ideally for as long as the operator is confident that the schedule will continue to be operated." + }, + { + "rule": "feed_info_lang_and_agency_lang_mismatch", + "description": " The default language may be multilingual for datasets with the original text in multiple languages. In such cases, the feed_lang field should contain the language code mul defined by the norm ISO 639-2." + }, + { + "rule": "inconsistent_agency_lang", + "description": "Agencies from GTFS `agency.txt` have been found to have different languages." + }, + { + "rule": "leading_or_trailing_whitespaces", + "description": "The value in CSV file has leading or trailing whitespaces." + }, + { + "rule": "missing_feed_info_date", + "description": "Even though `feed_info.start_date` and `feed_info.end_date` are optional, if one field is provided the second one should also be provided." + }, + { + "rule": "missing_recommended_file", + "description": "A recommended file is missing." + }, + { + "rule": "missing_recommended_field", + "description": "The given field has no value in some input row, even though values are recommended." + }, + { + "rule": "missing_timepoint_column", + "description": "The `timepoint` column should be provided." + }, + { + "rule": "missing_timepoint_value", + "description": "Even though the column `timepoint` is optional in `stop_times.txt` according to the specification, `stop_times.timepoint` should not be empty when provided. " + }, + { + "rule": "more_than_one_entity", + "description": "The file is expected to have a single entity but has more (e.g., \"feed_info.txt\")." + }, + { + "rule": "non_ascii_or_non_printable_char", + "description": "A value of a field with type `id` contains non ASCII or non printable characters. This is not recommended." + }, + { + "rule": "pathway_dangling_generic_node", + "description": "A generic node has only one incident location in a pathway graph. Such generic node is useless" + }, + { + "rule": "pathway_loop", + "description": "A pathway should not have same values for `from_stop_id` and `to_stop_id`." + }, + { + "rule": "platform_without_parent_station", + "description": "A platform has no `parent_station` field set." + }, + { + "rule": "route_color_contrast", + "description": "A route's color and `route_text_color` should be contrasting." + }, + { + "rule": "route_short_and_long_name_equal", + "description": "A single route has the same values for `route_short_name` and `route_long_name`." + }, + { + "rule": "route_short_name_too_long", + "description": "Short name of a route is too long (more than 12 characters)." + }, + { + "rule": "same_name_and_description_for_route", + "description": "The GTFS spec defines `routes.txt` [route_desc](https://gtfs.org/reference/static/#routestxt) as:" + }, + { + "rule": "same_name_and_description_for_stop", + "description": "The GTFS spec defines `stops.txt` [stop_description](https://gtfs.org/reference/static/#stopstxt) as:" + }, + { + "rule": "same_route_and_agency_url", + "description": "A route should not have the same `routes.route_url` as a record from `agency.txt`." + }, + { + "rule": "same_stop_and_agency_url", + "description": "A stop should not have the same `stops.stop_url` as a record from `agency.txt`." + }, + { + "rule": "same_stop_and_route_url", + "description": "A stop should not have the same `stop.stop_url` as a record from `routes.txt`." + }, + { + "rule": "stop_has_too_many_matches_for_shape", + "description": "A stop entry that has many potential matches to the trip's path of travel, as defined by the shape entry in `shapes.txt`." + }, + { + "rule": "stops_match_shape_out_of_order", + "description": "Two stop entries in `stop_times.txt` are different than their arrival-departure order as defined by the shape in the `shapes.txt` file." + }, + { + "rule": "stop_too_far_from_shape", + "description": "Per GTFS Best Practices, route alignments (in `shapes.txt`) should be within 100 meters of stop locations which a trip serves." + }, + { + "rule": "stop_too_far_from_shape_using_user_distance", + "description": "A stop time entry that is a large distance away from the location of the shape in `shapes.txt` as defined by `shape_dist_traveled` values." + }, + { + "rule": "stop_without_stop_time", + "description": "A stop in `stops.txt` is not referenced by any `stop_times.stop_id`, so it is not used by any trip." + }, + { + "rule": "translation_unknown_table_name", + "description": "A translation references an unknown or missing GTFS table." + }, + { + "rule": "unexpected_enum_value", + "description": "An enum has an unexpected value." + }, + { + "rule": "unusable_trip", + "description": "A trip must visit more than one stop in stop_times.txt to be usable by passengers for boarding and alighting." + }, + { + "rule": "unused_shape", + "description": "All records defined by GTFS `shapes.txt` should be used in `trips.txt`." + }, + { + "rule": "unused_trip", + "description": "Trips should be referred to at least once in `stop_times.txt`." + }, + { "rule": "unknown_column", "description": "A column is unknown." }, + { "rule": "unknown_file", "description": "A file is unknown." }, + { "rule": "i_o_error", "description": "Error in IO operation." }, + { + "rule": "runtime_exception_in_loader_error", + "description": "A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred while loading a table. This normally indicates a bug in validator." + }, + { + "rule": "runtime_exception_in_validator_error", + "description": "A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred during validation. This normally indicates a bug in validator code, e.g., in a custom validator class." + }, + { + "rule": "thread_execution_error", + "description": "An [ExecutionException](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutionException.html) occurred during multithreaded validation." + }, + { + "rule": "u_r_i_syntax_error", + "description": "A string could not be parsed as a URI reference." + } +] diff --git a/lib/manager/util/version.js b/lib/manager/util/version.js index ffa856eb6..0abfa7f95 100644 --- a/lib/manager/util/version.js +++ b/lib/manager/util/version.js @@ -145,6 +145,13 @@ export const validationErrorIconLookup = { UNKNOWN: 'question-circle' } +export const mobilityDataValidationErrorMapping = { + ERROR: 'HIGH', + WARNING: 'MEDIUM', + INFO: 'LOW', + SYSTEM_ERROR: 'UNKNOWN' +} + /** * Get the appropriate comparison feed version or summarized feed version * summary given a project, the feed source within the project and a comparison From d9dd26331fc4c34ae854078cd721d586818cd87f Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 1 Mar 2023 11:40:39 -0500 Subject: [PATCH 06/28] chore(MobilityDataValidationResult): add key events --- .../components/validation/MobilityDataValidationResult.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 0e82890e9..c1b805919 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ /* eslint-disable no-fallthrough */ import React, { useState } from 'react' import Icon from '@conveyal/woonerf/components/icon' @@ -97,11 +98,12 @@ const MobilityDataValidationResult = (props) => { const errorClass = `gtfs-error-${mobilityDataValidationErrorMapping[notice.severity]}` const [expanded, setExpanded] = useState(notice.totalNotices < 2) + const onRowSelect = () => setExpanded(!expanded) + return (
      - {/* TODO: animations */} -

      setExpanded(!expanded)} style={{cursor: 'pointer'}}> +

      Date: Wed, 1 Mar 2023 11:46:27 -0500 Subject: [PATCH 07/28] chore: correct `mobilityDataResult` type --- __tests__/test-utils/mock-data/manager.js | 1 + lib/types/index.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/__tests__/test-utils/mock-data/manager.js b/__tests__/test-utils/mock-data/manager.js index eeb5674e7..1510bdff7 100644 --- a/__tests__/test-utils/mock-data/manager.js +++ b/__tests__/test-utils/mock-data/manager.js @@ -340,6 +340,7 @@ export const mockFeedVersion = { feedVersionId: 'mock-feed-version-id', loadFailureReason: null, loadStatus: 'SUCCESS', + mobilityDataResult: {}, routeCount: 10, startDate: '20180801', stopCount: 237, diff --git a/lib/types/index.js b/lib/types/index.js index 7c1affa49..8ae8cecbd 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -539,7 +539,7 @@ export type FeedVersion = FeedVersionSummary & { fileTimestamp: number, isCreating?: boolean, isochrones?: any, - mobilityDataResult: ?any, + mobilityDataResult?: any, nextVersionId: ?string, noteCount: number, notes: Array, From ffafb0e8e0ae3c018074a08128649b9b1e27bad6 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 1 Mar 2023 11:52:10 -0500 Subject: [PATCH 08/28] refactor(MobilityDataValidationResult): update strings --- lib/manager/components/validation/GtfsValidationViewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js index a9761e9f4..a11c79978 100644 --- a/lib/manager/components/validation/GtfsValidationViewer.js +++ b/lib/manager/components/validation/GtfsValidationViewer.js @@ -278,7 +278,7 @@ export default class GtfsValidationViewer extends Component { ))} {(!version.mobilityDataResult || version.mobilityDataResult.notices.length === 0) && - The MobilityData validator has not produced any errors! This may be because the validator is still running. + The MobilityData validator has not produced any errors or warnings. This may be because the validator is still running. } From 095d60dd4d1ef1e7e6c964142dd29020d6a95516 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 1 Mar 2023 11:52:30 -0500 Subject: [PATCH 09/28] test: update snapshots --- .../__tests__/__snapshots__/DeploymentsPanel.js.snap | 7 +++++++ .../__tests__/__snapshots__/FeedSourceTable.js.snap | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap b/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap index 5f4ab1a06..ec6ec2bbf 100644 --- a/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap +++ b/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap @@ -108,6 +108,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -344,6 +345,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -637,6 +639,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -801,6 +804,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -1153,6 +1157,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -1282,6 +1287,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -1690,6 +1696,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, diff --git a/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap b/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap index b73c61a2c..b056fef5a 100644 --- a/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap +++ b/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap @@ -107,6 +107,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -406,6 +407,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -699,6 +701,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -925,6 +928,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -3050,6 +3054,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -3172,6 +3177,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -3321,6 +3327,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -3646,6 +3653,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -4045,6 +4053,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -4312,6 +4321,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, @@ -4461,6 +4471,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds "feedVersionId": "mock-feed-version-id", "loadFailureReason": null, "loadStatus": "SUCCESS", + "mobilityDataResult": Object {}, "routeCount": 10, "startDate": "20180801", "stopCount": 237, From a46cb70e25c44256b232e9f6e9817cf5e5af5c50 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 7 Mar 2023 16:03:52 -0500 Subject: [PATCH 10/28] clean up table cells --- .../components/validation/MobilityDataValidationResult.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index c1b805919..683c32da8 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -41,7 +41,7 @@ const NoticeTable = ({ headerOverides = {}, notices }) => {

From 539dc078c274dd51193df6411345918eec85c5bc Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 7 Mar 2023 16:04:18 -0500 Subject: [PATCH 11/28] rough version of dual labels --- .../components/version/FeedVersionViewer.js | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/manager/components/version/FeedVersionViewer.js b/lib/manager/components/version/FeedVersionViewer.js index 0bf9555ff..50863716c 100644 --- a/lib/manager/components/version/FeedVersionViewer.js +++ b/lib/manager/components/version/FeedVersionViewer.js @@ -23,7 +23,7 @@ import * as plusActions from '../../../gtfsplus/actions/gtfsplus' import ActiveGtfsPlusVersionSummary from '../../../gtfsplus/containers/ActiveGtfsPlusVersionSummary' import NotesViewer from '../NotesViewer' import TransformationsViewer from '../TransformationsViewer' -import { errorPriorityLevels, getTableFatalExceptions } from '../../util/version' +import { errorPriorityLevels, getTableFatalExceptions, mobilityDataValidationErrorMapping } from '../../util/version' import GtfsValidationViewer from '../validation/GtfsValidationViewer' import type { Feed, FeedVersion, GtfsPlusValidation, Note, Project } from '../../../types' import type { GtfsState, ManagerUserState } from '../../../types/reducers' @@ -150,6 +150,7 @@ type SelectorProps = { } class VersionSectionSelector extends Component { + // eslint-disable-next-line complexity _renderIssuesLabel = () => { const { comparedVersion, version } = this.props const { feedLoadResult, validationResult, validationSummary } = version @@ -171,10 +172,28 @@ class VersionSectionSelector extends Component { }) } - const text = hasCriticalError + // Do the same for the mobility data results + let highestMBPriority = 'UNKNOWN' + if (version && version.mobilityDataResult) { + version.mobilityDataResult.notices.forEach(notice => { + const level = errorPriorityLevels[mobilityDataValidationErrorMapping[notice.severity]] + if (level < errorPriorityLevels[highestMBPriority]) { + highestMBPriority = mobilityDataValidationErrorMapping[notice.severity] + } + }) + } + + const dtValidationCount = hasCriticalError ? 'critical error' : validationSummary.errorCount + const mbValidationCount = + version.mobilityDataResult && + version.mobilityDataResult.notices && + version.mobilityDataResult.notices.reduce((prev, cur) => { + return prev + cur.totalNotices + }, 0) + let diffLabel if (comparedVersion) { const diff = validationSummary.errorCount - comparedVersion.validationSummary.errorCount @@ -190,8 +209,11 @@ class VersionSectionSelector extends Component { return (
- {text} + DT {dtValidationCount} + {mbValidationCount && + MD {mbValidationCount} + } {diffLabel}
) From b2115b0f3de2019d7398c0908284e3d2a9d79a1e Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 7 Mar 2023 17:30:37 -0500 Subject: [PATCH 12/28] include link to mobility data docs --- .../MobilityDataValidationResult.js | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 683c32da8..1b3214fd6 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -102,29 +102,46 @@ const MobilityDataValidationResult = (props) => { return ( -
-

+
+

{toSentenceCase(notice.code.replaceAll('_', ' ').toLowerCase())} {' '} - — {notice.totalNotices} case{notice.totalNotices > 1 ? 's' : ''}{' '} - found + — {notice.totalNotices} case + {notice.totalNotices > 1 ? 's' : ''} found

- {expanded &&

{rule.description}

} + {expanded && ( + <> +

{rule.description}

+

+ + More details + +

+ + )}
{expanded && renderNoticeDetail(notice)} From 504c534e7b3b586e895c4760c03c59430908b1f2 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 7 Mar 2023 17:32:59 -0500 Subject: [PATCH 13/28] add initial mobility data rule import script --- lib/manager/components/validation/rules.json | 430 +------------------ package.json | 1 + scripts/import-mobility-data-rules.mjs | 39 ++ 3 files changed, 41 insertions(+), 429 deletions(-) create mode 100644 scripts/import-mobility-data-rules.mjs diff --git a/lib/manager/components/validation/rules.json b/lib/manager/components/validation/rules.json index 2b3250dac..ce9f7a6d4 100644 --- a/lib/manager/components/validation/rules.json +++ b/lib/manager/components/validation/rules.json @@ -1,429 +1 @@ -[ - { - "rule": "block_trips_with_overlapping_stop_times", - "description": "Trips with the same block id have overlapping stop times." - }, - { - "rule": "csv_parsing_failed", - "description": "Parsing of a CSV file failed. One common case of the problem is when a cell value contains more than 4096 characters." - }, - { - "rule": "decreasing_shape_distance", - "description": "When sorted by `shape.shape_pt_sequence`, two consecutive shape points must not have decreasing values for `shape_dist_traveled`. " - }, - { - "rule": "decreasing_or_equal_stop_time_distance", - "description": "When sorted by `stop_times.stop_sequence`, two consecutive entries in `stop_times.txt` should have increasing distance, based on the field `shape_dist_traveled`. If the values are equal, this is considered as an error. " - }, - { - "rule": "duplicated_column", - "description": "The input file CSV header has the same column name repeated." - }, - { - "rule": "duplicate_key", - "description": "The values of the given key and rows are duplicates." - }, - { - "rule": "empty_column_name", - "description": "A column name has not been provided. Such columns are skipped by the validator." - }, - { - "rule": "empty_file", - "description": "Empty csv file found in the archive: file does not have any headers, or is a required file and does not have any data. The GTFS specification requires the first line of each file to contain field names and required files must have data." - }, - { - "rule": "equal_shape_distance_diff_coordinates", - "description": "When sorted by shape.shape_pt_sequence, the values for shape_dist_traveled must increase along a shape. Two consecutive points with equal values for shape_dist_traveled and different coordinates indicate an error." - }, - { - "rule": "fare_transfer_rule_duration_limit_type_without_duration_limit", - "description": "A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit_type` field but no `duration_limit` specified." - }, - { - "rule": "fare_transfer_rule_duration_limit_without_type", - "description": "A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit` field but no `duration_limit_type` specified." - }, - { - "rule": "fare_transfer_rule_invalid_transfer_count", - "description": "A row from GTFS file `fare_transfer_rules.txt` has a defined `transfer_count` with an invalid value." - }, - { - "rule": "fare_transfer_rule_missing_transfer_count", - "description": "A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` equal to `to_leg_group_id`, but has no `transfer_count` specified. Per the spec, `transfer_count` is required if the two leg group ids are equal." - }, - { - "rule": "fare_transfer_rule_with_forbidden_transfer_count", - "description": "A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` not equal to `to_leg_group_id`, but has `transfer_count` specified. Per the spec, `transfer_count` is forbidden if the two leg group ids are not equal." - }, - { - "rule": "foreign_key_violation", - "description": "A foreign key references the primary key of another file. A foreign key violation means that the foreign key referenced from a given row (the child file) cannot be found in the corresponding file (the parent file). The Foreign keys are defined in the specification under \"Type\" for each file." - }, - { - "rule": "inconsistent_agency_timezone", - "description": "Agencies from GTFS `agency.txt` have been found to have different timezones." - }, - { - "rule": "invalid_color", - "description": "Value of field with type `color` is not valid. A color must be encoded as a six-digit hexadecimal number. The leading \"#\" is not included." - }, - { - "rule": "invalid_currency", - "description": "Value of field with type `currency` is not valid. Currency code must follow ISO 4217" - }, - { - "rule": "invalid_currency_amount", - "description": "A currency amount field has a value that does not match the format (e.g. expected number of decimal places) of its corresponding currency code field. The number of decimal places is specified by ISO 4217." - }, - { - "rule": "invalid_date", - "description": "Value of field with type `date` is not valid. Dates must have the YYYYMMDD format." - }, - { - "rule": "invalid_email", - "description": "Value of field with type `email` is not valid. Definitions for valid emails are quite vague. We perform strict validation using the Apache Commons EmailValidator." - }, - { - "rule": "invalid_float", - "description": "Value of field with type `float` is not valid. " - }, - { - "rule": "invalid_integer", - "description": "Value of field with type `integer` is not valid. " - }, - { - "rule": "invalid_language_code", - "description": "Value of field with type `language` is not valid. Language codes must follow IETF BCP 47." - }, - { - "rule": "invalid_phone_number", - "description": "Value of field with type `phone number` is not valid. This rule uses the [PhoneNumberUtil](https://www.javadoc.io/doc/com.googlecode.libphonenumber/libphonenumber/8.4.1/com/google/i18n/phonenumbers/PhoneNumberUtil.html) class to validate a phone number based on a country code. If no country code is provided in the parameters used to run the validator, this notice won't be emitted. " - }, - { - "rule": "invalid_row_length", - "description": "A row in the input file has a different number of values than specified by the CSV header." - }, - { - "rule": "invalid_time", - "description": "Value of field with type `time` is not valid. Time must be in the `H:MM:SS`, `HH:MM:SS` or `HHH:MM:SS` format." - }, - { - "rule": "invalid_timezone", - "description": "Value of field with type `timezone` is not valid.Timezones are defined at www.iana.org. Timezone names never contain the space character but may contain an underscore. Refer to Wikipedia for a list of valid values." - }, - { - "rule": "invalid_url", - "description": "Value of field with type `url` is not valid. Definitions for valid URLs are quite vague. We perform strict validation using the Apache Commons UrlValidator." - }, - { - "rule": "location_without_parent_station", - "description": "A location that must have `parent_station` field does not have it. The following location types must have `parent_station`: entrance, generic node, boarding_area." - }, - { - "rule": "location_with_unexpected_stop_time", - "description": "Referenced locations (using `stop_times.stop_id`) must be stops/platforms, i.e. their `stops.location_type` value must be 0 or empty." - }, - { - "rule": "missing_calendar_and_calendar_date_files", - "description": "Both files calendar_dates.txt and calendar.txt are missing from the GTFS archive. At least one of the files must be provided." - }, - { - "rule": "missing_level_id", - "description": "GTFS file `levels.txt` is required for elevator (`pathway_mode=5`). A row from `stops.txt` linked to an elevator pathway has no value for `stops.level_id`." - }, - { - "rule": "missing_required_column", - "description": "A required column is missing in the input file." - }, - { - "rule": "missing_required_field", - "description": "The given field has no value in some input row, even though values are required." - }, - { - "rule": "missing_required_file", - "description": "A required file is missing. If this notice is triggered for every core file, it might be a problem with the input. To create a zip file from the GTFS `.txt` files: select all the `.txt` files, right-click, and compress. Do not compress the folder containing the files. " - }, - { - "rule": "missing_stop_name", - "description": "`stops.stop_name` is required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)." - }, - { - "rule": "missing_trip_edge", - "description": "First and last stop of a trip must define both `arrival_time` and `departure_time` fields." - }, - { - "rule": "new_line_in_value", - "description": "A value in CSV file has a new line or carriage return." - }, - { - "rule": "number_out_of_range", - "description": "The values in the given column of the input rows are out of range." - }, - { - "rule": "overlapping_frequency", - "description": "Trip frequencies must not overlap in time" - }, - { - "rule": "pathway_to_platform_with_boarding_areas", - "description": "A pathway has an endpoint that is a platform which has boarding areas. A platform that has boarding" - }, - { - "rule": "pathway_to_wrong_location_type", - "description": "A pathway has an endpoint that is a station. Pathways endpoints must be platforms (stops)," - }, - { - "rule": "pathway_unreachable_location", - "description": "A location belongs to a station that has pathways and is not reachable at least in one direction:" - }, - { - "rule": "point_near_origin", - "description": "A point is too close to origin `(0, 0)`." - }, - { - "rule": "point_near_pole", - "description": "A point is too close to the North or South Pole." - }, - { - "rule": "route_both_short_and_long_name_missing", - "description": "Both short_name and long_name are missing for a route." - }, - { - "rule": "start_and_end_range_equal", - "description": "The fields `frequencies.start_date` and `frequencies.end_date` have been found equal in `frequencies.txt`. The GTFS spec is currently unclear how this case should be handled (e.g., is it a trip that circulates once?). It is recommended to use a trip not defined via frequencies.txt for this case." - }, - { - "rule": "start_and_end_range_out_of_order", - "description": "Date or time fields have been found out of order in `calendar.txt`, `feed_info.txt` and `stop_times.txt`." - }, - { - "rule": "station_with_parent_station", - "description": "Field `parent_station` must be empty when `location_type` is 1." - }, - { - "rule": "stop_time_timepoint_without_times", - "description": "Any records with `stop_times.timepoint` set to 1 must define a value for `stop_times.arrival_time` and `stop_times.departure_time` fields." - }, - { - "rule": "stop_time_with_arrival_before_previous_departure_time", - "description": "For a given `trip_id`, the `arrival_time` of (n+1)-th stoptime in sequence must not precede the `departure_time` of n-th stoptime in sequence in `stop_times.txt`." - }, - { - "rule": "stop_time_with_only_arrival_or_departure_time", - "description": "Missing `stop_time.arrival_time` or `stop_time.departure_time`" - }, - { - "rule": "stop_without_location", - "description": "`stop_lat` and/or `stop_lon` are required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)." - }, - { - "rule": "stop_without_zone_id", - "description": "If `fare_rules.txt` is provided, and `fare_rules.txt` uses at least one column among `origin_id`, `destination_id`, and `contains_id`, then all stops and platforms (location_type = 0) must have `stops.zone_id` assigned. " - }, - { - "rule": "too_many_rows", - "description": "A CSV file has too many rows. Feeds with too large files cannot be processed in a reasonable time by GTFS consumers." - }, - { - "rule": "transfer_with_invalid_stop_location_type", - "description": "A `from_stop_id` or `to_stop_id` field from GTFS file `transfers.txt` references a stop that has a `location_type` other than 0 or 1 (aka Stop/Platform or Station)." - }, - { - "rule": "transfer_with_invalid_trip_and_route", - "description": "A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a route that does not match its `trips.txt` `route_id`." - }, - { - "rule": "transfer_with_invalid_trip_and_stop", - "description": "A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a stop that is not included in the referenced trip's stop-times." - }, - { - "rule": "transfer_with_suspicious_mid_trip_in_seat", - "description": "A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` with an in-seat transfer type references a stop that is not in the expected position in the trip's stop-times. For in-seat transfers, we expect the stop to be the last stop-time in the trip sequence for `from_stop_id` and the first stop-time for `to_stop_id`. If you are intentionally using this feature to model mid-trip transfers, you can ignore this warning, but be aware that this functionality is still considered to be partially experimental in some interpretations of the spec." - }, - { - "rule": "translation_foreign_key_violation", - "description": "An entity with the given `record_id` and `record_sub_id` cannot be found in the referenced table." - }, - { - "rule": "translation_unexpected_value", - "description": "A field in a translations row has value but must be empty." - }, - { - "rule": "wrong_parent_location_type", - "description": "Value of field `location_type` of parent found in field `parent_station` is invalid." - }, - { - "rule": "attribution_without_role", - "description": "At least one of the fields `is_producer`, `is_operator`, or `is_authority` should be set to 1." - }, - { - "rule": "duplicate_route_name", - "description": "All routes of the same `route_type` with the same `agency_id` should have unique combinations of `route_short_name` and `route_long_name`." - }, - { - "rule": "empty_row", - "description": "A row in the input file has only spaces." - }, - { - "rule": "equal_shape_distance_same_coordinates", - "description": "When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and the same coordinates indicate a duplicative shape point." - }, - { - "rule": "fast_travel_between_consecutive_stops", - "description": "A transit vehicle moves too fast between two consecutive stops. The speed threshold depends on route type." - }, - { - "rule": "fast_travel_between_far_stops", - "description": "A transit vehicle moves too fast between far consecutive stops (more than in 10 km apart). " - }, - { - "rule": "feed_expiration_date7_days", - "description": "The dataset expiration date defined in `feed_info.txt` is in seven days or less. At any time, the published GTFS dataset should be valid for at least the next 7 days." - }, - { - "rule": "feed_expiration_date30_days", - "description": "At any time, the GTFS dataset should cover at least the next 30 days of service, and ideally for as long as the operator is confident that the schedule will continue to be operated." - }, - { - "rule": "feed_info_lang_and_agency_lang_mismatch", - "description": " The default language may be multilingual for datasets with the original text in multiple languages. In such cases, the feed_lang field should contain the language code mul defined by the norm ISO 639-2." - }, - { - "rule": "inconsistent_agency_lang", - "description": "Agencies from GTFS `agency.txt` have been found to have different languages." - }, - { - "rule": "leading_or_trailing_whitespaces", - "description": "The value in CSV file has leading or trailing whitespaces." - }, - { - "rule": "missing_feed_info_date", - "description": "Even though `feed_info.start_date` and `feed_info.end_date` are optional, if one field is provided the second one should also be provided." - }, - { - "rule": "missing_recommended_file", - "description": "A recommended file is missing." - }, - { - "rule": "missing_recommended_field", - "description": "The given field has no value in some input row, even though values are recommended." - }, - { - "rule": "missing_timepoint_column", - "description": "The `timepoint` column should be provided." - }, - { - "rule": "missing_timepoint_value", - "description": "Even though the column `timepoint` is optional in `stop_times.txt` according to the specification, `stop_times.timepoint` should not be empty when provided. " - }, - { - "rule": "more_than_one_entity", - "description": "The file is expected to have a single entity but has more (e.g., \"feed_info.txt\")." - }, - { - "rule": "non_ascii_or_non_printable_char", - "description": "A value of a field with type `id` contains non ASCII or non printable characters. This is not recommended." - }, - { - "rule": "pathway_dangling_generic_node", - "description": "A generic node has only one incident location in a pathway graph. Such generic node is useless" - }, - { - "rule": "pathway_loop", - "description": "A pathway should not have same values for `from_stop_id` and `to_stop_id`." - }, - { - "rule": "platform_without_parent_station", - "description": "A platform has no `parent_station` field set." - }, - { - "rule": "route_color_contrast", - "description": "A route's color and `route_text_color` should be contrasting." - }, - { - "rule": "route_short_and_long_name_equal", - "description": "A single route has the same values for `route_short_name` and `route_long_name`." - }, - { - "rule": "route_short_name_too_long", - "description": "Short name of a route is too long (more than 12 characters)." - }, - { - "rule": "same_name_and_description_for_route", - "description": "The GTFS spec defines `routes.txt` [route_desc](https://gtfs.org/reference/static/#routestxt) as:" - }, - { - "rule": "same_name_and_description_for_stop", - "description": "The GTFS spec defines `stops.txt` [stop_description](https://gtfs.org/reference/static/#stopstxt) as:" - }, - { - "rule": "same_route_and_agency_url", - "description": "A route should not have the same `routes.route_url` as a record from `agency.txt`." - }, - { - "rule": "same_stop_and_agency_url", - "description": "A stop should not have the same `stops.stop_url` as a record from `agency.txt`." - }, - { - "rule": "same_stop_and_route_url", - "description": "A stop should not have the same `stop.stop_url` as a record from `routes.txt`." - }, - { - "rule": "stop_has_too_many_matches_for_shape", - "description": "A stop entry that has many potential matches to the trip's path of travel, as defined by the shape entry in `shapes.txt`." - }, - { - "rule": "stops_match_shape_out_of_order", - "description": "Two stop entries in `stop_times.txt` are different than their arrival-departure order as defined by the shape in the `shapes.txt` file." - }, - { - "rule": "stop_too_far_from_shape", - "description": "Per GTFS Best Practices, route alignments (in `shapes.txt`) should be within 100 meters of stop locations which a trip serves." - }, - { - "rule": "stop_too_far_from_shape_using_user_distance", - "description": "A stop time entry that is a large distance away from the location of the shape in `shapes.txt` as defined by `shape_dist_traveled` values." - }, - { - "rule": "stop_without_stop_time", - "description": "A stop in `stops.txt` is not referenced by any `stop_times.stop_id`, so it is not used by any trip." - }, - { - "rule": "translation_unknown_table_name", - "description": "A translation references an unknown or missing GTFS table." - }, - { - "rule": "unexpected_enum_value", - "description": "An enum has an unexpected value." - }, - { - "rule": "unusable_trip", - "description": "A trip must visit more than one stop in stop_times.txt to be usable by passengers for boarding and alighting." - }, - { - "rule": "unused_shape", - "description": "All records defined by GTFS `shapes.txt` should be used in `trips.txt`." - }, - { - "rule": "unused_trip", - "description": "Trips should be referred to at least once in `stop_times.txt`." - }, - { "rule": "unknown_column", "description": "A column is unknown." }, - { "rule": "unknown_file", "description": "A file is unknown." }, - { "rule": "i_o_error", "description": "Error in IO operation." }, - { - "rule": "runtime_exception_in_loader_error", - "description": "A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred while loading a table. This normally indicates a bug in validator." - }, - { - "rule": "runtime_exception_in_validator_error", - "description": "A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred during validation. This normally indicates a bug in validator code, e.g., in a custom validator class." - }, - { - "rule": "thread_execution_error", - "description": "An [ExecutionException](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutionException.html) occurred during multithreaded validation." - }, - { - "rule": "u_r_i_syntax_error", - "description": "A string could not be parsed as a URI reference." - } -] +[{"rule":"block_trips_with_overlapping_stop_times","description":"Trips with the same block id have overlapping stop times."},{"rule":"csv_parsing_failed","description":"Parsing of a CSV file failed. One common case of the problem is when a cell value contains more than 4096 characters."},{"rule":"decreasing_shape_distance","description":"When sorted by `shape.shape_pt_sequence`, two consecutive shape points must not have decreasing values for `shape_dist_traveled`. "},{"rule":"decreasing_or_equal_stop_time_distance","description":"When sorted by `stop_times.stop_sequence`, two consecutive entries in `stop_times.txt` should have increasing distance, based on the field `shape_dist_traveled`. If the values are equal, this is considered as an error. "},{"rule":"duplicated_column","description":"The input file CSV header has the same column name repeated."},{"rule":"duplicate_key","description":"The values of the given key and rows are duplicates."},{"rule":"empty_column_name","description":"A column name has not been provided. Such columns are skipped by the validator."},{"rule":"empty_file","description":"Empty csv file found in the archive: file does not have any headers, or is a required file and does not have any data. The GTFS specification requires the first line of each file to contain field names and required files must have data."},{"rule":"equal_shape_distance_diff_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and different coordinates indicate an error."},{"rule":"fare_transfer_rule_duration_limit_type_without_duration_limit","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit_type` field but no `duration_limit` specified."},{"rule":"fare_transfer_rule_duration_limit_without_type","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit` field but no `duration_limit_type` specified."},{"rule":"fare_transfer_rule_invalid_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `transfer_count` with an invalid value."},{"rule":"fare_transfer_rule_missing_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` equal to `to_leg_group_id`, but has no `transfer_count` specified. Per the spec, `transfer_count` is required if the two leg group ids are equal."},{"rule":"fare_transfer_rule_with_forbidden_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` not equal to `to_leg_group_id`, but has `transfer_count` specified. Per the spec, `transfer_count` is forbidden if the two leg group ids are not equal."},{"rule":"foreign_key_violation","description":"A foreign key references the primary key of another file. A foreign key violation means that the foreign key referenced from a given row (the child file) cannot be found in the corresponding file (the parent file). The Foreign keys are defined in the specification under \"Type\" for each file."},{"rule":"inconsistent_agency_timezone","description":"Agencies from GTFS `agency.txt` have been found to have different timezones."},{"rule":"invalid_color","description":"Value of field with type `color` is not valid. A color must be encoded as a six-digit hexadecimal number. The leading \"#\" is not included."},{"rule":"invalid_currency","description":"Value of field with type `currency` is not valid. Currency code must follow ISO 4217"},{"rule":"invalid_currency_amount","description":"A currency amount field has a value that does not match the format (e.g. expected number of decimal places) of its corresponding currency code field. The number of decimal places is specified by ISO 4217."},{"rule":"invalid_date","description":"Value of field with type `date` is not valid. Dates must have the YYYYMMDD format."},{"rule":"invalid_email","description":"Value of field with type `email` is not valid. Definitions for valid emails are quite vague. We perform strict validation using the Apache Commons EmailValidator."},{"rule":"invalid_float","description":"Value of field with type `float` is not valid. "},{"rule":"invalid_integer","description":"Value of field with type `integer` is not valid. "},{"rule":"invalid_language_code","description":"Value of field with type `language` is not valid. Language codes must follow IETF BCP 47."},{"rule":"invalid_phone_number","description":"Value of field with type `phone number` is not valid. This rule uses the [PhoneNumberUtil](https://www.javadoc.io/doc/com.googlecode.libphonenumber/libphonenumber/8.4.1/com/google/i18n/phonenumbers/PhoneNumberUtil.html) class to validate a phone number based on a country code. If no country code is provided in the parameters used to run the validator, this notice won't be emitted. "},{"rule":"invalid_row_length","description":"A row in the input file has a different number of values than specified by the CSV header."},{"rule":"invalid_time","description":"Value of field with type `time` is not valid. Time must be in the `H:MM:SS`, `HH:MM:SS` or `HHH:MM:SS` format."},{"rule":"invalid_timezone","description":"Value of field with type `timezone` is not valid.Timezones are defined at www.iana.org. Timezone names never contain the space character but may contain an underscore. Refer to Wikipedia for a list of valid values."},{"rule":"invalid_url","description":"Value of field with type `url` is not valid. Definitions for valid URLs are quite vague. We perform strict validation using the Apache Commons UrlValidator."},{"rule":"location_without_parent_station","description":"A location that must have `parent_station` field does not have it. The following location types must have `parent_station`: entrance, generic node, boarding_area."},{"rule":"location_with_unexpected_stop_time","description":"Referenced locations (using `stop_times.stop_id`) must be stops/platforms, i.e. their `stops.location_type` value must be 0 or empty."},{"rule":"missing_calendar_and_calendar_date_files","description":"Both files calendar_dates.txt and calendar.txt are missing from the GTFS archive. At least one of the files must be provided."},{"rule":"missing_level_id","description":"GTFS file `levels.txt` is required for elevator (`pathway_mode=5`). A row from `stops.txt` linked to an elevator pathway has no value for `stops.level_id`."},{"rule":"missing_required_column","description":"A required column is missing in the input file."},{"rule":"missing_required_field","description":"The given field has no value in some input row, even though values are required."},{"rule":"missing_required_file","description":"A required file is missing. If this notice is triggered for every core file, it might be a problem with the input. To create a zip file from the GTFS `.txt` files: select all the `.txt` files, right-click, and compress. Do not compress the folder containing the files. "},{"rule":"missing_stop_name","description":"`stops.stop_name` is required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"missing_trip_edge","description":"First and last stop of a trip must define both `arrival_time` and `departure_time` fields."},{"rule":"new_line_in_value","description":"A value in CSV file has a new line or carriage return."},{"rule":"number_out_of_range","description":"The values in the given column of the input rows are out of range."},{"rule":"overlapping_frequency","description":"Trip frequencies must not overlap in time"},{"rule":"pathway_to_platform_with_boarding_areas","description":"A pathway has an endpoint that is a platform which has boarding areas. A platform that has boarding"},{"rule":"pathway_to_wrong_location_type","description":"A pathway has an endpoint that is a station. Pathways endpoints must be platforms (stops),"},{"rule":"pathway_unreachable_location","description":"A location belongs to a station that has pathways and is not reachable at least in one direction:"},{"rule":"point_near_origin","description":"A point is too close to origin `(0, 0)`."},{"rule":"point_near_pole","description":"A point is too close to the North or South Pole."},{"rule":"route_both_short_and_long_name_missing","description":"Both short_name and long_name are missing for a route."},{"rule":"start_and_end_range_equal","description":"The fields `frequencies.start_date` and `frequencies.end_date` have been found equal in `frequencies.txt`. The GTFS spec is currently unclear how this case should be handled (e.g., is it a trip that circulates once?). It is recommended to use a trip not defined via frequencies.txt for this case."},{"rule":"start_and_end_range_out_of_order","description":"Date or time fields have been found out of order in `calendar.txt`, `feed_info.txt` and `stop_times.txt`."},{"rule":"station_with_parent_station","description":"Field `parent_station` must be empty when `location_type` is 1."},{"rule":"stop_time_timepoint_without_times","description":"Any records with `stop_times.timepoint` set to 1 must define a value for `stop_times.arrival_time` and `stop_times.departure_time` fields."},{"rule":"stop_time_with_arrival_before_previous_departure_time","description":"For a given `trip_id`, the `arrival_time` of (n+1)-th stoptime in sequence must not precede the `departure_time` of n-th stoptime in sequence in `stop_times.txt`."},{"rule":"stop_time_with_only_arrival_or_departure_time","description":"Missing `stop_time.arrival_time` or `stop_time.departure_time`"},{"rule":"stop_without_location","description":"`stop_lat` and/or `stop_lon` are required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"stop_without_zone_id","description":"If `fare_rules.txt` is provided, and `fare_rules.txt` uses at least one column among `origin_id`, `destination_id`, and `contains_id`, then all stops and platforms (location_type = 0) must have `stops.zone_id` assigned. "},{"rule":"too_many_rows","description":"A CSV file has too many rows. Feeds with too large files cannot be processed in a reasonable time by GTFS consumers."},{"rule":"transfer_with_invalid_stop_location_type","description":"A `from_stop_id` or `to_stop_id` field from GTFS file `transfers.txt` references a stop that has a `location_type` other than 0 or 1 (aka Stop/Platform or Station)."},{"rule":"transfer_with_invalid_trip_and_route","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a route that does not match its `trips.txt` `route_id`."},{"rule":"transfer_with_invalid_trip_and_stop","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a stop that is not included in the referenced trip's stop-times."},{"rule":"transfer_with_suspicious_mid_trip_in_seat","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` with an in-seat transfer type references a stop that is not in the expected position in the trip's stop-times. For in-seat transfers, we expect the stop to be the last stop-time in the trip sequence for `from_stop_id` and the first stop-time for `to_stop_id`. If you are intentionally using this feature to model mid-trip transfers, you can ignore this warning, but be aware that this functionality is still considered to be partially experimental in some interpretations of the spec."},{"rule":"translation_foreign_key_violation","description":"An entity with the given `record_id` and `record_sub_id` cannot be found in the referenced table."},{"rule":"translation_unexpected_value","description":"A field in a translations row has value but must be empty."},{"rule":"wrong_parent_location_type","description":"Value of field `location_type` of parent found in field `parent_station` is invalid."},{"rule":"attribution_without_role","description":"At least one of the fields `is_producer`, `is_operator`, or `is_authority` should be set to 1."},{"rule":"duplicate_route_name","description":"All routes of the same `route_type` with the same `agency_id` should have unique combinations of `route_short_name` and `route_long_name`."},{"rule":"empty_row","description":"A row in the input file has only spaces."},{"rule":"equal_shape_distance_same_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and the same coordinates indicate a duplicative shape point."},{"rule":"fast_travel_between_consecutive_stops","description":"A transit vehicle moves too fast between two consecutive stops. The speed threshold depends on route type."},{"rule":"fast_travel_between_far_stops","description":"A transit vehicle moves too fast between far consecutive stops (more than in 10 km apart). "},{"rule":"feed_expiration_date7_days","description":"The dataset expiration date defined in `feed_info.txt` is in seven days or less. At any time, the published GTFS dataset should be valid for at least the next 7 days."},{"rule":"feed_expiration_date30_days","description":"At any time, the GTFS dataset should cover at least the next 30 days of service, and ideally for as long as the operator is confident that the schedule will continue to be operated."},{"rule":"feed_info_lang_and_agency_lang_mismatch","description":" The default language may be multilingual for datasets with the original text in multiple languages. In such cases, the feed_lang field should contain the language code mul defined by the norm ISO 639-2."},{"rule":"inconsistent_agency_lang","description":"Agencies from GTFS `agency.txt` have been found to have different languages."},{"rule":"leading_or_trailing_whitespaces","description":"The value in CSV file has leading or trailing whitespaces."},{"rule":"missing_feed_info_date","description":"Even though `feed_info.start_date` and `feed_info.end_date` are optional, if one field is provided the second one should also be provided."},{"rule":"missing_recommended_file","description":"A recommended file is missing."},{"rule":"missing_recommended_field","description":"The given field has no value in some input row, even though values are recommended."},{"rule":"missing_timepoint_column","description":"The `timepoint` column should be provided."},{"rule":"missing_timepoint_value","description":"Even though the column `timepoint` is optional in `stop_times.txt` according to the specification, `stop_times.timepoint` should not be empty when provided. "},{"rule":"more_than_one_entity","description":"The file is expected to have a single entity but has more (e.g., \"feed_info.txt\")."},{"rule":"non_ascii_or_non_printable_char","description":"A value of a field with type `id` contains non ASCII or non printable characters. This is not recommended."},{"rule":"pathway_dangling_generic_node","description":"A generic node has only one incident location in a pathway graph. Such generic node is useless"},{"rule":"pathway_loop","description":"A pathway should not have same values for `from_stop_id` and `to_stop_id`."},{"rule":"platform_without_parent_station","description":"A platform has no `parent_station` field set."},{"rule":"route_color_contrast","description":"A route's color and `route_text_color` should be contrasting."},{"rule":"route_short_and_long_name_equal","description":"A single route has the same values for `route_short_name` and `route_long_name`."},{"rule":"route_short_name_too_long","description":"Short name of a route is too long (more than 12 characters)."},{"rule":"same_name_and_description_for_route","description":"The GTFS spec defines `routes.txt` [route_desc](https://gtfs.org/reference/static/#routestxt) as:"},{"rule":"same_name_and_description_for_stop","description":"The GTFS spec defines `stops.txt` [stop_description](https://gtfs.org/reference/static/#stopstxt) as:"},{"rule":"same_route_and_agency_url","description":"A route should not have the same `routes.route_url` as a record from `agency.txt`."},{"rule":"same_stop_and_agency_url","description":"A stop should not have the same `stops.stop_url` as a record from `agency.txt`."},{"rule":"same_stop_and_route_url","description":"A stop should not have the same `stop.stop_url` as a record from `routes.txt`."},{"rule":"stop_has_too_many_matches_for_shape","description":"A stop entry that has many potential matches to the trip's path of travel, as defined by the shape entry in `shapes.txt`."},{"rule":"stops_match_shape_out_of_order","description":"Two stop entries in `stop_times.txt` are different than their arrival-departure order as defined by the shape in the `shapes.txt` file."},{"rule":"stop_too_far_from_shape","description":"Per GTFS Best Practices, route alignments (in `shapes.txt`) should be within 100 meters of stop locations which a trip serves."},{"rule":"stop_too_far_from_shape_using_user_distance","description":"A stop time entry that is a large distance away from the location of the shape in `shapes.txt` as defined by `shape_dist_traveled` values."},{"rule":"stop_without_stop_time","description":"A stop in `stops.txt` is not referenced by any `stop_times.stop_id`, so it is not used by any trip."},{"rule":"translation_unknown_table_name","description":"A translation references an unknown or missing GTFS table."},{"rule":"unexpected_enum_value","description":"An enum has an unexpected value."},{"rule":"unusable_trip","description":"A trip must visit more than one stop in stop_times.txt to be usable by passengers for boarding and alighting."},{"rule":"unused_shape","description":"All records defined by GTFS `shapes.txt` should be used in `trips.txt`."},{"rule":"unused_trip","description":"Trips should be referred to at least once in `stop_times.txt`."},{"rule":"unknown_column","description":"A column is unknown."},{"rule":"unknown_file","description":"A file is unknown."},{"rule":"i_o_error","description":"Error in IO operation."},{"rule":"runtime_exception_in_loader_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred while loading a table. This normally indicates a bug in validator."},{"rule":"runtime_exception_in_validator_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred during validation. This normally indicates a bug in validator code, e.g., in a custom validator class."},{"rule":"thread_execution_error","description":"An [ExecutionException](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutionException.html) occurred during multithreaded validation."},{"rule":"u_r_i_syntax_error","description":"A string could not be parsed as a URI reference."}] \ No newline at end of file diff --git a/package.json b/package.json index b2bdf6e2b..ddd728d87 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "cover-end-to-end": "cross-env COLLECT_COVERAGE=true npm run test-end-to-end", "deploy": "mastarm deploy --minify --env production", "flow": "mastarm flow", + "import-mobility-data": "node scripts/import-mobility-data-rules.mjs", "lint": "mastarm lint __tests__ lib scripts --quiet", "lint-messages": "node scripts/lint-messages.js", "postinstall": "patch-package && husky install", diff --git a/scripts/import-mobility-data-rules.mjs b/scripts/import-mobility-data-rules.mjs new file mode 100644 index 000000000..08b1da749 --- /dev/null +++ b/scripts/import-mobility-data-rules.mjs @@ -0,0 +1,39 @@ +import { writeFileSync } from 'fs' + +import fetch from 'isomorphic-fetch' + +const findDescription = (text, header) => { + // Split the rules.MD file into lines + let lines = text.split('\n') + // Remove the distracting top part of the file + const moreDetails = lines.findIndex(l => l.includes('More details')) + lines = lines.splice(moreDetails) + + const description = lines.findIndex(l => l.includes(header)) + + // Edge case? Something is strange about the file + if (header === 'equal_shape_distance_diff_coordinates') { + return lines[description + 4] + } + + return lines[description + 2] +} + +fetch( + 'https://raw.githubusercontent.com/MobilityData/gtfs-validator/master/RULES.md' +) + .then((raw) => raw.text()) + .then((text) => { + // Match all rule headings + const rules = text + .match(/### .*/g) + .filter((rule) => rule.includes('_')) + .map((rule) => rule.split('### ')[1]) + + // Extract descriptions + const rulesAndDescriptions = rules.map((rule) => ({ + rule, + description: findDescription(text, rule) + })) + writeFileSync('lib/manager/components/validation/rules.json', JSON.stringify(rulesAndDescriptions)) + }) From 7b999472abc268f4ed448ec1edfcf1c13c1089cc Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 8 Mar 2023 14:04:07 -0500 Subject: [PATCH 14/28] lint --- .../components/validation/MobilityDataValidationResult.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 1b3214fd6..cdbc65473 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -113,11 +113,7 @@ const MobilityDataValidationResult = (props) => { title={`${toSentenceCase(notice.severity)} priority`} > {toSentenceCase(notice.code.replaceAll('_', ' ').toLowerCase())} From 177a10684acf982b5b44f068e723b47fdbf733f6 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 9 Mar 2023 08:43:31 -0500 Subject: [PATCH 15/28] render MB rules as html --- .../components/validation/MobilityDataValidationResult.js | 4 +++- package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index cdbc65473..3dcee42e3 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -3,6 +3,7 @@ import React, { useState } from 'react' import Icon from '@conveyal/woonerf/components/icon' import { ListGroupItem, Table } from 'react-bootstrap' +import Markdown from 'markdown-to-jsx'; import toSentenceCase from '../../../common/util/to-sentence-case' import { @@ -128,10 +129,11 @@ const MobilityDataValidationResult = (props) => {

{expanded && ( <> -

{rule.description}

+

{rule.description}

More details diff --git a/package.json b/package.json index ddd728d87..5250d8019 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "leaflet": "^1.7.1", "leaflet-textpath": "^1.2.3", "lodash": "^4.17.10", + "markdown-to-jsx": "^7.1.9", "moment": "^2.29.4", "numeral": "2.0.4", "object-path": "^0.11.5", diff --git a/yarn.lock b/yarn.lock index d91b16d50..8d2a47ed3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8709,6 +8709,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-to-jsx@^7.1.9: + version "7.1.9" + resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.9.tgz#1ffae0cda07c189163d273bd57a5b8f8f8745586" + integrity sha512-x4STVIKIJR0mGgZIZ5RyAeQD7FEZd5tS8m/htbcVGlex32J+hlSLj+ExrHCxP6nRKF1EKbcO7i6WhC1GtOpBlA== + marked-terminal@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-4.1.0.tgz#01087372d3636dc7cb286475a1d6147187f500e0" From 7907ced6000afb3a02385db8e7d628087240aefd Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 9 Mar 2023 09:05:40 -0500 Subject: [PATCH 16/28] import-mobility-data-rules: support edge cases --- lib/manager/components/validation/rules.json | 2 +- scripts/import-mobility-data-rules.mjs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/manager/components/validation/rules.json b/lib/manager/components/validation/rules.json index ce9f7a6d4..bd270746c 100644 --- a/lib/manager/components/validation/rules.json +++ b/lib/manager/components/validation/rules.json @@ -1 +1 @@ -[{"rule":"block_trips_with_overlapping_stop_times","description":"Trips with the same block id have overlapping stop times."},{"rule":"csv_parsing_failed","description":"Parsing of a CSV file failed. One common case of the problem is when a cell value contains more than 4096 characters."},{"rule":"decreasing_shape_distance","description":"When sorted by `shape.shape_pt_sequence`, two consecutive shape points must not have decreasing values for `shape_dist_traveled`. "},{"rule":"decreasing_or_equal_stop_time_distance","description":"When sorted by `stop_times.stop_sequence`, two consecutive entries in `stop_times.txt` should have increasing distance, based on the field `shape_dist_traveled`. If the values are equal, this is considered as an error. "},{"rule":"duplicated_column","description":"The input file CSV header has the same column name repeated."},{"rule":"duplicate_key","description":"The values of the given key and rows are duplicates."},{"rule":"empty_column_name","description":"A column name has not been provided. Such columns are skipped by the validator."},{"rule":"empty_file","description":"Empty csv file found in the archive: file does not have any headers, or is a required file and does not have any data. The GTFS specification requires the first line of each file to contain field names and required files must have data."},{"rule":"equal_shape_distance_diff_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and different coordinates indicate an error."},{"rule":"fare_transfer_rule_duration_limit_type_without_duration_limit","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit_type` field but no `duration_limit` specified."},{"rule":"fare_transfer_rule_duration_limit_without_type","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit` field but no `duration_limit_type` specified."},{"rule":"fare_transfer_rule_invalid_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `transfer_count` with an invalid value."},{"rule":"fare_transfer_rule_missing_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` equal to `to_leg_group_id`, but has no `transfer_count` specified. Per the spec, `transfer_count` is required if the two leg group ids are equal."},{"rule":"fare_transfer_rule_with_forbidden_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` not equal to `to_leg_group_id`, but has `transfer_count` specified. Per the spec, `transfer_count` is forbidden if the two leg group ids are not equal."},{"rule":"foreign_key_violation","description":"A foreign key references the primary key of another file. A foreign key violation means that the foreign key referenced from a given row (the child file) cannot be found in the corresponding file (the parent file). The Foreign keys are defined in the specification under \"Type\" for each file."},{"rule":"inconsistent_agency_timezone","description":"Agencies from GTFS `agency.txt` have been found to have different timezones."},{"rule":"invalid_color","description":"Value of field with type `color` is not valid. A color must be encoded as a six-digit hexadecimal number. The leading \"#\" is not included."},{"rule":"invalid_currency","description":"Value of field with type `currency` is not valid. Currency code must follow ISO 4217"},{"rule":"invalid_currency_amount","description":"A currency amount field has a value that does not match the format (e.g. expected number of decimal places) of its corresponding currency code field. The number of decimal places is specified by ISO 4217."},{"rule":"invalid_date","description":"Value of field with type `date` is not valid. Dates must have the YYYYMMDD format."},{"rule":"invalid_email","description":"Value of field with type `email` is not valid. Definitions for valid emails are quite vague. We perform strict validation using the Apache Commons EmailValidator."},{"rule":"invalid_float","description":"Value of field with type `float` is not valid. "},{"rule":"invalid_integer","description":"Value of field with type `integer` is not valid. "},{"rule":"invalid_language_code","description":"Value of field with type `language` is not valid. Language codes must follow IETF BCP 47."},{"rule":"invalid_phone_number","description":"Value of field with type `phone number` is not valid. This rule uses the [PhoneNumberUtil](https://www.javadoc.io/doc/com.googlecode.libphonenumber/libphonenumber/8.4.1/com/google/i18n/phonenumbers/PhoneNumberUtil.html) class to validate a phone number based on a country code. If no country code is provided in the parameters used to run the validator, this notice won't be emitted. "},{"rule":"invalid_row_length","description":"A row in the input file has a different number of values than specified by the CSV header."},{"rule":"invalid_time","description":"Value of field with type `time` is not valid. Time must be in the `H:MM:SS`, `HH:MM:SS` or `HHH:MM:SS` format."},{"rule":"invalid_timezone","description":"Value of field with type `timezone` is not valid.Timezones are defined at www.iana.org. Timezone names never contain the space character but may contain an underscore. Refer to Wikipedia for a list of valid values."},{"rule":"invalid_url","description":"Value of field with type `url` is not valid. Definitions for valid URLs are quite vague. We perform strict validation using the Apache Commons UrlValidator."},{"rule":"location_without_parent_station","description":"A location that must have `parent_station` field does not have it. The following location types must have `parent_station`: entrance, generic node, boarding_area."},{"rule":"location_with_unexpected_stop_time","description":"Referenced locations (using `stop_times.stop_id`) must be stops/platforms, i.e. their `stops.location_type` value must be 0 or empty."},{"rule":"missing_calendar_and_calendar_date_files","description":"Both files calendar_dates.txt and calendar.txt are missing from the GTFS archive. At least one of the files must be provided."},{"rule":"missing_level_id","description":"GTFS file `levels.txt` is required for elevator (`pathway_mode=5`). A row from `stops.txt` linked to an elevator pathway has no value for `stops.level_id`."},{"rule":"missing_required_column","description":"A required column is missing in the input file."},{"rule":"missing_required_field","description":"The given field has no value in some input row, even though values are required."},{"rule":"missing_required_file","description":"A required file is missing. If this notice is triggered for every core file, it might be a problem with the input. To create a zip file from the GTFS `.txt` files: select all the `.txt` files, right-click, and compress. Do not compress the folder containing the files. "},{"rule":"missing_stop_name","description":"`stops.stop_name` is required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"missing_trip_edge","description":"First and last stop of a trip must define both `arrival_time` and `departure_time` fields."},{"rule":"new_line_in_value","description":"A value in CSV file has a new line or carriage return."},{"rule":"number_out_of_range","description":"The values in the given column of the input rows are out of range."},{"rule":"overlapping_frequency","description":"Trip frequencies must not overlap in time"},{"rule":"pathway_to_platform_with_boarding_areas","description":"A pathway has an endpoint that is a platform which has boarding areas. A platform that has boarding"},{"rule":"pathway_to_wrong_location_type","description":"A pathway has an endpoint that is a station. Pathways endpoints must be platforms (stops),"},{"rule":"pathway_unreachable_location","description":"A location belongs to a station that has pathways and is not reachable at least in one direction:"},{"rule":"point_near_origin","description":"A point is too close to origin `(0, 0)`."},{"rule":"point_near_pole","description":"A point is too close to the North or South Pole."},{"rule":"route_both_short_and_long_name_missing","description":"Both short_name and long_name are missing for a route."},{"rule":"start_and_end_range_equal","description":"The fields `frequencies.start_date` and `frequencies.end_date` have been found equal in `frequencies.txt`. The GTFS spec is currently unclear how this case should be handled (e.g., is it a trip that circulates once?). It is recommended to use a trip not defined via frequencies.txt for this case."},{"rule":"start_and_end_range_out_of_order","description":"Date or time fields have been found out of order in `calendar.txt`, `feed_info.txt` and `stop_times.txt`."},{"rule":"station_with_parent_station","description":"Field `parent_station` must be empty when `location_type` is 1."},{"rule":"stop_time_timepoint_without_times","description":"Any records with `stop_times.timepoint` set to 1 must define a value for `stop_times.arrival_time` and `stop_times.departure_time` fields."},{"rule":"stop_time_with_arrival_before_previous_departure_time","description":"For a given `trip_id`, the `arrival_time` of (n+1)-th stoptime in sequence must not precede the `departure_time` of n-th stoptime in sequence in `stop_times.txt`."},{"rule":"stop_time_with_only_arrival_or_departure_time","description":"Missing `stop_time.arrival_time` or `stop_time.departure_time`"},{"rule":"stop_without_location","description":"`stop_lat` and/or `stop_lon` are required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"stop_without_zone_id","description":"If `fare_rules.txt` is provided, and `fare_rules.txt` uses at least one column among `origin_id`, `destination_id`, and `contains_id`, then all stops and platforms (location_type = 0) must have `stops.zone_id` assigned. "},{"rule":"too_many_rows","description":"A CSV file has too many rows. Feeds with too large files cannot be processed in a reasonable time by GTFS consumers."},{"rule":"transfer_with_invalid_stop_location_type","description":"A `from_stop_id` or `to_stop_id` field from GTFS file `transfers.txt` references a stop that has a `location_type` other than 0 or 1 (aka Stop/Platform or Station)."},{"rule":"transfer_with_invalid_trip_and_route","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a route that does not match its `trips.txt` `route_id`."},{"rule":"transfer_with_invalid_trip_and_stop","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a stop that is not included in the referenced trip's stop-times."},{"rule":"transfer_with_suspicious_mid_trip_in_seat","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` with an in-seat transfer type references a stop that is not in the expected position in the trip's stop-times. For in-seat transfers, we expect the stop to be the last stop-time in the trip sequence for `from_stop_id` and the first stop-time for `to_stop_id`. If you are intentionally using this feature to model mid-trip transfers, you can ignore this warning, but be aware that this functionality is still considered to be partially experimental in some interpretations of the spec."},{"rule":"translation_foreign_key_violation","description":"An entity with the given `record_id` and `record_sub_id` cannot be found in the referenced table."},{"rule":"translation_unexpected_value","description":"A field in a translations row has value but must be empty."},{"rule":"wrong_parent_location_type","description":"Value of field `location_type` of parent found in field `parent_station` is invalid."},{"rule":"attribution_without_role","description":"At least one of the fields `is_producer`, `is_operator`, or `is_authority` should be set to 1."},{"rule":"duplicate_route_name","description":"All routes of the same `route_type` with the same `agency_id` should have unique combinations of `route_short_name` and `route_long_name`."},{"rule":"empty_row","description":"A row in the input file has only spaces."},{"rule":"equal_shape_distance_same_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and the same coordinates indicate a duplicative shape point."},{"rule":"fast_travel_between_consecutive_stops","description":"A transit vehicle moves too fast between two consecutive stops. The speed threshold depends on route type."},{"rule":"fast_travel_between_far_stops","description":"A transit vehicle moves too fast between far consecutive stops (more than in 10 km apart). "},{"rule":"feed_expiration_date7_days","description":"The dataset expiration date defined in `feed_info.txt` is in seven days or less. At any time, the published GTFS dataset should be valid for at least the next 7 days."},{"rule":"feed_expiration_date30_days","description":"At any time, the GTFS dataset should cover at least the next 30 days of service, and ideally for as long as the operator is confident that the schedule will continue to be operated."},{"rule":"feed_info_lang_and_agency_lang_mismatch","description":" The default language may be multilingual for datasets with the original text in multiple languages. In such cases, the feed_lang field should contain the language code mul defined by the norm ISO 639-2."},{"rule":"inconsistent_agency_lang","description":"Agencies from GTFS `agency.txt` have been found to have different languages."},{"rule":"leading_or_trailing_whitespaces","description":"The value in CSV file has leading or trailing whitespaces."},{"rule":"missing_feed_info_date","description":"Even though `feed_info.start_date` and `feed_info.end_date` are optional, if one field is provided the second one should also be provided."},{"rule":"missing_recommended_file","description":"A recommended file is missing."},{"rule":"missing_recommended_field","description":"The given field has no value in some input row, even though values are recommended."},{"rule":"missing_timepoint_column","description":"The `timepoint` column should be provided."},{"rule":"missing_timepoint_value","description":"Even though the column `timepoint` is optional in `stop_times.txt` according to the specification, `stop_times.timepoint` should not be empty when provided. "},{"rule":"more_than_one_entity","description":"The file is expected to have a single entity but has more (e.g., \"feed_info.txt\")."},{"rule":"non_ascii_or_non_printable_char","description":"A value of a field with type `id` contains non ASCII or non printable characters. This is not recommended."},{"rule":"pathway_dangling_generic_node","description":"A generic node has only one incident location in a pathway graph. Such generic node is useless"},{"rule":"pathway_loop","description":"A pathway should not have same values for `from_stop_id` and `to_stop_id`."},{"rule":"platform_without_parent_station","description":"A platform has no `parent_station` field set."},{"rule":"route_color_contrast","description":"A route's color and `route_text_color` should be contrasting."},{"rule":"route_short_and_long_name_equal","description":"A single route has the same values for `route_short_name` and `route_long_name`."},{"rule":"route_short_name_too_long","description":"Short name of a route is too long (more than 12 characters)."},{"rule":"same_name_and_description_for_route","description":"The GTFS spec defines `routes.txt` [route_desc](https://gtfs.org/reference/static/#routestxt) as:"},{"rule":"same_name_and_description_for_stop","description":"The GTFS spec defines `stops.txt` [stop_description](https://gtfs.org/reference/static/#stopstxt) as:"},{"rule":"same_route_and_agency_url","description":"A route should not have the same `routes.route_url` as a record from `agency.txt`."},{"rule":"same_stop_and_agency_url","description":"A stop should not have the same `stops.stop_url` as a record from `agency.txt`."},{"rule":"same_stop_and_route_url","description":"A stop should not have the same `stop.stop_url` as a record from `routes.txt`."},{"rule":"stop_has_too_many_matches_for_shape","description":"A stop entry that has many potential matches to the trip's path of travel, as defined by the shape entry in `shapes.txt`."},{"rule":"stops_match_shape_out_of_order","description":"Two stop entries in `stop_times.txt` are different than their arrival-departure order as defined by the shape in the `shapes.txt` file."},{"rule":"stop_too_far_from_shape","description":"Per GTFS Best Practices, route alignments (in `shapes.txt`) should be within 100 meters of stop locations which a trip serves."},{"rule":"stop_too_far_from_shape_using_user_distance","description":"A stop time entry that is a large distance away from the location of the shape in `shapes.txt` as defined by `shape_dist_traveled` values."},{"rule":"stop_without_stop_time","description":"A stop in `stops.txt` is not referenced by any `stop_times.stop_id`, so it is not used by any trip."},{"rule":"translation_unknown_table_name","description":"A translation references an unknown or missing GTFS table."},{"rule":"unexpected_enum_value","description":"An enum has an unexpected value."},{"rule":"unusable_trip","description":"A trip must visit more than one stop in stop_times.txt to be usable by passengers for boarding and alighting."},{"rule":"unused_shape","description":"All records defined by GTFS `shapes.txt` should be used in `trips.txt`."},{"rule":"unused_trip","description":"Trips should be referred to at least once in `stop_times.txt`."},{"rule":"unknown_column","description":"A column is unknown."},{"rule":"unknown_file","description":"A file is unknown."},{"rule":"i_o_error","description":"Error in IO operation."},{"rule":"runtime_exception_in_loader_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred while loading a table. This normally indicates a bug in validator."},{"rule":"runtime_exception_in_validator_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred during validation. This normally indicates a bug in validator code, e.g., in a custom validator class."},{"rule":"thread_execution_error","description":"An [ExecutionException](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutionException.html) occurred during multithreaded validation."},{"rule":"u_r_i_syntax_error","description":"A string could not be parsed as a URI reference."}] \ No newline at end of file +[{"rule":"block_trips_with_overlapping_stop_times","description":"Trips with the same block id have overlapping stop times."},{"rule":"csv_parsing_failed","description":"Parsing of a CSV file failed. One common case of the problem is when a cell value contains more than 4096 characters."},{"rule":"decreasing_shape_distance","description":"When sorted by `shape.shape_pt_sequence`, two consecutive shape points must not have decreasing values for `shape_dist_traveled`. "},{"rule":"decreasing_or_equal_stop_time_distance","description":"When sorted by `stop_times.stop_sequence`, two consecutive entries in `stop_times.txt` should have increasing distance, based on the field `shape_dist_traveled`. If the values are equal, this is considered as an error. "},{"rule":"duplicated_column","description":"The input file CSV header has the same column name repeated."},{"rule":"duplicate_key","description":"The values of the given key and rows are duplicates."},{"rule":"empty_column_name","description":"A column name has not been provided. Such columns are skipped by the validator."},{"rule":"empty_file","description":"Empty csv file found in the archive: file does not have any headers, or is a required file and does not have any data. The GTFS specification requires the first line of each file to contain field names and required files must have data."},{"rule":"equal_shape_distance_diff_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and different coordinates indicate an error."},{"rule":"fare_transfer_rule_duration_limit_type_without_duration_limit","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit_type` field but no `duration_limit` specified."},{"rule":"fare_transfer_rule_duration_limit_without_type","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit` field but no `duration_limit_type` specified."},{"rule":"fare_transfer_rule_invalid_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `transfer_count` with an invalid value."},{"rule":"fare_transfer_rule_missing_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` equal to `to_leg_group_id`, but has no `transfer_count` specified. Per the spec, `transfer_count` is required if the two leg group ids are equal."},{"rule":"fare_transfer_rule_with_forbidden_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` not equal to `to_leg_group_id`, but has `transfer_count` specified. Per the spec, `transfer_count` is forbidden if the two leg group ids are not equal."},{"rule":"foreign_key_violation","description":"A foreign key references the primary key of another file. A foreign key violation means that the foreign key referenced from a given row (the child file) cannot be found in the corresponding file (the parent file). The Foreign keys are defined in the specification under \"Type\" for each file."},{"rule":"inconsistent_agency_timezone","description":"Agencies from GTFS `agency.txt` have been found to have different timezones."},{"rule":"invalid_color","description":"Value of field with type `color` is not valid. A color must be encoded as a six-digit hexadecimal number. The leading \"#\" is not included."},{"rule":"invalid_currency","description":"Value of field with type `currency` is not valid. Currency code must follow ISO 4217"},{"rule":"invalid_currency_amount","description":"A currency amount field has a value that does not match the format (e.g. expected number of decimal places) of its corresponding currency code field. The number of decimal places is specified by ISO 4217."},{"rule":"invalid_date","description":"Value of field with type `date` is not valid. Dates must have the YYYYMMDD format."},{"rule":"invalid_email","description":"Value of field with type `email` is not valid. Definitions for valid emails are quite vague. We perform strict validation using the Apache Commons EmailValidator."},{"rule":"invalid_float","description":"Value of field with type `float` is not valid. "},{"rule":"invalid_integer","description":"Value of field with type `integer` is not valid. "},{"rule":"invalid_language_code","description":"Value of field with type `language` is not valid. Language codes must follow IETF BCP 47."},{"rule":"invalid_phone_number","description":"Value of field with type `phone number` is not valid. This rule uses the [PhoneNumberUtil](https://www.javadoc.io/doc/com.googlecode.libphonenumber/libphonenumber/8.4.1/com/google/i18n/phonenumbers/PhoneNumberUtil.html) class to validate a phone number based on a country code. If no country code is provided in the parameters used to run the validator, this notice won't be emitted. "},{"rule":"invalid_row_length","description":"A row in the input file has a different number of values than specified by the CSV header."},{"rule":"invalid_time","description":"Value of field with type `time` is not valid. Time must be in the `H:MM:SS`, `HH:MM:SS` or `HHH:MM:SS` format."},{"rule":"invalid_timezone","description":"Value of field with type `timezone` is not valid.Timezones are defined at www.iana.org. Timezone names never contain the space character but may contain an underscore. Refer to Wikipedia for a list of valid values."},{"rule":"invalid_url","description":"Value of field with type `url` is not valid. Definitions for valid URLs are quite vague. We perform strict validation using the Apache Commons UrlValidator."},{"rule":"location_without_parent_station","description":"A location that must have `parent_station` field does not have it. The following location types must have `parent_station`: entrance, generic node, boarding_area."},{"rule":"location_with_unexpected_stop_time","description":"Referenced locations (using `stop_times.stop_id`) must be stops/platforms, i.e. their `stops.location_type` value must be 0 or empty."},{"rule":"missing_calendar_and_calendar_date_files","description":"Both files calendar_dates.txt and calendar.txt are missing from the GTFS archive. At least one of the files must be provided."},{"rule":"missing_level_id","description":"GTFS file `levels.txt` is required for elevator (`pathway_mode=5`). A row from `stops.txt` linked to an elevator pathway has no value for `stops.level_id`."},{"rule":"missing_required_column","description":"A required column is missing in the input file."},{"rule":"missing_required_field","description":"The given field has no value in some input row, even though values are required."},{"rule":"missing_required_file","description":"A required file is missing. If this notice is triggered for every core file, it might be a problem with the input. To create a zip file from the GTFS `.txt` files: select all the `.txt` files, right-click, and compress. Do not compress the folder containing the files. "},{"rule":"missing_stop_name","description":"`stops.stop_name` is required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"missing_trip_edge","description":"First and last stop of a trip must define both `arrival_time` and `departure_time` fields."},{"rule":"new_line_in_value","description":"A value in CSV file has a new line or carriage return."},{"rule":"number_out_of_range","description":"The values in the given column of the input rows are out of range."},{"rule":"overlapping_frequency","description":"Trip frequencies must not overlap in time"},{"rule":"pathway_to_platform_with_boarding_areas","description":"A pathway has an endpoint that is a platform which has boarding areas. A platform that has boarding"},{"rule":"pathway_to_wrong_location_type","description":"A pathway has an endpoint that is a station. Pathways endpoints must be platforms (stops),"},{"rule":"pathway_unreachable_location","description":"A location belongs to a station that has pathways and is not reachable at least in one direction:"},{"rule":"point_near_origin","description":"A point is too close to origin `(0, 0)`."},{"rule":"point_near_pole","description":"A point is too close to the North or South Pole."},{"rule":"route_both_short_and_long_name_missing","description":"Both `route_short_name` and `route_long_name` are missing for a route."},{"rule":"start_and_end_range_equal","description":"The fields `frequencies.start_date` and `frequencies.end_date` have been found equal in `frequencies.txt`. The GTFS spec is currently unclear how this case should be handled (e.g., is it a trip that circulates once?). It is recommended to use a trip not defined via frequencies.txt for this case."},{"rule":"start_and_end_range_out_of_order","description":"Date or time fields have been found out of order in `calendar.txt`, `feed_info.txt` and `stop_times.txt`."},{"rule":"station_with_parent_station","description":"Field `parent_station` must be empty when `location_type` is 1."},{"rule":"stop_time_timepoint_without_times","description":"Any records with `stop_times.timepoint` set to 1 must define a value for `stop_times.arrival_time` and `stop_times.departure_time` fields."},{"rule":"stop_time_with_arrival_before_previous_departure_time","description":"For a given `trip_id`, the `arrival_time` of (n+1)-th stoptime in sequence must not precede the `departure_time` of n-th stoptime in sequence in `stop_times.txt`."},{"rule":"stop_time_with_only_arrival_or_departure_time","description":"Missing `stop_time.arrival_time` or `stop_time.departure_time`"},{"rule":"stop_without_location","description":"`stop_lat` and/or `stop_lon` are required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"stop_without_zone_id","description":"If `fare_rules.txt` is provided, and `fare_rules.txt` uses at least one column among `origin_id`, `destination_id`, and `contains_id`, then all stops and platforms (location_type = 0) must have `stops.zone_id` assigned. "},{"rule":"too_many_rows","description":"A CSV file has too many rows. Feeds with too large files cannot be processed in a reasonable time by GTFS consumers."},{"rule":"transfer_with_invalid_stop_location_type","description":"A `from_stop_id` or `to_stop_id` field from GTFS file `transfers.txt` references a stop that has a `location_type` other than 0 or 1 (aka Stop/Platform or Station)."},{"rule":"transfer_with_invalid_trip_and_route","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a route that does not match its `trips.txt` `route_id`."},{"rule":"transfer_with_invalid_trip_and_stop","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a stop that is not included in the referenced trip's stop-times."},{"rule":"transfer_with_suspicious_mid_trip_in_seat","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` with an in-seat transfer type references a stop that is not in the expected position in the trip's stop-times. For in-seat transfers, we expect the stop to be the last stop-time in the trip sequence for `from_stop_id` and the first stop-time for `to_stop_id`. If you are intentionally using this feature to model mid-trip transfers, you can ignore this warning, but be aware that this functionality is still considered to be partially experimental in some interpretations of the spec."},{"rule":"translation_foreign_key_violation","description":"An entity with the given `record_id` and `record_sub_id` cannot be found in the referenced table."},{"rule":"translation_unexpected_value","description":"A field in a translations row has value but must be empty."},{"rule":"wrong_parent_location_type","description":"Value of field `location_type` of parent found in field `parent_station` is invalid."},{"rule":"attribution_without_role","description":"At least one of the fields `is_producer`, `is_operator`, or `is_authority` should be set to 1."},{"rule":"duplicate_route_name","description":"All routes of the same `route_type` with the same `agency_id` should have unique combinations of `route_short_name` and `route_long_name`."},{"rule":"empty_row","description":"A row in the input file has only spaces."},{"rule":"equal_shape_distance_same_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and the same coordinates indicate a duplicative shape point."},{"rule":"fast_travel_between_consecutive_stops","description":"A transit vehicle moves too fast between two consecutive stops. The speed threshold depends on route type."},{"rule":"fast_travel_between_far_stops","description":"A transit vehicle moves too fast between far consecutive stops (more than in 10 km apart). "},{"rule":"feed_expiration_date7_days","description":"The dataset expiration date defined in `feed_info.txt` is in seven days or less. At any time, the published GTFS dataset should be valid for at least the next 7 days."},{"rule":"feed_expiration_date30_days","description":"At any time, the GTFS dataset should cover at least the next 30 days of service, and ideally for as long as the operator is confident that the schedule will continue to be operated."},{"rule":"feed_info_lang_and_agency_lang_mismatch","description":" The default language may be multilingual for datasets with the original text in multiple languages. In such cases, the feed_lang field should contain the language code mul defined by the norm ISO 639-2."},{"rule":"inconsistent_agency_lang","description":"Agencies from GTFS `agency.txt` have been found to have different languages."},{"rule":"leading_or_trailing_whitespaces","description":"The value in CSV file has leading or trailing whitespaces."},{"rule":"missing_feed_info_date","description":"Even though `feed_info.start_date` and `feed_info.end_date` are optional, if one field is provided the second one should also be provided."},{"rule":"missing_recommended_file","description":"A recommended file is missing."},{"rule":"missing_recommended_field","description":"The given field has no value in some input row, even though values are recommended."},{"rule":"missing_timepoint_column","description":"The `timepoint` column should be provided."},{"rule":"missing_timepoint_value","description":"Even though the column `timepoint` is optional in `stop_times.txt` according to the specification, `stop_times.timepoint` should not be empty when provided. "},{"rule":"mixed_case_recommended_field","description":"This field contains customer-facing text and should use Mixed Case (upper and lower case letters) to ensure good readability when displayed to riders. Avoid the use of abbreviations throughout the feed (e.g. St. for Street) unless a location is called by its abbreviated name (e.g. “JFK Airport”). Abbreviations may be problematic for accessibility by screen reader software and voice user interfaces."},{"rule":"more_than_one_entity","description":"The file is expected to have a single entity but has more (e.g., \"feed_info.txt\")."},{"rule":"non_ascii_or_non_printable_char","description":"A value of a field with type `id` contains non ASCII or non printable characters. This is not recommended."},{"rule":"pathway_dangling_generic_node","description":"A generic node has only one incident location in a pathway graph. Such generic node is useless"},{"rule":"pathway_loop","description":"A pathway should not have same values for `from_stop_id` and `to_stop_id`."},{"rule":"platform_without_parent_station","description":"A platform has no `parent_station` field set."},{"rule":"route_color_contrast","description":"A route's color and `route_text_color` should be contrasting."},{"rule":"route_long_name_contains_short_name","description":"In routes.txt, `route_long_name` should not contain the value for `route_short_name`, because when both are provided, they are often combined by transit applications. Note that only one of the two fields is required. If there is no short name used for a route, use `route_long_name` only."},{"rule":"route_short_name_too_long","description":"Short name of a route is too long (more than 12 characters)."},{"rule":"same_name_and_description_for_route","description":"The GTFS spec defines `routes.txt` [route_desc](https://gtfs.org/reference/static/#routestxt) as:"},{"rule":"same_name_and_description_for_stop","description":"The GTFS spec defines `stops.txt` [stop_description](https://gtfs.org/reference/static/#stopstxt) as:\n\n> Description of the location that provides useful, quality information. Do not simply duplicate the name of the location."},{"rule":"same_route_and_agency_url","description":"A route should not have the same `routes.route_url` as a record from `agency.txt`."},{"rule":"same_stop_and_agency_url","description":"A stop should not have the same `stops.stop_url` as a record from `agency.txt`."},{"rule":"same_stop_and_route_url","description":"A stop should not have the same `stop.stop_url` as a record from `routes.txt`."},{"rule":"stop_has_too_many_matches_for_shape","description":"A stop entry that has many potential matches to the trip's path of travel, as defined by the shape entry in `shapes.txt`."},{"rule":"stops_match_shape_out_of_order","description":"Two stop entries in `stop_times.txt` are different than their arrival-departure order as defined by the shape in the `shapes.txt` file."},{"rule":"stop_too_far_from_shape","description":"Per GTFS Best Practices, route alignments (in `shapes.txt`) should be within 100 meters of stop locations which a trip serves."},{"rule":"stop_too_far_from_shape_using_user_distance","description":"A stop time entry that is a large distance away from the location of the shape in `shapes.txt` as defined by `shape_dist_traveled` values."},{"rule":"stop_without_stop_time","description":"A stop in `stops.txt` is not referenced by any `stop_times.stop_id`, so it is not used by any trip."},{"rule":"translation_unknown_table_name","description":"A translation references an unknown or missing GTFS table."},{"rule":"unexpected_enum_value","description":"An enum has an unexpected value."},{"rule":"unusable_trip","description":"A trip must visit more than one stop in stop_times.txt to be usable by passengers for boarding and alighting."},{"rule":"unused_shape","description":"All records defined by GTFS `shapes.txt` should be used in `trips.txt`."},{"rule":"unused_trip","description":"Trips should be referred to at least once in `stop_times.txt`."},{"rule":"unknown_column","description":"A column is unknown."},{"rule":"unknown_file","description":"A file is unknown."},{"rule":"i_o_error","description":"Error in IO operation."},{"rule":"runtime_exception_in_loader_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred while loading a table. This normally indicates a bug in validator."},{"rule":"runtime_exception_in_validator_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred during validation. This normally indicates a bug in validator code, e.g., in a custom validator class."},{"rule":"thread_execution_error","description":"An [ExecutionException](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutionException.html) occurred during multithreaded validation."},{"rule":"u_r_i_syntax_error","description":"A string could not be parsed as a URI reference."}] \ No newline at end of file diff --git a/scripts/import-mobility-data-rules.mjs b/scripts/import-mobility-data-rules.mjs index 08b1da749..5df5ef0e9 100644 --- a/scripts/import-mobility-data-rules.mjs +++ b/scripts/import-mobility-data-rules.mjs @@ -11,10 +11,13 @@ const findDescription = (text, header) => { const description = lines.findIndex(l => l.includes(header)) - // Edge case? Something is strange about the file + // Edge cases? Something is strange about the file if (header === 'equal_shape_distance_diff_coordinates') { return lines[description + 4] } + if (header === 'same_name_and_description_for_stop' || header === 'same_name_and_description_for_route') { + return lines[description + 2] + '\n\n' + lines[description + 4] + } return lines[description + 2] } From 56262cf9ffcc472de71ff94a444126ff0ef5f341 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 9 Mar 2023 09:57:21 -0500 Subject: [PATCH 17/28] use number summaries for validation error count --- lib/manager/components/version/FeedVersionViewer.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/manager/components/version/FeedVersionViewer.js b/lib/manager/components/version/FeedVersionViewer.js index 50863716c..060c69ed2 100644 --- a/lib/manager/components/version/FeedVersionViewer.js +++ b/lib/manager/components/version/FeedVersionViewer.js @@ -32,6 +32,7 @@ import VersionButtonToolbar from './VersionButtonToolbar' import VersionDateLabel from './VersionDateLabel' import FeedVersionReport from './FeedVersionReport' import DeltaStat from './DeltaStat' +import numeral from 'numeral' export type Props = { comparedVersion?: FeedVersion, @@ -185,14 +186,16 @@ class VersionSectionSelector extends Component { const dtValidationCount = hasCriticalError ? 'critical error' - : validationSummary.errorCount + : numeral(validationSummary.errorCount).format("0a") const mbValidationCount = version.mobilityDataResult && version.mobilityDataResult.notices && - version.mobilityDataResult.notices.reduce((prev, cur) => { - return prev + cur.totalNotices - }, 0) + numeral( + version.mobilityDataResult.notices.reduce((prev, cur) => { + return prev + cur.totalNotices; + }, 0) + ).format("0a") let diffLabel if (comparedVersion) { From c3e8ec8e76367fe3124e40d865eceb8c4d36611f Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 9 Mar 2023 11:39:41 -0500 Subject: [PATCH 18/28] lint --- .../components/validation/MobilityDataValidationResult.js | 5 +++-- lib/manager/components/version/FeedVersionViewer.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 3dcee42e3..c511d06b7 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -3,7 +3,7 @@ import React, { useState } from 'react' import Icon from '@conveyal/woonerf/components/icon' import { ListGroupItem, Table } from 'react-bootstrap' -import Markdown from 'markdown-to-jsx'; +import Markdown from 'markdown-to-jsx' import toSentenceCase from '../../../common/util/to-sentence-case' import { @@ -133,7 +133,8 @@ const MobilityDataValidationResult = (props) => {

More details diff --git a/lib/manager/components/version/FeedVersionViewer.js b/lib/manager/components/version/FeedVersionViewer.js index 060c69ed2..e079806b8 100644 --- a/lib/manager/components/version/FeedVersionViewer.js +++ b/lib/manager/components/version/FeedVersionViewer.js @@ -186,16 +186,16 @@ class VersionSectionSelector extends Component { const dtValidationCount = hasCriticalError ? 'critical error' - : numeral(validationSummary.errorCount).format("0a") + : numeral(validationSummary.errorCount).format('0a') const mbValidationCount = version.mobilityDataResult && version.mobilityDataResult.notices && numeral( version.mobilityDataResult.notices.reduce((prev, cur) => { - return prev + cur.totalNotices; + return prev + cur.totalNotices }, 0) - ).format("0a") + ).format('0a') let diffLabel if (comparedVersion) { From 4597d6cc614a38f972d4ab3ad515239da4c59cda Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 10 Mar 2023 09:47:35 -0500 Subject: [PATCH 19/28] repair link rel --- .../components/validation/MobilityDataValidationResult.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index c511d06b7..55afc8e61 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -134,7 +134,7 @@ const MobilityDataValidationResult = (props) => { More details From bee31dea00a4946932868bb6094f9b32d9585df2 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 10 Mar 2023 09:55:59 -0500 Subject: [PATCH 20/28] render complete info for missing_recommended_field --- .../components/validation/MobilityDataValidationResult.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 55afc8e61..7b6ed2da4 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -69,7 +69,6 @@ const renderNoticeDetail = (notice) => { notice.filename = 'fare_transfer_rules.txt' case 'empty_file': case 'emtpy_row': - case 'missing_recommended_field': case 'missing_timepoint_column': case 'missing_required_file': case 'missing_recommended_file': From d1438a407a6ebc861d6c3c01824b6f75f99e8907 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 10 Mar 2023 09:58:00 -0500 Subject: [PATCH 21/28] e2e tests: use correct java version --- __tests__/e2e/server/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/__tests__/e2e/server/Dockerfile b/__tests__/e2e/server/Dockerfile index a1289e2f4..f2abbfbd3 100644 --- a/__tests__/e2e/server/Dockerfile +++ b/__tests__/e2e/server/Dockerfile @@ -1,5 +1,6 @@ # syntax=docker/dockerfile:1 -FROM maven:3.8.6-openjdk-8 +FROM maven:3.8.7-openjdk-18 + WORKDIR /datatools ARG E2E_AUTH0_USERNAME From 195c2ec966766a3b3ba05ecafcc1a703c9f0fb50 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 10 Mar 2023 10:25:56 -0500 Subject: [PATCH 22/28] add wget to e2e image --- __tests__/e2e/server/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/__tests__/e2e/server/Dockerfile b/__tests__/e2e/server/Dockerfile index f2abbfbd3..68b7af3e6 100644 --- a/__tests__/e2e/server/Dockerfile +++ b/__tests__/e2e/server/Dockerfile @@ -33,6 +33,7 @@ ARG AWS_SECRET_ACCESS_KEY # Grab latest dev build of Datatools Server RUN git clone https://github.com/ibi-group/datatools-server.git +RUN microdnf install wget WORKDIR /datatools/datatools-server RUN mvn package -DskipTests From 2abfd0a2c8af9315e8ea86279b3a650c8f03bdb4 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 10 Mar 2023 11:55:28 -0500 Subject: [PATCH 23/28] refactor: address pr feedback --- .../components/validation/MobilityDataValidationResult.js | 4 ++-- lib/types/index.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 7b6ed2da4..bb5978937 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -101,7 +101,7 @@ const MobilityDataValidationResult = (props) => { const onRowSelect = () => setExpanded(!expanded) return ( - +

{ )}

- {expanded && renderNoticeDetail(notice)} +
{expanded && renderNoticeDetail(notice)}
) } diff --git a/lib/types/index.js b/lib/types/index.js index 8ae8cecbd..e1c635dcf 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -543,7 +543,6 @@ export type FeedVersion = FeedVersionSummary & { nextVersionId: ?string, noteCount: number, notes: Array, - // TODO: Type previousVersionId: ?string, processedByExternalPublisher: ?number, sentToExternalPublisher: ?number, From 81b1f608cd293ce58f536fb4fdb736995e1d4624 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 17 Mar 2023 14:56:23 -0400 Subject: [PATCH 24/28] refactor: sticky headers --- .../components/validation/MobilityDataValidationResult.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index bb5978937..0b6762008 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -27,11 +27,11 @@ const NoticeTable = ({ headerOverides = {}, notices }) => { const headers = Object.keys(notices[0]) return ( - +
{headers.map((header) => ( - ))} @@ -74,7 +74,7 @@ const renderNoticeDetail = (notice) => { case 'missing_recommended_file': case 'unknown_file': return ( -
    +
      {notice.sampleNotices.map((notice) => (
    • {notice.filename} @@ -141,7 +141,7 @@ const MobilityDataValidationResult = (props) => { )} -
      {expanded && renderNoticeDetail(notice)}
      +
      {expanded && renderNoticeDetail(notice)}
      ) } From 1faf20fde4659af5f8d2c0ebfe810ce0b372a213 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 17 Mar 2023 15:35:38 -0400 Subject: [PATCH 25/28] attempt to shorten problematically wide table --- .../validation/MobilityDataValidationResult.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 0b6762008..49e437955 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -21,7 +21,7 @@ function unCamelCase (s) { .toLowerCase() } -const NoticeTable = ({ headerOverides = {}, notices }) => { +const NoticeTable = ({ headerOverides = {'stopSequence1': 'Stop seq-uence 1', 'stopSequence2': 'Stop seq-uence 2'}, notices }) => { if (notices.length === 0) return null const headers = Object.keys(notices[0]) @@ -44,9 +44,14 @@ const NoticeTable = ({ headerOverides = {}, notices }) => { const FieldWrapper = (header === 'fieldValue' || header === 'message') ? 'pre' : React.Fragment + let field = notice[header] + if (header.endsWith('Km') || header.endsWith('Kph')) { + field = Math.round(field) + } + return (
) })} From 5500c45c8d43ced924d1df2778be5e98aafd936a Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 22 Mar 2023 09:11:46 -0400 Subject: [PATCH 26/28] refactor: address pr feedback --- .../validation/MobilityDataValidationResult.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 49e437955..1fbb3a150 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -21,7 +21,10 @@ function unCamelCase (s) { .toLowerCase() } -const NoticeTable = ({ headerOverides = {'stopSequence1': 'Stop seq-uence 1', 'stopSequence2': 'Stop seq-uence 2'}, notices }) => { +const NoticeTable = ({ headerOverides = { + 'stopSequence1': 'Stop seq-uence 1', + 'stopSequence2': 'Stop seq-uence 2' +}, notices }) => { if (notices.length === 0) return null const headers = Object.keys(notices[0]) @@ -121,7 +124,11 @@ const MobilityDataValidationResult = (props) => { type={validationErrorIconLookup[mobilityDataValidationErrorMapping[notice.severity]]} /> - {toSentenceCase(notice.code.replaceAll('_', ' ').toLowerCase())} + {toSentenceCase(notice.code + .replaceAll('_', ' ') + .split(/(?=[1-9])/) + .join(' ') + .toLowerCase())} {' '} — {notice.totalNotices} case From 7725b9e7b4839016eb28b90f2e8dd3cbfa6f8bf2 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 29 Mar 2023 10:20:30 -0400 Subject: [PATCH 27/28] refactor: address pr feedback --- lib/common/util/text.js | 19 +++++++++++++++++++ lib/common/util/to-sentence-case.js | 8 -------- .../MobilityDataValidationResult.js | 11 +++-------- scripts/import-mobility-data-rules.mjs | 8 ++++++++ 4 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 lib/common/util/text.js delete mode 100644 lib/common/util/to-sentence-case.js diff --git a/lib/common/util/text.js b/lib/common/util/text.js new file mode 100644 index 000000000..3a3385897 --- /dev/null +++ b/lib/common/util/text.js @@ -0,0 +1,19 @@ +// @flow + +import toLower from 'lodash/toLower' +import upperFirst from 'lodash/upperFirst' + +export default function toSentenceCase (s: string): string { + return upperFirst(toLower(s)) +} + +/** + * This method takes a string like expires_in_7days and ensures + * that 7days is replaced with 7 days + */ +export function spaceOutNumbers (s: string): string { + return s.replaceAll('_', ' ') + .split(/(?=[1-9])/) + .join(' ') + .toLowerCase() +} diff --git a/lib/common/util/to-sentence-case.js b/lib/common/util/to-sentence-case.js deleted file mode 100644 index 2bb811e5f..000000000 --- a/lib/common/util/to-sentence-case.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow - -import toLower from 'lodash/toLower' -import upperFirst from 'lodash/upperFirst' - -export default function toSentenceCase (s: string): string { - return upperFirst(toLower(s)) -} diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js index 1fbb3a150..de81e0d5e 100644 --- a/lib/manager/components/validation/MobilityDataValidationResult.js +++ b/lib/manager/components/validation/MobilityDataValidationResult.js @@ -5,7 +5,7 @@ import Icon from '@conveyal/woonerf/components/icon' import { ListGroupItem, Table } from 'react-bootstrap' import Markdown from 'markdown-to-jsx' -import toSentenceCase from '../../../common/util/to-sentence-case' +import toSentenceCase, { spaceOutNumbers } from '../../../common/util/text' import { mobilityDataValidationErrorMapping, validationErrorIconLookup @@ -98,8 +98,7 @@ const renderNoticeDetail = (notice) => { } } -const MobilityDataValidationResult = (props) => { - const { notice } = props +const MobilityDataValidationResult = ({notice}) => { const rule = rules.find((rd) => rd.rule === notice.code) if (!rule) return null @@ -124,11 +123,7 @@ const MobilityDataValidationResult = (props) => { type={validationErrorIconLookup[mobilityDataValidationErrorMapping[notice.severity]]} /> - {toSentenceCase(notice.code - .replaceAll('_', ' ') - .split(/(?=[1-9])/) - .join(' ') - .toLowerCase())} + {toSentenceCase(spaceOutNumbers(notice.code))} {' '} — {notice.totalNotices} case diff --git a/scripts/import-mobility-data-rules.mjs b/scripts/import-mobility-data-rules.mjs index 5df5ef0e9..2afab0017 100644 --- a/scripts/import-mobility-data-rules.mjs +++ b/scripts/import-mobility-data-rules.mjs @@ -2,6 +2,10 @@ import { writeFileSync } from 'fs' import fetch from 'isomorphic-fetch' +const logger = (msg) => { + console.log(`[MOBILITY DATA RULES IMPORTER]: ${msg}`) +} + const findDescription = (text, header) => { // Split the rules.MD file into lines let lines = text.split('\n') @@ -22,11 +26,13 @@ const findDescription = (text, header) => { return lines[description + 2] } +logger('Writing gtfs-validator rules.MD to JSON') fetch( 'https://raw.githubusercontent.com/MobilityData/gtfs-validator/master/RULES.md' ) .then((raw) => raw.text()) .then((text) => { + logger('rules.MD downloaded!') // Match all rule headings const rules = text .match(/### .*/g) @@ -38,5 +44,7 @@ fetch( rule, description: findDescription(text, rule) })) + logger('rules.MD data extracted successfully!') writeFileSync('lib/manager/components/validation/rules.json', JSON.stringify(rulesAndDescriptions)) + logger('Wrote gtfs-validator rules.MD to JSON') }) From 8ec815b1705fd7f35527b37274d53063330c0539 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 29 Mar 2023 11:27:47 -0400 Subject: [PATCH 28/28] chore: update imports --- lib/admin/components/OrganizationSettings.js | 2 +- lib/alerts/components/AlertEditor.js | 2 +- lib/alerts/components/AlertsList.js | 2 +- lib/common/util/text.js | 1 + lib/editor/components/EditorInput.js | 2 +- lib/editor/components/ScheduleExceptionForm.js | 2 +- lib/editor/components/pattern/EditSettings.js | 2 +- lib/manager/components/FeedSourceSettings.js | 2 +- lib/manager/components/validation/GtfsValidationViewer.js | 2 +- lib/manager/components/validation/ServicePerModeChart.js | 2 +- lib/manager/components/validation/TripsChart.js | 2 +- lib/manager/components/version/VersionRetrievalBadge.js | 2 +- 12 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/admin/components/OrganizationSettings.js b/lib/admin/components/OrganizationSettings.js index 729ed5252..3aef5f7b4 100644 --- a/lib/admin/components/OrganizationSettings.js +++ b/lib/admin/components/OrganizationSettings.js @@ -8,7 +8,7 @@ import moment from 'moment' import * as organizationActions from '../actions/organizations' import {getComponentMessages} from '../../common/util/config' -import toSentenceCase from '../../common/util/to-sentence-case' +import toSentenceCase from '../../common/util/text' import type {Organization, Project} from '../../types' type Props = { diff --git a/lib/alerts/components/AlertEditor.js b/lib/alerts/components/AlertEditor.js index 833f08298..e1230e845 100644 --- a/lib/alerts/components/AlertEditor.js +++ b/lib/alerts/components/AlertEditor.js @@ -15,7 +15,7 @@ import ManagerPage from '../../common/components/ManagerPage' import PageNotFound from '../../common/components/PageNotFound' import {isModuleEnabled} from '../../common/util/config' import {checkEntitiesForFeeds} from '../../common/util/permissions' -import toSentenceCase from '../../common/util/to-sentence-case' +import toSentenceCase from '../../common/util/text' import GtfsMapSearch from '../../gtfs/components/gtfsmapsearch' import GlobalGtfsFilter from '../../gtfs/containers/GlobalGtfsFilter' import {CAUSES, EFFECTS, isNew} from '../util' diff --git a/lib/alerts/components/AlertsList.js b/lib/alerts/components/AlertsList.js index d48a5cac1..135cac972 100644 --- a/lib/alerts/components/AlertsList.js +++ b/lib/alerts/components/AlertsList.js @@ -18,7 +18,7 @@ import AlertPreview from './AlertPreview' import Loading from '../../common/components/Loading' import OptionButton from '../../common/components/OptionButton' import { getFeedId } from '../../common/util/modules' -import toSentenceCase from '../../common/util/to-sentence-case' +import toSentenceCase from '../../common/util/text' import { FILTERS, SORT_OPTIONS } from '../util' import type {Props as ContainerProps} from '../containers/VisibleAlertsList' diff --git a/lib/common/util/text.js b/lib/common/util/text.js index 3a3385897..32fbd5528 100644 --- a/lib/common/util/text.js +++ b/lib/common/util/text.js @@ -11,6 +11,7 @@ export default function toSentenceCase (s: string): string { * This method takes a string like expires_in_7days and ensures * that 7days is replaced with 7 days */ +// $FlowFixMe flow needs to learn about new es2021 features! export function spaceOutNumbers (s: string): string { return s.replaceAll('_', ' ') .split(/(?=[1-9])/) diff --git a/lib/editor/components/EditorInput.js b/lib/editor/components/EditorInput.js index 33cbd8abc..b72248c54 100644 --- a/lib/editor/components/EditorInput.js +++ b/lib/editor/components/EditorInput.js @@ -15,7 +15,7 @@ import {doesNotExist} from '../util/validation' import TimezoneSelect from '../../common/components/TimezoneSelect' import LanguageSelect from '../../common/components/LanguageSelect' import {getComponentMessages} from '../../common/util/config' -import toSentenceCase from '../../common/util/to-sentence-case' +import toSentenceCase from '../../common/util/text' import type {Entity, Feed, GtfsSpecField, GtfsAgency, GtfsStop} from '../../types' import type {EditorTables} from '../../types/reducers' diff --git a/lib/editor/components/ScheduleExceptionForm.js b/lib/editor/components/ScheduleExceptionForm.js index bff08c2b5..d15a615fa 100644 --- a/lib/editor/components/ScheduleExceptionForm.js +++ b/lib/editor/components/ScheduleExceptionForm.js @@ -13,7 +13,7 @@ import Select from 'react-select' import FlipMove from 'react-flip-move' import {updateActiveGtfsEntity} from '../actions/active' -import toSentenceCase from '../../common/util/to-sentence-case' +import toSentenceCase from '../../common/util/text' import {getRangesForDates} from '../../common/util/exceptions' import {EXCEPTION_EXEMPLARS} from '../util' import {getTableById} from '../util/gtfs' diff --git a/lib/editor/components/pattern/EditSettings.js b/lib/editor/components/pattern/EditSettings.js index d353495fe..c398c45fb 100644 --- a/lib/editor/components/pattern/EditSettings.js +++ b/lib/editor/components/pattern/EditSettings.js @@ -6,7 +6,7 @@ import Rcslider from 'rc-slider' import {updateEditSetting} from '../../actions/active' import {CLICK_OPTIONS} from '../../util' -import toSentenceCase from '../../../common/util/to-sentence-case' +import toSentenceCase from '../../../common/util/text' import type {EditSettingsState} from '../../../types/reducers' type Props = { diff --git a/lib/manager/components/FeedSourceSettings.js b/lib/manager/components/FeedSourceSettings.js index 26a508a44..c254efa81 100644 --- a/lib/manager/components/FeedSourceSettings.js +++ b/lib/manager/components/FeedSourceSettings.js @@ -12,7 +12,7 @@ import {LinkContainer} from 'react-router-bootstrap' import * as feedsActions from '../actions/feeds' import {getComponentMessages, isExtensionEnabled} from '../../common/util/config' -import toSentenceCase from '../../common/util/to-sentence-case' +import toSentenceCase from '../../common/util/text' import type {Feed, Project} from '../../types' import type {ManagerUserState} from '../../types/reducers' diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js index a11c79978..6c6503e59 100644 --- a/lib/manager/components/validation/GtfsValidationViewer.js +++ b/lib/manager/components/validation/GtfsValidationViewer.js @@ -17,7 +17,7 @@ import { import Loading from '../../../common/components/Loading' import OptionButton from '../../../common/components/OptionButton' import {getComponentMessages} from '../../../common/util/config' -import toSentenceCase from '../../../common/util/to-sentence-case' +import toSentenceCase from '../../../common/util/text' import { BLOCKING_ERROR_TYPES, getTableFatalExceptions, diff --git a/lib/manager/components/validation/ServicePerModeChart.js b/lib/manager/components/validation/ServicePerModeChart.js index 5e7e3a6f9..bc8e86dd9 100644 --- a/lib/manager/components/validation/ServicePerModeChart.js +++ b/lib/manager/components/validation/ServicePerModeChart.js @@ -4,7 +4,7 @@ import React, { Component } from 'react' import moment from 'moment' import Loading from '../../../common/components/Loading' -import toSentenceCase from '../../../common/util/to-sentence-case' +import toSentenceCase from '../../../common/util/text' import {getChartMax, getChartPeriod} from '../../util' import type {ValidationResult} from '../../../types' diff --git a/lib/manager/components/validation/TripsChart.js b/lib/manager/components/validation/TripsChart.js index 8e29beb2d..4875d7cab 100644 --- a/lib/manager/components/validation/TripsChart.js +++ b/lib/manager/components/validation/TripsChart.js @@ -4,7 +4,7 @@ import React, { Component } from 'react' import moment from 'moment' import Loading from '../../../common/components/Loading' -import toSentenceCase from '../../../common/util/to-sentence-case' +import toSentenceCase from '../../../common/util/text' import {getChartMax, getChartPeriod} from '../../util' import type {ValidationResult} from '../../../types' diff --git a/lib/manager/components/version/VersionRetrievalBadge.js b/lib/manager/components/version/VersionRetrievalBadge.js index cdba8b31c..094b95c7b 100644 --- a/lib/manager/components/version/VersionRetrievalBadge.js +++ b/lib/manager/components/version/VersionRetrievalBadge.js @@ -3,7 +3,7 @@ import Icon from '@conveyal/woonerf/components/icon' import React from 'react' -import toSentenceCase from '../../../common/util/to-sentence-case' +import toSentenceCase from '../../../common/util/text' import type { FeedVersionSummary, RetrievalMethod } from '../../../types' type Props = {
+ {headerOverides[header] || toSentenceCase(unCamelCase(header))} - {notice[header]} + {field}