-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
379 lines (289 loc) · 10.5 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# Hate Crimes in the US
# Flask App
# Load dependencies
from flask import Flask, jsonify, render_template
from sqlalchemy import create_engine, MetaData, inspect, Table, Column, Integer, String, func, distinct, and_
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy.sql.expression import cast
import time
# Get database information from config file
# Note: config.py must be created locally and is not stored in Github
from config import postgresql_flag, db_username, db_password, db_host, db_port, db_name, db_options
app = Flask(__name__)
# Create a SQLAlchemy database engine
if postgresql_flag:
db_url = f'postgresql://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}{db_options}'
else:
db_url = 'sqlite:///../database/us_hate_crimes_sqlite.db'
engine = create_engine(db_url)
# Create MetaData object and reflect existing database
metadata = MetaData()
metadata.reflect(engine)
# Create table objects to set primary key for tables in sqlite database
# Sqlite tables were created with Pandas to_sql which does not support setting primary keys
Table(
'state',
metadata,
Column('state_abbr', String, primary_key=True, nullable=False),
autoload_with=engine,
extend_existing=True
)
Table(
'bias',
metadata,
Column('bias_id', Integer, primary_key=True, nullable=False),
autoload_with=engine,
extend_existing=True
)
# Set table objects to set primary key for views
Table(
'year_view',
metadata,
Column('id', Integer, primary_key=True, nullable=False),
autoload_with=engine
)
Table(
'incident_view',
metadata,
Column('id', Integer, primary_key=True, nullable=False),
autoload_with=engine
)
Table(
'population_view',
metadata,
Column('id', Integer, primary_key=True, nullable=False),
autoload_with=engine
)
# Create mappings
Base = automap_base(metadata=metadata)
Base.prepare(autoload_with=engine)
# Define variables for related tables
B = Base.classes.bias
S = Base.classes.state
Y = Base.classes.year_view
I = Base.classes.incident_view
P = Base.classes.population_view
print('Connected to database')
# Define static routes
# Launch site
@app.route('/')
def index():
return render_template('index.html')
# Route to get list of routes
@app.route('/api/routes')
def get_routes():
# Define a list of available routes in the API
routes = [
'/api/lists',
'/api/biasdata/<state>',
'/api/ratedata/<year>/<bias_category>',
'/api/offensedata/<year>/<state>/<bias_category>'
]
# Return the list of routes as a JSON response
return jsonify({"Available routes": routes})
# Route to get lists for select dropdowns to filter charts
@app.route('/api/lists')
def get_lists():
try:
session = Session(engine)
# Year list
year_results = session.query(Y.year).order_by(Y.year.desc()).all()
year_list = [row.year for row in year_results]
# State list
state_results = session.query(S.state).order_by(S.state.asc()).all()
state_list = [row.state for row in state_results]
# Bias categories list
bias_results = session.query(B.bias_category
).group_by(B.bias_category
).order_by(B.bias_category.asc()
).all()
bias_list = [row.bias_category for row in bias_results]
session.close()
dataToReturn = {'states': state_list, 'years': year_list, 'bias': bias_list}
return jsonify(dataToReturn)
except Exception as e:
print("Error accessing the table:", str(e))
return jsonify({"error": "Table access failed"}), 500
# ROute to get data for bias charts
@app.route('/api/biasdata/<state>')
def get_data(state):
# Call functions to get data for each chart
inc_list = get_inc_data(state)
bias_list = get_bias_data(state)
# Create dictionary for return
dataToReturn = {'incident': inc_list, 'bias': bias_list}
return jsonify(dataToReturn)
# ROute to get data for offense chart
@app.route('/api/offensedata/<year>/<state>/<bias_category>')
def get_offense_data(year, state, bias_category):
# Call function to get data for each chart
offense_list = get_offense_data(year, state, bias_category)
# Create dictionary for return
dataToReturn = {'offense': offense_list}
return jsonify(dataToReturn)
# Route to get data for incident rate chart
@app.route('/api/ratedata/<year>/<bias_category>')
def get_rate_data(year, bias_category):
# Call functions to get data for each chart
rate_list = get_rate_data(year, bias_category)
# Create dictionary for return
dataToReturn = {'rate': rate_list}
return jsonify(dataToReturn)
# Define functions for routes
# Get query results for chart functions
def get_query_results(table, columns, filters, groups_orders):
session = Session(engine)
results = session.query(table).with_entities(*columns
).filter(*filters
).group_by(*groups_orders
).order_by(*groups_orders
).all()
session.close()
return results
# Get summary data for bias chart
def get_inc_data(state):
start_time = time.time()
# Initiatize query variables
columns = []
filters = []
# Year - no filtering
columns.append(I.incident_year)
# State
if state == 'All':
columns.append(I._all)
else:
columns.append(I.state)
filters.append(I.state == state)
# Bias category - no filtering
columns.append(I._all)
# Finalize query variables and run query
groups_orders = columns.copy()
columns.append(func.count(distinct(I.incident_id)).label('incidents'))
results = get_query_results(I, columns, filters, groups_orders)
# Create list of dictionaries
keys = ['year', 'state', 'bias_category', 'incidents', 'population']
data_list = [dict(zip(keys, row)) for row in results]
print('Incident data: completed in %s seconds' % (time.time() - start_time))
return data_list
# Get data for offense chart
def get_offense_data(year, state, bias_category):
start_time = time.time()
# Initiatize query variables
columns = []
filters = []
# Year
if year == 'All':
columns.append(I._all)
else:
columns.append(I.incident_year)
filters.append(I.incident_year == year)
# State
if state == 'All':
columns.append(I._all)
else:
columns.append(I.state)
filters.append(I.state == state)
# Bias category
if bias_category == 'All':
columns.append(I._all)
else:
columns.append(I.bias_category)
filters.append(I.bias_category == bias_category)
# Finalize query variables and run query
columns.append(I.offense)
groups_orders = columns.copy()
columns.append(func.count(distinct(I.incident_id)).label('incidents'))
results = get_query_results(I, columns, filters, groups_orders)
# Create list of dictionaries
keys = ['year', 'state', 'bias_category', 'offense', 'incidents']
data_list = [dict(zip(keys, row)) for row in results]
print('Offense data: completed in %s seconds' % (time.time() - start_time))
return data_list
# Get data for bias charts
def get_bias_data(state):
start_time = time.time()
# Initiatize query variables
columns = []
filters = []
# Year - no filtering
columns.append(I.incident_year)
# State
if state == 'All':
columns.append(I._all)
else:
columns.append(I.state)
filters.append(I.state == state)
# Bias category - no filtering
columns.append(I.bias_category)
# Finalize query variables and run query
groups_orders = columns.copy()
columns.append(func.count(distinct(I.incident_id)).label('incidents'))
results = get_query_results(I, columns, filters, groups_orders)
# Create list of dictionaries
keys = ['year', 'state', 'bias_category', 'incidents']
data_list = [dict(zip(keys, row)) for row in results]
print('Bias data: completed in %s seconds' % (time.time() - start_time))
return data_list
# Get list of dictionaries for incident rate chart
def get_rate_data(year, bias_category):
start_time = time.time()
session = Session(engine)
# Initiatize query variables
columns = []
columnsSub = []
filters = []
filtersSub = []
joins = []
# Year
if year == 'All':
columns.append(P._all)
columnsSub.append(I.incident_year)
else:
columns.append(P.year)
columnsSub.append(I.incident_year)
filters.append(P.year == year)
filtersSub.append(I.incident_year == year)
# State - no filtering
columns.append(P.state)
columnsSub.append(I.state)
# Bias category
if bias_category == 'All':
columnsSub.append(I._all)
else:
columnsSub.append(I.bias_category)
filtersSub.append(I.bias_category == bias_category)
# Finalize query variables and run subquery
groupsOrdersSub = columnsSub.copy()
columnsSub.append(cast(func.count(distinct(I.incident_id)), Integer).label('incidents'))
# Get incident count per state per year for main query
subquery = session.query(I).with_entities(*columnsSub
).filter(*filtersSub
).group_by(*groupsOrdersSub).subquery()
# Finalize query variables and run mainquery
if bias_category == 'All':
columns.append(subquery.c._all)
else:
columns.append(subquery.c.bias_category)
groupsOrders = columns.copy()
columns.append(cast(func.round(func.avg(subquery.c.incidents), 0), Integer).label('incidents'))
columns.append(cast(func.round(func.avg(P.population), 0), Integer).label('population'))
if year != 'All':
joins = [and_(subquery.c.incident_year == P.year, subquery.c.state == P.state)]
else:
joins = [and_(subquery.c.state == P.state)]
# Get average incident count and population if year = 'All'
results = session.query(P).with_entities(*columns
).join(subquery, *joins
).filter(*filters
).group_by(*groupsOrders
).order_by(*groupsOrders
).all()
session.close()
# Create list of dictionaries
keys = ['year', 'state', 'bias_category', 'incidents', 'population']
data_list = [dict(zip(keys, row)) for row in results]
print('Incident rate data: completed in %s seconds' % (time.time() - start_time))
return data_list
if __name__ == '__main__':
app.run(debug=True)