Skip to content

Commit 68aebcc

Browse files
committed
WIP refactor reconciliation report
1 parent f41ea86 commit 68aebcc

File tree

3 files changed

+195
-59
lines changed

3 files changed

+195
-59
lines changed
Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,45 @@
1-
SELECT clin.code AS "Clinic code",
2-
appt.episode_type AS "Episode type",
3-
appt.status AS "Status",
4-
COUNT(appt.id) AS "Count"
5-
FROM notifications_appointment appt
6-
JOIN notifications_clinic clin ON clin.id = appt.clinic_id
7-
WHERE appt.created_at::date = %s
8-
AND clin.bso_code = %s
9-
GROUP BY clin.code, appt.episode_type, appt.status
1+
SELECT appt.nhs_number::text AS "NHS number",
2+
clin.name || ' (' || clin.code || ')' AS "Clinic name and code",
3+
CASE
4+
WHEN appt.episode_type = 'F' THEN 'Routine first call'
5+
WHEN appt.episode_type = 'G' THEN 'GP Referral'
6+
WHEN appt.episode_type = 'H' THEN 'Very high risk'
7+
WHEN appt.episode_type = 'N' THEN 'Early recall'
8+
WHEN appt.episode_type = 'R' THEN 'Routine recall'
9+
WHEN appt.episode_type = 'S' THEN 'Self referral'
10+
WHEN appt.episode_type = 'T' THEN 'VHR short-term recall'
11+
END AS "Episode type",
12+
CASE
13+
WHEN appt.status = 'B' THEN 'Booked'
14+
WHEN appt.status = 'C' THEN 'Cancelled'
15+
END AS "Appointment status",
16+
CASE
17+
WHEN let_sts.status IN (
18+
'cancelled', 'virus_scan_failed', 'validation_failed',
19+
'technical_failure', 'permanent_failure'
20+
) THEN 'Failed'
21+
WHEN msg.sent_at IS NOT NULL THEN 'Notified'
22+
ELSE 'Pending'
23+
END AS "Notification status",
24+
TO_CHAR(appt.created_at, 'yyyy-mm-dd HH:MM') AS "Appointment created at",
25+
TO_CHAR(appt.starts_at, 'yyyy-mm-dd HH:MM') AS "Appointment date and time",
26+
TO_CHAR(appt.cancelled_at, 'yyyy-mm-dd HH:MM') AS "Appointment cancelled at",
27+
TO_CHAR(msg.sent_at, 'yyyy-mm-dd HH:MM') AS "Notification sent at",
28+
TO_CHAR(nhs_sts.status_updated_at, 'yyyy-mm-dd HH:MM') AS "NHSApp message read at",
29+
TO_CHAR(sms_sts.status_updated_at, 'yyyy-mm-dd HH:MM') AS "SMS delivered at",
30+
TO_CHAR(let_sts.status_updated_at, 'yyyy-mm-dd HH:MM') AS "Letter sent at"
31+
FROM notifications_appointment appt
32+
JOIN notifications_clinic clin ON clin.id = appt.clinic_id
33+
LEFT JOIN notifications_message msg ON msg.appointment_id = appt.id
34+
LEFT OUTER JOIN notifications_channelstatus nhs_sts ON nhs_sts.message_id = msg.id
35+
AND nhs_sts.channel = 'nhsapp'
36+
AND nhs_sts.status = 'read'
37+
LEFT OUTER JOIN notifications_channelstatus sms_sts ON sms_sts.message_id = msg.id
38+
AND sms_sts.channel = 'sms'
39+
AND sms_sts.status = 'delivered'
40+
LEFT OUTER JOIN notifications_channelstatus let_sts ON let_sts.message_id = msg.id
41+
AND sms_sts.channel = 'letter'
42+
AND sms_sts.status = 'received'
43+
WHERE appt.created_at::date > CURRENT_DATE::date - INTERVAL %s
44+
AND clin.bso_code = %s
45+
ORDER BY appt.created_at DESC

manage_breast_screening/notifications/tests/factories.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ class Meta:
2626
clinic = SubFactory(ClinicFactory)
2727
nhs_number = Sequence(lambda n: int("999%07d" % n))
2828
batch_id = Sequence(lambda n: "AAA%06d" % n)
29-
starts_at = datetime.now(tz=models.ZONE_INFO) + timedelta(weeks=4, days=4)
29+
starts_at = datetime.now(tz=models.ZONE_INFO) + timedelta(weeks=4)
3030
status = "B"
3131
nbss_id = Sequence(lambda n: int("123%06d" % n))
3232
episode_type = "S"
33-
episode_started_at = datetime.now(tz=models.ZONE_INFO) - timedelta(weeks=5, days=5)
33+
episode_started_at = datetime.now(tz=models.ZONE_INFO) - timedelta(weeks=5)
3434
assessment = False
3535
number = "1"
3636

