Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
from datetime import datetime
from logging import getLogger

Expand All @@ -23,53 +22,37 @@ class ReportConfig:

def __init__(
self,
query_filename: str,
name: str,
params: list,
report_filename: str | None = None,
should_send_email: bool = False,
send_email: bool = False,
query: str | None = None,
):
self.query_filename = query_filename
self.name = name
self.params = params
self.report_filename = report_filename or query_filename
self.should_send_email = should_send_email
self.send_email = send_email
self.query = query or name


class Command(BaseCommand):
"""
Django Admin command which generates and stores CSV report data based on
common reporting queries:
'aggregate' covers all notifications sent, failures and deliveries counts
grouped by appointment date, clinic code and bso code. This report covers
a 3 month time period and can be resource intensive.
'failures' covers all failed status updates from NHS Notify and contains
NHS numbers, Clinic and BSO code and failure dates and reasons for one day.
common reporting queries.
Reports are generated sequentially.
Reports are stored in Azure Blob storage.
"""

SMOKE_TEST_BSO_CODE = "SM0K3"
BSO_CODES = ["MBD"]

REPORTS = [
ReportConfig("aggregate", ["3 months"], True),
ReportConfig(
query_filename="aggregate",
params=["3 months"],
report_filename="aggregate",
should_send_email=True,
),
ReportConfig(
query_filename="failures",
params=[datetime.now(tz=ZONE_INFO).date()],
report_filename="invites_not_sent",
should_send_email=True,
),
ReportConfig(
query_filename="reconciliation",
params=[datetime.now(tz=ZONE_INFO).date()],
report_filename="reconciliation",
should_send_email=True,
"invites-not-sent", [datetime.now(tz=ZONE_INFO).date()], True, "failures"
),
ReportConfig("reconciliation", [datetime.now(tz=ZONE_INFO).date()], True),
]
SMOKE_TEST_BSO_CODE = "SM0K3"
SMOKE_TEST_CONFIG = ReportConfig(
"reconciliation", [datetime.now(tz=ZONE_INFO).date()], False
)

def add_arguments(self, parser):
parser.add_argument("--smoke-test", action="store_true")
Expand All @@ -79,53 +62,44 @@ def handle(self, *args, **options):
logger.info("Create Report Command started")

bso_codes, report_configs = self.configuration(options)
external_reports = {}

for bso_code in bso_codes:
for report_config in report_configs:
dataframe = pandas.read_sql(
Helper.sql(report_config.query_filename),
Helper.sql(report_config.query),
connection,
params=(report_config.params + [bso_code]),
)

csv = dataframe.to_csv(index=False)
filename = self.filename(bso_code, report_config.name)

BlobStorage().add(
self.filename(bso_code, report_config.report_filename),
csv,
content_type="text/csv",
container_name=os.getenv("REPORTS_CONTAINER_NAME"),
)
if (
not self.is_smoke_test(options)
and report_config.should_send_email
):
NhsMail().send_report_email(
attachment_data=csv,
attachment_filename=self.filename(
bso_code, report_config.report_filename
),
report_type=report_config.report_filename,
)

logger.info("Report %s created", report_config.report_filename)
BlobStorage().add(filename, csv, content_type="text/csv")

if report_config.send_email:
external_reports[filename] = csv

logger.info("Report %s created", report_config.name)

if bool(external_reports):
NhsMail().send_reports_email(external_reports)
logger.info(f"Reports sent: {', '.join(external_reports.keys())}")

logger.info("Create Report Command finished")

def configuration(self, options: dict) -> tuple[list[str], list[ReportConfig]]:
if self.is_smoke_test(options):
reconciliation_report_config = self.REPORTS[2]
if options.get("smoke_test", False):
bso_codes = [self.SMOKE_TEST_BSO_CODE]
report_configs = [reconciliation_report_config]
report_configs = [self.SMOKE_TEST_CONFIG]
else:
bso_codes = self.BSO_CODES
report_configs = self.REPORTS

return bso_codes, report_configs

def filename(self, bso_code: str, report_type: str) -> str:
name = f"{bso_code}-{report_type.replace('_', '-')}-report.csv"
name = f"{bso_code}-{report_type}-report.csv"
if bso_code != self.SMOKE_TEST_BSO_CODE:
name = f"{datetime.today().strftime('%Y-%m-%dT%H:%M:%S')}-{name}"
return name

def is_smoke_test(self, options):
return options.get("smoke_test", False)
35 changes: 15 additions & 20 deletions manage_breast_screening/notifications/services/nhs_mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,14 @@ def __init__(self) -> None:
self._sender_email = os.getenv("NOTIFICATIONS_SMTP_USERNAME", "")
self._sender_password = os.getenv("NOTIFICATIONS_SMTP_PASSWORD", "")

def send_report_email(
def send_reports_email(
self,
attachment_data: str,
attachment_filename: str,
report_type="invites_not_sent",
attachments_data: dict[str, str],
):
email = self._get_email_content(
attachment_data, attachment_filename, report_type
)
email = self._get_email_content(attachments_data)

logger.info(
f"Email for report type {report_type} created with attachment {attachment_filename}"
f"Email for reports {', '.join(attachments_data.keys())} created and sent"
)

if boolean_env("NOTIFICATIONS_SMTP_IS_ENABLED", False):
Expand Down Expand Up @@ -66,31 +62,30 @@ def _send_via_smtp(self, email):
else:
logger.info("Email sent")

def _get_email_content(self, attachment_data, attachment_filename, report_type):
def _get_email_content(self, attachments):
todays_date = datetime.today().strftime("%d-%m-%Y")
message = MIMEMultipart()

message.attach(MIMEText(self._body(report_type), "html"))
message.attach(MIMEText(self._body(), "html"))

message["Subject"] = self._subject(report_type, todays_date)
message["Subject"] = self._subject(todays_date)
message["From"] = self._sender_email
message["To"] = ",".join(self._recipient_emails)

attachment = MIMEApplication(attachment_data, Name=attachment_filename)
attachment["Content-Disposition"] = (
f'attachment; filename="{attachment_filename}"'
)
message.attach(attachment)
for filename, data in attachments.items():
attachment = MIMEApplication(data, Name=filename)
attachment["Content-Disposition"] = f'attachment; filename="{filename}"'
message.attach(attachment)

return message

def _subject(self, report_type, date, bso="Birmingham (MCR)") -> str:
default_subject = f"Breast screening digital comms {report_type.replace('_', ' ')} report – {date} – {bso}"
def _subject(self, date, bso="Birmingham (MCR)") -> str:
default_subject = f"Breast screening digital comms reports – {date} – {bso}"
environment = os.getenv("DJANGO_ENV", "local")
if environment != "prod":
return f"[{environment.upper()}] {default_subject}"
else:
return default_subject

def _body(self, report_type) -> str:
return render_to_string(f"report_emails/{report_type}.html")
def _body(self) -> str:
return render_to_string("report_emails/reports.html")

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<body>
<p>Hello.</p>
<p>
Attached to the email is the daily report for breast screening digital
Attached to the email are the daily reports for breast screening digital
comms. For more information on the data in the report, please check the
guidance here.
</p>
Expand Down
Loading