add passenger rights heuristic for missed connections

This commit is contained in:
Daniel Friesel 2019-09-13 23:39:28 +02:00
parent 29d99fe140
commit 35cc18dfae
4 changed files with 265 additions and 27 deletions

View file

@ -2890,6 +2890,7 @@ sub startup {
$authed_r->get('/account/insight')->to('account#insight'); $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('/fgr')->to('passengerrights#list_candidates');
$authed_r->get('/account/password')->to('account#password_form'); $authed_r->get('/account/password')->to('account#password_form');
$authed_r->get('/account/mail')->to('account#change_mail'); $authed_r->get('/account/mail')->to('account#change_mail');
$authed_r->get('/export.json')->to('account#json_export'); $authed_r->get('/export.json')->to('account#json_export');

View file

@ -4,6 +4,82 @@ use Mojo::Base 'Mojolicious::Controller';
use DateTime; use DateTime;
use CAM::PDF; use CAM::PDF;
sub mark_if_missed_connection {
my ( $self, $journey, $next_journey ) = @_;
my $possible_delay
= ( $next_journey->{rt_departure}->epoch
- $journey->{sched_arrival}->epoch ) / 60;
my $wait_time
= ( $next_journey->{rt_departure}->epoch - $journey->{rt_arrival}->epoch )
/ 60;
if (
$wait_time < 120
and ( $possible_delay >= 120
or ( $journey->{delay} < 60 and $possible_delay >= 60 ) )
)
{
$journey->{connection_missed} = 1;
$journey->{connection} = $next_journey;
$journey->{possible_delay} = $possible_delay;
$journey->{wait_time} = $wait_time;
return 1;
}
return 0;
}
sub list_candidates {
my ($self) = @_;
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
my $range_start = $now->clone->subtract( months => 12 );
my @journeys = $self->get_user_travels(
after => $range_start,
before => $now
);
@journeys = grep { $_->{sched_arrival}->epoch and $_->{rt_arrival}->epoch }
@journeys;
for my $i ( 0 .. $#journeys ) {
my $journey = $journeys[$i];
$journey->{delay}
= ( $journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch )
/ 60;
if ( $journey->{delay} < 3 or $journey->{delay} >= 120 ) {
next;
}
if ( $i > 0 ) {
$self->mark_if_missed_connection( $journey, $journeys[ $i - 1 ] );
}
}
@journeys = grep { $_->{delay} >= 60 or $_->{connection_missed} } @journeys;
push(
@journeys,
map { $_->{cancelled} = 1; $_ } $self->get_user_travels(
after => $range_start,
before => $now,
cancelled => 1
)
);
@journeys
= sort { $b->{sched_departure}->epoch <=> $a->{sched_departure}->epoch }
@journeys;
$self->respond_to(
json => { json => [@journeys] },
any => {
template => 'passengerrights',
journeys => [@journeys]
}
);
}
sub generate { sub generate {
my ($self) = @_; my ($self) = @_;
my $journey_id = $self->param('id'); my $journey_id = $self->param('id');
@ -34,44 +110,128 @@ sub generate {
return; return;
} }
$journey->{delay}
= ( $journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch )
/ 60;
if ( $journey->{delay} < 120 ) {
my @connections = $self->get_user_travels(
uid => $uid,
after => $journey->{rt_arrival},
before => $journey->{rt_arrival}->clone->add( hours => 2 )
);
if (@connections) {
$self->mark_if_missed_connection( $journey, $connections[-1] );
}
}
my $pdf = CAM::PDF->new('public/static/pdf/fahrgastrechteformular.pdf'); my $pdf = CAM::PDF->new('public/static/pdf/fahrgastrechteformular.pdf');
# from station
$pdf->fillFormFields( 'S1F4', $journey->{from_name} ); $pdf->fillFormFields( 'S1F4', $journey->{from_name} );
if ( $journey->{connection} ) {
# to station
$pdf->fillFormFields( 'S1F7', $journey->{connection}{to_name} );
# missed connection in:
$pdf->fillFormFields( 'S1F22', $journey->{to_name} );
# last change in:
$pdf->fillFormFields( 'S1F24', $journey->{to_name} );
}
else {
# to station
$pdf->fillFormFields( 'S1F7', $journey->{to_name} ); $pdf->fillFormFields( 'S1F7', $journey->{to_name} );
}
if ( not $journey->{cancelled} ) { if ( not $journey->{cancelled} ) {
# arived with: TRAIN NO
if ( $journey->{connection} ) {
$pdf->fillFormFields( 'S1F13', $journey->{connection}{type} );
$pdf->fillFormFields( 'S1F14', $journey->{connection}{no} );
}
else {
$pdf->fillFormFields( 'S1F13', $journey->{type} ); $pdf->fillFormFields( 'S1F13', $journey->{type} );
$pdf->fillFormFields( 'S1F14', $journey->{no} ); $pdf->fillFormFields( 'S1F14', $journey->{no} );
} }
}
# first delayed train: TRAIN NO
$pdf->fillFormFields( 'S1F17', $journey->{type} ); $pdf->fillFormFields( 'S1F17', $journey->{type} );
$pdf->fillFormFields( 'S1F18', $journey->{no} ); $pdf->fillFormFields( 'S1F18', $journey->{no} );
if ( $journey->{sched_departure}->epoch ) { if ( $journey->{sched_departure}->epoch ) {
# journey YYMMDD
$pdf->fillFormFields( 'S1F1', $pdf->fillFormFields( 'S1F1',
$journey->{sched_departure}->strftime('%d') ); $journey->{sched_departure}->strftime('%d') );
$pdf->fillFormFields( 'S1F2', $pdf->fillFormFields( 'S1F2',
$journey->{sched_departure}->strftime('%m') ); $journey->{sched_departure}->strftime('%m') );
$pdf->fillFormFields( 'S1F3', $pdf->fillFormFields( 'S1F3',
$journey->{sched_departure}->strftime('%y') ); $journey->{sched_departure}->strftime('%y') );
# sched departure HHMM
$pdf->fillFormFields( 'S1F5', $pdf->fillFormFields( 'S1F5',
$journey->{sched_departure}->strftime('%H') ); $journey->{sched_departure}->strftime('%H') );
$pdf->fillFormFields( 'S1F6', $pdf->fillFormFields( 'S1F6',
$journey->{sched_departure}->strftime('%M') ); $journey->{sched_departure}->strftime('%M') );
# first delayed train: sched departure HHMM
$pdf->fillFormFields( 'S1F19', $pdf->fillFormFields( 'S1F19',
$journey->{sched_departure}->strftime('%H') ); $journey->{sched_departure}->strftime('%H') );
$pdf->fillFormFields( 'S1F20', $pdf->fillFormFields( 'S1F20',
$journey->{sched_departure}->strftime('%M') ); $journey->{sched_departure}->strftime('%M') );
} }
if ( $journey->{sched_arrival}->epoch ) { if ( $journey->{sched_arrival}->epoch ) {
# sched arrival HHMM
if ( $journey->{connection} ) {
# TODO (needs plan data for non-journey trains)
}
else {
$pdf->fillFormFields( 'S1F8', $pdf->fillFormFields( 'S1F8',
$journey->{sched_arrival}->strftime('%H') ); $journey->{sched_arrival}->strftime('%H') );
$pdf->fillFormFields( 'S1F9', $pdf->fillFormFields( 'S1F9',
$journey->{sched_arrival}->strftime('%M') ); $journey->{sched_arrival}->strftime('%M') );
} }
}
if ( $journey->{rt_arrival}->epoch and not $journey->{cancelled} ) { if ( $journey->{rt_arrival}->epoch and not $journey->{cancelled} ) {
$pdf->fillFormFields( 'S1F10', $journey->{rt_arrival}->strftime('%d') );
$pdf->fillFormFields( 'S1F11', $journey->{rt_arrival}->strftime('%m') ); if ( $journey->{connection} ) {
$pdf->fillFormFields( 'S1F12', $journey->{rt_arrival}->strftime('%y') );
$pdf->fillFormFields( 'S1F15', $journey->{rt_arrival}->strftime('%H') ); # arrival YYMMDD
$pdf->fillFormFields( 'S1F16', $journey->{rt_arrival}->strftime('%M') ); $pdf->fillFormFields( 'S1F10',
$journey->{connection}{rt_arrival}->strftime('%d') );
$pdf->fillFormFields( 'S1F11',
$journey->{connection}{rt_arrival}->strftime('%m') );
$pdf->fillFormFields( 'S1F12',
$journey->{connection}{rt_arrival}->strftime('%y') );
# arrival HHMM
$pdf->fillFormFields( 'S1F15',
$journey->{connection}{rt_arrival}->strftime('%H') );
$pdf->fillFormFields( 'S1F16',
$journey->{connection}{rt_arrival}->strftime('%M') );
}
else {
# arrival YYMMDD
$pdf->fillFormFields( 'S1F10',
$journey->{rt_arrival}->strftime('%d') );
$pdf->fillFormFields( 'S1F11',
$journey->{rt_arrival}->strftime('%m') );
$pdf->fillFormFields( 'S1F12',
$journey->{rt_arrival}->strftime('%y') );
# arrival HHMM
$pdf->fillFormFields( 'S1F15',
$journey->{rt_arrival}->strftime('%H') );
$pdf->fillFormFields( 'S1F16',
$journey->{rt_arrival}->strftime('%M') );
}
} }
$self->res->headers->content_type('application/pdf'); $self->res->headers->content_type('application/pdf');

View file

@ -7,20 +7,6 @@
</div> </div>
</div> </div>
<div class="row">
<div class="col s12 m12 l3 center-align">
<a href="/cancelled" class="waves-effect waves-light btn"><i class="material-icons left">cancel</i> Zugausfälle</a>
</div>
<div class="col s12 m12 l1">&nbsp;</div>
<div class="col s12 m12 l4 center-align">
<a href="/journey/add" class="waves-effect waves-light btn"><i class="material-icons left">add</i> Neue Fahrt</a>
</div>
<div class="col s12 m12 l1">&nbsp;</div>
<div class="col s12 m12 l3 center-align">
<a href="/history.json" class="waves-effect waves-light btn"><i class="material-icons left">cloud</i> JSON-Export</a>
</div>
</div>
<h2>Nach Jahr</h2> <h2>Nach Jahr</h2>
%= include '_history_years', current => ''; %= include '_history_years', current => '';
% if(0) { % if(0) {
@ -41,6 +27,28 @@
</div> </div>
% } % }
<h2>Ausfälle und Verspätungen</h2>
<div class="row">
<div class="col s12 m12 l5 center-align">
<a href="/cancelled" class="waves-effect waves-light btn"><i class="material-icons left">cancel</i> Zugausfälle</a>
</div>
<div class="col s12 m12 l2">&nbsp;</div>
<div class="col s12 m12 l5 center-align">
<a href="/fgr" class="waves-effect waves-light btn"><i class="material-icons left">feedback</i> Fahrgastrechte</a>
</div>
</div>
<h2>Rohdaten</h2>
<div class="row">
<div class="col s12 m12 l5 center-align">
<a href="/history.json" class="waves-effect waves-light btn"><i class="material-icons left">cloud</i> JSON-Export</a>
</div>
<div class="col s12 m12 l2">&nbsp;</div>
<div class="col s12 m12 l5 center-align">
<a href="/journey/add" class="waves-effect waves-light btn"><i class="material-icons left">add</i> Neue Fahrt</a>
</div>
</div>
% if (stash('statistics')) { % if (stash('statistics')) {
%= include '_history_stats', stats => stash('statistics'); %= include '_history_stats', stats => stash('statistics');
% } % }

View file

@ -0,0 +1,69 @@
<h1>Fahrgastrechte</h1>
<div class="row">
<div class="col s12">
<p>
Gemäß der Fahrgastrechte im Eisenbahnverkehr besteht ab 60 Minuten
Verspätung am Ziel ein Entschädigungsanspruch gegenüber dem
Eisenbahnverkehrsunternehmen. Dieser kann mit dem
Fahrgastrechteformular geltend gemacht werden.
</p>
<p>
Die folgenden Zugfahrten sind wahrscheinliche Kandidaten dafür.
Details zur jeweiligen Zugfahrt sind bereits im Formular eingetragen.
</p>
</div>
</div>
<div class="row">
<div class="col s12">
<table class="striped">
<thead>
<tr>
<th>Datum</th>
<th>Zug</th>
<th>Grund</th>
<th>Formular</th>
</tr>
</thead>
<tbody>
% for my $journey (@{$journeys}) {
% my $detail_link = '/journey/' . $journey->{id};
<tr>
<td><%= $journey->{sched_departure}->strftime('%d.%m.%Y') %></td>
<td><a href="<%= $detail_link %>">
<%= $journey->{type} %> <%= $journey->{line} // $journey->{no} %>
→ <%= $journey->{to_name} %>
% if ($journey->{connection}) {
<br/>
<%= $journey->{connection}{type} %> <%= $journey->{connection}{line} // $journey->{connection}{no} %>
→ <%= $journey->{connection}{to_name} %>
% }
</a></td>
<td>
% if ($journey->{cancelled}) {
Ausfall
% }
% else {
%= sprintf('%+d', $journey->{delay})
% if ($journey->{connection}) {
<br/>
%= sprintf('Mit Anschluss: %+d?', $journey->{possible_delay})
% }
% }
</td>
<td>
% my $form_target = sprintf('/journey/passenger_rights/FGR %s %s %s.pdf', $journey->{sched_departure}->ymd, $journey->{type}, $journey->{no});
%= form_for $form_target => (method => 'POST') => begin
%= csrf_field
%= hidden_field id => $journey->{id}
<button class="btn waves-effect waves-light grey darken-3" type="submit" name="action" value="generate">
<i class="material-icons">file_download</i>
</button>
%= end
</td>
</tr>
% }
</tbody>
</table>
</div>
</div>