Skip to content

Commit

Permalink
fixed chart, added chart handler, allow for downloading older reports
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Allen authored and Basil Shkara committed Jun 7, 2010
1 parent 7bf68b5 commit e9436ff
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 153 deletions.
141 changes: 141 additions & 0 deletions chart.py
@@ -0,0 +1,141 @@
import sys
import settings
import models.data
import datetime
from google.appengine.ext import db
sys.path.insert(0, settings.APP_ROOT_DIR + '/lib')
from graphy.backends import google_chart_api
from graphy import formatters
from graphy import line_chart

class SalesChart(object):
def units_chart(self, pid):
overall_chart = google_chart_api.LineChart()

sales_query = db.Query(models.data.Sale)
sales_query.filter('pid =', pid)
sales_query.order('report_date')
sales = []
for sale in sales_query:
sales.append([sale.income_units, sale.report_date])

if len(sales) == 0: return (None, None)
sales, dates = zip(*sales)

# Make dates readable
dates = [date.strftime('%d %b') for date in dates]

# Add sales line
overall_chart.AddLine(sales, width=line_chart.LineStyle.THICK, label='Sales')

# Determine if an upgrades line needs to be drawn
sales_start = sales_query.get().report_date
# Use settings file as the definitive source of upgrade start date because iTunes Connect sometimes reports false upgrade numbers
versions = settings.PRODUCTS[pid]['versions']
upgrades = []
if len(versions) > 1:
# Convert to datetime to allow for timedelta calculation
upgrades_start = datetime.datetime.combine(versions[1]['date'], datetime.time(sales_start.hour, sales_start.minute))
difference_in_days = (upgrades_start - sales_start).days

upgrades_query = db.Query(models.data.Upgrade)
upgrades_query.filter('pid =', pid)
upgrades_query.order('report_date')
upgrades_query.filter('report_date >', upgrades_start)

# Pad upgrades list with time before upgrade commenced
for i in range(0, difference_in_days):
upgrades.append(0)
for upgrade in upgrades_query:
upgrades.append(upgrade.income_units)
# Add upgrades line
overall_chart.AddLine(upgrades, width=line_chart.LineStyle.THICK, label='Upgrades')

# Add horizontal labels
max_num_horizontal_labels = 15
segment_gap = 1
if len(dates) > max_num_horizontal_labels:
segment_gap = len(dates) / max_num_horizontal_labels

overall_chart.bottom.min = 0
overall_chart.bottom.max = max_num_horizontal_labels
overall_chart.bottom.labels = dates
overall_chart.bottom.labels = dates[::segment_gap]

# Add vertical labels
max_num_vertical_labels = 15

max_sales = 0
min_sales = 0
max_upgrades = 0
min_upgrades = 0
if sales:
max_sales = max(sales)
min_sales = min(sales)
if upgrades:
max_upgrades = max(upgrades)
min_upgrades = min(upgrades)

overall_chart.left.max = max_upgrades if max_upgrades > max_sales else max_sales
overall_chart.left.min = min_upgrades if min_upgrades < min_sales else min_sales
vertical_labels = []
segment_gap = overall_chart.left.max / max_num_vertical_labels
for i in range(0, max_num_vertical_labels + 1):
vertical_labels.append(i * segment_gap)
if len(vertical_labels) == max_num_vertical_labels + 1: break

overall_chart.left.labels = vertical_labels
overall_chart.bottom.label_gridlines = True

# Build concentrated chart if there is enough data for one
concentrated_chart = self.concentrated_units_chart(sales, upgrades, dates)
if concentrated_chart != None:
concentrated_chart = concentrated_chart.display.Url(1000, 300)

return (overall_chart.display.Url(1000, 300), concentrated_chart)

def concentrated_units_chart(self, sales, upgrades, dates):
# Want results for the last 2 weeks
concentrated_result_set_num = 14
concentrated_chart = None
if len(sales) > concentrated_result_set_num:
concentrated_chart = google_chart_api.LineChart()
# Slice to create the line for the concentrated chart
calc_concentrated_result_set = lambda x: x[len(sales) - concentrated_result_set_num :len(sales)]
sales_concentrated = calc_concentrated_result_set(sales)
dates_concentrated = calc_concentrated_result_set(dates)
upgrades_concentrated = calc_concentrated_result_set(upgrades)

