# Contributors:
# Christopher P. Barnes <senrabc@gmail.com>
# Andrei Sura: github.com/indera
# Mohan Das Katragadda <mohan.das142@gmail.com>
# Philip Chase <philipbchase@gmail.com>
# Ruchi Vivek Desai <ruchivdesai@gmail.com>
# Taeber Rapczak <taeber@ufl.edu>
# Nicholas Rejack <nrejack@ufl.edu>
# Josh Hanna <josh@hanna.io>
# Copyright (c) 2014-2015, University of Florida
# All rights reserved.
#
# Distributed under the BSD 3-Clause License
# For full text of the BSD 3-Clause License see http://opensource.org/licenses/BSD-3-Clause
import abc
import time
import pkg_resources
from datetime import datetime, timedelta
from lxml import etree
from utils import redi_email
REDI_PACKAGE_NAME = 'redi'
[docs]class ReportCourier(object):
@abc.abstractmethod
[docs] def deliver(self, report):
raise NotImplementedError()
[docs]class ReportFileWriter(ReportCourier):
def __init__(self, output_file, logger):
self._output_file = output_file
self._logger = logger
[docs] def deliver(self, report):
"""
Deliver the summary report by writing it to a file
or logging it to the console if writing the file fails
:html_report_path the path where the report will be stored
:html the actual report content
"""
logger = self._logger
html_report_path = self._output_file
html = report
problem_found = False
try:
report_file = open(html_report_path, 'w')
except (IOError, OSError) as e:
logger.exception('Could not open file: %s' % html_report_path)
problem_found = True
else:
try:
report_file.write(html)
logger.info("==> You can review the summary report by opening: {}"\
" in your browser".format(html_report_path))
except IOError:
logger.exception('Could not write file: %s' % html_report_path)
problem_found = True
finally:
report_file.close()
if problem_found:
logger.info("== Summary report ==" + html)
[docs]class ReportEmailSender(ReportCourier):
def __init__(self, settings, logger):
self._settings = settings
self._logger = logger
[docs] def deliver(self, report):
"""
Deliver summary report as an email
:email_settings dictinary with email parameters
:html the actual report content
"""
logger = self._logger
email_settings = self._settings
html = report
# TODO: Replace this with a "backup_courier" constructor injection
deliver_report_as_file = self.__backup_courier_wrapper()
try:
redi_email.send_email_data_import_completed(email_settings, html)
logger.info("Summary report was emailed: parameter 'send_email = Y'")
except Exception as e:
logger.error("Unable to deliver the summary report due error: %s" % e)
deliver_report_as_file("report.html", html)
def __backup_courier_wrapper(self):
# Needed for backwards-compatibility with the old
# deliver_report_by_file() call from deliver_report_by_email()
logger = self._logger
def wrapper(filename, report):
return ReportFileWriter(filename, logger).deliver(report)
return wrapper
[docs]class ReportCreator(object):
def __init__(self, report_file_path, project_name, redcap_uri,
sort_by_lab_id, writer):
self._report_parameters = {
'report_file_path': report_file_path,
'project': project_name,
'redcap_uri': redcap_uri,
'is_sort_by_lab_id': sort_by_lab_id
}
self._writer = writer
[docs] def create_report(self, report_data, alert_summary, collection_date_summary_dict, duration_dict):
report_parameters = self._report_parameters
write_element_tree_to_file = self._writer
root = etree.Element("report")
root.append(etree.Element("header"))
root.append(etree.Element("summary"))
root.append(etree.Element("alerts"))
root.append(etree.Element("subjectsDetails"))
root.append(etree.Element("errors"))
root.append(etree.Element("summaryOfSpecimenTakenTimes"))
updateReportHeader(root, report_parameters)
updateReportSummary(root, report_data)
updateSubjectDetails(root, report_data['subject_details'])
updateReportAlerts(root, alert_summary)
updateReportErrors(root, report_data['errors'])
updateSummaryOfSpecimenTakenTimes(root, collection_date_summary_dict)
# TODO: remove dependency on the order of the xml elements in the report
sort_by_value = 'lab_id' if report_parameters['is_sort_by_lab_id'] else 'redcap_id'
root.append(gen_ele("sort_details_by", sort_by_value))
start = duration_dict['all']['start']
end = duration_dict['all']['end']
diff = self.get_time_diff(end, start)
root.append(gen_ele('time_all_start', start[-8:]))
root.append(gen_ele('time_all_end', end[-8:]))
root.append(gen_ele('time_all_diff', self.format_seconds_as_string(diff)))
tree = etree.ElementTree(root)
write_element_tree_to_file(tree,report_parameters.get('report_file_path'))
report_xsl = pkg_resources.resource_filename(REDI_PACKAGE_NAME,
'utils/report.xsl')
xslt = etree.parse(report_xsl)
transform = etree.XSLT(xslt)
html_report = transform(tree)
html_str = etree.tostring(html_report, method='html', pretty_print=True)
return html_str
[docs] def get_time_diff(self, end, start):
"""
Get time difference in seconds from the two dates
Parameters
----------
end : string
The end timestamp
start : string
The start timestamp
"""
# sqlite: select strftime('%s', rbEndTime) - strftime('%s', rbStartTime) from RediBatch;
fmt = '%Y-%m-%d %H:%M:%S'
dt_end = datetime.strptime(end, fmt)
dt_start = datetime.strptime(start, fmt)
diff = (dt_end - dt_start).total_seconds()
return diff
[docs]def updateReportSummary(root, report_data):
summary = root[1]
subjectCount = etree.SubElement(summary, "subjectCount")
subjectCount.text = str(report_data.get('total_subjects'))
forms = etree.SubElement(summary, "forms")
form_data = report_data['form_details']
for k in sorted(form_data.keys()):
form = etree.SubElement(forms, "form")
name_element = etree.SubElement(form, "form_name")
name_element.text = k
count_element = etree.SubElement(form, "form_count")
count_element.text = str(form_data.get(k))
[docs]def updateReportAlerts(root, alert_summary):
alerts = root[2]
too_many_forms = etree.SubElement(alerts, 'tooManyForms')
too_many_values = etree.SubElement(alerts, 'tooManyValues')
for event in alert_summary['max_event_alert']:
event_alert = etree.SubElement(too_many_forms, 'eventAlert')
msg = etree.SubElement(event_alert, 'message')
msg.text = event
for value in alert_summary['multiple_values_alert']:
values_alert = etree.SubElement(too_many_values, 'valuesAlert')
msg = etree.SubElement(values_alert, 'message')
msg.text = value
[docs]def updateSubjectDetails(root, subject_details):
"""
Helper method for #create_summary_report()
Adds subject information to the xml tree which is later formated
by `redi/utils/report.xsl` into the html `table#subject_details"`
"""
subjectsDetails = root[3]
for key in sorted(subject_details.keys()):
subject = etree.SubElement(subjectsDetails, "subject")
details = subject_details.get(key)
subject.append(gen_ele("redcap_id", key))
forms = etree.SubElement(subject, "forms")
for k in sorted(details.keys()):
if(k.endswith("_Forms")):
form = etree.SubElement(forms, "form")
name_element = etree.SubElement(form, "form_name")
name_element.text = k
count_element = etree.SubElement(form, "form_count")
count_element.text = str(details.get(k))
else:
element = etree.SubElement(subject, k)
element.text = str(details.get(k))
[docs]def updateReportErrors(root, errors):
errorsRoot = root[4]
for error in errors:
errorElement = etree.SubElement(errorsRoot, "error")
errorElement.text = str(error)
[docs]def updateSummaryOfSpecimenTakenTimes(root, collection_date_summary_dict):
timeSummaryRoot = root[5]
totalElement = etree.SubElement(timeSummaryRoot, "total")
totalElement.text = str(collection_date_summary_dict['total'])
blankElement = etree.SubElement(timeSummaryRoot, "blank")
blankElement.text = str(collection_date_summary_dict['blank'])
percentElement = etree.SubElement(timeSummaryRoot, "percent")
percentElement.text = str((float(collection_date_summary_dict['blank'])/\
collection_date_summary_dict['total'])*100)
[docs]def gen_ele(ele_name, ele_text):
""" Create an xml element with given name and content """
return etree.XML("<{}>{}</{}>".format(ele_name, ele_text, ele_name))
[docs]def gen_subele(parent, subele_name, subele_text):
subele = etree.SubElement(parent, subele_name)
subele.text = subele_text
return subele