Skip to content

Commit bd17c1d

Browse files
add extract model and populate in create_appointments
1 parent 96781ed commit bd17c1d

File tree

6 files changed

+95
-6
lines changed

6 files changed

+95
-6
lines changed

manage_breast_screening/notifications/management/commands/create_appointments.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Appointment,
1515
AppointmentStatusChoices,
1616
Clinic,
17+
Extract,
1718
)
1819
from manage_breast_screening.notifications.services.blob_storage import BlobStorage
1920

@@ -52,6 +53,11 @@ def handle(self, *args, **options):
5253
).readall()
5354

5455
data_frame = self.raw_data_to_data_frame(blob_content)
56+
57+
extract = Extract.objects.create(created_at = datetime.now(tz=ZONE_INFO),
58+
bso_code = data_frame.iloc[0]["BSO"],
59+
filename = blob.name,
60+
sequence_number = data_frame.iloc[0]["Sequence"])
5561

5662
for idx, row in data_frame.iterrows():
5763
if self.is_not_holding_clinic(row):
@@ -60,7 +66,7 @@ def handle(self, *args, **options):
6066
logger.info("%s created", clinic)
6167

6268
appt, appt_created = self.update_or_create_appointment(
63-
row, clinic
69+
row, clinic, extract
6470
)
6571
logger.info("Processed %s rows from %s", len(data_frame), blob.name)
6672
logger.info("Create Appointments command finished successfully")
@@ -99,7 +105,7 @@ def find_or_create_clinic(self, row: pandas.Series) -> tuple[Clinic, bool]:
99105
)
100106

101107
def update_or_create_appointment(
102-
self, row: pandas.Series, clinic: Clinic
108+
self, row: pandas.Series, clinic: Clinic, extract: Extract
103109
) -> tuple[Appointment | None, bool]:
104110
appointment = Appointment.objects.filter(nbss_id=row["Appointment ID"]).first()
105111

@@ -127,6 +133,7 @@ def update_or_create_appointment(
127133
== "A"
128134
),
129135
)
136+
extract.appointments.add(new_appointment)
130137
logger.info("%s created", new_appointment)
131138
return (new_appointment, True)
132139
elif self.is_cancelling_existing_appointment(row, appointment):
@@ -136,6 +143,7 @@ def update_or_create_appointment(
136143
row["Action Timestamp"]
137144
)
138145
appointment.save()
146+
extract.appointments.add(appointment)
139147
logger.info("%s cancelled", appointment)
140148
elif self.is_completed_appointment(row, appointment):
141149
appointment.status = row["Status"]
@@ -144,6 +152,7 @@ def update_or_create_appointment(
144152
row["Action Timestamp"]
145153
)
146154
appointment.save()
155+
extract.appointments.add(appointment)
147156
logger.info("%s marked completed (%s)", appointment, row.get("Status"))
148157
elif appointment is None:
149158
logger.info(
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 5.2.7 on 2025-11-19 08:28
2+
3+
import uuid
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('notifications', '0021_alter_clinic_code_clinic_notificatio_code_55dbdb_idx_and_more'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='Extract',
16+
fields=[
17+
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
18+
('created_at', models.DateTimeField(auto_now_add=True)),
19+
('filename', models.CharField(max_length=255)),
20+
('bso_code', models.CharField(max_length=255)),
21+
('sequence_number', models.IntegerField()),
22+
('appointments', models.ManyToManyField(blank=True, to='notifications.appointment')),
23+
],
24+
),
25+
]

