-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmodel.py
452 lines (326 loc) · 14.5 KB
/
model.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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
""" Models and db functions for SDG db. """
from flask_sqlalchemy import SQLAlchemy
import pdb
db = SQLAlchemy()
###########################
# ORM Models
###########################
### Countries ###
class Country(db.Model):
""" Country model.
World Bank doesn't calculate world, region, or income numbers on the
fly; they are their own values. Weighted? Who knows. But I'm sticking
with their methods, albeit with a reduced number of groupings.
"""
__tablename__ = 'countries'
country_id = db.Column(db.String(3), primary_key=True)
char2 = db.Column(db.String(2), unique=True, nullable=False)
name = db.Column(db.Text, unique=True, nullable=False)
region = db.Column(db.Text, nullable=True)
income = db.Column(db.Text, nullable=True)
wikiurl = db.Column(db.Text, nullable=True)
def __repr__(self):
return ('<Country "{}" id={} region="{}" income="{}" (c.groups) >\n'
.format(self.name, self.country_id, self.region, self.income))
@classmethod
def get_db_objs(cls, country_id=None, name=None, region=None,
income=None, limit=None):
""" Returns list of db objects from Country table ordered by name.
Optionally, can inclusively filter and order by id, name, region,
and/or income as well.
"""
query = cls.query.order_by(cls.name)
if country_id:
country_id = "%" + str(country_id) + "%"
query = (query.filter(cls.country_id.ilike(country_id))
.order_by(cls.country_id))
if name:
name = "%" + str(name) + "%"
query = (query.filter(cls.name.ilike(name))
.order_by(cls.name))
if region:
region = "%" + str(region) + "%"
query = (query.filter(cls.region.ilike(region))
.order_by(cls.region))
if income:
income = "%" + str(income) + "%"
query = (query.filter(cls.income.ilike(income))
.order_by(cls.income))
if limit:
limit = int(limit)
query = query.limit(limit)
db_objs = query.all()
return db_objs
class Group(db.Model):
""" Group model.
Region, income, and other classifications based on economic, geographic,
and cultural characteristics. Eg: Arab World, Small States, Pacific
Island Small States.
"""
__tablename__ = 'groups'
group_id = db.Column(db.String(3), primary_key=True)
char2 = db.Column(db.String(2), unique=True, nullable=False)
name = db.Column(db.Text, unique=True, nullable=False)
countries = db.relationship('Country',
secondary='groups_countries',
order_by='Country.name',
backref=db.backref('groups',
order_by=group_id))
def __repr__(self):
return ('<Group "{}" id={} (g.countries) >\n'
.format(self.name, self.group_id))
@classmethod
def get_db_objs(cls, group_id=None, name=None, char2=None, limit=None):
""" Returns list of db objects from Country table ordered by name.
Optionally, can inclusively filter and order by id, name, and/or
char2 as well.
"""
query = cls.query.order_by(cls.name)
if group_id:
group_id = "%" + str(group_id) + "%"
query = (query.filter(cls.group_id.ilike(group_id))
.order_by(cls.group_id))
if name:
name = "%" + str(name) + "%"
query = (query.filter(cls.name.ilike(name))
.order_by(cls.name))
if char2:
char2 = "%" + str(char2) + "%"
query = (query.filter(cls.char2.ilike(char2))
.order_by(cls.char2))
if limit:
limit = int(limit)
query = query.limit(limit)
db_objs = query.all()
return db_objs
class GroupCountry(db.Model):
""" Association table for group-country pairs. """
__tablename__ = 'groups_countries'
gc_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
group_id = db.Column(db.String(3),
db.ForeignKey('groups.group_id'),
nullable=False)
country_id = db.Column(db.String(3),
db.ForeignKey('countries.country_id'),
nullable=False)
### Goals and Indicators ###
class GoalDesign(db.Model):
""" GoalDesign model.
Most importantly, this table holds the goal prefix ('goal_pre' 2-char
value from 01 to 17) used by the 'goal' table.
Also, each goal has a color and icon associated with it on the UN site;
this front end 'might' replicate those and therefore stores them very
simply as the goal number, hex value, and icon urls. The field 'hexval'
includes the leading '#'.
"""
__tablename__ = 'goal_design'
goal_pre = db.Column(db.String(2), primary_key=True)
title = db.Column(db.Text, unique=True, nullable=False)
hexval = db.Column(db.String(7), unique=True, nullable=False)
iurl_blank = db.Column(db.Text, unique=True, nullable=False)
iurl_full = db.Column(db.Text, unique=True, nullable=False)
unurl = db.Column(db.Text, unique=True, nullable=False)
def __repr__(self):
return ('<GoalDesign goal_pre={} hex="{}" >\n'
.format(self.goal_pre, self.hexval))
@classmethod
def get_db_objs(cls, goal_pre=None):
""" Returns list of db objects from GoalDesign table ordered by goal
prefix. Has no additional filter or order by fields.
"""
query = cls.query.order_by(cls.goal_pre)
if goal_pre:
goal_pre = "%" + str(goal_pre) + "%"
query = (query.filter(cls.goal_pre.ilike(goal_pre))
.order_by(cls.goal_pre))
db_objs = query.all()
return db_objs
class Goal(db.Model):
""" Goal model.
Each goal has targets. Main goals are 'EE00' where 'EE' is the
'goal_pre' (01 to 17). Targets are 'EEZZ' where 'EE' is the 'goal_pre'
and 'ZZ' is the 'goal_suf' -- 2-char code either a number ('03', '11')
or a single 0 and the letter ('0a'). Not every goal/target detailed
by the SDG accord is accounted for here.
"""
__tablename__ = 'goals'
goal_id = db.Column(db.String(4), primary_key=True)
goal_pre = db.Column(db.String(2),
db.ForeignKey('goal_design.goal_pre'),
nullable=False)
goal_suf = db.Column(db.String(2), nullable=False)
description = db.Column(db.Text, unique=True, nullable=False)
indicators = db.relationship('Indicator',
order_by='Indicator.indicator_id',
secondary='goals_indicators',
backref=db.backref('goals', order_by=goal_id))
design = db.relationship('GoalDesign',
backref=db.backref('goals', order_by=goal_id))
def __repr__(self):
return ('<Goal id={} descr="{}" (g.indicators) (g.design) >\n'
.format(self.goal_id, self.description[:50]))
class Indicator(db.Model):
""" Indicator model. An indicator is a World Bank metric.
World Bank has assigned each metric a code which serves as the
'indicator_id'. Not every metric detailed by the World Bank is accounted
for here.
"""
__tablename__ = 'indicators'
indicator_id = db.Column(db.Text, primary_key=True)
title = db.Column(db.Text, unique=True, nullable=False)
description = db.Column(db.Text, nullable=True)
scale_inverse = db.Column(db.Boolean, nullable=True, default=False)
display_math = db.Column(db.Text, nullable=True)
wburl = db.Column(db.Text, unique=True, nullable=True)
def __repr__(self):
return ('<Indicator id={} title="{}" (i.goals) >\n'
.format(self.indicator_id, self.title[:50]))
@classmethod
def get_db_objs(cls, indicator_id=None, title=None,
scale_inverse=None, display_math=None):
""" Returns list of db objects from Datum table ordered by indicator id.
Optionally, can filter and order by country id, year, and/or value
as well.
"""
query = cls.query.order_by(cls.indicator_id)
if indicator_id:
indicator_id = "%" + str(indicator_id) + "%"
query = (query.filter(cls.indicator_id.ilike(indicator_id))
.order_by(cls.indicator_id))
if title:
title = "%" + str(title) + "%"
query = (query.filter(cls.title.ilike(title))
.order_by(cls.title))
if scale_inverse:
scale_inverse = "%" + str(scale_inverse) + "%"
query = (query.filter(cls.scale_inverse.ilike(scale_inverse))
.order_by(cls.scale_inverse))
if display_math:
display_math = "%" + str(display_math) + "%"
query = (query.filter(cls.display_math.ilike(display_math))
.order_by(cls.display_math))
db_objs = query.all()
return db_objs
class GoalIndic(db.Model):
""" Association table for goal-indicator pairs. """
__tablename__ = 'goals_indicators'
gi_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
goal_id = db.Column(db.String(4),
db.ForeignKey('goals.goal_id'),
nullable=False)
indicator_id = db.Column(db.Text,
db.ForeignKey('indicators.indicator_id'),
nullable=False)
### Data ###
class Datum(db.Model):
""" Data point model.
Data points for each country-indicator-year combination.
"""
__tablename__ = 'data_points'
datum_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
country_id = db.Column(db.String(3),
db.ForeignKey('countries.country_id'),
index=True,
nullable=False)
indicator_id = db.Column(db.Text,
db.ForeignKey('indicators.indicator_id'),
index=True,
nullable=False)
year = db.Column(db.Integer, nullable=False)
value = db.Column(db.Float, nullable=False)
scaled_value = db.Column(db.Float, nullable=True)
display_value = db.Column(db.Float, nullable=True)
country = db.relationship('Country',
backref=db.backref('data_points',
order_by=indicator_id))
indicator = db.relationship('Indicator',
backref=db.backref('data_points',
order_by=country_id))
# goals = db.relationship('Goal',
# backref=db.backref('data_points',
# order_by='country_id'))
def __repr__(self):
return ('<Datum id={} val={} country="{}" indic="{}" year={} >\n'
.format(self.datum_id, self.value, self.country.name,
self.indicator.title, self.year))
@classmethod
def get_db_objs(cls, country_id=None, indicator_id=None,
year=None, value=None, display_value=None):
""" Returns list of db objects from Datum table ordered by indicator id.
Optionally, can filter and order by country id, year, and/or value
as well.
"""
query = cls.query.order_by(cls.indicator_id)
if indicator_id:
indicator_id = "%" + str(indicator_id) + "%"
query = (query.filter(cls.indicator_id.ilike(indicator_id))
.order_by(cls.indicator_id))
if country_id:
country_id = "%" + str(country_id) + "%"
query = (query.filter(cls.country_id.ilike(country_id))
.order_by(cls.country_id))
if year:
query = (query.filter(cls.year == year)
.order_by(cls.year))
if value:
value_f = float(value[2:])
if value[0:2] == '==':
query = (query.filter(cls.value == value_f)
.order_by(cls.value))
if display_value == 'None':
query = query.filter(cls.display_value == None)
db_objs = query.all()
return db_objs
###########################
# Helper functions
###########################
def connect_to_db(app):
""" Connect the database to the Flask app. """
# Configure to use PostgreSQL production database
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql:///sdgdash'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.app = app
db.init_app(app)
if __name__ == "__main__":
# As a convenience, if we run this module interactively, it will leave
# us in a state of being able to work with the database directly.
from server import app
connect_to_db(app)
print "\n\nConnected to DB.\n"
###########################
### DEPRECATED
###########################
# def update_indicator_scale():
# """ After ALTER TABLE, set scale_inverse value for each indicator and note
# which indicators will have math in order to display on 0-10 scale.
# """
# # Get dict of indicator and scale
# scale_dict = {}
# f = open('sortedindic.txt', 'r')
# for row in f:
# row.rstrip()
# s, indic = row.split("|")
# scale_dict[indic[:-1]] = s
# f.close()
# indicators = Indicator.query.all()
# for indic in indicators:
# if scale_dict.get(indic.indicator_id) == 'i':
# indic.scale_inverse = True
# elif scale_dict.get(indic.indicator_id) == 'm':
# indic.display_math = 'm'
# db.session.commit()
# def sort_indicators(indicators):
# """ Sort indicators into _norm, _inv, or _math. """
# try_again = []
# f = open('sortedindic.txt', 'a')
# for indic in indicators:
# print
# print indic
# location = raw_input("(N)ormal, (I)nverted, or (M)ath? ")
# if location.upper() in ("N", "I", "M"):
# f.write("\n" + location + "|" + indic.indicator_id)
# else:
# try_again.append(indic)
# f.close()
# if try_again:
# sort_indicators(try_again)