-
Notifications
You must be signed in to change notification settings - Fork 0
/
models.py
153 lines (119 loc) · 5.06 KB
/
models.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
import base64, datetime, math, random, struct
from google.appengine.ext import ndb
AUTHOR_KEY = 'author'
ENCODED_IDS_KEY = 'encoded_ids'
ENTRY_KEY = 'entry'
ENTRY_TYPE_KEY = 'entry_type'
ENTRY_TYPES_KEY = 'entry_types'
ERRORS_KEY = 'errors'
IS_NEW_KEY = 'is_new'
POEM_TYPE_KEY = 'poem_type'
POEMS_KEY = 'poems'
RANK_KEY = 'rank'
SHOW_INSTRUCTIONS_KEY = 'show_instructions'
TEMPLATE_KEY = 'template'
TEXT_KEY = 'text'
TYPE_KEY = 'type'
def randint64():
return random.getrandbits(64) - 2 ** 63
def compute_score(upvotes, downvotes):
z = 1.96
n = float(upvotes + downvotes)
if n == 0:
return 0
phat = upvotes / float(n)
return ((phat + z * z / (2 * n) - z * math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n)) /
(1 + z * z / n))
def encode_ids(ids):
return base64.urlsafe_b64encode(struct.pack('q' * len(ids), *ids))
def decode_ids(idString):
idAsciiString = idString.encode('ascii')
return struct.unpack('q' * (len(idAsciiString) * 6 / 64), base64.urlsafe_b64decode(idAsciiString))
class Entry(ndb.Model):
order = ndb.ComputedProperty(lambda self: randint64(), indexed=True)
TYPES = QUESTION, ANSWER, CONDITION, CONSEQUENCE = (
'question', 'answer', 'condition', 'consequence'
)
type = ndb.StringProperty(required=True, choices=TYPES, indexed=True)
text = ndb.StringProperty(required=True, indexed=False)
author = ndb.StringProperty(required=True, default='Anonymous', indexed=False)
upvotes = ndb.IntegerProperty(required=True, default=0, indexed=False)
downvotes = ndb.IntegerProperty(required=True, default=0, indexed=False)
score = ndb.ComputedProperty(lambda self: compute_score(self.upvotes, self.downvotes),
indexed=True)
is_flagged = ndb.BooleanProperty(required=True, default=False, indexed=True)
created = ndb.DateTimeProperty(required=True, indexed=False, auto_now_add=datetime.datetime.now)
@classmethod
def get_random(cls, type):
result = (cls.query(cls.type == type, cls.order >= randint64(), cls.is_flagged == False)
.order(cls.order)
.fetch(1))
# If we shot too high, just get the lowest order entry.
if len(result) == 0:
result = cls.query(cls.type == type, cls.is_flagged == False).order(cls.order).fetch(1)
return result[0]
def canonicalized_text(self):
if self.type == Entry.QUESTION:
return self.text[0].upper() + self.text[1:] + ('?' if self.text[-1] not in '?.!,;"\'' else '')
elif self.type == Entry.ANSWER:
return self.text[0].upper() + self.text[1:] + ('.' if self.text[-1] not in '.!?,;"\'' else '')
elif self.type == Entry.CONDITION:
text = self.text
if text.lower().startswith('if'):
text = text[2:]
return text.strip(' .!?,;')
elif self.type == Entry.CONSEQUENCE:
return self.text + ('.' if self.text[-1] not in '.!?,;"\'' else '')
else:
return self.text
def to_dict(self):
return {
TEXT_KEY: self.canonicalized_text(),
AUTHOR_KEY: self.author,
}
class Poem(ndb.Model):
order = ndb.ComputedProperty(lambda self: randint64(), indexed=True)
TYPES = QUESTION_ANSWER, CONDITIONAL, THE_EXQUISITE_CORPSE = ('question-answer', 'conditional',
'the-exquisite-corpse')
type = ndb.StringProperty(required=True, choices=TYPES, indexed=True)
entry_keys = ndb.KeyProperty(kind=Entry, repeated=True, indexed=False)
entries = []
upvotes = ndb.IntegerProperty(required=True, default=0, indexed=False)
downvotes = ndb.IntegerProperty(required=True, default=0, indexed=False)
score = ndb.ComputedProperty(lambda self: compute_score(self.upvotes, self.downvotes),
indexed=True)
debug_text = ndb.StringProperty(indexed=False)
rank = None
@classmethod
def create_random(cls, type):
poem = cls(type=type)
poem.entries = [Entry.get_random(entry_type) for entry_type in POEM_TYPE_ENTRY_TYPES[type]]
poem.entry_keys = [entry.key for entry in poem.entries]
poem.debug_text = ', '.join(entry.text for entry in poem.entries)
return poem
@classmethod
def get_random(cls, type, n):
return cls.query(cls.type == type, cls.order >= randint64()).order(cls.order).fetch(n)
@classmethod
def get_ranked(cls, type, rank, n):
poems = cls.query(cls.type == type).order(-cls.score).fetch(n, offset=rank)
for i, poem in enumerate(poems, start=rank):
poem.rank = i
return poems
def fetch_entries(self):
self.entries = self.entries or ndb.get_multi(self.entry_keys)
if None in self.entries:
raise Exception('Invalid entry key.')
def to_dict(self):
d = {TYPE_KEY: self.type}
self.fetch_entries()
for i, entry_type in enumerate(POEM_TYPE_ENTRY_TYPES[self.type]):
d[entry_type] = self.entries[i]
d[ENCODED_IDS_KEY] = encode_ids(tuple(entry.key.id() for entry in self.entries))
d[RANK_KEY] = self.rank
d[IS_NEW_KEY] = self.key == None
return d
POEM_TYPE_ENTRY_TYPES = {
Poem.QUESTION_ANSWER: (Entry.QUESTION, Entry.ANSWER),
Poem.CONDITIONAL: (Entry.CONDITION, Entry.CONSEQUENCE),
}