@opentripplanner/core-utils 14.0.0-alpha.2 → 14.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +0 -4
  2. package/esm/itinerary.js +70 -32
  3. package/esm/itinerary.js.map +1 -1
  4. package/esm/otpSchema.json +7321 -5821
  5. package/esm/planQuery.graphql +12 -0
  6. package/esm/profile.js +1 -1
  7. package/esm/profile.js.map +1 -1
  8. package/esm/query-gen.js +2 -2
  9. package/esm/query-gen.js.map +1 -1
  10. package/esm/query-params.js +2 -2
  11. package/esm/query-params.js.map +1 -1
  12. package/esm/query.js +2 -5
  13. package/esm/query.js.map +1 -1
  14. package/esm/storage.js +2 -6
  15. package/esm/storage.js.map +1 -1
  16. package/esm/time.js +1 -2
  17. package/esm/time.js.map +1 -1
  18. package/lib/index.js +50 -35
  19. package/lib/index.js.map +1 -1
  20. package/lib/itinerary.d.ts +25 -8
  21. package/lib/itinerary.d.ts.map +1 -1
  22. package/lib/itinerary.js +524 -495
  23. package/lib/itinerary.js.map +1 -1
  24. package/lib/map.js +40 -39
  25. package/lib/map.js.map +1 -1
  26. package/lib/otpSchema.json +7321 -5821
  27. package/lib/planQuery.graphql +12 -0
  28. package/lib/profile.js +1 -1
  29. package/lib/profile.js.map +1 -1
  30. package/lib/query-gen.js +134 -138
  31. package/lib/query-gen.js.map +1 -1
  32. package/lib/query-params.js +2 -2
  33. package/lib/query-params.js.map +1 -1
  34. package/lib/query.js +3 -6
  35. package/lib/query.js.map +1 -1
  36. package/lib/route.js +230 -248
  37. package/lib/route.js.map +1 -1
  38. package/lib/storage.d.ts.map +1 -1
  39. package/lib/storage.js +22 -28
  40. package/lib/storage.js.map +1 -1
  41. package/lib/suspense.js +28 -16
  42. package/lib/suspense.js.map +1 -1
  43. package/lib/time.js +36 -49
  44. package/lib/time.js.map +1 -1
  45. package/lib/ui.js +33 -36
  46. package/lib/ui.js.map +1 -1
  47. package/package.json +3 -3
  48. package/src/__tests__/__snapshots__/query-params.ts.snap +203 -203
  49. package/src/__tests__/__snapshots__/query.js.snap +28 -28
  50. package/src/__tests__/__snapshots__/route.js.snap +76 -76
  51. package/src/__tests__/__snapshots__/time.js.snap +2 -2
  52. package/src/__tests__/itinerary.ts +108 -23
  53. package/src/__tests__/query.js +1 -1
  54. package/src/__tests__/route.js +1 -1
  55. package/src/core-utils.story.tsx +34 -23
  56. package/src/itinerary.ts +85 -37
  57. package/src/otpSchema.json +7321 -5821
  58. package/src/planQuery.graphql +12 -0
  59. package/src/profile.js +1 -1
  60. package/src/query-gen.ts +1 -1
  61. package/src/query-params.jsx +2 -2
  62. package/src/query.js +2 -5
  63. package/src/storage.ts +5 -9
  64. package/tsconfig.tsbuildinfo +1 -1
@@ -13,7 +13,7 @@ import {
13
13
  planParamsToQuery
14
14
  } from "../query";
15
15
 
16
- const config = require("./__mocks__/config.json");
16
+ import config from "./__mocks__/config.json";
17
17
 
