service.py

changeset 91
209f5930d038
parent 90
36efdea68d03
child 92
16a5c37e4e67
--- a/service.py	Thu Apr 19 14:44:54 2018 +0300
+++ b/service.py	Fri Apr 20 13:50:02 2018 +0300
@@ -268,7 +268,29 @@
 	adjusted_time = time - timedelta(hours = 4, minutes = 30)
 	return adjusted_time.weekday() in [4, 5] and is_night_time(time)
 
-def describe(bus_stop):
+encircled = '\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf' \
+	'\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb' \
+	'\u24cc\u24cd\u24ce\u24cf'
+
+def encircle(char):
+	from string import ascii_uppercase
+	try:
+		return encircled[ascii_uppercase.index(char.upper())]
+	except IndexError:
+		return char
+
+def variant_abbreviations(variants):
+	'''Makes a mapping of route destination variants to letter abbreviations'''
+	suggestion = [variant[-1][0].upper() for variant in variants]
+	if len(set(suggestion)) != len(suggestion):
+		from string import ascii_uppercase
+		suggestion = ascii_uppercase
+	return dict(zip(variants, suggestion))
+
+def schedule_entry_hash(schedule_entry):
+	return schedule_entry['trip'].name, schedule_entry['time']
+
+def describe(bus_stop, week_schedule):
 	schedule = []
 	from collections import defaultdict, Counter
 	from busroute import simplify_name
@@ -288,22 +310,44 @@
 	from collections import defaultdict
 	night_routes = defaultdict(lambda: True)
 	num_leaves = 0
-	all_routes = Counter()
-	for schedule_entry in week_schedule(bus_stop, arrivals = True): #bus_stop.schedule(max_amount = 500, arrivals = True):
+	trip_mapping = {}
+	for schedule_entry in week_schedule:
+		from busroute import greatly_simplify_name
 		sign_tuple = tuple(sign_elements(schedule_entry, format = 'short'))
+		sign_tuple = tuple(greatly_simplify_name(k) for k in sign_tuple)
 		route = schedule_entry['trip'].route.reference
 		night_routes[route] &= is_weekend_night(schedule_entry['time'])
 		destinations_per_route[route][sign_tuple] += 1
-		all_routes[route] += 1
+		trip_mapping[schedule_entry_hash(schedule_entry)] = (route, sign_tuple)
 		num_leaves += 1
 	night_routes = {key for key, value in night_routes.items() if value}
 	routes_per_destination = defaultdict(set)
-	for route in destinations_per_route:
-		winner, count = destinations_per_route[route].most_common()[0]
-		if all_routes[route] >= 10 or all_routes[route] / num_leaves >= 0.01:
-			winner = filter_names(winner)
-			#destinations_per_route[route] = winner and ' - '.join(winner) or ''
-			routes_per_destination[winner].add(route)
+	all_variants = []
+	variant_names = {}
+	# Collect all variants
+	for route, tally in destinations_per_route.items():
+		variants = variant_abbreviations(tally.keys())
+		winner, count = tally.most_common()[0]
+		for destination, count in tally.items():
+			route_name = route
+			if destination != winner:
+				route_name += encircle(variants[destination])
+				if route in night_routes:
+					night_routes.add(route_name)
+			variant_names[route, destination] = route_name
+			all_variants.append({
+				'name': route_name,
+				'destination': filter_names(destination),
+				'count': count
+			})
+	all_variants.sort(key = lambda k: k['count'])
+	route_variant_count = len(all_variants)
+	# Only consider variants so that they cover at least 99% of bus leaves
+	coverage = 0
+	while all_variants: #coverage / num_leaves < 0.99:
+		variant = all_variants.pop()
+		routes_per_destination[variant['destination']].add(variant['name'])
+		coverage += variant['count']
 	for key in routes_per_destination:
 		routes_per_destination[key] = sorted(routes_per_destination[key], key = route_key)
 	def route_len(route):
@@ -316,7 +360,11 @@
 		return length or len(route)
 	from math import inf
 	def route_key(route):
-		return (route in night_routes, route_len(route), str(route))
+		return (
+			route in night_routes,
+			route_len(route),
+			str(route)
+		)
 	def routes_key(routes):
 		return min(route_key(route) for route in routes)
 	result = []
@@ -331,9 +379,10 @@
 	return {
 		'night-routes': night_routes,
 		'all-night-routes': lambda entry, description: all(route in description['night-routes'] for route in entry[0]),
-		'simple': len(all_routes) <= 1,
+		'simple': route_variant_count <= 1,
 		'description': result,
 		'wtf': destinations_per_route,
+		'variant-map': {k:variant_names[v] for k, v in trip_mapping.items()},
 	}
 
 @app.route('/stop_description/<reference>')
@@ -344,7 +393,7 @@
 		bus_stop = bus_stops[reference]
 	except KeyError:
 		abort(404)
-	return '<pre>' + pformat(describe(bus_stop)) + '</pre>'
+	return '<pre>' + pformat(describe(bus_stop, week_schedule(bus_stop, arrivals = True))) + '</pre>'
 
 @app.route('/api/describe_destination/<trip_reference>/<stop_reference>/<int:index>')
 def describe_destination(trip_reference, stop_reference, index):
@@ -576,7 +625,9 @@
 	except KeyError:
 		abort(404)
 	week_model = {}
-	for schedule_entry in week_schedule(bus_stop, arrivals = True):
+	bus_stop_schedule = list(week_schedule(bus_stop, arrivals = True))
+	description = describe(bus_stop, bus_stop_schedule)
+	for schedule_entry in bus_stop_schedule:
 		route_ref = schedule_entry['trip'].route.reference
 		if route_filter(route_ref) and dest_filter(schedule_entry['trip']):
 			time = schedule_entry['time']
@@ -588,7 +639,7 @@
 				day_model[time.hour] = []
 			hour_model = day_model[time.hour]
 			hour_model.append({
-				'route': route_ref,
+				'route': description['variant-map'][schedule_entry_hash(schedule_entry)],
 				'route-splice': split_route_ref(route_ref),
 				'trip': schedule_entry['stop'].trip.name,
 				'night': is_night_time(schedule_entry['time']),
@@ -635,7 +686,7 @@
 		name = tr(bus_stop.name, 'bus-stops'),
 		tr = tr,
 		week = week_model,
-		description = describe(bus_stop),
+		description = description,
 		typename = bus_stop.typename,
 	)
 

mercurial