concentrated_chart.AddLine(sales_concentrated, width=line_chart.LineStyle.THICK, label='Sales')
if len(upgrades_concentrated) == concentrated_result_set_num - 1:
concentrated_chart.AddLine(upgrades_concentrated, width=line_chart.LineStyle.THICK, label='Upgrades')

concentrated_chart.left.min = 0
max_upgrades_concentrated = 0
max_sales_concentrated = 0
if upgrades_concentrated:
max_upgrades_concentrated = max(upgrades_concentrated)
if sales_concentrated:
max_sales_concentrated = max(sales_concentrated)

concentrated_chart.left.max = max_upgrades_concentrated if max_upgrades_concentrated > max_sales_concentrated else max_sales_concentrated
segment_gap = concentrated_chart.left.max / concentrated_result_set_num
concentrated_vertical_labels = []

for i in range(0, concentrated_result_set_num + 1):
concentrated_vertical_labels.append(i * segment_gap)
if len(concentrated_vertical_labels) == concentrated_result_set_num + 1: break
if concentrated_vertical_labels[-1] < concentrated_chart.left.max:
new_max = concentrated_vertical_labels[-1] + segment_gap
concentrated_vertical_labels.append(new_max)
concentrated_chart.left.max = new_max

concentrated_chart.left.labels = concentrated_vertical_labels
concentrated_chart.bottom.labels = dates_concentrated
concentrated_chart.left.label_gridlines = True
concentrated_chart.bottom.label_gridlines = True
return concentrated_chart
else:
return None


11 changes: 10 additions & 1 deletion handlers/admin.py
Expand Up @@ -7,7 +7,7 @@
import tarfile
import settings
from processors import report_persister

from chart import SalesChart

PAGE_NAME = 'Admin'
TEMPLATE_PATH = os.path.join(settings.SETTINGS['template_path'], 'admin.html')
Expand Down Expand Up @@ -46,3 +46,12 @@ def post(self):
}

self.response.out.write(template.render(TEMPLATE_PATH, template_values))

class ChartHandler(webapp.RequestHandler):
def get(self):
pid = self.request.get("pid", None)
if not pid:
return
overall_chart_url, concentrated_chart_url = SalesChart().units_chart(pid)

self.response.out.write("<img src='%s'/>" % overall_chart_url)
137 changes: 2 additions & 135 deletions jobs/email_report.py
Expand Up @@ -13,13 +13,7 @@

import settings
import models.data

# Append lib path to sys.path for Graphy
sys.path.insert(0, settings.APP_ROOT_DIR + '/lib')
from graphy.backends import google_chart_api
from graphy import formatters
from graphy import line_chart

from chart import SalesChart

class EmailReport(webapp.RequestHandler):

Expand Down Expand Up @@ -104,7 +98,7 @@ def get(self):
rankings.append(dict)
rankings = sorted(rankings, key=lambda k: k['country'])

