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 {
my ( $self, $station, $train_id ) = @_;
my $status = $self->get_departures( $station, 140, 30 );
my $status = $self->get_departures( $station, 140, 40 );
if ( $status->{errstr} ) {
return ( undef, $status->{errstr} );
}
@ -753,6 +753,10 @@ sub startup {
return $res_h->{id};
}
if ( $opt{readonly} ) {
return;
}
$self->pg->db->insert(
'stations',
{
@ -1495,14 +1499,11 @@ sub startup {
);
$self->helper(
'get_connection_targets' => sub {
'get_latest_dest_id' => 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 $uid = $opt{uid} // $self->current_user->{id};
my $db = $opt{db} // $self->pg->db;
my $journey = $db->select( 'in_transit', ['checkout_station_id'],
{ user_id => $uid } )->hash;
@ -1525,6 +1526,37 @@ sub startup {
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(
qq{
select
@ -1539,11 +1571,12 @@ sub startup {
order by count desc;
},
$uid,
$journey->{checkout_station_id},
$dest_id,
$threshold
);
my @destinations = $res->hashes->grep( sub { shift->{count} > 2 } )
->map( sub { shift->{dest} } )->each;
my @destinations
= $res->hashes->grep( sub { shift->{count} >= $min_count } )
->map( sub { shift->{dest} } )->each;
return @destinations;
}
);
@ -1552,21 +1585,41 @@ sub startup {
'get_connecting_trains' => sub {
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;
}
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 ) {
return;
}
my $stationboard
= $self->get_departures( $status->{arr_ds100}, 0, 60 );
my $stationboard = $self->get_departures( $ds100, 0, 40 );
if ( $stationboard->{errstr} ) {
return;
}
@ -1576,16 +1629,19 @@ sub startup {
if ( not $train->departure ) {
next;
}
if ( $train->departure->epoch < $status->{real_arrival}->epoch )
if ( $exclude_before
and $train->departure->epoch < $exclude_before )
{
next;
}
if ( $train->train_id eq $status->{train_id} ) {
if ( $exclude_train_id
and $train->train_id eq $exclude_train_id )
{
next;
}
my @via = ( $train->route_post, $train->route_end );
for my $dest (@destinations) {
if ( $via_count{$dest} < 3
if ( $via_count{$dest} < 2
and List::Util::any { $_ eq $dest } @via )
{
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(
'get_user_travels' => sub {
my ( $self, %opt ) = @_;
@ -2113,6 +2187,7 @@ sub startup {
$authed_r->get('/account')->to('account#account');
$authed_r->get('/account/privacy')->to('account#privacy');
$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('/cancelled')->to('traveling#cancelled');
$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->post('/account/privacy')->to('account#privacy');
$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/edit')->to('traveling#edit_journey');
$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 {

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 {
my ($self) = @_;

View file

@ -79,6 +79,10 @@
% }
% if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} < (20*60)) {
% 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;
% }
% }

View file

@ -5,6 +5,8 @@
bis <a href="/s/<%= $journey->{arr_ds100} %>"><%= $journey->{arr_name} %></a></p>
% if (now()->epoch - $journey->{timestamp}->epoch < (30*60)) {
% 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};
% }
% }

View file

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

View file

@ -16,6 +16,9 @@
% elsif ($success eq 'privacy') {
<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') {
<span class="card-title">Web Hook aktualisiert</span>
% }
@ -28,6 +31,7 @@
<h1>Account</h1>
% my $acc = current_user();
% my $hook = get_webhook();
% my $use_history = account_use_history($acc->{id});
<div class="row">
<div class="col s12">
<table class="striped">
@ -43,6 +47,18 @@
<th scope="row">Passwort</th>
<td><a href="/account/password"><i class="material-icons">edit</i></a></td>
</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>
<th scope="row">Öffentliche Daten</th>
<td>

View file

@ -1,5 +1,22 @@
<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="col s12 m1 l1">
1.5

View file

@ -1,7 +1,13 @@
<div class="row">
<div class="col s12">
% my $status = $self->get_user_status;
% if ($status->{checked_in}) {
<div class="col s12 center-align"><b>
%= $station
</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-content">
<span class="card-title">Aktuell eingecheckt</span>
@ -14,31 +20,40 @@
</a>
</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="col s12">
%= $station
% if (@{$results}) {
Zug auswählen zum Einchecken.
% }
% else {
Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor
und maximal 120 Minuten nach Abfahrt möglich.
% }
<br/>
<p>
% if ($have_connections) {
Alle Abfahrten
% }
% if (@{$results}) {
Zug auswählen zum Einchecken.
% }
% else {
Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor
und maximal 120 Minuten nach Abfahrt möglich.
% }
</p>
<table class="striped">
<thead>
<tr>
<th>Zug</th>
<th></th>
<th>Abfahrt</th>
</tr>
</thead>
<tbody>
% for my $result (@{$results}) {
% 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