diff options
Diffstat (limited to 'lib/Travelynx')
| -rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 84 | ||||
| -rwxr-xr-x | lib/Travelynx/Model/Journeys.pm | 213 | 
2 files changed, 288 insertions, 9 deletions
| diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index aa25e5c..7a00cd0 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -1448,11 +1448,73 @@ sub csv_history {  	);  } +sub year_in_review { +	my ($self) = @_; +	my $year = $self->stash('year'); +	my @journeys; + +	# DateTime is very slow when looking far into the future due to DST changes +	# -> Limit time range to avoid accidental DoS. +	if ( not( $year =~ m{ ^ [0-9]{4} $ }x and $year > 1990 and $year < 2100 ) ) +	{ +		$self->render('not_found'); +		return; +	} + +	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 ); +	@journeys = $self->journeys->get( +		uid           => $self->current_user->{id}, +		after         => $interval_start, +		before        => $interval_end, +		with_datetime => 1 +	); + +	if ( not @journeys ) { +		$self->render( 'not_found', +			message => 'Keine Zugfahrten im angefragten Jahr gefunden.' ); +		return; +	} + +	my $now = $self->now; +	if ( +		not( $year < $now->year or ( $now->month == 12 and $now->day == 31 ) ) ) +	{ +		$self->render( 'not_found', +			message => +'Der aktuelle Jahresrückblick wird erst zum Jahresende (am 31.12.) freigeschaltet' +		); +		return; +	} + +	my ( $stats, $review ) = $self->journeys->get_stats( +		uid    => $self->current_user->{id}, +		year   => $year, +		review => 1 +	); + +	$self->render( +		'year_in_review', +		title  => "travelynx Jahresrückblick $year", +		year   => $year, +		stats  => $stats, +		review => $review +	); + +} +  sub yearly_history {  	my ($self) = @_;  	my $year = $self->stash('year');  	my @journeys; -	my $stats;  	# DateTime is very slow when looking far into the future due to DST changes  	# -> Limit time range to avoid accidental DoS. @@ -1484,11 +1546,17 @@ sub yearly_history {  		return;  	} -	$stats = $self->journeys->get_stats( +	my $stats = $self->journeys->get_stats(  		uid  => $self->current_user->{id},  		year => $year  	); +	my $with_review; +	my $now = $self->now; +	if ( $year < $now->year or ( $now->month == 12 and $now->day == 31 ) ) { +		$with_review = 1; +	} +  	$self->respond_to(  		json => {  			json => { @@ -1497,10 +1565,11 @@ sub yearly_history {  			}  		},  		any => { -			template   => 'history_by_year', -			journeys   => [@journeys], -			year       => $year, -			statistics => $stats +			template    => 'history_by_year', +			journeys    => [@journeys], +			year        => $year, +			have_review => $with_review, +			statistics  => $stats  		}  	); @@ -1511,7 +1580,6 @@ sub monthly_history {  	my $year   = $self->stash('year');  	my $month  = $self->stash('month');  	my @journeys; -	my $stats;  	my @months  	  = (  		qw(Januar Februar März April Mai Juni Juli August September Oktober November Dezember) @@ -1552,7 +1620,7 @@ sub monthly_history {  		return;  	} -	$stats = $self->journeys->get_stats( +	my $stats = $self->journeys->get_stats(  		uid   => $self->current_user->{id},  		year  => $year,  		month => $month diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index b6647d2..1ab179a 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -34,6 +34,53 @@ sub epoch_to_dt {  	);  } +sub min_to_human { +	my ($minutes) = @_; + +	my @ret; + +	if ( $minutes >= 14 * 24 * 60 ) { +		push( @ret, int( $minutes / ( 7 * 24 * 60 ) ) . ' Wochen' ); +	} +	elsif ( $minutes >= 7 * 24 * 60 ) { +		push( @ret, '1 Woche' ); +	} +	$minutes %= 7 * 24 * 60; + +	if ( $minutes >= 2 * 24 * 60 ) { +		push( @ret, int( $minutes / ( 24 * 60 ) ) . ' Tage' ); +	} +	elsif ( $minutes >= 24 * 60 ) { +		push( @ret, '1 Tag' ); +	} +	$minutes %= 24 * 60; + +	if ( $minutes >= 2 * 60 ) { +		push( @ret, int( $minutes / 60 ) . ' Stunden' ); +	} +	elsif ( $minutes >= 60 ) { +		push( @ret, '1 Stunde' ); +	} +	$minutes %= 60; + +	if ( $minutes >= 2 ) { +		push( @ret, "$minutes Minuten" ); +	} +	elsif ($minutes) { +		push( @ret, "1 Minute" ); +	} + +	if ( @ret == 1 ) { +		return $ret[0]; +	} + +	if ( @ret > 2 ) { +		my $last = pop(@ret); +		return join( ', ', @ret ) . " und $last"; +	} +	return "$ret[0] und $ret[1]"; +} +  sub new {  	my ( $class, %opt ) = @_; @@ -991,6 +1038,165 @@ sub get_travel_distance {  		$distance_beeline, $skipped );  } +sub compute_review { +	my ( $self, $stats, @journeys ) = @_; +	my $longest_km; +	my $longest_t; +	my $shortest_km; +	my $shortest_t; +	my $message_count +	  ; # anzahl fahrten bei denen irgendeine nachricht vermerkt war -> irgendwas war anders als geplant +	my %num_by_message;    # für jede nachricht +	my %num_by_wrtype +	  ;    # zugtyp, sofern wagenreihung verfügbar. 'none' für nicht verfügbar. +	my %num_by_linetype;    # zugtyp nach "ICE 123" / "RE 127". +	my %num_by_stop;        # arr/dep name + +	if ( not $stats or not @journeys or $stats->{num_trains} == 0 ) { +		return; +	} + +	my %review; + +	my $trains_per_journey = $stats->{num_trains} / $stats->{num_journeys}; +	my $avg_change_count   = sprintf( '%.1f', $trains_per_journey - 1 ); +	my $min_total = $stats->{min_travel_real} + $stats->{min_interchange_real}; + +	for my $journey (@journeys) { +		if ( $journey->{rt_duration} ) { +			if ( not $longest_t +				or $journey->{rt_duration} > $longest_t->{rt_duration} ) +			{ +				$longest_t = $journey; +			} +			if ( not $shortest_t +				or $journey->{rt_duration} < $shortest_t->{rt_duration} ) +			{ +				$shortest_t = $journey; +			} +		} +		if ( $journey->{km_route} ) { +			if ( not $longest_km +				or $journey->{km_route} > $longest_km->{km_route} ) +			{ +				$longest_km = $journey; +			} +			if ( not $shortest_km +				or $journey->{km_route} < $shortest_km->{km_route} ) +			{ +				$shortest_km = $journey; +			} +		} +		if ( $journey->{messages} and @{ $journey->{messages} } ) { +			$message_count += 1; +			for my $message ( @{ $journey->{messages} } ) { +				$num_by_message{ $message->[1] } += 1; +			} +		} +		if ( $journey->{type} ) { +			$num_by_linetype{ $journey->{type} } += 1; +		} +		if ( $journey->{from_name} ) { +			$num_by_stop{ $journey->{from_name} } += 1; +		} +		if ( $journey->{to_name} ) { +			$num_by_stop{ $journey->{to_name} } += 1; +		} +	} + +	my @linetypes = sort { $b->[1] <=> $a->[1] } +	  map { [ $_, $num_by_linetype{$_} ] } keys %num_by_linetype; +	my @stops = sort { $b->[1] <=> $a->[1] } +	  map { [ $_, $num_by_stop{$_} ] } keys %num_by_stop; + +	my @reasons = sort { $b->[1] <=> $a->[1] } +	  map { [ $_, $num_by_message{$_} ] } keys %num_by_message; + +	$review{num_stops}      = scalar @stops; +	$review{trains_per_day} = sprintf( '%.1f', $stats->{num_trains} / 365 ); +	$review{km_route}       = sprintf( '%.0f', $stats->{km_route} ); +	$review{km_beeline}     = sprintf( '%.0f', $stats->{km_beeline} ); +	$review{km_circle}      = sprintf( '%.1f', $stats->{km_route} / 40030 ); +	$review{km_diag}        = sprintf( '%.1f', $stats->{km_route} / 12742 ); + +	$review{traveling_min_total} = $min_total; +	$review{traveling_percentage_year} +	  = sprintf( "%.1f%%", $min_total * 100 / 525948.77 ); +	$review{traveling_time_year} = min_to_human($min_total); + +	if (@linetypes) { +		$review{typical_type} = $linetypes[0][0]; +	} +	if ( @stops >= 3 ) { +		my $desc = q{}; +		$review{typical_stops_3} = [ $stops[0][0], $stops[1][0], $stops[2][0] ]; +	} +	elsif ( @stops == 2 ) { +		$review{typical_stops_2} = [ $stops[0][0], $stops[1][0] ]; +	} +	$review{typical_time} +	  = min_to_human( $stats->{min_travel_real} / $stats->{num_trains} ); +	$review{typical_km} +	  = sprintf( '%.0f', $stats->{km_route} / $stats->{num_trains} ); +	$review{typical_kmh} = sprintf( '%.0f', +		$stats->{km_route} / ( $stats->{min_travel_real} / 60 ) ); +	$review{typical_delay_dep} +	  = sprintf( '%.0f', $stats->{delay_dep} / $stats->{num_trains} ); +	$review{typical_delay_dep_h} = min_to_human( $review{typical_delay_dep} ); +	$review{typical_delay_arr} +	  = sprintf( '%.0f', $stats->{delay_arr} / $stats->{num_trains} ); +	$review{typical_delay_arr_h} = min_to_human( $review{typical_delay_arr} ); + +	$review{longest_t_time}   = min_to_human( $longest_t->{rt_duration} / 60 ); +	$review{longest_t_type}   = $longest_t->{type}; +	$review{longest_t_lineno} = $longest_t->{line} // $longest_t->{no}; +	$review{longest_t_from}   = $longest_t->{from_name}; +	$review{longest_t_to}     = $longest_t->{to_name}; +	$review{longest_t_id}     = $longest_t->{id}; + +	$review{longest_km_km}     = sprintf( '%.0f', $longest_km->{km_route} ); +	$review{longest_km_type}   = $longest_km->{type}; +	$review{longest_km_lineno} = $longest_km->{line} // $longest_km->{no}; +	$review{longest_km_from}   = $longest_km->{from_name}; +	$review{longest_km_to}     = $longest_km->{to_name}; +	$review{longest_km_id}     = $longest_km->{id}; + +	$review{shortest_t_time} = min_to_human( $shortest_t->{rt_duration} / 60 ); +	$review{shortest_t_type} = $shortest_t->{type}; +	$review{shortest_t_lineno} = $shortest_t->{line} // $shortest_t->{no}; +	$review{shortest_t_from}   = $shortest_t->{from_name}; +	$review{shortest_t_to}     = $shortest_t->{to_name}; +	$review{shortest_t_id}     = $shortest_t->{id}; + +	$review{shortest_km_m} = sprintf( '%.0f', $shortest_km->{km_route} * 1000 ); +	$review{shortest_km_type}   = $shortest_km->{type}; +	$review{shortest_km_lineno} = $shortest_km->{line} // $shortest_km->{no}; +	$review{shortest_km_from}   = $shortest_km->{from_name}; +	$review{shortest_km_to}     = $shortest_km->{to_name}; +	$review{shortest_km_id}     = $shortest_km->{id}; + +	$review{issue_percent} +	  = sprintf( '%.0f%%', $message_count * 100 / $stats->{num_trains} ); +	for my $i ( 0 .. 2 ) { +		if ( $reasons[$i] ) { +			my $p = 'issue' . ( $i + 1 ); +			$review{"${p}_count"} = $reasons[$i][1]; +			$review{"${p}_text"}  = $reasons[$i][0]; +		} +	} + +	printf( "In %.0f%% der Fahrten war irgendetwas nicht wie vorgesehen\n", +		$message_count * 100 / $stats->{num_trains} ); +	say "Die drei häufigsten Anmerkungen waren:"; +	for my $i ( 0 .. 2 ) { +		if ( $reasons[$i] ) { +			printf( "%d× %s\n", $reasons[$i][1], $reasons[$i][0] ); +		} +	} + +	return \%review; +} +  sub compute_stats {  	my ( $self, @journeys ) = @_;  	my $km_route         = 0; @@ -1093,7 +1299,8 @@ sub get_stats {  	# checks out of a train or manually edits/adds a journey.  	if ( -		not $opt{write_only} +		    not $opt{write_only} +		and not $opt{review}  		and my $stats = $self->stats_cache->get(  			uid   => $uid,  			db    => $db, @@ -1148,6 +1355,10 @@ sub get_stats {  		stats => $stats  	); +	if ( $opt{review} ) { +		return ( $stats, $self->compute_review( $stats, @journeys ) ); +	} +  	return $stats;  } | 
