diff --git a/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index 19388d004..e6168e1e0 100644 --- a/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -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) } diff --git a/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt b/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt index 23d42d3e2..1de94ea2e 100644 --- a/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt +++ b/projects/assessment-summary-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt @@ -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, diff --git a/projects/assessment-summary-and-delius/src/dev/resources/simulations/__files/A000012-no-existing-level.json b/projects/assessment-summary-and-delius/src/dev/resources/simulations/__files/A000012-no-existing-level.json new file mode 100644 index 000000000..8f08b6cf3 --- /dev/null +++ b/projects/assessment-summary-and-delius/src/dev/resources/simulations/__files/A000012-no-existing-level.json @@ -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" + } + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/projects/assessment-summary-and-delius/src/dev/resources/simulations/mappings/oasys-mappings.json b/projects/assessment-summary-and-delius/src/dev/resources/simulations/mappings/oasys-mappings.json index a043b0867..c51fd2638 100644 --- a/projects/assessment-summary-and-delius/src/dev/resources/simulations/mappings/oasys-mappings.json +++ b/projects/assessment-summary-and-delius/src/dev/resources/simulations/mappings/oasys-mappings.json @@ -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", diff --git a/projects/assessment-summary-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/assessment-summary-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt index 2cb98bff0..1ee054c31 100644 --- a/projects/assessment-summary-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt +++ b/projects/assessment-summary-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt @@ -380,10 +380,9 @@ internal class IntegrationTest { channelManager.getChannel(queueName).publishAndWait(message) - val domainEvents = domainEventRepository.findAll() - .map { objectMapper.readValue(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)) @@ -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)) @@ -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("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) @@ -459,4 +479,8 @@ internal class IntegrationTest { private fun List.ofType(type: RiskType) = filter { it.additionalInformation["registerTypeCode"] == type.code } + + private fun DomainEventRepository.findAllForCrn(crn: String) = findAll() + .map { objectMapper.readValue(it.messageBody) } + .filter { it.crn() == crn } } diff --git a/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/entity/Registration.kt b/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/entity/Registration.kt index 79397ee40..d9a3eb57e 100644 --- a/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/entity/Registration.kt +++ b/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/entity/Registration.kt @@ -44,7 +44,7 @@ class Registration( @ManyToOne @JoinColumn(name = "register_level_id") - val level: ReferenceData? = null, + var level: ReferenceData? = null, var nextReviewDate: LocalDate? = null, diff --git a/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/RiskService.kt b/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/RiskService.kt index 3bb025493..025d29e86 100644 --- a/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/RiskService.kt +++ b/projects/assessment-summary-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/RiskService.kt @@ -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 = """ @@ -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" +}