manage_breast_screening/notifications/models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,21 @@ class MessageStatus(models.Model):
216216
status_updated_at = models.DateTimeField(null=False)
217217
created_at = models.DateTimeField(null=False, auto_now_add=True)
218218
updated_at = models.DateTimeField(null=False, auto_now_add=True)
219+
220+
221+
class Extract(models.Model):
222+
"""
223+
A model to store the extracted data from the message
224+
"""
225+
226+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
227+
appointments = models.ManyToManyField("notifications.Appointment", blank=True)
228+
created_at = models.DateTimeField(auto_now_add=True)
229+
filename = models.CharField(max_length=255, null=False)
230+
bso_code = models.CharField(max_length=255, null=False)
231+
sequence_number = models.IntegerField(null=False)
232+
constraints = [
233+
models.UniqueConstraint(
234+
fields=["bso_code", "sequence_number"], name="unique_code_bso_code"
235+
)
236+
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"NBSSAPPT_HDR"|"00000013"|"20250128"|"170922"|"000003"
2+
"NBSSAPPT_FLDS"|"Sequence"|"BSO"|"Action"|"Clinic Code"|"Holding Clinic"|"Status"|"Attended Not Scr"|"Appointment ID"|"NHS Num"|"Epsiode Type"|"Episode Start"|"BatchID"|"Screen or Asses"|"Screen Appt num"|"Booked By"|"Cancelled By"|"Appt Date"|"Appt Time"|"Location"|"Clinic Name"|"Clinic Name (Let)"|"Clinic Address 1"|"Clinic Address 2"|"Clinic Address 3"|"Clinic Address 4"|"Clinic Address 5"|"Postcode"|"Action Timestamp"
3+
"NBSSAPPT_DATA"|"000001"|"KMK"|"B"|"BU003"|"N"|"B"|"N"|"BU003-67215-RA1-DN-Z2222-1"|"9449304424"|"S"|"20250102"|"KMKS02441"|"S"|""|"C"|"H"|"20250110"|"0845"|"MKGH"|"BREAST CARE UNIT"|"BREAST CARE UNIT"|"BREAST CARE UNIT"|"MILTON KEYNES HOSPITAL"|"STANDING WAY"|"MILTON KEYNES"|"MK6 5LD"|"MK6 5LD"|"20250123-121433"
4+
"NBSSAPPT_END"|"00000013"|"20250128"|"17:09:22"|"000003"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"NBSSAPPT_HDR"|"00000013"|"20250128"|"170922"|"000003"
2+
"NBSSAPPT_FLDS"|"Sequence"|"BSO"|"Action"|"Clinic Code"|"Holding Clinic"|"Status"|"Attended Not Scr"|"Appointment ID"|"NHS Num"|"Epsiode Type"|"Episode Start"|"BatchID"|"Screen or Asses"|"Screen Appt num"|"Booked By"|"Cancelled By"|"Appt Date"|"Appt Time"|"Location"|"Clinic Name"|"Clinic Name (Let)"|"Clinic Address 1"|"Clinic Address 2"|"Clinic Address 3"|"Clinic Address 4"|"Clinic Address 5"|"Postcode"|"Action Timestamp"
3+
"NBSSAPPT_DATA"|"000001"|"KMK"|"C"|"BU003"|"N"|"C"|"N"|"BU003-67215-RA1-DN-Z2222-1"|"9449304424"|"S"|"20250102"|"KMKS02441"|"S"|""|"C"|"H"|"20250110"|"0845"|"MKGH"|"BREAST CARE UNIT"|"BREAST CARE UNIT"|"BREAST CARE UNIT"|"MILTON KEYNES HOSPITAL"|"STANDING WAY"|"MILTON KEYNES"|"MK6 5LD"|"MK6 5LD"|"20250123-121433"
4+
"NBSSAPPT_END"|"00000013"|"20250128"|"17:09:22"|"000003"

manage_breast_screening/notifications/tests/management/commands/test_create_appointments.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
from contextlib import contextmanager
33
from datetime import datetime, timezone
4-
from unittest.mock import Mock, PropertyMock, patch
4+
from unittest.mock import Mock, patch
55

66
import pytest
77
from azure.storage.blob import BlobProperties
@@ -10,7 +10,12 @@
1010
from manage_breast_screening.notifications.management.commands.create_appointments import (
1111
Command,
1212
)
13-
from manage_breast_screening.notifications.models import ZONE_INFO, Appointment, Clinic
13+
from manage_breast_screening.notifications.models import (
14+
ZONE_INFO,
15+
Appointment,
16+
Clinic,
17+
Extract,
18+
)
1419
from manage_breast_screening.notifications.tests.factories import (
1520
AppointmentFactory,
1621
ClinicFactory,
@@ -20,6 +25,8 @@
2025
UPDATED_APPOINTMENT_FILE = "ABC_20241202091321_APPT_107.dat"
2126
HOLDING_CLINIC_APPOINTMENT_FILE = "ABC_20241202091421_APPT_108.dat"
2227
COMPLETED_APPOINTMENT_FILE = "ABC_20241202091521_APPT_109.dat"
28+
SINGLE_APPOINTMENT_FILE = "ABC_20251118150721_APPT_101.dat"
29+
CANCELLED_SINGLE_APPOINTMENT_FILE = "ABC_20251118150721_APPT_102.dat"
2330

2431

2532
def fixture_file_path(filename):
@@ -44,7 +51,7 @@ def stored_blob_data(prefix_dir: str, filenames: list[str]):
4451
mock_blob_contents = []
4552
for filename in filenames:
4653
mock_blob = Mock(spec=BlobProperties)
47-
mock_blob.name = PropertyMock(return_value=f"{prefix_dir}/{filename}")
54+
mock_blob.name = f"{prefix_dir}/{filename}"
4855
mock_blobs.append(mock_blob)
4956
mock_blob_contents.append(open(fixture_file_path(filename)).read())
5057

@@ -118,6 +125,9 @@ def test_handle_creates_records(self):
118125

119126
assert appointments[1].assessment is True
120127

128+
assert Extract.objects.count() == 1
129+
assert Extract.objects.first().appointments.count() == 2
130+
121131
def test_handles_holding_clinics(self):
122132
"""Test does not create appointments for valid NBSS data marked as a Holding Clinic"""
123133
today_dirname = datetime.today().strftime("%Y-%m-%d")
@@ -196,7 +206,7 @@ def test_only_updates_cancelled_appointments(self):
196206
),
197207
status="C",
198208
)
199-
209+
200210
nbss_id_cancelled = "BU003-67215-RA1-DN-Z2222-1"
201211
existing_appt_to_cancel = AppointmentFactory(
202212
nbss_id=nbss_id_cancelled,
@@ -320,3 +330,22 @@ def test_calls_insights_logger_if_exception_raised(
320330
Command().handle(**{"date_str": "Noooo!"})
321331

322332
mock_insights_logger.assert_called_with("CreateAppointmentsError: Error!")
333+
334+
def test_create_extract_and_cancel(self):
335+
"""Test Extract creation for new booked appointments in NBSS data, stored in Azure storage blob"""
336+
today_dirname = datetime.now().strftime("%Y-%m-%d")
337+
338+
with stored_blob_data(today_dirname, [SINGLE_APPOINTMENT_FILE]):
339+
Command().handle(**{"date_str": today_dirname})
340+
341+
assert Extract.objects.count() == 1
342+
assert Extract.objects.first().appointments.count() == 1
343+
assert Extract.objects.first().appointments
344+
345+
346+
with stored_blob_data(today_dirname, [CANCELLED_SINGLE_APPOINTMENT_FILE]):
347+
Command().handle(**{"date_str": today_dirname})
348+
349+
assert Extract.objects.count() == 2
350+
351+
assert Extract.objects.all()[0].appointments.get().id == Extract.objects.all()[1].appointments.get().id

0 commit comments

Comments
 (0)