Skip to content

Commit

Permalink
Return characteristics in space search results
Browse files Browse the repository at this point in the history
This commit adds a new ‘characteristics’ list to space search results, providing premise characteristics. This uses an enumerated API type, which is preferred to the type used on the existing but unpopulated ‘premiseCharacteristics’ list.
  • Loading branch information
davidatkinsuk committed Dec 23, 2024
1 parent 7ebbd80 commit 1da0784
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ FROM
p.town AS town,
p.postcode AS postcode,
aa.id AS ap_area_id,
aa.name AS ap_area_name
aa.name AS ap_area_name,
ARRAY_AGG (characteristics.property_name) as characteristics
FROM approved_premises ap
INNER JOIN premises p ON ap.premises_id = p.id
INNER JOIN probation_regions pr ON p.probation_region_id = pr.id
Expand Down Expand Up @@ -115,10 +116,25 @@ class Cas1SpaceSearchRepository(
rs.getString("postcode"),
rs.getUUID("ap_area_id"),
rs.getString("ap_area_name"),
toStringList(rs.getArray("characteristics")),
)
}
}

private fun toStringList(array: java.sql.Array?): List<String> {
if (array == null) {
return emptyList()
}

val result = (array.array as Array<String>).toList()

return if (result.size == 1 && result[0] == null) {
emptyList()
} else {
result
}
}