manage_breast_screening/notifications/tests/queries/test_reconciliation_query.py

Lines changed: 148 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,171 @@
11
from datetime import datetime, timedelta
22

33
import pytest
4+
import time_machine
45

5-
from manage_breast_screening.notifications.models import ZONE_INFO
6+
from manage_breast_screening.notifications.models import ZONE_INFO, Clinic
67
from manage_breast_screening.notifications.queries.helper import Helper
78
from manage_breast_screening.notifications.tests.factories import (
89
AppointmentFactory,
10+
ChannelStatusFactory,
911
ClinicFactory,
12+
MessageFactory,
1013
)
1114

1215

16+
def df(dt: datetime):
17+
return dt.strftime("%Y-%m-%d %H:%M")
18+
19+
20+
def create_appointment_set(
21+
nhs_number: str,
22+
date: datetime,
23+
clinic: Clinic,
24+
appointment_status: str,
25+
episode_type: str,
26+
message_status: str | None = None,
27+
channel_statuses: dict[str, str] | None = None,
28+
):
29+
appt = AppointmentFactory(
30+
clinic=clinic,
31+
status=appointment_status,
32+
episode_type=episode_type,
33+
nhs_number=nhs_number,
34+
starts_at=date,
35+
)
36+
if appointment_status == "C":
37+
appt.cancelled_at = datetime.now(tz=ZONE_INFO)
38+
appt.save()
39+
40+
if message_status:
41+
sent_at = datetime.now(tz=ZONE_INFO) - timedelta(days=3)
42+
message = MessageFactory(
43+
appointment=appt, status=message_status, sent_at=sent_at
44+
)
45+
for channel, status in channel_statuses.items():
46+
status_updated_at = sent_at
47+
if channel != "nhsapp":
48+
status_updated_at = sent_at + timedelta(days=3)
49+
50+
ChannelStatusFactory(
51+
message=message,
52+
channel=channel,
53+
status=status,
54+
status_updated_at=status_updated_at,
55+
)
56+
return appt
57+
58+
1359
@pytest.mark.django_db
1460
class TestReconciliationQuery:
61+
@time_machine.travel(datetime.now(tz=ZONE_INFO), tick=False)
1562
def test_appointments_for_today_by_episode_type(self):
16-
clinic1 = ClinicFactory(bso_code="MDB", code="BU001")
17-
clinic2 = ClinicFactory(bso_code="MDB", code="BU002")
18-
clinic3 = ClinicFactory(bso_code="MDB", code="BU003")
19-
20-
AppointmentFactory.create_batch(size=7, clinic=clinic1)
21-
AppointmentFactory.create_batch(size=8, clinic=clinic1, episode_type="R")
22-
AppointmentFactory.create_batch(size=2, clinic=clinic1, episode_type="G")
23-
AppointmentFactory.create_batch(
24-
size=2, clinic=clinic1, episode_type="G", status="C"
25-
)
26-
AppointmentFactory.create_batch(size=3, clinic=clinic1, episode_type="F")
27-
AppointmentFactory.create_batch(size=5, clinic=clinic1, episode_type="H")
28-
AppointmentFactory.create_batch(size=2, clinic=clinic2, episode_type="F")
29-
AppointmentFactory.create_batch(
30-
size=3, clinic=clinic2, episode_type="F", status="A"
31-
)
32-
AppointmentFactory.create_batch(size=9, clinic=clinic2, episode_type="H")
33-
AppointmentFactory.create_batch(size=5, clinic=clinic2, episode_type="N")
34-
AppointmentFactory.create_batch(size=2, clinic=clinic3)
35-
AppointmentFactory.create_batch(size=5, clinic=clinic3, episode_type="F")
36-
AppointmentFactory.create_batch(size=4, clinic=clinic3, episode_type="G")
37-
AppointmentFactory.create_batch(size=6, clinic=clinic3, episode_type="R")
63+
now = datetime.now(tz=ZONE_INFO)
64+
breakpoint()
3865

39-
tomorrow_appt = AppointmentFactory(clinic=clinic1)
40-
tomorrow_appt.created_at = datetime.now(tz=ZONE_INFO) + timedelta(days=1)
41-
tomorrow_appt.save()
66+
clinic1 = ClinicFactory(code="BU001", bso_code="BSO1", name="BSU 1")
67+
clinic2 = ClinicFactory(code="BU002", bso_code="BSO1", name="BSU 2")
4268

