Skip to content

Commit 949e8a7

Browse files
committed
added summary api
1 parent 074b93e commit 949e8a7

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

driveshare_graph/app.py

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sqlite3
88
import uptime
99
import minmax
10+
import farmer_summary
1011

1112

1213
app = Flask(__name__)
@@ -55,5 +56,14 @@ def daily_data():
5556
json_totals = json.dumps(json_totals, default=json_util.default)
5657
return json_totals
5758

59+
60+
@app.route("/api/summary/<btc_addr>")
61+
def api_summary(btc_addr):
62+
conn = sqlite3.connect('summary.db')
63+
cursor = conn.cursor()
64+
json_summary = farmer_summary.json_month_summary(conn, cursor, btc_addr)
65+
return json_summary
66+
67+
5868
if __name__ == "__main__":
5969
app.run(host='0.0.0.0', port=5000, debug=True)

driveshare_graph/farmer_summary.py

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/usr/local/bin/python3
2+
3+
import sqlite3
4+
from pymongo import MongoClient
5+
import datetime as dt
6+
from datetime import timedelta
7+
import time
8+
from math import exp
9+
import sys
10+
import os
11+
import simplejson
12+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
13+
14+
INDIVIDUAL_MAX_HEIGHT = 199999
15+
SECONDS_IN_DAY = 86400
16+
17+
18+
def create_summary_table(conn, cursor): # pragma: no cover
19+
"""Create the summaries table."""
20+
cursor.execute('''CREATE TABLE summaries
21+
(payout_address CHAR(34) NOT NULL,
22+
date TEXT NOT NULL,
23+
auth_address TEXT NOT NULL,
24+
uptime REAL,
25+
duration REAL,
26+
height INTEGER,
27+
points REAL default 0,
28+
PRIMARY KEY (auth_address, date));''')
29+
conn.commit()
30+
31+
32+
def init_table(conn, cursor, collection):
33+
"""Initializes the summaries table."""
34+
client = MongoClient('localhost', 27017)
35+
collection = client['GroupB']['farmers']
36+
first_date = begin_date(collection)
37+
last_date = end_date((collection))
38+
day_count = (last_date - first_date).days
39+
for single_date in (first_date + timedelta(days=n) for n in range(day_count)):
40+
create_daily_summary(conn, cursor, collection, single_date)
41+
assign_points(conn, cursor, single_date)
42+
43+
44+
def update_table(conn, cursor, collection): # pragma: no cover
45+
"""Updates the summaries table if there is new data in collection."""
46+
cursor.execute('SELECT MAX(date) FROM summaries')
47+
date = cursor.fetchone()[0]
48+
max_date = dt.strptime(date, '%Y-%M-%D %H:%M:%S')
49+
next_date = max_date + timedelta(days = 1)
50+
last_date = end_date(collection)
51+
day_count = (last_date - next_date).days
52+
for single_date in (next_date + timedelta(days=n) for n in range(day_count)):
53+
create_daily_summary(conn, cursor, collection, single_date)
54+
assign_points(conn, cursor, single_date)
55+
56+
57+
def create_daily_summary(conn, cursor, collection, date):
58+
"""
59+
Inserts the farmers' summaries for the specified date.
60+
61+
Args:
62+
conn: sqlite db connection
63+
cursor: conn's cursor
64+
collection: MongoDB farmers collection
65+
date: generate summary entries for this date
66+
"""
67+
next_date = date + timedelta(days = 1)
68+
first_dates = {}
69+
last_dates = {}
70+
payout_addresses = {}
71+
uptimes = {}
72+
previous_time = 0
73+
for doc in collection.find({'time': {'$gte': date, '$lt': next_date}}):
74+
doc_time = time.mktime(doc['time'].timetuple())
75+
for farmer in doc['farmers']:
76+
auth_address = farmer['btc_addr']
77+
if (auth_address in first_dates):
78+
if last_dates[auth_address] == previous_time:
79+
uptimes[auth_address] += doc_time - previous_time
80+
last_dates[auth_address] = doc_time
81+
else:
82+
first_dates[auth_address] = doc_time
83+
last_dates[auth_address] = doc_time
84+
payout_addresses[auth_address] = farmer['payout_addr']
85+
uptimes[auth_address] = 0
86+
previous_time = doc_time
87+
for key in first_dates:
88+
auth_address = key
89+
summary_date = date
90+
payout_address = payout_addresses[key]
91+
uptime = uptimes[key]
92+
duration = last_dates[key] - first_dates[key]
93+
height = average_height(auth_address, date, next_date, collection)
94+
cursor.execute('''INSERT INTO summaries (auth_address, date,
95+
payout_address, uptime, duration, height)
96+
VALUES (?, ?, ?, ?, ? ,?)''',
97+
(str(auth_address), str(summary_date), payout_address, uptime,
98+
duration, height))
99+
conn.commit()
100+
101+
102+
def assign_points(conn, cursor, date):
103+
"""Returns the number of points awarded for the given size,
104+
uptime, and duration."""
105+
cursor.execute('''SELECT auth_address FROM summaries WHERE date=?''',
106+
(str(date),))
107+
address_list = cursor.fetchall()
108+
for address in address_list:
109+
address = ''.join(address)
110+
cursor.execute('''SELECT auth_address, uptime, duration, height FROM summaries
111+
WHERE auth_address = ? AND date = ?''',
112+
(str(address), str(date),))
113+
data = cursor.fetchone()
114+
address = data[0]
115+
uptime = data[1]
116+
duration = data[2]
117+
size = data[3]
118+
if duration == 0:
119+
points = 0
120+
else:
121+
points = (height_function(size) *
122+
uptime_logistic_function(uptime/duration) *
123+
(duration / 86400))
124+
cursor.execute('UPDATE summaries SET points = ? WHERE auth_address = ?'
125+
'AND date = ?', (points, str(address), str(date),))
126+
conn.commit()
127+
128+
129+
def average_height(btc_address, first_date, last_date, collection):
130+
"""Returns the average height of the specified btc address
131+
between the first_date and last_date.
132+
133+
Args:
134+
btc_address: btc_address (authentication address) for farmer
135+
first_date: first datetime in date range
136+
last_date: last datetime in date range
137+
collection: MongoDB collection of data on farmers
138+
139+
Returns:
140+
avg_height: average height of the build with btc_address between
141+
first_date and last_date
142+
"""
143+
pipeline = [
144+
{'$match': {'farmers.btc_addr': btc_address,
145+
'time': {'$gte': first_date, '$lt': last_date}}},
146+
{'$project': {'_id': 0, 'farmers.btc_addr': 1, 'farmers.height': 1}},
147+
{'$unwind': '$farmers'},
148+
{'$match': {'farmers.btc_addr': btc_address}}
149+
]
150+
height_array = []
151+
for doc in collection.aggregate(pipeline):
152+
height_array.append(doc['farmers']['height'])
153+
return sum(height_array)/len(height_array)
154+
155+
156+
def uptime_logistic_function(uptime):
157+
"""Returns a value between 0 and 1.
158+
159+
Args:
160+
uptime: uptime percentage (between 0 and 1)
161+
162+
Returns:
163+
value: output of a logistic function for given uptime (ranges between
164+
0 and 1)
165+
"""
166+
value = 1 / (1 + exp((-uptime + 0.85) * 19)) + 0.0547
167+
return value
168+
169+
170+
def height_function(height):
171+
"""Returns value between 0 and 1.
172+
173+
Args:
174+
height: farmer's capacity (number of 128MB shards it can store)
175+
176+
Returns:
177+
value: output of function y = 0.1 + 0.9 * h (if, h > 0)
178+
y = 0 (if, h == 0)
179+
"""
180+
if height == 0:
181+
return 0
182+
height_percentage = height / INDIVIDUAL_MAX_HEIGHT
183+
value = 0.01 + 0.99 * height_percentage
184+
return value
185+
186+
187+
def begin_date(farmers_collection): # pragma: no cover
188+
"""Returns the first date in the collection"""
189+
for doc in farmers_collection.find({}, {'time': 1, '_id': 0}
190+
).sort('time', 1).limit(1):
191+
begin_date = doc['time']
192+
year = begin_date.year
193+
month = begin_date.month
194+
day = begin_date.day
195+
begin_date = dt.datetime(year, month, day, 0, 0, 0, 0)
196+
return begin_date
197+
198+
199+
def end_date(farmers_collection): # pragma: no cover
200+
"""Returns the last date in the collection"""
201+
for doc in farmers_collection.find({}, {'time': 1, '_id': 0}
202+
).sort('time', -1).limit(1):
203+
end_date = doc['time']
204+
year = end_date.year
205+
month = end_date.month
206+
day = end_date.day
207+
last_date = dt.datetime(year, month, day, 0, 0, 0, 0)
208+
return last_date
209+
210+
211+
def json_month_summary(cursor, btc_addr):
212+
"""Return json summary for btc_addr in the past month"""
213+
summaries = []
214+
current_date = dt.datetime.now() - timedelta(days = 30)
215+
last_date = dt.datetime(current_date.year, current_date.month, current_date.day, 0, 0, 0)
216+
first_date = last_date - timedelta(days = 1)
217+
day_count = (last_date - first_date).days + 1
218+
for single_date in (first_date + timedelta(days=n) for n in range(day_count)):
219+
cursor.execute('SELECT date, uptime, duration, height, points FROM summaries '
220+
'WHERE auth_address = ? AND date = ?', (str(btc_addr), str(single_date),))
221+
data = cursor.fetchone()
222+
date = data[0]
223+
uptime = data[1]
224+
duration = data[2]
225+
height = data[3]
226+
points = data[4]
227+
cursor.execute('SELECT SUM(points) FROM summaries WHERE date = ?', (str(single_date),))
228+
total_points = cursor.fetchone()[0]
229+
summary_as_dict = {
230+
'date': date,
231+
'uptime': uptime,
232+
'duration': duration,
233+
'height': height,
234+
'points': points,
235+
'total_points': total_points
236+
}
237+
summaries.append(summary_as_dict)
238+
return simplejson.dumps(summaries)
239+
240+
241+
242+

driveshare_graph/update_summary.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import farmer_summary
2+
import sqlite3
3+
from pymongo import MongoClient
4+
from time import sleep
5+
6+
7+
while(True):
8+
conn = sqlite3.connect('summary.db')
9+
cursor = conn.cursor()
10+
client = MongoClient('localhost', 27017)
11+
collection = client['GroupB']['farmers']
12+
farmer_summary.update_table(conn, cursor, collection)
13+
sleep(86400)

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
flask==0.10.1
22
pymongo==3.0.3
33
pygal==2.0.7
4+
simplejson==3.8.1
45

setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ omit =
55
driveshare_graph/app.py
66
driveshare_graph/settings.py
77
driveshare_graph/updateSQL.py
8+
driveshare_graph/farmer_summary.py
9+
driveshare_graph/update_summary.py
810
source =
911
driveshare_graph
1012

0 commit comments

Comments
 (0)