private fun resolveCandidatePremisesQueryTemplate(
targetPostcodeDistrict: String,
apType: ApprovedPremisesType?,
Expand Down Expand Up @@ -194,4 +210,5 @@ data class CandidatePremises(
val postcode: String,
val apAreaId: UUID,
val apAreaName: String,
val characteristics: List<String>,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer.cas1

import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1PremiseCharacteristic
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1PremisesSearchResultSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceSearchParameters
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceSearchResults
Expand All @@ -11,6 +13,10 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceSearc

@Component
class Cas1SpaceSearchResultsTransformer {

private val log = LoggerFactory.getLogger(this::class.java)

@SuppressWarnings("SwallowedException")
fun transformDomainToApi(searchParameters: Cas1SpaceSearchParameters, results: List<CandidatePremises>) =
Cas1SpaceSearchResults(
resultsCount = results.size,
Expand All @@ -29,7 +35,15 @@ class Cas1SpaceSearchResultsTransformer {
id = candidatePremises.apAreaId,
name = candidatePremises.apAreaName,
),
premisesCharacteristics = listOf(),
premisesCharacteristics = emptyList(),
characteristics = candidatePremises.characteristics.mapNotNull {
try {
Cas1PremiseCharacteristic.valueOf(it)
} catch (e: IllegalArgumentException) {
log.warn("Couldn't find an enum entry for propertyName $it")
null
}
},
),
distanceInMiles = candidatePremises.distanceInMiles.toBigDecimal(),
spacesAvailable = listOf(),
Expand Down
36 changes: 36 additions & 0 deletions src/main/resources/static/cas1-schemas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,20 @@ components:
$ref: '_shared.yml#/components/schemas/NamedId'
premisesCharacteristics:
type: array
deprecated: true
description: "This is not populated. Instead, use 'characteristics'"
items:
$ref: '_shared.yml#/components/schemas/CharacteristicPair'
characteristics:
type: array
items:
$ref: '#/components/schemas/Cas1PremiseCharacteristic'
required:
- id
- apType
- name
- apArea
- characteristics
Cas1PremisesSummary:
type: object
properties:
Expand Down Expand Up @@ -118,6 +130,30 @@ components:
required:
- startInclusive
- endInclusive
Cas1PremiseCharacteristic:
type: string
description: All premise characteristics
enum:
- acceptsChildSexOffenders
- acceptsHateCrimeOffenders
- acceptsNonSexualChildOffenders
- acceptsSexOffenders
- additionalRestrictions
- hasBrailleSignage
- hasHearingLoop
- hasLift
- hasStepFreeAccessToCommunalAreas
- hasTactileFlooring
- hasWheelChairAccessibleBathrooms
- hasWideAccessToCommunalAreas
- hasWideStepFreeAccess
- isCatered
- isESAP
- isIAP
- isPIPE
- isRecoveryFocussed
- isSuitableForVulnerable
- isSemiSpecialistMentalHealth
Cas1SpaceCharacteristic:
type: string
description: All of the characteristics of both premises and rooms
Expand Down
38 changes: 37 additions & 1 deletion src/main/resources/static/codegen/built-cas1-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6102,8 +6102,20 @@ components:
$ref: '#/components/schemas/NamedId'
premisesCharacteristics:
type: array
deprecated: true
description: "This is not populated. Instead, use 'characteristics'"
items:
$ref: '#/components/schemas/CharacteristicPair'
characteristics:
type: array
items:
$ref: '#/components/schemas/Cas1PremiseCharacteristic'
required:
- id
- apType
- name
- apArea
- characteristics
Cas1PremisesSummary:
type: object
properties:
Expand Down Expand Up @@ -6168,6 +6180,30 @@ components:
required:
- startInclusive
- endInclusive
Cas1PremiseCharacteristic:
type: string
description: All premise characteristics
enum:
- acceptsChildSexOffenders
- acceptsHateCrimeOffenders
- acceptsNonSexualChildOffenders
- acceptsSexOffenders
- additionalRestrictions
- hasBrailleSignage
- hasHearingLoop
- hasLift
- hasStepFreeAccessToCommunalAreas
- hasTactileFlooring
- hasWheelChairAccessibleBathrooms
- hasWideAccessToCommunalAreas
- hasWideStepFreeAccess
- isCatered
- isESAP
- isIAP
- isPIPE
- isRecoveryFocussed
- isSuitableForVulnerable
- isSemiSpecialistMentalHealth
Cas1SpaceCharacteristic:
type: string
description: All of the characteristics of both premises and rooms
Expand Down Expand Up @@ -6277,7 +6313,7 @@ components:
properties:
apTypes:
deprecated: true
description: "Searching on multiple types is not supported, instead use specialistApType. if this is used, the first type wll be matched, and type 'normal' will be ignored"
description: "Searching on multiple types is not supported, instead use apType. If this is used, the first type wll be matched, and type 'normal' will be ignored"
type: array
items:
$ref: '#/components/schemas/ApType'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource
import org.springframework.beans.factory.annotation.Autowired
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ApType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1PremiseCharacteristic
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceCharacteristic
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceSearchParameters
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceSearchRequirements
Expand Down Expand Up @@ -62,6 +63,13 @@ class Cas1SpaceSearchTest : InitialiseDatabasePerClassTestBase() {
withLatitude((it * -0.01) - 0.08)
withLongitude((it * 0.01) + 51.49)
withSupportsSpaceBookings(true)
withCharacteristicsList(
listOf(
characteristicRepository.findCas1ByPropertyName("hasWideAccessToCommunalAreas")!!,
characteristicRepository.findCas1ByPropertyName("hasWideStepFreeAccess")!!,
characteristicRepository.findCas1ByPropertyName("hasLift")!!,
),
)
}

// premise that doesn't support space bookings
Expand Down Expand Up @@ -100,11 +108,17 @@ class Cas1SpaceSearchTest : InitialiseDatabasePerClassTestBase() {
assertThat(results.resultsCount).isEqualTo(5)
assertThat(results.searchCriteria).isEqualTo(searchParameters)

assertThatResultMatches(results.results[0], premises[0])
assertThatResultMatches(results.results[1], premises[1])
assertThatResultMatches(results.results[2], premises[2])
assertThatResultMatches(results.results[3], premises[3])
assertThatResultMatches(results.results[4], premises[4])
val expectedCharacteristics = listOf(
Cas1PremiseCharacteristic.hasWideAccessToCommunalAreas,
Cas1PremiseCharacteristic.hasWideStepFreeAccess,
Cas1PremiseCharacteristic.hasLift,
)

assertThatResultMatches(results.results[0], premises[0], expectedCharacteristics = expectedCharacteristics)
assertThatResultMatches(results.results[1], premises[1], expectedCharacteristics = expectedCharacteristics)
assertThatResultMatches(results.results[2], premises[2], expectedCharacteristics = expectedCharacteristics)
assertThatResultMatches(results.results[3], premises[3], expectedCharacteristics = expectedCharacteristics)
assertThatResultMatches(results.results[4], premises[4], expectedCharacteristics = expectedCharacteristics)
}
}

Expand Down Expand Up @@ -327,18 +341,24 @@ class Cas1SpaceSearchTest : InitialiseDatabasePerClassTestBase() {
actual: Cas1SpaceSearchResult,
expected: ApprovedPremisesEntity,
expectedApType: ApType = ApType.normal,
expectedCharacteristics: List<Cas1PremiseCharacteristic>? = null,
) {
assertThat(actual.spacesAvailable).isEmpty()
assertThat(actual.distanceInMiles).isGreaterThan(0f.toBigDecimal())
assertThat(actual.premises).isNotNull
assertThat(actual.premises!!.id).isEqualTo(expected.id)
assertThat(actual.premises!!.apType).isEqualTo(expectedApType)
assertThat(actual.premises!!.name).isEqualTo(expected.name)
assertThat(actual.premises!!.addressLine1).isEqualTo(expected.addressLine1)
assertThat(actual.premises!!.addressLine2).isEqualTo(expected.addressLine2)
assertThat(actual.premises!!.town).isEqualTo(expected.town)
assertThat(actual.premises!!.postcode).isEqualTo(expected.postcode)
assertThat(actual.premises!!.premisesCharacteristics).isEmpty()
val premises = actual.premises!!
assertThat(premises.id).isEqualTo(expected.id)
assertThat(premises.apType).isEqualTo(expectedApType)
assertThat(premises.name).isEqualTo(expected.name)
assertThat(premises.addressLine1).isEqualTo(expected.addressLine1)
assertThat(premises.addressLine2).isEqualTo(expected.addressLine2)
assertThat(premises.town).isEqualTo(expected.town)
assertThat(premises.postcode).isEqualTo(expected.postcode)
assertThat(premises.premisesCharacteristics).isEmpty()

if (expectedCharacteristics != null) {
assertThat(premises.characteristics).containsExactlyInAnyOrder(*expectedCharacteristics.toTypedArray())
}
}

@ParameterizedTest
Expand Down Expand Up @@ -516,21 +536,17 @@ class Cas1SpaceSearchTest : InitialiseDatabasePerClassTestBase() {
}
}

private fun ApType.asCharacteristicEntity() = when (this) {
private fun ApType.asCharacteristicProperty() = when (this) {
ApType.normal -> null
ApType.pipe -> "isPIPE"
ApType.esap -> "isESAP"
ApType.rfap -> "isRecoveryFocussed"
ApType.mhapStJosephs, ApType.mhapElliottHouse -> "isSemiSpecialistMentalHealth"
}?.let {
}

private fun ApType.asCharacteristicEntity() = this.asCharacteristicProperty()?.let {
characteristicRepository.findByPropertyName(it, ServiceName.approvedPremises.value)
}

private fun Cas1SpaceCharacteristic.asCharacteristicEntity() = characteristicRepository.findByPropertyName(this.value, ServiceName.approvedPremises.value)
?: characteristicEntityFactory.produceAndPersist {
withName(this@asCharacteristicEntity.value)
withPropertyName(this@asCharacteristicEntity.value)
withServiceScope(ServiceName.approvedPremises.value)
withModelScope("*")
}
private fun Cas1SpaceCharacteristic.asCharacteristicEntity() = characteristicRepository.findByPropertyName(this.value, ServiceName.approvedPremises.value)!!
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises2 = CandidatePremises(
Expand All @@ -105,6 +106,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises3 = CandidatePremises(
Expand All @@ -118,6 +120,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val spaceCharacteristics = Cas1SpaceCharacteristic.entries.map { characteristicWithRandomModelScopeCalled(it.value) }
Expand Down Expand Up @@ -199,6 +202,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises2 = CandidatePremises(
Expand All @@ -212,6 +216,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises3 = CandidatePremises(
Expand All @@ -225,6 +230,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val spaceCharacteristics = Cas1SpaceCharacteristic.entries.map { characteristicWithRandomModelScopeCalled(it.value) }
Expand Down Expand Up @@ -298,6 +304,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises2 = CandidatePremises(
Expand All @@ -311,6 +318,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises3 = CandidatePremises(
Expand All @@ -324,6 +332,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val spaceCharacteristics = Cas1SpaceCharacteristic.entries.map { characteristicWithRandomModelScopeCalled(it.value) }
Expand Down Expand Up @@ -396,6 +405,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises2 = CandidatePremises(
Expand All @@ -409,6 +419,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val candidatePremises3 = CandidatePremises(
Expand All @@ -422,6 +433,7 @@ class Cas1SpaceSearchServiceTest {
"TB1 2AB",
UUID.randomUUID(),
"Some AP Area",
characteristics = emptyList(),
)

val spaceCharacteristicEntities = spaceCharacteristics.map { characteristicWithRandomModelScopeCalled(it.value) }
Expand Down
Loading

0 comments on commit 1da0784

Please sign in to comment.