43-
results = Helper.fetchall(
44-
"reconciliation",
45-
(
46-
datetime.now(tz=ZONE_INFO).date(),
47-
"MDB",
48-
),
49-
)
69+
date1 = now - timedelta(days=4)
70+
date2 = now - timedelta(days=5)
71+
date3 = now - timedelta(days=6)
72+
73+
nhsapp_read = {"nhsapp": "read"}
74+
sms_delivered = {"nhsapp": "unnotified", "sms": "delivered"}
75+
letter_sent = {
76+
"nhsapp": "unnotified",
77+
"sms": "permanent_failure",
78+
"letter": "received",
79+
}
80+
failed = {
81+
"nhsapp": "unnotified",
82+
"sms": "permanent_failure",
83+
"letter": "validation_failed",
84+
}
85+
86+
test_data = [
87+
["9991112211", date1, clinic1, "B", "R"],
88+
["9991112212", date1, clinic2, "B", "S"],
89+
["9991112213", date2, clinic1, "C", "S"],
90+
["9991112214", date3, clinic2, "C", "G"],
91+
["9991112221", date1, clinic1, "B", "R", "failed", failed],
92+
["9991112222", date3, clinic1, "B", "S", "delivered", nhsapp_read],
93+
["9991112223", date2, clinic1, "B", "S", "delivered", sms_delivered],
94+
["9991112225", date3, clinic2, "B", "F", "delivered", nhsapp_read],
95+
["9991112226", date1, clinic2, "B", "F", "delivered", sms_delivered],
96+
["9991112227", date1, clinic2, "C", "S", "delivered", nhsapp_read],
97+
["9991112228", date3, clinic1, "B", "F", "failed", failed],
98+
["9991112229", date2, clinic1, "B", "R", "delivered", letter_sent],
99+
["9991112231", date3, clinic2, "B", "F", "delivered", nhsapp_read],
100+
]
50101

51-
assert len(results) == 14
102+
expectations = [
103+
[
104+
"9991112211",
105+
"BSU 1 (BU001)",
106+
"Routine recall",
107+
"Booked",
108+
"Pending",
109+
df(now),
110+
df(date1),
111+
None,
112+
None,
113+
None,
114+
None,
115+
None,
116+
],
117+
[
118+
"9991112212",
119+
"BSU 2 (BU002)",
120+
"Self referal",
121+
"Booked",
122+
"Pending",
123+
df(now),
124+
df(date1),
125+
None,
126+
None,
127+
None,
128+
None,
129+
None,
130+
],
131+
[
132+
"9991112213",
133+
"BSU 1 (BU001)",
134+
"Self referal",
135+
"Cancelled",
136+
"Pending",
137+
df(now),
138+
df(date2),
139+
df(now),
140+
None,
141+
None,
142+
None,
143+
None,
144+
],
145+
[
146+
"9991112214",
147+
"BSU 1 (BU001)",
148+
"Self referal",
149+
"Cancelled",
150+
"Pending",
151+
df(now),
152+
df(date3),
153+
df(now),
154+
None,
155+
None,
156+
None,
157+
None,
158+
],
159+
]
52160

53-
assert results[0] == ("BU001", "F", "B", 3)
54-
assert results[1] == ("BU001", "G", "B", 2)
55-
assert results[2] == ("BU001", "G", "C", 2)
56-
assert results[3] == ("BU001", "H", "B", 5)
57-
assert results[4] == ("BU001", "R", "B", 8)
58-
assert results[5] == ("BU001", "S", "B", 7)
161+
for d in test_data:
162+
create_appointment_set(*d)
59163

60-
assert results[6] == ("BU002", "F", "A", 3)
61-
assert results[7] == ("BU002", "F", "B", 2)
62-
assert results[8] == ("BU002", "H", "B", 9)
63-
assert results[9] == ("BU002", "N", "B", 5)
164+
results = Helper.fetchall("reconciliation", ["1 week", "BSO1"])
64165

65-
assert results[10] == ("BU003", "F", "B", 5)
66-
assert results[11] == ("BU003", "G", "B", 4)
67-
assert results[12] == ("BU003", "R", "B", 6)
68-
assert results[13] == ("BU003", "S", "B", 2)
166+
for expectation in expectations:
167+
for idx, res in enumerate(results):
168+
assert expectation == list(res)
69169

70170
def test_appointments_filtered_for_specified_bso(self):
71171
clinic1 = ClinicFactory(bso_code="MDB", code="BU001")

0 commit comments

Comments
 (0)