Skip to content

Commit

Permalink
PI-2703 Add level to existing registrations in-place (#4523)
Browse files Browse the repository at this point in the history
instead of replacing it with a new registration
  • Loading branch information
marcus-bcl authored Jan 2, 2025
1 parent eabb9e9 commit a434f55
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class DataLoader(
RegistrationGenerator.TYPES[RiskType.PUBLIC.code]!! to RiskLevel.M,
RegistrationGenerator.ALT_TYPE to null,
)
PersonGenerator.EXISTING_RISKS_WITHOUT_LEVEL.withEvent().withRisks(
RegistrationGenerator.TYPES[RiskType.CHILDREN.code]!! to null
)
PersonGenerator.FEATURE_FLAG.withEvent().withRiskOfSeriousHarm(V)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ object PersonGenerator {
val NO_EXISTING_RISKS = generate("A000009")
val EXISTING_RISKS = generate("A000010")
val FEATURE_FLAG = generate("A000011")
val EXISTING_RISKS_WITHOUT_LEVEL = generate("A000012")

fun generate(
crn: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"probNumber": "A000012",
"assessments": [
{
"assessmentPk": 12,
"riskChildrenCustody": "High",
"riskChildrenCommunity": null,
"riskPrisonersCustody": null,
"riskStaffCustody": null,
"riskStaffCommunity": null,
"riskKnownAdultCustody": null,
"riskKnownAdultCommunity": null,
"riskPublicCustody": null,
"riskPublicCommunity": null,
"assessmentType": "LAYER3",
"dateCompleted": "2023-12-07T12:22:44",
"assessorSignedDate": "2023-12-07T12:16:12",
"initiationDate": "2023-12-07T11:49:57",
"assessmentStatus": "COMPLETE",
"superStatus": "COMPLETE",
"laterWIPAssessmentExists": false,
"latestWIPDate": null,
"laterSignLockAssessmentExists": false,
"latestSignLockDate": null,
"laterPartCompUnsignedAssessmentExists": false,
"latestPartCompUnsignedDate": "2023-11-03T09:51:25",
"laterPartCompSignedAssessmentExists": false,
"latestPartCompSignedDate": null,
"laterCompleteAssessmentExists": false,
"latestCompleteDate": "2023-12-15T08:34:44",
"currentConcernsRiskOfSuicide": "Yes",
"reviewSpDate": null,
"initialSpDate": null,
"reviewNum": "01",
"currentConcernsBreachOfTrust": "Yes",
"currentConcernsDisruptive": "Yes",
"currentConcernsEscape": "Yes",
"currentConcernsVulnerablity": "Yes",
"currentConcernsHostel": "No",
"currentConcernsCustody": "Yes",
"currentConcernsRiskOfSelfHarm": "Yes",
"weightedScores": {
"accommodationWeightedScore": 8,
"eteWeightedScore": 7,
"relationshipsWeightedScore": 4,
"lifestyleWeightedScore": 5,
"drugWeightedScore": 4,
"alcoholWeightedScore": 0,
"thinkingWeightedScore": 5,
"attitudesWeightedScore": 5
},
"furtherInformation": {
"totWeightedScore": 108,
"pOAssessment": "270",
"pOAssessmentDesc": "Review",
"assessorName": "LevelTwo CentralSupport",
"ogrs1Year": 43,
"ogrs2Year": 61,
"reviewTerm": "N",
"cmsEventNumber": 1,
"courtCode": "LVRPCC",
"courtType": "CC",
"courtName": "Liverpool Crown Court"
},
"offender": {
"riskToOthers": "H",
"offenderPk": 7374816
},
"ogpOvp": {
"ogpNC": "N",
"ovpNC": "N",
"ogp1Year": 55,
"ogp2Year": 69,
"ovp1Year": 22,
"ovp2Year": 35,
"ogrs3RiskRecon": "M",
"ogpRisk": "H",
"ovpRisk": "M"
},
"basicSentencePlan": null,
"offences": [
{
"offenceCode": "008",
"offenceSubcode": "57",
"additionalOffence": "N"
}
],
"sentencePlan": {
"objectives": [
{
"objectiveCodeDesc": "Increased knowledge of physical/ psychological/ emotional self harm linked to drug use",
"objectiveSequence": 1,
"criminogenicNeeds": [
{
"criminogenicNeed": "IHD",
"criminogenicNeedDesc": "Risk to Public"
}
],
"actions": [
{
"actionDesc": "Drug counselling",
"actionComment": "Comments about the action"
}
]
},
{
"objectiveCodeDesc": "Improve employment related skills",
"objectiveSequence": 2,
"criminogenicNeeds": [
{
"criminogenicNeed": "I4",
"criminogenicNeedDesc": "Education Training and Employment"
}
],
"actions": [
{
"action": "VIII1",
"actionDesc": "Basic skills",
"actionComment": "Some comment about their basic skills"
}
]
}
]
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@
"bodyFileName": "A000011-multiple-risks.json"
}
},
{
"request": {
"method": "GET",
"urlPath": "/eor/oasys/ass/asssumm/A000012/ALLOW/12/COMPLETE"
},
"response": {
"headers": {
"Content-Type": "application/json"
},
"status": 200,
"bodyFileName": "A000012-no-existing-level.json"
}
},
{
"request": {
"method": "GET",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,9 @@ internal class IntegrationTest {

channelManager.getChannel(queueName).publishAndWait(message)

val domainEvents = domainEventRepository.findAll()
.map { objectMapper.readValue<HmppsDomainEvent>(it.messageBody) }
.filter { it.crn() == PersonGenerator.EXISTING_RISKS.crn }
val domainEvents = domainEventRepository.findAllForCrn(PersonGenerator.EXISTING_RISKS.crn)

// Unchanged value from OASys - adds a review
val riskToChildren =
registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.CHILDREN.code).single()
assertThat(riskToChildren.level?.code, equalTo(RiskLevel.H.code))
Expand All @@ -397,25 +396,30 @@ internal class IntegrationTest {
assertThat(riskToChildren.reviews[1].contact.notes?.trim(), equalTo(expectedReviewNotes))
assertThat(domainEvents.ofType(RiskType.CHILDREN), hasSize(0))

// No existing registration - add new
val riskToPrisoner =
registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.PRISONER.code).single()
assertThat(riskToPrisoner.level?.code, equalTo(RiskLevel.M.code))
assertThat(riskToPrisoner.reviews, hasSize(1))
assertThat(domainEvents.ofType(RiskType.PRISONER), hasSize(1))
// removes any in duplicate group
val altRiskToPrisoner =
registrationRepository.findByPersonIdAndTypeCode(person.id, RegistrationGenerator.ALT_TYPE.code)
assertThat(altRiskToPrisoner, empty())

// null from OASys - no change
val riskToStaff = registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.STAFF.code).single()
assertThat(riskToStaff.level?.code, equalTo(RiskLevel.V.code))
assertThat(riskToStaff.reviews, hasSize(1))
assertThat(domainEvents.ofType(RiskType.STAFF), hasSize(0))

// Low from OASys - remove existing
val riskToAdult = registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.KNOWN_ADULT.code)
assertThat(riskToAdult, hasSize(0))
assertThat(domainEvents.ofType(RiskType.KNOWN_ADULT), hasSize(1))
assertThat(entityManager.find(RegistrationReview::class.java, riskToAdultReviewId), nullValue())

// Changed level - remove existing and add new
val riskToPublic = registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.PUBLIC.code).single()
assertThat(riskToPublic.level?.code, equalTo(RiskLevel.V.code))
assertThat(riskToPublic.reviews, hasSize(1))
Expand All @@ -430,6 +434,22 @@ internal class IntegrationTest {
)
}

@Test
fun `level is added in-place to existing risk registrations with no level`() {
val person = personRepository.getByCrn(PersonGenerator.EXISTING_RISKS_WITHOUT_LEVEL.crn)
val message = notification<HmppsDomainEvent>("assessment-summary-produced").withCrn(person.crn)

channelManager.getChannel(queueName).publishAndWait(message)

val riskToChildren =
registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.CHILDREN.code).single()
assertThat(riskToChildren.level?.code, equalTo(RiskLevel.H.code))
assertThat(riskToChildren.reviews, hasSize(2))

val domainEvents = domainEventRepository.findAllForCrn(PersonGenerator.EXISTING_RISKS_WITHOUT_LEVEL.crn)
assertThat(domainEvents.ofType(RiskType.CHILDREN), hasSize(0))
}

@Test
fun `risks are not changed when feature flag is disabled`() {
whenever(featureFlags.enabled("assessment-summary-additional-risks")).thenReturn(false)
Expand Down Expand Up @@ -459,4 +479,8 @@ internal class IntegrationTest {

private fun List<HmppsDomainEvent>.ofType(type: RiskType) =
filter { it.additionalInformation["registerTypeCode"] == type.code }

private fun DomainEventRepository.findAllForCrn(crn: String) = findAll()
.map { objectMapper.readValue<HmppsDomainEvent>(it.messageBody) }
.filter { it.crn() == crn }
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Registration(

@ManyToOne
@JoinColumn(name = "register_level_id")
val level: ReferenceData? = null,
var level: ReferenceData? = null,

var nextReviewDate: LocalDate? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,21 @@ class RiskService(
// Deregister existing registrations if OASys identified the level as low risk
if (riskLevel == RiskLevel.L) return@flatMap registrations.map { person.removeRegistration(it) }

// Add the level to any existing registrations with no level
val level = referenceDataRepository.registerLevel(riskLevel.code)
registrations.filter { it.level == null }.forEach { it.level = level }

// Remove any existing registrations with a different level
val (matchingRegistrations, registrationsToRemove) = registrations
.partition { it.level != null && it.level.code == riskLevel.code }
val (matchingRegistrations, registrationsToRemove) = registrations.partition { it.level!!.code == riskLevel.code }
val events = registrationsToRemove.map { person.removeRegistration(it) }.toMutableList()

// Add registration with the identified level if it doesn't already exist
val type = registerTypeRepository.getByCode(riskType.code)
val level = referenceDataRepository.registerLevel(riskLevel.code)
val existingLevel = RiskLevel.maxByCode(registrationsToRemove.mapNotNull { it.level?.code })
val assessmentNote =
"The OASys assessment of ${summary.furtherInformation.pOAssessmentDesc} on ${summary.dateCompleted.toDeliusDate()} identified the ${type.description} ${
when {
existingLevel == null -> "to be"
existingLevel.ordinal < riskLevel.ordinal -> "to have increased to"
existingLevel.ordinal > riskLevel.ordinal -> "to have decreased to"
else -> "to have remained"
}
} ${level.description}."
val assessmentNote = "The OASys assessment of ${summary.furtherInformation.pOAssessmentDesc} on " +
"${summary.dateCompleted.toDeliusDate()} identified the ${type.description} " +
"${existingLevel.increasedOrDecreasedTo(riskLevel)} ${level.description}."

// Add registration with the identified level if it doesn't already exist
if (matchingRegistrations.isEmpty()) {
val roshSummary = ordsClient.getRoshSummary(summary.assessmentPk)?.assessments?.singleOrNull()
val notes = """
Expand Down Expand Up @@ -173,3 +169,10 @@ fun reviewNotes(type: RegisterType, nextReviewDate: LocalDate?) = listOfNotNull(
).joinToString(System.lineSeparator())

fun Registration.notes(): String = reviewNotes(type, nextReviewDate)

private fun RiskLevel?.increasedOrDecreasedTo(riskLevel: RiskLevel) = when {
this == null -> "to be"
this.ordinal < riskLevel.ordinal -> "to have increased to"
this.ordinal > riskLevel.ordinal -> "to have decreased to"
else -> "to have remained"
}

0 comments on commit a434f55

Please sign in to comment.