Skip to content

PI-3011: MPOP overview performance improvement #4982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 2, 2025
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
Expand Up @@ -9,17 +9,17 @@ import java.time.ZonedDateTime
data class Activity(

val id: Long,
val eventNumber: String?,
val eventNumber: String? = null,
val type: String,
val startDateTime: ZonedDateTime,
val endDateTime: ZonedDateTime?,
val rarToolKit: String?,
val rarToolKit: String? = null,
val appointmentNotes: List<NoteDetail>? = null,
val appointmentNote: NoteDetail? = null,
val isSensitive: Boolean?,
val hasOutcome: Boolean?,
val wasAbsent: Boolean?,
val officerName: Name?,
val officerName: Name? = null,
val isInitial: Boolean,
val isNationalStandard: Boolean,
var location: OfficeAddress? = null,
Expand All @@ -29,12 +29,12 @@ data class Activity(
val didTheyComply: Boolean?,
val absentWaitingEvidence: Boolean?,
val rearrangeOrCancelReason: String?,
val rescheduledBy: Name?,
val rescheduledBy: Name? = null,
val repeating: Boolean? = null,
val nonComplianceReason: String?,
val documents: List<Document>,
val isRarRelated: Boolean = false,
val rarCategory: String?,
val documents: List<Document> = emptyList(),
val isRarRelated: Boolean? = false,
val rarCategory: String? = null,
val acceptableAbsence: Boolean?,
val acceptableAbsenceReason: String?,
val isAppointment: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import jakarta.persistence.*
import org.hibernate.annotations.Immutable
import org.hibernate.annotations.SQLRestriction
import org.hibernate.type.NumericBooleanConverter
import org.springframework.data.jpa.repository.EntityGraph
import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDate
import java.time.ZonedDateTime
Expand Down Expand Up @@ -93,7 +94,7 @@ class NsiType(
}

interface NsiRepository : JpaRepository<Nsi, Long> {

@EntityGraph(attributePaths = ["type", "nsiStatus"])
fun findByPersonIdAndTypeCode(personId: Long, typeCode: String): List<Nsi>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import uk.gov.justice.digital.hmpps.datetime.EuropeLondon
import uk.gov.justice.digital.hmpps.exception.NotFoundException
import uk.gov.justice.digital.hmpps.integrations.delius.personalDetails.entity.ContactDocument
import uk.gov.justice.digital.hmpps.integrations.delius.referencedata.entity.ReferenceData
import uk.gov.justice.digital.hmpps.integrations.delius.sentence.entity.ContactTypeOutcome
import uk.gov.justice.digital.hmpps.integrations.delius.user.entity.LatestSentence
import uk.gov.justice.digital.hmpps.integrations.delius.user.entity.Staff
import uk.gov.justice.digital.hmpps.integrations.delius.user.entity.User
import java.io.Serializable
Expand All @@ -35,11 +33,11 @@ class Contact(
@Column(name = "offender_id")
val personId: Long,

@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "event_id")
val event: Event? = null,

@ManyToOne
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "contact_type_id")
val type: ContactType,

Expand All @@ -65,11 +63,11 @@ class Contact(
@Convert(converter = YesNoConverter::class)
val complied: Boolean? = null,

@ManyToOne
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "contact_outcome_type_id")
val outcome: ContactOutcome? = null,

@OneToMany(mappedBy = "contact")
@OneToMany(mappedBy = "contact", fetch = FetchType.LAZY)
val documents: List<ContactDocument> = emptyList(),

@ManyToOne
Expand Down Expand Up @@ -100,10 +98,10 @@ class Contact(

@Column(name = "last_updated_datetime")
val lastUpdated: ZonedDateTime,

@ManyToOne
@JoinColumn(name = "offender_id", insertable = false, updatable = false)
val latestSentence: LatestSentence? = null,
//
// @ManyToOne
// @JoinColumn(name = "offender_id", insertable = false, updatable = false)
// val latestSentence: LatestSentence? = null,

@ManyToOne
@JoinColumn(name = "last_updated_user_id")
Expand All @@ -123,12 +121,11 @@ class Contact(
fun endDateTime(): ZonedDateTime? =
if (endTime != null) ZonedDateTime.of(date, endTime.toLocalTime(), EuropeLondon) else null

fun canHaveOutcomeRecorded(): Boolean? {
val hasPossibleOutcome = type.possibleOutcomes.any { it.outcome.selectable }
return when (hasPossibleOutcome) {
true -> outcome != null
else -> null
fun hasARequiredOutcome(): Boolean? {
if (type.contactOutcomeFlag != true) {
return null
}
return outcome != null
}

fun isInitial(): Boolean = setOf(
Expand Down Expand Up @@ -175,12 +172,9 @@ class ContactType(
@Column
val description: String,

@OneToMany(mappedBy = "id.contactTypeId")
@OneToMany(mappedBy = "id.contactTypeId", fetch = FetchType.LAZY)
val categories: List<ContactCategory> = emptyList(),

@OneToMany(mappedBy = "id.contactTypeId")
val possibleOutcomes: List<ContactTypeOutcome> = emptyList(),

@Column(name = "sgc_flag", columnDefinition = "number")
@Convert(converter = NumericBooleanConverter::class)
val systemGenerated: Boolean = false,
Expand Down Expand Up @@ -280,6 +274,29 @@ interface ContactRepository : JpaRepository<Contact, Long> {
@Query(
"""
select c from Contact c
left join fetch c.lastUpdatedUser u
left join fetch u.staff st
left join fetch st.provider prov
left join fetch c.requirement rqmnt
left join fetch rqmnt.mainCategory rmc
left join fetch rmc.unitDetails ud
left join fetch c.event e
left join fetch c.type t
left join fetch c.location l
left join fetch l.ldu old
left join fetch old.borough brgh
left join fetch c.outcome o
left join fetch t.categories cats
left join fetch c.action a
left join fetch a.contactType ct
left join fetch e.disposal d
left join fetch d.lengthUnit lu
left join fetch d.terminationReason tr
left join fetch e.court crt
left join fetch d.type dt
left join fetch e.mainOffence mo
left join fetch mo.offence moo
left join fetch rqmnt.subCategory rsc
where c.personId = :personId
order by c.date desc, c.startTime desc
"""
Expand All @@ -297,6 +314,7 @@ interface ContactRepository : JpaRepository<Contact, Long> {
select c.*
from contact c
join r_contact_type ct on c.contact_type_id = ct.contact_type_id
left join r_contact_type_outcome cot on cot.contact_outcome_type_id = c.contact_outcome_type_id
where c.offender_id = :personId and ct.attendance_contact = 'Y'
and (to_char(c.contact_date, 'YYYY-MM-DD') > :dateNow
or (to_char(c.contact_date, 'YYYY-MM-DD') = :dateNow and to_char(c.contact_start_time, 'HH24:MI') > :timeNow))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.hibernate.annotations.FetchMode
import org.hibernate.annotations.Immutable
import org.hibernate.annotations.SQLRestriction
import org.hibernate.type.NumericBooleanConverter
import org.springframework.data.jpa.repository.EntityGraph
import org.springframework.data.jpa.repository.JpaRepository
import uk.gov.justice.digital.hmpps.integrations.delius.referencedata.entity.ReferenceData
import uk.gov.justice.digital.hmpps.integrations.delius.user.entity.User
Expand Down Expand Up @@ -49,5 +50,7 @@ class Disability(
)

interface DisabilityRepository : JpaRepository<Disability, Long> {

@EntityGraph(attributePaths = ["type", "lastUpdatedUser", "lastUpdatedUser.staff", "lastUpdatedUser.staff.provider"])
fun findByPersonId(personId: Long): List<Disability>
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ interface EventRepository : JpaRepository<Event, Long> {
"LEFT JOIN FETCH e.additionalOffences ao " +
"LEFT JOIN FETCH m.offence mo " +
"LEFT JOIN FETCH ao.offence aoo " +
"LEFT JOIN FETCH d.terminationReason tr " +
"LEFT JOIN FETCH d.lengthUnit lu " +
"WHERE e.personId = :personId " +
"ORDER BY e.dateCreated DESC"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.hibernate.type.YesNoConverter
import org.springframework.data.annotation.LastModifiedBy
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import org.springframework.data.jpa.repository.EntityGraph
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import uk.gov.justice.digital.hmpps.exception.NotFoundException
Expand Down Expand Up @@ -103,7 +104,7 @@ class Person(
@LastModifiedBy
var lastUpdatedUserId: Long = 0,

@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns(
JoinColumn(
name = "last_updated_user_id",
Expand All @@ -130,6 +131,7 @@ interface PersonSummaryEntity {

interface PersonRepository : JpaRepository<Person, Long> {

@EntityGraph(attributePaths = ["gender", "religion", "language", "sexualOrientation", "genderIdentity", "lastUpdatedUser"])
fun findByCrn(crn: String): Person?

@Query(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ interface PersonCircumstanceRepository : JpaRepository<PersonalCircumstance, Lon
@Query(
"""
select pc from PersonalCircumstance pc
left join fetch pc.subType sub
left join fetch pc.type type
where pc.personId = :personId
and (pc.endDate is null or pc.endDate > current_date )
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.hibernate.annotations.FetchMode
import org.hibernate.annotations.Immutable
import org.hibernate.annotations.SQLRestriction
import org.hibernate.type.NumericBooleanConverter
import org.springframework.data.jpa.repository.EntityGraph
import org.springframework.data.jpa.repository.JpaRepository
import uk.gov.justice.digital.hmpps.integrations.delius.referencedata.entity.ReferenceData
import uk.gov.justice.digital.hmpps.integrations.delius.user.entity.User
Expand Down Expand Up @@ -49,5 +50,6 @@ class Provision(
)

interface ProvisionRepository : JpaRepository<Provision, Long> {
@EntityGraph(attributePaths = ["type", "lastUpdatedUser", "lastUpdatedUser.staff", "lastUpdatedUser.staff.provider"])
fun findByPersonId(personId: Long): List<Provision>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.hibernate.annotations.FetchMode
import org.hibernate.annotations.Immutable
import org.hibernate.annotations.SQLRestriction
import org.hibernate.type.NumericBooleanConverter
import org.springframework.data.jpa.repository.EntityGraph
import org.springframework.data.jpa.repository.JpaRepository

@Immutable
Expand Down Expand Up @@ -36,6 +37,8 @@ class Registration(
)

interface RegistrationRepository : JpaRepository<Registration, Long> {

@EntityGraph(attributePaths = ["type"])
fun findByPersonId(personId: Long): List<Registration>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ interface RiskFlagRepository : JpaRepository<RiskFlag, Long> {
@Query(
"""
select r from RiskFlag r
left join fetch r.category c
left join fetch r.type t
left join fetch r.level l
where r.type.code = 'MAPP'
and r.personId = :offenderId
and r.softDeleted = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class User(
@Column(name = "user_id")
val id: Long,

@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "staff_id")
val staff: Staff? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ class ComplianceService(
fun sentenceActivity(eventNumber: String) = allActiveSentenceActivity.filter { it.eventNumber == eventNumber }

fun getRarCategoryFromSentence(eventNumber: String) =
allActiveSentenceActivity.firstOrNull { it.eventNumber == eventNumber && it.isRarRelated }?.rarCategory
allActiveSentenceActivity.firstOrNull { it.eventNumber == eventNumber && it.isRarRelated == true }?.rarCategory

fun rarActivity(eventNumber: String) =
allActiveSentenceActivity.filter { it.eventNumber == eventNumber && it.isRarRelated }
allActiveSentenceActivity.filter { it.eventNumber == eventNumber && it.isRarRelated == true }

fun Event.toSentenceCompliance() = mainOffence?.offence?.let { offence ->
SentenceCompliance(
Expand All @@ -66,7 +66,7 @@ class ComplianceService(
status = it.nsiStatus?.description
)
},
activity = toRarActivityCounts(
activity = toActivityCounts(
rarActivity(
eventNumber
)
Expand Down Expand Up @@ -104,7 +104,7 @@ class ComplianceService(
)
}

fun toRarActivityCounts(activities: List<Activity>) = ActivityCount(
fun toActivityCounts(activities: List<Activity>) = ActivityCount(
waitingForEvidenceCount = activities.count { it.isPastAppointment && it.absentWaitingEvidence == true },
absentCount = activities.count { it.isPastAppointment && it.wasAbsent == true },
attendedButDidNotComplyCount = activities.count { it.isPastAppointment && it.wasAbsent == false && it.didTheyComply == false },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@ class OverviewService(
val allContacts = contactRepository.findByPersonId(person.id)
val previousAppointments = contactRepository.getPreviousAppointments(person.id)
val previousAppointmentNoOutcome =
previousAppointments.filter { it.attended != false && it.outcome == null }.size
previousAppointments.filter { it.outcome == null && it.type.contactOutcomeFlag == true }.size
val absentWithoutEvidence = previousAppointments.filter { it.attended == false && it.outcome == null }.size
val schedule = Schedule(contactRepository.firstAppointment(person.id)?.toNextAppointment())
val events = eventRepository.findByPersonId(person.id)
val activeEvents = events.filter { !it.isInactiveEvent() }
val sentences = activeEvents.map { it.toSentence() }
val allBreaches = nsiRepository.getAllBreaches(person.id)
val previousOrders = events.filter { it.isInactiveEvent() }
val previousOrdersBreached = allBreaches.filter { it -> it.eventId in previousOrders.map { it.id } }.size
val compliance = toSentenceCompliance(previousAppointments.map { it.toActivity() }, allBreaches)
val previousOrdersBreached =
allBreaches.filter { breach -> breach.eventId in previousOrders.map { it.id } }.size
val compliance = toSentenceCompliance(previousAppointments.map { it.toActivityOverview() }, allBreaches)
val registrations = registrationRepository.findByPersonId(person.id)
val mappa = riskFlagRepository.findActiveMappaRegistrationByOffenderId(person.id, PageRequest.of(0, 1))
.firstOrNull()
Expand All @@ -60,7 +61,7 @@ class OverviewService(
schedule = schedule,
previousOrders = PreviousOrders(previousOrdersBreached, previousOrders.size),
sentences = sentences.mapNotNull { it },
activity = toRarActivityCounts(allContacts.map { it.toActivity() }),
activity = toActivityCounts(allContacts.map { it.toActivityOverview() }),
compliance = compliance,
registrations = registrations.map { it.type.description },
mappa = mappa?.toMappa()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,41 @@ fun OfficeLocation.toOfficeAddress() = OfficeAddress.from(
telephoneNumber = telephoneNumber
)

fun Contact.toActivityOverview() = Activity(
id = id,
type = type.description,
isNationalStandard = type.nationalStandardsContact,
isSensitive = sensitive,
didTheyComply = complied,
acceptableAbsence = outcome?.outcomeAttendance == false && outcome.outcomeCompliantAcceptable == true,
acceptableAbsenceReason = if (outcome?.outcomeAttendance == false && outcome.outcomeCompliantAcceptable == true)
outcome.description else null,
absentWaitingEvidence = attended == false && outcome == null,
startDateTime = startDateTime(),
endDateTime = endDateTime(),
hasOutcome = hasARequiredOutcome(),
isInitial = isInitial(),
lastUpdated = lastUpdated,
lastUpdatedBy = Name(forename = lastUpdatedUser.forename, surname = lastUpdatedUser.surname),
wasAbsent = outcome?.outcomeAttendance == false,
nonComplianceReason = if (outcome?.outcomeCompliantAcceptable == false) type.description else null,
countsTowardsRAR = rarActivity,
rescheduled = rescheduledPop(),
rescheduledStaff = rescheduledPop() || rescheduledStaff(),
rescheduledPop = rescheduledPop(),
rearrangeOrCancelReason = if (rescheduled()) outcome?.description else null,
isAppointment = type.attendanceContact,
action = action?.description,
isSystemContact = type.systemGenerated,
isEmailOrTextFromPop = isEmailOrTextFromPop(),
isEmailOrTextToPop = isEmailOrTextToPop(),
isPhoneCallFromPop = isPhoneCallFromPop(),
isPhoneCallToPop = isPhoneCallToPop(),
isCommunication = isCommunication(),
description = description,
outcome = outcome?.description,
)

fun Contact.toActivity(noteId: Int? = null) = Activity(
id = id,
type = type.description,
Expand All @@ -72,7 +107,7 @@ fun Contact.toActivity(noteId: Int? = null) = Activity(
documents = documents.map { it.toDocument() },
startDateTime = startDateTime(),
endDateTime = endDateTime(),
hasOutcome = canHaveOutcomeRecorded(),
hasOutcome = hasARequiredOutcome(),
isInitial = isInitial(),
lastUpdated = lastUpdated,
lastUpdatedBy = Name(forename = lastUpdatedUser.forename, surname = lastUpdatedUser.surname),
Expand Down
Loading