2019-04-23 19:30:31 +00:00
|
|
|
package Travelynx::Command::work;
|
|
|
|
use Mojo::Base 'Mojolicious::Command';
|
|
|
|
|
|
|
|
use DateTime;
|
2019-05-26 15:28:21 +00:00
|
|
|
use JSON;
|
2019-12-15 12:42:11 +00:00
|
|
|
use List::Util;
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
has description =>
|
|
|
|
'Perform automatic checkout when users arrive at their destination';
|
|
|
|
|
|
|
|
has usage => sub { shift->extract_usage };
|
|
|
|
|
|
|
|
sub run {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
2019-06-10 16:09:54 +00:00
|
|
|
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
|
2019-05-26 15:28:21 +00:00
|
|
|
my $json = JSON->new;
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
my $db = $self->app->pg->db;
|
|
|
|
|
|
|
|
for my $entry (
|
|
|
|
$db->select( 'in_transit_str', '*', { cancelled => 0 } )->hashes->each )
|
|
|
|
{
|
|
|
|
|
2019-05-02 08:05:49 +00:00
|
|
|
my $uid = $entry->{user_id};
|
2019-12-23 21:57:45 +00:00
|
|
|
my $dep = $entry->{dep_eva};
|
|
|
|
my $arr = $entry->{arr_eva};
|
2019-05-02 08:05:49 +00:00
|
|
|
my $train_id = $entry->{train_id};
|
2019-04-23 19:30:31 +00:00
|
|
|
|
2019-06-10 16:09:54 +00:00
|
|
|
# Note: IRIS data is not always updated in real-time. Both departure and
|
|
|
|
# arrival delays may take several minutes to appear, especially in case
|
|
|
|
# of large-scale disturbances. We work around this by continuing to
|
|
|
|
# update departure data for up to 15 minutes after departure and
|
|
|
|
# delaying automatic checkout by at least 10 minutes.
|
|
|
|
|
2019-04-23 19:30:31 +00:00
|
|
|
eval {
|
2019-06-10 16:09:54 +00:00
|
|
|
if ( $now->epoch - $entry->{real_dep_ts} < 900 ) {
|
2020-08-06 14:04:12 +00:00
|
|
|
my $status = $self->app->iris->get_departures(
|
|
|
|
station => $dep,
|
|
|
|
lookbehind => 30,
|
|
|
|
lookahead => 30
|
|
|
|
);
|
2019-04-23 19:30:31 +00:00
|
|
|
if ( $status->{errstr} ) {
|
|
|
|
die("get_departures($dep): $status->{errstr}\n");
|
|
|
|
}
|
|
|
|
|
2019-12-15 12:42:11 +00:00
|
|
|
my ($train) = List::Util::first { $_->train_id eq $train_id }
|
|
|
|
@{ $status->{results} };
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
if ( not $train ) {
|
2019-05-02 08:05:49 +00:00
|
|
|
die("could not find train $train_id at $dep\n");
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
|
2020-01-23 16:14:01 +00:00
|
|
|
# selecting on user_id and train_no avoids a race condition when
|
|
|
|
# a user checks into a new train while we are fetching data for
|
|
|
|
# their previous journey. In this case, the new train would
|
|
|
|
# receive data from the previous journey.
|
2019-04-23 19:30:31 +00:00
|
|
|
$db->update(
|
|
|
|
'in_transit',
|
2019-04-26 20:12:34 +00:00
|
|
|
{
|
2019-05-18 15:10:53 +00:00
|
|
|
dep_platform => $train->platform,
|
2019-04-26 20:12:34 +00:00
|
|
|
real_departure => $train->departure,
|
2019-05-26 15:28:21 +00:00
|
|
|
route =>
|
2019-10-04 21:27:45 +00:00
|
|
|
$json->encode( [ $self->app->route_diff($train) ] ),
|
2019-05-26 15:28:21 +00:00
|
|
|
messages => $json->encode(
|
|
|
|
[
|
|
|
|
map { [ $_->[0]->epoch, $_->[1] ] }
|
|
|
|
$train->messages
|
|
|
|
]
|
|
|
|
),
|
2019-04-26 20:12:34 +00:00
|
|
|
},
|
2020-01-23 16:14:01 +00:00
|
|
|
{
|
|
|
|
user_id => $uid,
|
|
|
|
train_no => $train->train_no
|
|
|
|
}
|
2019-04-23 19:30:31 +00:00
|
|
|
);
|
2020-02-17 20:13:07 +00:00
|
|
|
if ( $train->departure_is_cancelled and $arr ) {
|
|
|
|
|
|
|
|
# depending on the amount of users in transit, some time may
|
|
|
|
# have passed between fetching $entry from the database and
|
|
|
|
# now. Ensure that the user is still checked into this train
|
|
|
|
# before calling checkout to mark the cancellation.
|
|
|
|
if (
|
|
|
|
$db->select(
|
|
|
|
'in_transit',
|
|
|
|
'count(*) as count',
|
|
|
|
{
|
2020-02-18 16:38:53 +00:00
|
|
|
user_id => $uid,
|
|
|
|
train_no => $train->train_no,
|
|
|
|
checkin_station_id => $dep,
|
|
|
|
checkout_station_id => $arr,
|
2020-02-17 20:13:07 +00:00
|
|
|
}
|
|
|
|
)->hash->{count}
|
|
|
|
)
|
|
|
|
{
|
|
|
|
$db->update(
|
|
|
|
'in_transit',
|
|
|
|
{
|
|
|
|
cancelled => 1,
|
|
|
|
},
|
|
|
|
{
|
2020-02-18 16:38:53 +00:00
|
|
|
user_id => $uid,
|
|
|
|
train_no => $train->train_no,
|
|
|
|
checkin_station_id => $dep,
|
|
|
|
checkout_station_id => $arr,
|
2020-02-17 20:13:07 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
# check out (adds a cancelled journey and resets journey state
|
|
|
|
# to checkin
|
2020-09-30 17:12:29 +00:00
|
|
|
$self->app->checkout(
|
|
|
|
station => $arr,
|
|
|
|
force => 1,
|
|
|
|
uid => $uid
|
|
|
|
);
|
2020-02-17 20:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->app->add_route_timestamps( $uid, $train, 1 );
|
|
|
|
}
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->app->log->error("work($uid)/departure: $@");
|
|
|
|
}
|
|
|
|
|
|
|
|
eval {
|
|
|
|
if (
|
2019-12-23 21:57:45 +00:00
|
|
|
$arr
|
2019-04-23 19:30:31 +00:00
|
|
|
and ( not $entry->{real_arr_ts}
|
2019-06-10 16:09:54 +00:00
|
|
|
or $now->epoch - $entry->{real_arr_ts} < 600 )
|
2019-04-23 19:30:31 +00:00
|
|
|
)
|
|
|
|
{
|
2020-08-06 14:04:12 +00:00
|
|
|
my $status = $self->app->iris->get_departures(
|
|
|
|
station => $arr,
|
|
|
|
lookbehind => 20,
|
|
|
|
lookahead => 220
|
|
|
|
);
|
2019-04-23 19:30:31 +00:00
|
|
|
if ( $status->{errstr} ) {
|
|
|
|
die("get_departures($arr): $status->{errstr}\n");
|
|
|
|
}
|
|
|
|
|
2019-12-15 12:42:11 +00:00
|
|
|
# Note that a train may pass the same station several times.
|
|
|
|
# Notable example: S41 / S42 ("Ringbahn") both starts and
|
|
|
|
# terminates at Berlin Südkreuz
|
|
|
|
my ($train) = List::Util::first {
|
|
|
|
$_->train_id eq $train_id
|
|
|
|
and $_->sched_arrival
|
|
|
|
and $_->sched_arrival->epoch > $entry->{sched_dep_ts}
|
|
|
|
}
|
|
|
|
@{ $status->{results} };
|
|
|
|
|
|
|
|
$train //= List::Util::first { $_->train_id eq $train_id }
|
|
|
|
@{ $status->{results} };
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
if ( not $train ) {
|
2019-06-01 10:38:26 +00:00
|
|
|
|
|
|
|
# If we haven't seen the train yet, its arrival is probably
|
|
|
|
# too far in the future. This is not critical.
|
|
|
|
return;
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
|
2020-01-23 16:14:01 +00:00
|
|
|
# selecting on user_id, train_no and checkout_station_id avoids a
|
|
|
|
# race condition when a user checks into a new train or changes
|
|
|
|
# their destination station while we are fetching times based on no
|
|
|
|
# longer valid database entries.
|
2019-04-23 19:30:31 +00:00
|
|
|
$db->update(
|
|
|
|
'in_transit',
|
|
|
|
{
|
2019-05-18 15:10:53 +00:00
|
|
|
arr_platform => $train->platform,
|
2019-04-23 19:30:31 +00:00
|
|
|
sched_arrival => $train->sched_arrival,
|
|
|
|
real_arrival => $train->arrival,
|
2019-05-26 15:28:21 +00:00
|
|
|
route =>
|
2019-10-04 21:27:45 +00:00
|
|
|
$json->encode( [ $self->app->route_diff($train) ] ),
|
2019-05-26 15:28:21 +00:00
|
|
|
messages => $json->encode(
|
|
|
|
[
|
|
|
|
map { [ $_->[0]->epoch, $_->[1] ] }
|
|
|
|
$train->messages
|
|
|
|
]
|
|
|
|
),
|
2019-04-23 19:30:31 +00:00
|
|
|
},
|
2020-01-23 16:14:01 +00:00
|
|
|
{
|
|
|
|
user_id => $uid,
|
|
|
|
train_no => $train->train_no,
|
|
|
|
checkout_station_id => $arr
|
|
|
|
}
|
2019-04-23 19:30:31 +00:00
|
|
|
);
|
2020-02-12 19:38:24 +00:00
|
|
|
if ( $train->arrival_is_cancelled ) {
|
|
|
|
|
2020-02-13 17:35:42 +00:00
|
|
|
# depending on the amount of users in transit, some time may
|
|
|
|
# have passed between fetching $entry from the database and
|
|
|
|
# now. Ensure that the user is still checked into this train
|
|
|
|
# before calling checkout to mark the cancellation.
|
|
|
|
if (
|
|
|
|
$db->select(
|
|
|
|
'in_transit',
|
|
|
|
'count(*) as count',
|
|
|
|
{
|
|
|
|
user_id => $uid,
|
|
|
|
train_no => $train->train_no,
|
|
|
|
checkout_station_id => $arr
|
|
|
|
}
|
|
|
|
)->hash->{count}
|
|
|
|
)
|
|
|
|
{
|
2020-02-12 19:38:24 +00:00
|
|
|
# check out (adds a cancelled journey and resets journey state
|
|
|
|
# to destination selection)
|
2020-09-30 17:12:29 +00:00
|
|
|
$self->app->checkout(
|
|
|
|
station => $arr,
|
|
|
|
force => 0,
|
|
|
|
uid => $uid
|
|
|
|
);
|
2020-02-13 17:35:42 +00:00
|
|
|
}
|
2020-02-12 19:38:24 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->app->add_route_timestamps( $uid, $train, 0 );
|
|
|
|
}
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
elsif ( $entry->{real_arr_ts} ) {
|
2020-09-30 17:12:29 +00:00
|
|
|
my ( undef, $error ) = $self->app->checkout(
|
|
|
|
station => $arr,
|
|
|
|
force => 1,
|
|
|
|
uid => $uid
|
|
|
|
);
|
2019-04-23 19:30:31 +00:00
|
|
|
if ($error) {
|
|
|
|
die("${error}\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->app->log->error("work($uid)/arrival: $@");
|
|
|
|
}
|
|
|
|
|
|
|
|
eval { }
|
|
|
|
}
|
2019-05-18 15:11:28 +00:00
|
|
|
|
2020-09-30 17:12:29 +00:00
|
|
|
for my $account_data ( $self->app->traewelling->get_pull_accounts ) {
|
|
|
|
|
|
|
|
# $account_data->{user_id} is the travelynx uid
|
|
|
|
# $account_data->{user_name} is the Träwelling username
|
|
|
|
$self->app->log->debug(
|
|
|
|
"Pulling Traewelling status for UID $account_data->{user_id}");
|
|
|
|
$self->app->traewelling_api->get_status_p(
|
|
|
|
username => $account_data->{data}{user_name},
|
|
|
|
token => $account_data->{token}
|
|
|
|
)->then(
|
|
|
|
sub {
|
|
|
|
my ($traewelling) = @_;
|
|
|
|
$self->app->traewelling_to_travelynx(
|
|
|
|
traewelling => $traewelling,
|
|
|
|
user_data => $account_data
|
|
|
|
);
|
|
|
|
}
|
|
|
|
)->catch(
|
|
|
|
sub {
|
|
|
|
my ($err) = @_;
|
|
|
|
$self->app->log->debug("Error $err");
|
|
|
|
}
|
|
|
|
)->wait;
|
|
|
|
}
|
|
|
|
|
2020-10-01 17:36:35 +00:00
|
|
|
for my $candidate ( $self->app->traewelling->get_pushable_accounts ) {
|
|
|
|
$self->app->log->debug(
|
|
|
|
"Pushing to Traewelling for UID $candidate->{uid}");
|
|
|
|
my $trip_id = $candidate->{journey_data}{trip_id};
|
|
|
|
if ( not $trip_id ) {
|
|
|
|
$self->app->log->debug("... trip_id is missing");
|
|
|
|
|
|
|
|
# TODO log traewelling error
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( $candidate->{data}{latest_push_ts}
|
|
|
|
and $candidate->{data}{latest_push_ts} == $candidate->{checkin_ts} )
|
|
|
|
{
|
|
|
|
$self->app->log->debug("... already handled");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$self->app->traewelling_api->checkin( %{$candidate},
|
|
|
|
trip_id => $trip_id );
|
|
|
|
}
|
|
|
|
|
2019-05-18 15:11:28 +00:00
|
|
|
# Computing yearly stats may take a while, but we've got all time in the
|
|
|
|
# world here. This means users won't have to wait when loading their
|
|
|
|
# own by-year journey log.
|
|
|
|
for my $user ( $db->select( 'users', 'id', { status => 1 } )->hashes->each )
|
|
|
|
{
|
|
|
|
$self->app->get_journey_stats(
|
|
|
|
uid => $user->{id},
|
|
|
|
year => $now->year
|
|
|
|
);
|
|
|
|
}
|
2020-09-30 17:12:29 +00:00
|
|
|
|
|
|
|
# TODO wait until all background jobs have terminated
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
Usage: index.pl work
|
|
|
|
|
|
|
|
Work Work Work.
|
|
|
|
|
|
|
|
Should be called from a cronjob every three minutes or so.
|