summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2020-04-19 16:49:31 +0200
committerDaniel Friesel <derf@finalrewind.org>2020-04-19 16:49:31 +0200
commit94932c410290081461eecb2819bc187335452672 (patch)
treec73dd31b3e8b20ef0ff035aa5a2fa44d69090f71
parent50d8256f6f0bb05416d67c4910d014eb1d91677f (diff)
add commute stats. not linked from nav yet.
-rwxr-xr-xlib/Travelynx.pm35
-rwxr-xr-xlib/Travelynx/Controller/Traveling.pm98
-rw-r--r--templates/commute.html.ep89
3 files changed, 220 insertions, 2 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index d5dca3f..36a3998 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -2719,6 +2719,40 @@ sub startup {
);
$self->helper(
+ 'get_top_destinations' => sub {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid} //= $self->current_user->{id};
+ my $db = $opt{db} //= $self->pg->db;
+
+ my @stations;
+
+ my $res = $db->query(
+ qq{
+ select arr_eva, count(arr_eva) as count
+ from journeys_str
+ where user_id = ?
+ and real_dep_ts between ? and ?
+ group by arr_eva
+ order by count
+ limit 5
+ }, $uid, $opt{after}->epoch, $opt{before}->epoch
+ );
+
+ for my $dest ( $res->hashes->each ) {
+ $self->app->log->debug( $dest->{arr_eva} );
+ $self->app->log->debug( $dest->{count} );
+ if ( my $station
+ = $self->app->station_by_eva->{ $dest->{arr_eva} } )
+ {
+ push( @stations, $station );
+ }
+ }
+
+ return @stations;
+ }
+ );
+
+ $self->helper(
'get_connection_targets' => sub {
my ( $self, %opt ) = @_;
@@ -4043,6 +4077,7 @@ sub startup {
$authed_r->get('/export.json')->to('account#json_export');
$authed_r->get('/history.json')->to('traveling#json_history');
$authed_r->get('/history')->to('traveling#history');
+ $authed_r->get('/history/commute')->to('traveling#commute');
$authed_r->get('/history/map')->to('traveling#map_history');
$authed_r->get('/history/:year')->to('traveling#yearly_history');
$authed_r->get('/history/:year/:month')->to('traveling#monthly_history');
diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm
index fb84f6b..bcc0090 100755
--- a/lib/Travelynx/Controller/Traveling.pm
+++ b/lib/Travelynx/Controller/Traveling.pm
@@ -104,7 +104,7 @@ sub user_status {
$tw_data{description} = sprintf(
'%s %s von %s nach %s',
$status->{train_type}, $status->{train_line} // $status->{train_no},
- $status->{dep_name}, $status->{arr_name} // 'irgendwo'
+ $status->{dep_name}, $status->{arr_name} // 'irgendwo'
);
if ( $status->{real_arrival}->epoch ) {
$tw_data{description} .= $status->{real_arrival}
@@ -439,7 +439,7 @@ sub station {
@results = map { $_->[0] }
sort { $b->[1] <=> $a->[1] }
- map { [ $_, $_->departure->epoch // $_->sched_departure->epoch ] }
+ map { [ $_, $_->departure->epoch // $_->sched_departure->epoch ] }
@results;
if ($train) {
@@ -488,6 +488,100 @@ sub history {
$self->render( template => 'history' );
}
+sub commute {
+ my ($self) = @_;
+
+ my $year = $self->param('year');
+ my $filter_type = $self->param('filter_type') || 'exact';
+ my $station = $self->param('station');
+
+ # DateTime is very slow when looking far into the future due to DST changes
+ # -> Limit time range to avoid accidental DoS.
+ if (
+ not( $year
+ and $year =~ m{ ^ [0-9]{4} $ }x
+ and $year > 1990
+ and $year < 2100 )
+ )
+ {
+ $year = DateTime->now( time_zone => 'Europe/Berlin' )->year - 1;
+ }
+ my $interval_start = DateTime->new(
+ time_zone => 'Europe/Berlin',
+ year => $year,
+ month => 1,
+ day => 1,
+ hour => 0,
+ minute => 0,
+ second => 0,
+ );
+ my $interval_end = $interval_start->clone->add( years => 1 );
+
+ if ( not $station ) {
+ my @top_station_ids = $self->get_top_destinations(
+ after => $interval_start,
+ before => $interval_end,
+ );
+ if (@top_station_ids) {
+ $station = $top_station_ids[0][1];
+ }
+ }
+
+ my @journeys = $self->get_user_travels(
+ after => $interval_start,
+ before => $interval_end,
+ with_datetime => 1,
+ );
+
+ my %journeys_by_month;
+ my $total = 0;
+
+ for my $journey ( reverse @journeys ) {
+ my $month = $journey->{rt_departure}->month;
+ if (
+ $filter_type eq 'exact'
+ and ( $journey->{to_name} eq $station
+ or $journey->{from_name} eq $station )
+ )
+ {
+ push( @{ $journeys_by_month{$month} }, $journey );
+ $total++;
+ }
+ elsif (
+ $filter_type eq 'substring'
+ and ( $journey->{to_name} =~ m{\Q$station\E}
+ or $journey->{from_name} =~ m{\Q$station\E} )
+ )
+ {
+ push( @{ $journeys_by_month{$month} }, $journey );
+ $total++;
+ }
+ elsif (
+ $filter_type eq 'regex'
+ and ( $journey->{to_name} =~ m{$station}
+ or $journey->{from_name} =~ m{$station} )
+ )
+ {
+ push( @{ $journeys_by_month{$month} }, $journey );
+ $total++;
+ }
+ }
+
+ $self->param( year => $year );
+ $self->param( filter_type => $filter_type );
+ $self->param( station => $station );
+
+ $self->render(
+ template => 'commute',
+ with_autocomplete => 1,
+ journeys_by_month => \%journeys_by_month,
+ total_journeys => $total,
+ months => [
+ qw(Januar Februar März April Mai Juni Juli August September Oktober November Dezember)
+ ],
+ );
+}
+
sub map_history {
my ($self) = @_;
diff --git a/templates/commute.html.ep b/templates/commute.html.ep
new file mode 100644
index 0000000..ef1c7ab
--- /dev/null
+++ b/templates/commute.html.ep
@@ -0,0 +1,89 @@
+<h1>Pendel-Statistiken</h1>
+
+<div class="row">
+ <div class="col s12">
+ <p>Diese Daten können zum Beispiel für die Angaben zur Pendlerpauschale
+ bei der Steuererklärung genutzt werden.</p>
+ </div>
+</div>
+
+%= form_for '/history/commute' => begin
+ <div class="row">
+ <div class="input-field col s12 m12 l12">
+ %= text_field 'year', id => 'year', class => 'validate', pattern => '[0-9][0-9][0-9][0-9]'
+ <label for="year">Jahr</label>
+ </div>
+ </div>
+ <div class="row">
+ <div class="input-field col s12 m12 l6">
+ <div>
+ <label>
+ %= radio_button filter_type => 'exact'
+ <span>Name der Station ist:</span>
+ </label>
+ </div>
+ <div>
+ <label>
+ %= radio_button filter_type => 'substring'
+ <span>Name der Station enthält:</span>
+ </label>
+ </div>
+ <div>
+ <label>
+ %= radio_button filter_type => 'regex'
+ <span>Name der Station erfüllt den regulären Ausdruck:</span>
+ </label>
+ </div>
+ </div>
+ <div class="input-field col s12 m12 l6">
+ %= text_field 'station', id => 'station', required => undef, class => 'autocomplete contrast-color-text'
+ <label for="station">Fahrtziel</label>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col s12 m12 l12 center-align">
+ <button class="btn waves-effect waves-light" type="submit" name="action" value="show">
+ <i class="material-icons left" aria-hidden="true">send</i>
+ Anzeigen
+ </button>
+ </div>
+ </div>
+%= end
+
+<h2><%= param('year') %></h2>
+<div class="row">
+ <div class="col s12 m12 l12 center-align">
+ <p>
+ An <b><%= $total_journeys %></b> Tagen im Jahr wurde mindestens
+ eine Zugfahrt von oder zu
+ % if (param('filter_type') eq 'exact') {
+ der ausgewählten Station
+ % }
+ % else {
+ den ausgewählten Stationen
+ % }
+ eingetragen.
+ </p>
+ <table class="striped">
+ <thead>
+ <tr>
+ <th>Monat</th>
+ <th>Tage mit Fahrten</th>
+ </tr>
+ </thead>
+ <tbody>
+ % for my $i (0 .. $#{$months}) {
+ <tr>
+ <td><%= $months->[$i] %></td>
+ <td><%= scalar @{$journeys_by_month->{$i+1} // []} %></td>
+ </tr>
+ % }
+ </tbody>
+ </table>
+ </div>
+</div>
+
+% for my $i (0 .. $#{$months}) {
+ <h2><%= $months->[$i] %></h2>
+ %= include '_history_trains', date_format => '%d.%m.', journeys => $journeys_by_month->{$i+1} // []
+% }