show journey suggestions on departure board as well

This commit is contained in:
Daniel Friesel 2019-05-20 19:15:21 +02:00
parent 531cb95c17
commit 1dc04eb45a
10 changed files with 279 additions and 48 deletions

View file

@ -317,7 +317,7 @@ sub startup {
'checkin' => sub { 'checkin' => sub {
my ( $self, $station, $train_id ) = @_; my ( $self, $station, $train_id ) = @_;
my $status = $self->get_departures( $station, 140, 30 ); my $status = $self->get_departures( $station, 140, 40 );
if ( $status->{errstr} ) { if ( $status->{errstr} ) {
return ( undef, $status->{errstr} ); return ( undef, $status->{errstr} );
} }
@ -753,6 +753,10 @@ sub startup {
return $res_h->{id}; return $res_h->{id};
} }
if ( $opt{readonly} ) {
return;
}
$self->pg->db->insert( $self->pg->db->insert(
'stations', 'stations',
{ {
@ -1495,14 +1499,11 @@ sub startup {
); );
$self->helper( $self->helper(
'get_connection_targets' => sub { 'get_latest_dest_id' => sub {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $uid = $opt{uid} // $self->current_user->{id}; my $uid = $opt{uid} // $self->current_user->{id};
my $threshold = $opt{threshold} my $db = $opt{db} // $self->pg->db;
// DateTime->now( time_zone => 'Europe/Berlin' )
->subtract( weeks => 6 );
my $db = $opt{db} // $self->pg->db;
my $journey = $db->select( 'in_transit', ['checkout_station_id'], my $journey = $db->select( 'in_transit', ['checkout_station_id'],
{ user_id => $uid } )->hash; { user_id => $uid } )->hash;
@ -1525,6 +1526,37 @@ sub startup {
return; return;
} }
return $journey->{checkout_station_id};
}
);
$self->helper(
'get_connection_targets' => sub {
my ( $self, %opt ) = @_;
my $uid = $opt{uid} //= $self->current_user->{id};
my $threshold = $opt{threshold}
// DateTime->now( time_zone => 'Europe/Berlin' )
->subtract( weeks => 6 );
my $db = $opt{db} //= $self->pg->db;
my $min_count = $opt{min_count} // 3;
my $dest_id;
if ( $opt{ds100} ) {
$dest_id = $self->get_station_id(
ds100 => $opt{ds100},
readonly => 1
);
}
else {
$dest_id = $self->get_latest_dest_id(%opt);
}
if ( not $dest_id ) {
return;
}
my $res = $db->query( my $res = $db->query(
qq{ qq{
select select
@ -1539,11 +1571,12 @@ sub startup {
order by count desc; order by count desc;
}, },
$uid, $uid,
$journey->{checkout_station_id}, $dest_id,
$threshold $threshold
); );
my @destinations = $res->hashes->grep( sub { shift->{count} > 2 } ) my @destinations
->map( sub { shift->{dest} } )->each; = $res->hashes->grep( sub { shift->{count} >= $min_count } )
->map( sub { shift->{dest} } )->each;
return @destinations; return @destinations;
} }
); );
@ -1552,21 +1585,41 @@ sub startup {
'get_connecting_trains' => sub { 'get_connecting_trains' => sub {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $status = $self->get_user_status; my $uid = $opt{uid} //= $self->current_user->{id};
my $use_history = $self->account_use_history($uid);
if ( not $status->{arr_ds100} ) { my ( $ds100, $exclude_via, $exclude_train_id, $exclude_before );
if ( $opt{ds100} ) {
if ( $use_history & 0x01 ) {
$ds100 = $opt{ds100};
}
}
else {
if ( $use_history & 0x02 ) {
my $status = $self->get_user_status;
$ds100 = $status->{arr_ds100};
$exclude_via = $status->{dep_name};
$exclude_train_id = $status->{train_id};
$exclude_before = $status->{real_arrival}->epoch;
}
}
if ( not $ds100 ) {
return; return;
} }
my @destinations = $self->get_connection_targets(%opt); my @destinations = $self->get_connection_targets(%opt);
@destinations = grep { $_ ne $status->{dep_name} } @destinations;
if ($exclude_via) {
@destinations = grep { $_ ne $exclude_via } @destinations;
}
if ( not @destinations ) { if ( not @destinations ) {
return; return;
} }
my $stationboard my $stationboard = $self->get_departures( $ds100, 0, 40 );
= $self->get_departures( $status->{arr_ds100}, 0, 60 );
if ( $stationboard->{errstr} ) { if ( $stationboard->{errstr} ) {
return; return;
} }
@ -1576,16 +1629,19 @@ sub startup {
if ( not $train->departure ) { if ( not $train->departure ) {
next; next;
} }
if ( $train->departure->epoch < $status->{real_arrival}->epoch ) if ( $exclude_before
and $train->departure->epoch < $exclude_before )
{ {
next; next;
} }
if ( $train->train_id eq $status->{train_id} ) { if ( $exclude_train_id
and $train->train_id eq $exclude_train_id )
{
next; next;
} }
my @via = ( $train->route_post, $train->route_end ); my @via = ( $train->route_post, $train->route_end );
for my $dest (@destinations) { for my $dest (@destinations) {
if ( $via_count{$dest} < 3 if ( $via_count{$dest} < 2
and List::Util::any { $_ eq $dest } @via ) and List::Util::any { $_ eq $dest } @via )
{ {
push( @results, [ $train, $dest ] ); push( @results, [ $train, $dest ] );
@ -1608,6 +1664,24 @@ sub startup {
} }
); );
$self->helper(
'account_use_history' => sub {
my ( $self, $uid, $value ) = @_;
if ($value) {
$self->pg->db->update(
'users',
{ use_history => $value },
{ id => $uid }
);
}
else {
return $self->pg->db->select( 'users', ['use_history'],
{ id => $uid } )->hash->{use_history};
}
}
);
$self->helper( $self->helper(
'get_user_travels' => sub { 'get_user_travels' => sub {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
@ -2113,6 +2187,7 @@ sub startup {
$authed_r->get('/account')->to('account#account'); $authed_r->get('/account')->to('account#account');
$authed_r->get('/account/privacy')->to('account#privacy'); $authed_r->get('/account/privacy')->to('account#privacy');
$authed_r->get('/account/hooks')->to('account#webhook'); $authed_r->get('/account/hooks')->to('account#webhook');
$authed_r->get('/account/insight')->to('account#insight');
$authed_r->get('/ajax/status_card.html')->to('traveling#status_card'); $authed_r->get('/ajax/status_card.html')->to('traveling#status_card');
$authed_r->get('/cancelled')->to('traveling#cancelled'); $authed_r->get('/cancelled')->to('traveling#cancelled');
$authed_r->get('/account/password')->to('account#password_form'); $authed_r->get('/account/password')->to('account#password_form');
@ -2128,6 +2203,7 @@ sub startup {
$authed_r->get('/confirm_mail/:token')->to('account#confirm_mail'); $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail');
$authed_r->post('/account/privacy')->to('account#privacy'); $authed_r->post('/account/privacy')->to('account#privacy');
$authed_r->post('/account/hooks')->to('account#webhook'); $authed_r->post('/account/hooks')->to('account#webhook');
$authed_r->post('/account/insight')->to('account#insight');
$authed_r->post('/journey/add')->to('traveling#add_journey_form'); $authed_r->post('/journey/add')->to('traveling#add_journey_form');
$authed_r->post('/journey/edit')->to('traveling#edit_journey'); $authed_r->post('/journey/edit')->to('traveling#edit_journey');
$authed_r->post('/account/password')->to('account#change_password'); $authed_r->post('/account/password')->to('account#change_password');

View file

@ -535,6 +535,17 @@ my @migrations = (
} }
); );
}, },
# v12 -> v13
sub {
my ($db) = @_;
$db->query(
qq{
alter table users add column use_history smallint default 255;
update schema_version set version = 13;
}
);
},
); );
sub setup_db { sub setup_db {

View file

@ -232,6 +232,38 @@ sub privacy {
} }
} }
sub insight {
my ($self) = @_;
my $user = $self->current_user;
my $use_history = $self->account_use_history( $user->{id} );
if ( $self->param('action') and $self->param('action') eq 'save' ) {
if ( $self->param('on_departure') ) {
$use_history |= 0x01;
}
else {
$use_history &= ~0x01;
}
if ( $self->param('on_arrival') ) {
$use_history |= 0x02;
}
else {
$use_history &= ~0x02;
}
$self->account_use_history( $user->{id}, $use_history );
$self->flash( success => 'use_history' );
$self->redirect_to('account');
}
$self->param( on_departure => $use_history & 0x01 ? 1 : 0 );
$self->param( on_arrival => $use_history & 0x02 ? 1 : 0 );
$self->render('use_history');
}
sub webhook { sub webhook {
my ($self) = @_; my ($self) = @_;

View file

@ -79,6 +79,10 @@
% } % }
% if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} < (20*60)) { % if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} < (20*60)) {
% if (my @connections = get_connecting_trains()) { % if (my @connections = get_connecting_trains()) {
<span class="card-title" style="margin-top: 2ex;">Verbindungen</span>
% if ($journey->{arrival_countdown} < 0) {
<p>Zug auswählen zum Einchecken mit Zielwahl.</p>
% }
%= include '_connections', connections => \@connections, checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_ds100} : undef; %= include '_connections', connections => \@connections, checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_ds100} : undef;
% } % }
% } % }

View file

@ -5,6 +5,8 @@
bis <a href="/s/<%= $journey->{arr_ds100} %>"><%= $journey->{arr_name} %></a></p> bis <a href="/s/<%= $journey->{arr_ds100} %>"><%= $journey->{arr_name} %></a></p>
% if (now()->epoch - $journey->{timestamp}->epoch < (30*60)) { % if (now()->epoch - $journey->{timestamp}->epoch < (30*60)) {
% if (my @connections = get_connecting_trains()) { % if (my @connections = get_connecting_trains()) {
<span class="card-title" style="margin-top: 2ex;">Verbindungen</span>
<p>Zug auswählen zum Einchecken mit Zielwahl.</p>
%= include '_connections', connections => \@connections, checkin_from => $journey->{arr_ds100}; %= include '_connections', connections => \@connections, checkin_from => $journey->{arr_ds100};
% } % }
% } % }

View file

@ -1,8 +1,4 @@
<span class="card-title" style="margin-top: 2ex;">Verbindungen</span> <div class="hide-on-med-and-up"><table class="striped"><tbody>
% if ($checkin_from) {
<p>Zug auswählen zum Einchecken mit Zielwahl.</p>
% }
<div class="hide-on-med-and-up"><table><tbody>
% for my $res (@{$connections}) { % for my $res (@{$connections}) {
% my ($train, $via) = @{$res}; % my ($train, $via) = @{$res};
<tr> <tr>
@ -30,7 +26,7 @@
</tr> </tr>
% } % }
</tbody></table></div> </tbody></table></div>
<div class="hide-on-small-only"><table><tbody> <div class="hide-on-small-only"><table class="striped"><tbody>
% for my $res (@{$connections}) { % for my $res (@{$connections}) {
% my ($train, $via) = @{$res}; % my ($train, $via) = @{$res};
<tr> <tr>

View file

@ -16,6 +16,9 @@
% elsif ($success eq 'privacy') { % elsif ($success eq 'privacy') {
<span class="card-title">Einstellungen zu öffentliche Account-Daten geändert</span> <span class="card-title">Einstellungen zu öffentliche Account-Daten geändert</span>
% } % }
% elsif ($success eq 'use_history') {
<span class="card-title">Einstellungen zu vorgeschlagenen Verbindungen geändert</span>
% }
% elsif ($success eq 'webhook') { % elsif ($success eq 'webhook') {
<span class="card-title">Web Hook aktualisiert</span> <span class="card-title">Web Hook aktualisiert</span>
% } % }
@ -28,6 +31,7 @@
<h1>Account</h1> <h1>Account</h1>
% my $acc = current_user(); % my $acc = current_user();
% my $hook = get_webhook(); % my $hook = get_webhook();
% my $use_history = account_use_history($acc->{id});
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<table class="striped"> <table class="striped">
@ -43,6 +47,18 @@
<th scope="row">Passwort</th> <th scope="row">Passwort</th>
<td><a href="/account/password"><i class="material-icons">edit</i></a></td> <td><a href="/account/password"><i class="material-icons">edit</i></a></td>
</tr> </tr>
<tr>
<th scope="row">Verbindungen</th>
<td>
<a href="/account/insight"><i class="material-icons">edit</i></a>
% if ($use_history & 0x03) {
Vorschläge aktiv
% }
% else {
<span style="color: #999999;">Vorschläge deaktiviert</span>
% }
</td>
</tr>
<tr> <tr>
<th scope="row">Öffentliche Daten</th> <th scope="row">Öffentliche Daten</th>
<td> <td>

View file

@ -1,5 +1,22 @@
<h1>Changelog</h1> <h1>Changelog</h1>
<div class="row">
<div class="col s12 m1 l1">
1.6
</div>
<div class="col s12 m11 l11">
<p>
<i class="material-icons left">add</i> Anzeige von häufig genutzten
Verbindungen in der Abfahrtstafel. Wie bei den Anschlusszügen kann
darüber direkt (inkl. Vorauswahl des Ziels) eingecheckt werden.
</p>
<p>
<i class="material-icons left">add</i> Konfigurationsseite, um die
Heuristik für Anschlusszüge und häufige Verbindungen zu deaktivieren.
</p>
</div>
</div>
<div class="row"> <div class="row">
<div class="col s12 m1 l1"> <div class="col s12 m1 l1">
1.5 1.5

View file

@ -1,7 +1,13 @@
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12 center-align"><b>
% my $status = $self->get_user_status; %= $station
% if ($status->{checked_in}) { </b></div>
</div>
% my $status = $self->get_user_status;
% my $have_connections = 0;
% if ($status->{checked_in}) {
<div class="row">
<div class="col s12">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<span class="card-title">Aktuell eingecheckt</span> <span class="card-title">Aktuell eingecheckt</span>
@ -14,31 +20,40 @@
</a> </a>
</div> </div>
</div> </div>
% } </div>
% elsif ($status->{timestamp_delta} < 180) {
%= include '_checked_out', journey => $status;
% }
</div> </div>
</div> % }
% elsif ($status->{timestamp_delta} < 180) {
<div class="row">
<div class="col s12">
%= include '_checked_out', journey => $status;
</div>
</div>
% }
% elsif (not param('train') and my @connections = get_connecting_trains(ds100 => $ds100)) {
% $have_connections = 1;
<div class="row">
<div class="col s12">
<p>Häufig genutzte Verbindungen Zug auswählen zum Einchecken mit Zielwahl</p>
%= include '_connections', connections => \@connections, checkin_from => $ds100;
</div>
</div>
% }
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
%= $station <p>
% if (@{$results}) { % if ($have_connections) {
Zug auswählen zum Einchecken. Alle Abfahrten
% } % }
% else { % if (@{$results}) {
Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor Zug auswählen zum Einchecken.
und maximal 120 Minuten nach Abfahrt möglich. % }
% } % else {
<br/> Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor
und maximal 120 Minuten nach Abfahrt möglich.
% }
</p>
<table class="striped"> <table class="striped">
<thead>
<tr>
<th>Zug</th>
<th></th>
<th>Abfahrt</th>
</tr>
</thead>
<tbody> <tbody>
% for my $result (@{$results}) { % for my $result (@{$results}) {
% my $td_class = ''; % my $td_class = '';

View file

@ -0,0 +1,62 @@
<h1>Bevorzugte Verbindungen</h1>
<div class="row">
<div class="col s12">
<p>
Travelynx kann anhand deiner vergangenen Fahrten Verbindungen zum
Einchecken vorschlagen. Fährst zu z.B regelmäßig von Dortmund Hbf
nach Essen Hbf, werden dir in Dortmund bevorzugt Züge angezeigt, die über
Essen fahren. Bei Auswahl dieser wird nicht nur in den Zug eingecheckt,
sondern auch direkt Essen Hbf als Ziel eingetragen.
<p/>
<!-- <p>
Falls du das nicht nützlich findest oder nicht möchtest, dass deine
regelmäßigen (Anschluss-)Züge auf deinem Bildschirm sichtbar sind,
kannst du dieses Feature hier
ausschalten.
</p> -->
</div>
</div>
<h2>Vorschläge aktiv für:</h2>
%= form_for '/account/insight' => (method => 'POST') => begin
%= csrf_field
<div class="row">
<div class="input-field col s12">
<label>
%= check_box on_departure => 1
<span>Abfahrtstafel</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
Zeige häufige Fahrten im Abfahrtsmonitor.
</div>
</div>
<div class="row">
<div class="input-field col s12">
<label>
%= check_box on_arrival => 1
<span>Reisestatus</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
Zeige Anschlussmöglichkeiten kurz vor Ankunft am Ziel der aktuellen
Reise. Sobald es erreicht wurde, ist über diese Liste auch ein Checkin
ohne Umweg über die Abfahrtstafel möglich.
</div>
</div>
<div class="row">
<div class="col s3 m3 l3">
</div>
<div class="col s6 m6 l6 center-align">
<button class="btn waves-effect waves-light" type="submit" name="action" value="save">
Speichern
<i class="material-icons right">send</i>
</button>
</div>
<div class="col s3 m3 l3">
</div>
</div>
%= end