overall_chart_url, concentrated_chart_url = self.units_chart(pid)
overall_chart_url, concentrated_chart_url = SalesChart().units_chart(pid)
product = {
'name': product_name,
'last_reported_sales_total': last_reported_sales_total,
Expand Down Expand Up @@ -146,133 +140,6 @@ def _date_string(self, date):
def _format_number(self, number):
locale.setlocale(locale.LC_ALL,"")
return locale.format('%d', number, True)

def units_chart(self, pid):
overall_chart = google_chart_api.LineChart()

sales_query = db.Query(models.data.Sale)
sales_query.filter('pid =', pid)
sales_query.order('report_date')
sales = []
for sale in sales_query:
sales.append([sale.income_units, sale.report_date])

if len(sales) == 0: return (None, None)
sales, dates = zip(*sales)

# Make dates readable
dates = [date.strftime('%d %b') for date in dates]

# Add sales line
overall_chart.AddLine(sales, width=line_chart.LineStyle.THICK, label='Sales')

# Determine if an upgrades line needs to be drawn
sales_start = sales_query.get().report_date
# Use settings file as the definitive source of upgrade start date because iTunes Connect sometimes reports false upgrade numbers
versions = settings.PRODUCTS[pid]['versions']
upgrades = []
if len(versions) > 1:
# Convert to datetime to allow for timedelta calculation
upgrades_start = datetime.datetime.combine(versions[1]['date'], datetime.time(sales_start.hour, sales_start.minute))
difference_in_days = (upgrades_start - sales_start).days

upgrades_query = db.Query(models.data.Upgrade)
upgrades_query.filter('pid =', pid)
upgrades_query.order('report_date')
upgrades_query.filter('report_date >', upgrades_start)

# Pad upgrades list with time before upgrade commenced
for i in range(0, difference_in_days):
upgrades.append(0)
for upgrade in upgrades_query:
upgrades.append(upgrade.income_units)

# Add upgrades line
overall_chart.AddLine(upgrades, width=line_chart.LineStyle.THICK, label='Upgrades')

# Add horizontal labels
max_num_horizontal_labels = 15
segment_gap = 1
if len(dates) > max_num_horizontal_labels:
segment_gap = len(dates) / max_num_horizontal_labels

overall_chart.bottom.min = 0
overall_chart.bottom.max = max_num_horizontal_labels
overall_chart.bottom.labels = dates
overall_chart.bottom.labels = dates[::segment_gap]

# Add vertical labels
max_num_vertical_labels = 15
overall_chart.left.min = 0

max_sales = 0
max_upgrades = 0
if sales:
max_sales = max(sales)
if upgrades:
max_upgrades = max(upgrades)

overall_chart.left.max = max_upgrades if max_upgrades > max_sales else max_sales
vertical_labels = []
segment_gap = overall_chart.left.max / max_num_vertical_labels
for i in range(0, max_num_vertical_labels + 1):
vertical_labels.append(i * segment_gap)
if len(vertical_labels) == max_num_vertical_labels + 1: break

overall_chart.left.labels = vertical_labels
overall_chart.bottom.label_gridlines = True

# Build concentrated chart if there is enough data for one
concentrated_chart = self.concentrated_units_chart(sales, upgrades, dates)
if concentrated_chart != None:
concentrated_chart = concentrated_chart.display.Url(1000, 300)

return (overall_chart.display.Url(1000, 300), concentrated_chart)

def concentrated_units_chart(self, sales, upgrades, dates):
# Want results for the last 2 weeks
concentrated_result_set_num = 14
concentrated_chart = None
if len(sales) > concentrated_result_set_num:
concentrated_chart = google_chart_api.LineChart()
# Slice to create the line for the concentrated chart
calc_concentrated_result_set = lambda x: x[len(sales) - concentrated_result_set_num :len(sales)]
sales_concentrated = calc_concentrated_result_set(sales)
dates_concentrated = calc_concentrated_result_set(dates)
upgrades_concentrated = calc_concentrated_result_set(upgrades)

concentrated_chart.AddLine(sales_concentrated, width=line_chart.LineStyle.THICK, label='Sales')
if len(upgrades_concentrated) == concentrated_result_set_num - 1:
concentrated_chart.AddLine(upgrades_concentrated, width=line_chart.LineStyle.THICK, label='Upgrades')

concentrated_chart.left.min = 0
max_upgrades_concentrated = 0
max_sales_concentrated = 0
if upgrades_concentrated:
max_upgrades_concentrated = max(upgrades_concentrated)
if sales_concentrated:
max_sales_concentrated = max(sales_concentrated)

concentrated_chart.left.max = max_upgrades_concentrated if max_upgrades_concentrated > max_sales_concentrated else max_sales_concentrated
segment_gap = concentrated_chart.left.max / concentrated_result_set_num
concentrated_vertical_labels = []

for i in range(0, concentrated_result_set_num + 1):
concentrated_vertical_labels.append(i * segment_gap)
if len(concentrated_vertical_labels) == concentrated_result_set_num + 1: break
if concentrated_vertical_labels[-1] < concentrated_chart.left.max:
new_max = concentrated_vertical_labels[-1] + segment_gap
concentrated_vertical_labels.append(new_max)
concentrated_chart.left.max = new_max

concentrated_chart.left.labels = concentrated_vertical_labels
concentrated_chart.bottom.labels = dates_concentrated
concentrated_chart.left.label_gridlines = True
concentrated_chart.bottom.label_gridlines = True
return concentrated_chart
else:
return None

def send_email(self, pid, subject, email_body):
message = mail.EmailMessage(sender=settings.SETTINGS['admin_email_address'],
subject=subject)
Expand Down
38 changes: 21 additions & 17 deletions jobs/pull_report.py
Expand Up @@ -15,28 +15,32 @@
class ReportJob(webapp.RequestHandler):

def get(self):
date = self.request.get("date", None)
# iTunes Connect stores reports with a 1 day delay
now = datetime.date.today()
one_day = datetime.timedelta(days=1)
yesterday = now - one_day
yesterday = yesterday.strftime('%m/%d/%Y')

if not date:
now = datetime.date.today()
one_day = datetime.timedelta(days=1)
yesterday = now - one_day
date = yesterday.strftime('%m/%d/%Y')
print date
# Fetch for all available accounts
for account_name in settings.ACCOUNTS:
try:
latest_report = itcscrape.getLastDayReport(settings.ACCOUNTS[account_name]['itunesconnect_username'], settings.ACCOUNTS[account_name]['itunesconnect_password'], yesterday)
report_persister.persist(latest_report['filename'], latest_report['content'])
except:
# Download failed (timeout or report not available yet)
# Send email to administrator
message = mail.EmailMessage(sender=settings.SETTINGS['admin_email_address'],
subject='[ASM] Report job failed for account: ' + account_name)
message.to = settings.SETTINGS['admin_email_address']
message.body = 'Failed to download the iTunes Connect sales report for: ' + yesterday
message.send()
#try:
latest_report = itcscrape.getLastDayReport(settings.ACCOUNTS[account_name]['itunesconnect_username'], settings.ACCOUNTS[account_name]['itunesconnect_password'], date)
report_persister.persist(latest_report['filename'], latest_report['content'])
#except:
## Download failed (timeout or report not available yet)
## Send email to administrator
#message = mail.EmailMessage(sender=settings.SETTINGS['admin_email_address'],
#subject='[ASM] Report job failed for account: ' + account_name)
#message.to = settings.SETTINGS['admin_email_address']
#message.body = 'Failed to download the iTunes Connect sales report for: ' + date
#message.send()

def main():
application = webapp.WSGIApplication([('/jobs/pull_report', ReportJob)], debug=True)
application = webapp.WSGIApplication([
('/jobs/pull_report', ReportJob)
], debug=True)
wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
Expand Down
1 change: 1 addition & 0 deletions main.py 100755 → 100644
Expand Up @@ -16,6 +16,7 @@ def main():
application = webapp.WSGIApplication([('/', admin.RootHandler),
('/admin', admin.RootHandler),
('/admin/upload', admin.UploadHandler),
('/admin/chart', admin.ChartHandler),
], debug=True)
wsgiref.handlers.CGIHandler().run(application)

Expand Down
8 changes: 8 additions & 0 deletions templates/admin.html
Expand Up @@ -13,4 +13,12 @@ <h3>Successfully uploaded: {{ file_name }}</h3>
<input type="submit" value="Upload" />
</form>

{% if imported_date %}
<h3>Sucessfully imported: {{ imported_date}}</h3>
{% endif %}
<form action="/jobs/pull_report" method="get">
<input type="text" name="date"/>
<input type="submit" value="Import"/>
</form>

{% endblock %}

0 comments on commit e9436ff

Please sign in to comment.