Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
felixguendling committed Mar 3, 2025
1 parent e3b82ef commit 7c19307
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 58 deletions.
13 changes: 6 additions & 7 deletions src/journey_to_response.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,12 @@ api::Itinerary journey_to_response(osr::ways const* w,
};
auto const to_rider_category =
[&](n::fares::rider_category const& r) -> api::RiderCategory {
return {
.riderCategoryName_ = std::string{tt.strings_.get(r.eligibility_url_)},
.isDefaultFareCategory_ = r.is_default_fare_category_,
.eligibilityUrl_ = tt.strings_.try_get(r.eligibility_url_)
.and_then([](std::string_view s) {
return std::optional{std::string{s}};
})};
return {.riderCategoryName_ = std::string{tt.strings_.get(r.name_)},
.isDefaultFareCategory_ = r.is_default_fare_category_,
.eligibilityUrl_ = tt.strings_.try_get(r.eligibility_url_)
.and_then([](std::string_view s) {
return std::optional{std::string{s}};
})};
};
auto const to_product =
[&](n::fares const& f,
Expand Down
40 changes: 33 additions & 7 deletions ui/src/lib/ConnectionDetail.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import type { Itinerary, Leg } from '$lib/openapi';
import type { FareProduct, Itinerary, Leg } from '$lib/openapi';
import Time from '$lib/Time.svelte';
import { routeBorderColor, routeColor } from '$lib/modeStyle';
import { formatDurationSec, formatDistanceMeters } from '$lib/formatDuration';
Expand Down Expand Up @@ -78,6 +78,36 @@
</div>
{/snippet}

{#snippet productInfo(product: FareProduct)}
{product.name}
({product.amount}
{product.currency})
{#if product.riderCategory}
for
{#if product.riderCategory.eligibilityUrl}
<a
class:italic={product.riderCategory.isDefaultFareCategory}
class="underline"
href={product.riderCategory.eligibilityUrl}
>
{product.riderCategory.riderCategoryName}
</a>
{:else}
<span class:italic={product.riderCategory.isDefaultFareCategory}>
{product.riderCategory.riderCategoryName}
</span>
{/if}
{/if}
{#if product.media}
as
{#if product.media.fareMediaName}
{product.media.fareMediaName}
{:else}
{product.media.fareMediaType}
{/if}
{/if}
{/snippet}

{#snippet ticketInfo(prevTransitLeg: Leg | undefined, l: Leg)}
{#if itinerary.fareTransfers != undefined && l.fareTransferIndex != undefined && l.effectiveFareLegIndex != undefined}
{@const fareTransfer = itinerary.fareTransfers[l.fareTransferIndex]}
Expand All @@ -100,9 +130,7 @@
{#if productOptions.length == 1}
{t.ticket}
{/if}
{product.name}
({product.amount}
{product.currency})
{@render productInfo(product)}
</li>
{/each}
</ul>
Expand Down Expand Up @@ -141,9 +169,7 @@
<br />
<span class="text-xs font-bold text-foreground">
Ticket: {pred.effectiveFareLegIndex}
{transferProduct.name}
({transferProduct.amount}
{transferProduct.currency})
{@render productInfo(transferProduct)}
</span>
{/if}
{/if}
Expand Down
52 changes: 52 additions & 0 deletions ui/src/lib/openapi/schemas.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,52 @@ to identify which effective fare leg this itinerary leg belongs to
}
} as const;

export const RiderCategorySchema = {
type: 'object',
required: ['riderCategoryName', 'isDefaultFareCategory'],
properties: {
riderCategoryName: {
description: 'Rider category name as displayed to the rider.',
type: 'string'
},
isDefaultFareCategory: {
description: 'Specifies if this category should be considered the default (i.e. the main category displayed to riders).',
type: 'boolean'
},
eligibilityUrl: {
description: 'URL to a web page providing detailed information about the rider category and/or its eligibility criteria.',
type: 'string'
}
}
} as const;

export const FareMediaTypeSchema = {
type: 'string',
enum: ['NONE', 'PAPER_TICKET', 'TRANSIT_CARD', 'CONTACTLESS_EMV', 'MOBILE_APP'],
enumDescriptions: {
NONE: 'No fare media involved (e.g., cash payment)',
PAPER_TICKET: 'Physical paper ticket',
TRANSIT_CARD: 'Physical transit card with stored value',
CONTACTLESS_EMV: 'cEMV (contactless payment)',
MOBILE_APP: 'Mobile app with virtual transit cards/passes'
}
} as const;

export const FareMediaSchema = {
type: 'object',
required: ['fareMediaType'],
properties: {
fareMediaName: {
description: 'Name of the fare media. Required for transit cards and mobile apps.',
type: 'string'
},
fareMediaType: {
description: 'The type of fare media.',
'$ref': '#/components/schemas/FareMediaType'
}
}
} as const;

export const FareProductSchema = {
type: 'object',
required: ['name', 'amount', 'currency'],
Expand All @@ -634,6 +680,12 @@ export const FareProductSchema = {
currency: {
description: 'ISO 4217 currency code. The currency of the cost of the fare product.',
type: 'string'
},
riderCategory: {
'$ref': '#/components/schemas/RiderCategory'
},
media: {
'$ref': '#/components/schemas/FareMedia'
}
}
} as const;
Expand Down
110 changes: 66 additions & 44 deletions ui/src/lib/openapi/services.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,62 @@ export const client = createClient(createConfig());
* The order in the response array corresponds to the order of coordinates of the \`many\` parameter in the query.
*
*/
export const oneToMany = <ThrowOnError extends boolean = false>(options: Options<OneToManyData, ThrowOnError>) => { return (options?.client ?? client).get<OneToManyResponse, OneToManyError, ThrowOnError>({
...options,
url: '/api/v1/one-to-many'
}); };
export const oneToMany = <ThrowOnError extends boolean = false>(options: Options<OneToManyData, ThrowOnError>) => {
return (options?.client ?? client).get<OneToManyResponse, OneToManyError, ThrowOnError>({
...options,
url: '/api/v1/one-to-many'
});
};

/**
* Translate coordinates to the closest address(es)/places/stops.
*/
export const reverseGeocode = <ThrowOnError extends boolean = false>(options: Options<ReverseGeocodeData, ThrowOnError>) => { return (options?.client ?? client).get<ReverseGeocodeResponse, ReverseGeocodeError, ThrowOnError>({
...options,
url: '/api/v1/reverse-geocode'
}); };
export const reverseGeocode = <ThrowOnError extends boolean = false>(options: Options<ReverseGeocodeData, ThrowOnError>) => {
return (options?.client ?? client).get<ReverseGeocodeResponse, ReverseGeocodeError, ThrowOnError>({
...options,
url: '/api/v1/reverse-geocode'
});
};

/**
* Autocompletion & geocoding that resolves user input addresses including coordinates
*/
export const geocode = <ThrowOnError extends boolean = false>(options: Options<GeocodeData, ThrowOnError>) => { return (options?.client ?? client).get<GeocodeResponse, GeocodeError, ThrowOnError>({
...options,
url: '/api/v1/geocode'
}); };
export const geocode = <ThrowOnError extends boolean = false>(options: Options<GeocodeData, ThrowOnError>) => {
return (options?.client ?? client).get<GeocodeResponse, GeocodeError, ThrowOnError>({
...options,
url: '/api/v1/geocode'
});
};

/**
* Get a trip as itinerary
*/
export const trip = <ThrowOnError extends boolean = false>(options: Options<TripData, ThrowOnError>) => { return (options?.client ?? client).get<TripResponse, TripError, ThrowOnError>({
...options,
url: '/api/v1/trip'
}); };
export const trip = <ThrowOnError extends boolean = false>(options: Options<TripData, ThrowOnError>) => {
return (options?.client ?? client).get<TripResponse, TripError, ThrowOnError>({
...options,
url: '/api/v1/trip'
});
};

/**
* Get the next N departures or arrivals of a stop sorted by time
*/
export const stoptimes = <ThrowOnError extends boolean = false>(options: Options<StoptimesData, ThrowOnError>) => { return (options?.client ?? client).get<StoptimesResponse, StoptimesError, ThrowOnError>({
...options,
url: '/api/v1/stoptimes'
}); };
export const stoptimes = <ThrowOnError extends boolean = false>(options: Options<StoptimesData, ThrowOnError>) => {
return (options?.client ?? client).get<StoptimesResponse, StoptimesError, ThrowOnError>({
...options,
url: '/api/v1/stoptimes'
});
};

/**
* Computes optimal connections from one place to another.
*/
export const plan = <ThrowOnError extends boolean = false>(options: Options<PlanData, ThrowOnError>) => { return (options?.client ?? client).get<PlanResponse, PlanError, ThrowOnError>({
...options,
url: '/api/v1/plan'
}); };
export const plan = <ThrowOnError extends boolean = false>(options: Options<PlanData, ThrowOnError>) => {
return (options?.client ?? client).get<PlanResponse, PlanError, ThrowOnError>({
...options,
url: '/api/v1/plan'
});
};

/**
* Given a area frame (box defined by top right and bottom left corner) and a time frame,
Expand All @@ -62,39 +74,49 @@ export const plan = <ThrowOnError extends boolean = false>(options: Options<Plan
* while on high zoom levels, also metros, buses and trams will be returned.
*
*/
export const trips = <ThrowOnError extends boolean = false>(options: Options<TripsData, ThrowOnError>) => { return (options?.client ?? client).get<TripsResponse, TripsError, ThrowOnError>({
...options,
url: '/api/v1/map/trips'
}); };
export const trips = <ThrowOnError extends boolean = false>(options: Options<TripsData, ThrowOnError>) => {
return (options?.client ?? client).get<TripsResponse, TripsError, ThrowOnError>({
...options,
url: '/api/v1/map/trips'
});
};

/**
* initial location to view the map at after loading based on where public transport should be visible
*/
export const initial = <ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) => { return (options?.client ?? client).get<InitialResponse, InitialError, ThrowOnError>({
...options,
url: '/api/v1/map/initial'
}); };
export const initial = <ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) => {
return (options?.client ?? client).get<InitialResponse, InitialError, ThrowOnError>({
...options,
url: '/api/v1/map/initial'
});
};

/**
* Get all stops for a map section
*/
export const stops = <ThrowOnError extends boolean = false>(options: Options<StopsData, ThrowOnError>) => { return (options?.client ?? client).get<StopsResponse, StopsError, ThrowOnError>({
...options,
url: '/api/v1/map/stops'
}); };
export const stops = <ThrowOnError extends boolean = false>(options: Options<StopsData, ThrowOnError>) => {
return (options?.client ?? client).get<StopsResponse, StopsError, ThrowOnError>({
...options,
url: '/api/v1/map/stops'
});
};

/**
* Get all available levels for a map section
*/
export const levels = <ThrowOnError extends boolean = false>(options: Options<LevelsData, ThrowOnError>) => { return (options?.client ?? client).get<LevelsResponse, LevelsError, ThrowOnError>({
...options,
url: '/api/v1/map/levels'
}); };
export const levels = <ThrowOnError extends boolean = false>(options: Options<LevelsData, ThrowOnError>) => {
return (options?.client ?? client).get<LevelsResponse, LevelsError, ThrowOnError>({
...options,
url: '/api/v1/map/levels'
});
};

/**
* Prints all footpaths of a timetable location (track, bus stop, etc.)
*/
export const footpaths = <ThrowOnError extends boolean = false>(options: Options<FootpathsData, ThrowOnError>) => { return (options?.client ?? client).get<FootpathsResponse, FootpathsError, ThrowOnError>({
...options,
url: '/api/debug/footpaths'
}); };
export const footpaths = <ThrowOnError extends boolean = false>(options: Options<FootpathsData, ThrowOnError>) => {
return (options?.client ?? client).get<FootpathsResponse, FootpathsError, ThrowOnError>({
...options,
url: '/api/debug/footpaths'
});
};
30 changes: 30 additions & 0 deletions ui/src/lib/openapi/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,34 @@ export type Leg = {
effectiveFareLegIndex?: number;
};

export type RiderCategory = {
/**
* Rider category name as displayed to the rider.
*/
riderCategoryName: string;
/**
* Specifies if this category should be considered the default (i.e. the main category displayed to riders).
*/
isDefaultFareCategory: boolean;
/**
* URL to a web page providing detailed information about the rider category and/or its eligibility criteria.
*/
eligibilityUrl?: string;
};

export type FareMediaType = 'NONE' | 'PAPER_TICKET' | 'TRANSIT_CARD' | 'CONTACTLESS_EMV' | 'MOBILE_APP';

export type FareMedia = {
/**
* Name of the fare media. Required for transit cards and mobile apps.
*/
fareMediaName?: string;
/**
* The type of fare media.
*/
fareMediaType: FareMediaType;
};

export type FareProduct = {
/**
* The name of the fare product as displayed to riders.
Expand All @@ -505,6 +533,8 @@ export type FareProduct = {
* ISO 4217 currency code. The currency of the cost of the fare product.
*/
currency: string;
riderCategory?: RiderCategory;
media?: FareMedia;
};

export type FareTransferRule = 'A_AB' | 'A_AB_B' | 'AB';
Expand Down

0 comments on commit 7c19307

Please sign in to comment.