Skip to content

Commit

Permalink
Merge branch 'main' into add-docs-manage-supervision-and-delius
Browse files Browse the repository at this point in the history
  • Loading branch information
jsimonsmoj authored Jan 22, 2025
2 parents 1c50ed6 + 23ab06d commit 4a6d119
Show file tree
Hide file tree
Showing 31 changed files with 4,176 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,23 @@ interface PrisonStaffRepository : JpaRepository<PrisonStaff, Long> {

@Query(
"""
select officer_code from staff
where regexp_like(officer_code, ?1, 'i')
order by officer_code desc
fetch next 1 rows only
select min(next_officer_code)
from ( select officer_code,
substr(officer_code, 1, 3) ||
case when cast(substr(officer_code, 5, 3) as number) = 999 then chr(ascii(substr(officer_code, 4, 1)) + 1)
else substr(officer_code, 4, 1) end ||
to_char(case when cast(substr(officer_code, 5, 3) as number) = 999 then 000
else cast(substr(officer_code, 5, 3) as number) + 1 end, 'FM000') next_officer_code
from staff
where officer_code like :regex || '%'
and substr(officer_code, 5, 1) in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
and substr(officer_code, 6, 1) in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
and substr(officer_code, 7, 1) in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') )
where next_officer_code not in ( select officer_code from staff)
""",
nativeQuery = true
)
fun getLatestStaffReference(regex: String): String?
fun getNextStaffReference(regex: String): String?

fun findByCode(code: String): PrisonStaff?
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
package uk.gov.justice.digital.hmpps.service

import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.exceptions.StaffCodeExhaustedException
import uk.gov.justice.digital.hmpps.repository.PrisonStaffRepository

@Component
class OfficerCodeGenerator(private val staffRepository: PrisonStaffRepository) {
private val alphabet = ('A'..'Z').toList()

fun generateFor(probationAreaCode: String, index: Int = 0): String {
if (index == alphabet.size) {
throw StaffCodeExhaustedException(probationAreaCode)
}
val prefix = probationAreaCode.substring(0, 3) + alphabet[index]
val latest = staffRepository.getLatestStaffReference("^$prefix\\d{3}$")
val number = latest?.substring(latest.length - 3)?.toInt()?.plus(1) ?: 1
return if (number > 999) {
generateFor(probationAreaCode, index + 1)
} else {
val suffix = number.toString().padStart(3, '0')
prefix + suffix
}
fun generateFor(probationAreaCode: String): String {
val prefix = probationAreaCode.substring(0, 3)
return staffRepository.getNextStaffReference(prefix) ?: "${prefix}A000"
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package uk.gov.justice.digital.hmpps.service

import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.whenever
import uk.gov.justice.digital.hmpps.exceptions.StaffCodeExhaustedException
import uk.gov.justice.digital.hmpps.repository.PrisonStaffRepository

@ExtendWith(MockitoExtension::class)
Expand All @@ -26,45 +23,15 @@ class OfficerCodeGeneratorTest {
private val probationAreaCode: String = ProbationAreaGenerator.DEFAULT.code

@Test
fun `if all possible options exhausted exception thrown`() {
whenever(staffRepository.getLatestStaffReference(anyString()))
fun `db function is called to get next staff reference`() {
val number = "981"
whenever(staffRepository.getNextStaffReference(anyString()))
.thenAnswer {
val regex = it.arguments[0] as String
val prefix = regex.substring(1, regex.length - 6)
prefix + "999"
val prefix = it.arguments[0] as String
prefix + number
}

val ex = assertThrows<StaffCodeExhaustedException> {
officerCodeGenerator.generateFor(probationAreaCode)
}

assertThat(ex.message, containsString(probationAreaCode))
}

@Test
fun `if no result returned for a given probation area A001 is used`() {
whenever(staffRepository.getLatestStaffReference(anyString())).thenReturn(null)

val code = officerCodeGenerator.generateFor(probationAreaCode)

assertThat(code, equalTo(probationAreaCode + "A001"))
}

@Test
fun `roll over to next letter is successful`() {
whenever(staffRepository.getLatestStaffReference(anyString()))
.thenAnswer {
val regex = it.arguments[0] as String
val prefix = regex.substring(1, regex.length - 6)
if (prefix == "${probationAreaCode}A") {
prefix + "999"
} else {
"${probationAreaCode}B001"
}
}

val code = officerCodeGenerator.generateFor(probationAreaCode)

assertThat(code, equalTo(probationAreaCode + "B002"))
val res = officerCodeGenerator.generateFor(probationAreaCode)
assertThat(res, equalTo(probationAreaCode + number))
}
}
64 changes: 62 additions & 2 deletions projects/assessment-summary-and-delius/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,63 @@
# assessment-summary-and-delius
# Assessment Summary and Delius

// TODO Describe the service
This service listens to domain events when a new assessment has been produced in the OASys system. The assessment is conducted for a person who has been sentenced to assess their needs and risks. Delius will record a summary of the assessment.

When any new risks are identified or a change to an existing risk level, a new registration is generated. Existing registrations which are no longer required or considered to be a low risk are recorded as deregistrations.

The risks are categorised as:

* Risk of serious harm (RoSH)
* Other risks

**Note:** The service cannot infer all types of risk from the assessment summary. Some risk registrations are manually added and maintained in Delius.

# Business need

The users typically log into Delius on a regular basis so the assessment summaries and registrations provide a quick overview of the person and risk level. This reduces the need of users having to log into a second system (OASys).


# Data dependencies

Assessment summary relies on the OASys ORDS API for retrieving the assessment summary and RoSH summary. The summary data is compared against the risk levels configured within the Delius database, and the result is used to updated the registration data. The service relies on Delius data for up to date information on a person's latest assessment and active registrations.

## Context Map - Assessment Summary Data

![](./tech-docs/source/img/assessment-summary-context-map.svg)

# Workflows

## Assessment summary produced workflow
The assessment summary is recorded in Delius. The risks are evaluated and stored as registrations.

| Business Event | Message Event Type / Filter |
| --------------------- | ---------------------------- |
| Assessment submitted | assessment.summary.produced |

### Record assessment
The previous assessment summary is deleted and replaced with the new assessment summary. The full assessment can be retrieved from OASys.

![](./tech-docs/source/img/assessment-summary-assessment-submitted.svg)

### Record risks
The registrations for risk of serious harm (RoSH) and other risks will be checked. Any risks which are no longer required or low risks will result in a deregistration being created. Registrations will be added or updated for active risks.

**Note:** The service cannot infer all types of risk. Some risk registrations are manually added and maintained in Delius.

#### Risk of serious harm

![](./tech-docs/source/img/assessment-summary-record-rosh.svg)


#### Other risks

![](./tech-docs/source/img/assessment-summary-record-other-risks.svg)

# Interfaces

## Message formats

The service responds to HMPPS Domain Event messages via the [Assessment Summary and Delius Queue](https://github.com/ministryofjustice/cloud-platform-environments/blob/main/namespaces/live.cloud-platform.service.justice.gov.uk/hmpps-probation-integration-services-prod/resources/assessment-summary-and-delius-queue.tf). The events are raised by OASys to communicate the creation of an assessment of a person.

Example [messages](./src/dev/resources/messages/) are in the development source tree.

Incoming messages are filtered on `eventType` by the [SQS queue policy](https://github.com/ministryofjustice/cloud-platform-environments/blob/main/namespaces/live.cloud-platform.service.justice.gov.uk/hmpps-probation-integration-services-prod/resources/assessment-summary-and-delius-queue.tf)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Assessment Summary Workflow: Assessment Submitted

direction: right

style { ...@../../../../script/style.style }
vars { ...@../../../../script/style.vars }
** { ...@../../../../script/style.all-style }

# What triggers the workflow?
assessment-submitted: Assessment Summary {
style.font-size: 24

explanation: |md
## Assessment Summary Produced
- When an assessment summary is\
produced the previous assessment\
summary is overwritten
|

assessment_summary_produced: Assessment summary produced
}

domain_event: "assessment-summary\n.assessment.summary.produced" {
style.font-size: 12
shape: queue
}

# What happens in Delius?
delius: Delius {
style.font-size: 24
grid-columns: 2

d_delete_prev: Delete previous assessment summary
d_save_assessment: Store new assessment summary
d_contact: Create Contact
d_record_risks: Record risks

d_delete_prev -> d_save_assessment -> d_contact -> d_record_risks

explanation: |md
## Assessment summary
- Delius is used to log and track the summary\
of the latest assessment of a person
- The risk levels of a person must be\
reviewed when an assessment is submitted
- If a person has accredited programme\
requirements, the IAPS is updated in the\
person record for syncing to the\
interventions manager
- A mapping exists in the Delius database\
to the full assessment summary in OASys
|

}

assessment-submitted -> domain_event -> delius
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

style { ...@../../../../script/style.style }
vars { ...@../../../../script/style.vars }
** { ...@../../../../script/style.all-style }

context-oasys: OASys Context {
entities: Entities {
grid-columns: 3
pop: Person on Probation
assessment: Assessment Summary
risk: Risk Flags
concern: Concern Flags
weighted-scores: Weighted scores
sentence-plan: Sentence Plan
offence: Offence
objective: Objective
need: Need
action: Action
rosh-summary: Rosh Summary
}
}

context-delius: Delius Context {

interface: Assessment summary\n and Delius {
type: Anti-Corruption\nLayer
}

entities: Entities {
grid-columns: 2
assessment: OASys Assessment
event: Event
contact: Contact
registration: Registration
person: Person on Probation
sentence-plan: Sentence Plan
}
}

context-oasys--context-delius.interface: Upstream\n\n\nDownstream
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Assessment Summary Workflow: Record risks

direction: right

style { ...@../../../../script/style.style }
vars { ...@../../../../script/style.vars }
** { ...@../../../../script/style.all-style }

# Record Other Risks
Record-other-risks: Record Other Risks {
style.font-size: 24
grid-columns: 2

d_create_dereg: Deregister low-level risks
d_update_reg: Add/Update registrations
d_create_contact: Create contacts
d_add_review: Add review

d_create_dereg -> d_update_reg -> d_create_contact -> d_add_review

explanation: |md
## Record other risks
- Deregister low-level risks
- Create a contact for each\
new deregistration
- Update registrations where risk\
levels have changed
- Add registrations for new risks
- Create a contact for each\
new registration
- Deregister registrations in\
the duplicate group
- Add a registration review
|

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Assessment Summary Workflow: Record risks

direction: right

style { ...@../../../../script/style.style }
vars { ...@../../../../script/style.vars }
** { ...@../../../../script/style.all-style }

# Record Risk of Serious Harm
record-rosh: Record Risk of Serious Harm (RoSH) {
style.font-size: 24
grid-columns: 2

d_create_dereg: Create deregistrations
d_create_reg: Create or update\n registrations
d_create_contacts: Create contacts
d_risk_colour: Set risk colour

d_create_dereg -> d_create_reg -> d_create_contacts -> d_risk_colour

explanation: |md
## Record risk of serious harm
- Create a deregistration for each registration\
where a RoSH no longer exists
- Create a contact for each\
new deregistration
- Create a new registration, if one\
does not exist, for each RoSH
- Create a contact for each\
new registration
- Set the highest risk colour\
in the person record
|

}
Loading

0 comments on commit 4a6d119

Please sign in to comment.