18
18
  describe("query", () => {
19
19
  afterEach(restoreDateNowBehavior);
@@ -5,7 +5,7 @@ import {
5
5
  makeRouteComparator
6
6
  } from "../route";
7
7
 
8
- const { otp1Routes, otp2Routes } = require("./__mocks__/routes.json");
8
+ import { otp1Routes, otp2Routes } from "./__mocks__/routes.json";
9
9
 
10
10
  function sortRoutes(...routes) {
11
11
  routes.sort(makeRouteComparator());
@@ -1,3 +1,4 @@
1
+ /* eslint-disable react/display-name */
1
2
  import React, { useState } from "react";
2
3
  import styled from "styled-components";
3
4
  import {
@@ -42,29 +43,39 @@ const ColorPair = ({ fg, bg }: { fg: string; bg: string }) => {
42
43
  );
43
44
  };
44
45
 
45
- export const RouteColorTester = (): JSX.Element => {
46
- const [fg, setFg] = useState("#333333");
47
- const [bg, setBg] = useState("#cbeb55");
48
- return (
49
- <>
50
- <ColorPair bg={bg} fg={fg}></ColorPair>
51
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
52
- <label>
53
- Foreground Color
54
- <input onChange={e => setFg(e.target.value)} type="color" value={fg} />
55
- </label>
56
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
57
- <label>
58
- Background Color
59
- <input onChange={e => setBg(e.target.value)} type="color" value={bg} />
60
- </label>
61
- </>
62
- );
63
- };
64
- // Disable color contrast checking for the uncorrected color pairs.
65
- RouteColorTester.parameters = {
66
- a11y: { config: { rules: [{ id: "color-contrast", reviewOnFail: true }] } },
67
- storyshots: { disable: true }
46
+ export const RouteColorTester = {
47
+ render: (): JSX.Element => {
48
+ const [fg, setFg] = useState("#333333");
49
+ const [bg, setBg] = useState("#cbeb55");
50
+ return (
51
+ <>
52
+ <ColorPair bg={bg} fg={fg}></ColorPair>
53
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
54
+ <label>
55
+ Foreground Color
56
+ <input
57
+ onChange={e => setFg(e.target.value)}
58
+ type="color"
59
+ value={fg}
60
+ />
61
+ </label>
62
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
63
+ <label>
64
+ Background Color
65
+ <input
66
+ onChange={e => setBg(e.target.value)}
67
+ type="color"
68
+ value={bg}
69
+ />
70
+ </label>
71
+ </>
72
+ );
73
+ },
74
+ // Disable color contrast checking for the uncorrected color pairs
75
+ parameters: {
76
+ a11y: { config: { rules: [{ id: "color-contrast", reviewOnFail: true }] } },
77
+ storyshots: { disable: true }
78
+ }
68
79
  };
69
80
 
70
81
  // Route sort logic story:
package/src/itinerary.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import polyline from "@mapbox/polyline";
2
2
  import {
3
+ AppliedFareProduct,
3
4
  Company,
4
5
  Config,
6
+ Currency,
5
7
  ElevationProfile,
6
8
  ElevationProfileComponent,
7
9
  FlexBookingInfo,
@@ -104,8 +106,6 @@ export function isFlex(leg: Leg): boolean {
104
106
  legContainsGeometry(leg)
105
107
  );
106
108
  }
107
-
108
- // alpha-only comment
109
109
  export function isRideshareLeg(leg: Leg): boolean {
110
110
  return !!leg.rideHailingEstimate?.provider?.id;
111
111
  }
@@ -609,45 +609,91 @@ export function getDisplayedStopId(placeOrStop: Place | Stop): string {
609
609
  return stopCode || stopId?.split(":")[1] || stopId;
610
610
  }
611
611
 
612
+ /**
613
+ * Removes the first part of the OTP standard scope (":"), if it is present
614
+ * @param item String that is potentially scoped with `:` character
615
+ * @returns descoped string
616
+ */
617
+ export const descope = (item: string): string => item?.split(":")?.[1];
618
+
619
+ export type ExtendedMoney = Money & { originalAmount?: number };
620
+
621
+ export const zeroDollars = (currency: Currency): Money => ({
622
+ amount: 0,
623
+ currency
624
+ });
625
+
612
626
  /**
613
627
  * Extracts useful data from the fare products on a leg, such as the leg cost and transfer info.
614
- * @param leg Leg with fare products (must have used getLegsWithFares)
615
- * @param category Rider category
616
- * @param container Fare container (cash, electronic)
617
- * @returns Object containing price as well as the transfer discount amount, if a transfer was used.
628
+ * @param leg Leg with Fares v2 information
629
+ * @param mediumId Desired medium ID to calculate fare for
630
+ * @param riderCategoryId Desire rider category to calculate fare for
631
+ * @param seenFareIds Fare IDs used on previous legs. Used to detect transfer discounts.
632
+ * @returns Object containing price as well as transfer/dependent
633
+ * fare information. `AppliedFareProduct` should contain
634
+ * all the information needed, but the other fields are kept to
635
+ * make the transition to Fares V2 less jarring.
618
636
  */
619
637
  export function getLegCost(
620
638
  leg: Leg,
621
639
  mediumId: string | null,
622
- riderCategoryId: string | null
640
+ riderCategoryId: string | null,
641
+ seenFareIds?: string[]
623
642
  ): {
643
+ alternateFareProducts?: AppliedFareProduct[];
644
+ appliedFareProduct?: AppliedFareProduct;
645
+ isDependent?: boolean;
624
646
  price?: Money;
625
- transferAmount?: Money | undefined;
626
647
  productUseId?: string;
627
648
  } {
628
649
  if (!leg.fareProducts) return { price: undefined };
629
- const relevantFareProducts = leg.fareProducts.filter(({ product }) => {
630
- // riderCategory and medium can be specifically defined as null to handle
631
- // generic GTFS based fares from OTP when there is no fare model
632
- return (
633
- (product.riderCategory === null ? null : product.riderCategory.id) ===
634
- riderCategoryId &&
635
- (product.medium === null ? null : product.medium.id) === mediumId
636
- );
637
- });
638
-
639
- // Custom fare models return "rideCost", generic GTFS fares return "regular"
640
- const totalCostProduct = relevantFareProducts.find(
641
- fp => fp.product.name === "rideCost" || fp.product.name === "regular"
642
- );
643
- const transferFareProduct = relevantFareProducts.find(
644
- fp => fp.product.name === "transfer"
645
- );
646
-
650
+ const relevantFareProducts = leg.fareProducts
651
+ .filter(({ product }) => {
652
+ // riderCategory and medium can be specifically defined as null to handle
653
+ // generic GTFS based fares from OTP when there is no fare model
654
+
655
+ // Remove (optional) agency scoping
656
+ const productRiderCategoryId =
657
+ descope(product?.riderCategory?.id) ||
658
+ product?.riderCategory?.id ||
659
+ null;
660
+
661
+ const productMediaId =
662
+ descope(product?.medium?.id) || product?.medium?.id || null;
663
+ return (
664
+ productRiderCategoryId === riderCategoryId &&
665
+ productMediaId === mediumId &&
666
+ // Make sure there's a price
667
+ // Some fare products don't have a price at all.
668
+ product?.price
669
+ );
670
+ })
671
+ .map(fare => {
672
+ const alreadySeen = seenFareIds?.indexOf(fare.id) > -1;
673
+ const { currency } = fare.product.price;
674
+ return {
675
+ id: fare.id,
676
+ product: {
677
+ ...fare.product,
678
+ legPrice: alreadySeen ? zeroDollars(currency) : fare.product.price
679
+ } as AppliedFareProduct
680
+ };
681
+ })
682
+ .sort((a, b) => a.product?.legPrice?.amount - b.product?.legPrice?.amount);
683
+
684
+ // Return the cheapest, but include other matches as well
685
+ const cheapestRelevantFareProduct = relevantFareProducts[0];
686
+
687
+ // TODO: return one object here instead of dumbing it down?
647
688
  return {
648
- price: totalCostProduct?.product.price,
649
- transferAmount: transferFareProduct?.product.price,
650
- productUseId: totalCostProduct?.id
689
+ alternateFareProducts: relevantFareProducts.splice(1).map(fp => fp.product),
690
+ appliedFareProduct: cheapestRelevantFareProduct?.product,
691
+ isDependent:
692
+ // eslint-disable-next-line no-underscore-dangle
693
+ cheapestRelevantFareProduct?.product.__typename ===
694
+ "DependentFareProduct",
695
+ price: cheapestRelevantFareProduct?.product.legPrice,
696
+ productUseId: cheapestRelevantFareProduct?.id
651
697
  };
652
698
  }
653
699
 
@@ -656,6 +702,7 @@ export function getLegCost(
656
702
  * @param legs Itinerary legs with fare products (must have used getLegsWithFares)
657
703
  * @param category Rider category (youth, regular, senior)
658
704
  * @param container Fare container (cash, electronic)
705
+ * @param seenFareIds List of fare product IDs that have already been seen on prev legs.
659
706
  * @returns Money object for the total itinerary cost.
660
707
  */
661
708
  export function getItineraryCost(
@@ -663,22 +710,23 @@ export function getItineraryCost(
663
710
  mediumId: string | null,
664
711
  riderCategoryId: string | null
665
712
  ): Money | undefined {
666
- const legCosts = legs
713
+ const legCostsObj = legs
667
714
  // Only legs with fares (no walking legs)
668
715
  .filter(leg => leg.fareProducts?.length > 0)
669
716
  // Get the leg cost object of each leg
670
717
  .map(leg => getLegCost(leg, mediumId, riderCategoryId))
671
- .filter(cost => cost.price !== undefined)
718
+ .filter(cost => cost.appliedFareProduct?.legPrice !== undefined)
672
719
  // Filter out duplicate use IDs
673
720
  // One fare product can be used on multiple legs,
674
721
  // and we don't want to count it more than once.
675
- .reduce<{ productUseId: string; price: Money }[]>((prev, cur) => {
676
- if (!prev.some(p => p.productUseId === cur.productUseId)) {
677
- prev.push({ productUseId: cur.productUseId, price: cur.price });
722
+ // Use an object keyed by productUseId to deduplicate, then extract prices
723
+ .reduce<{ [productUseId: string]: Money }>((acc, cur) => {
724
+ if (cur.productUseId && acc[cur.productUseId] === undefined) {
725
+ acc[cur.productUseId] = cur.appliedFareProduct?.legPrice;
678
726
  }
679
- return prev;
680
- }, [])
681
- .map(productUse => productUse.price);
727
+ return acc;
728
+ }, {});
729
+ const legCosts = Object.values(legCostsObj);
682
730
 
683
731
  if (legCosts.length === 0) return undefined;
684
732
  // Calculate the total