Skip to content

Commit

Permalink
Merge pull request #333 from mild-blue/check-vaccinations
Browse files Browse the repository at this point in the history
Check vaccinations in ISIN
  • Loading branch information
tomaspavlin authored Jun 17, 2021
2 parents be10169 + ee11372 commit 896a55a
Show file tree
Hide file tree
Showing 20 changed files with 257 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,9 @@ object Patients : ManagedTable("patients") {
* Patient was validated in isin
*/
val isinId = varchar("isin_id", DatabaseTypeLength.PATIENT_ISIN_ID).nullable()

/**
* It was checked that patient has no COV-19 vaccines in ISIN yet
*/
val isinReady = bool("isin_ready").nullable()
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,16 @@ class PatientRepository(
indication: String? = null,
answers: Map<EntityId, Boolean>? = null,
registrationEmailSent: Instant? = null,
isinId: String? = null
isinId: String? = null,
isinReady: Boolean? = null
): Boolean = newSuspendedTransaction {
// check if any property is not null
val isPatientEntityUpdateNecessary =
firstName ?: lastName
?: district ?: zipCode
?: phoneNumber ?: personalNumber ?: insuranceNumber ?: email
?: insuranceCompany ?: registrationEmailSent ?: indication ?: isinId
?: insuranceCompany ?: registrationEmailSent ?: indication
?: isinId ?: isinReady
// if so, perform update query
val patientUpdated = if (isPatientEntityUpdateNecessary != null) {
Patients.update(
Expand All @@ -79,6 +81,7 @@ class PatientRepository(
updateIfNotNull(indication, Patients.indication)
updateIfNotNull(registrationEmailSent, Patients.registrationEmailSent)
updateIfNotNull(isinId, Patients.isinId)
updateIfNotNull(isinReady, Patients.isinReady)
}
}
)
Expand Down Expand Up @@ -213,7 +216,8 @@ class PatientRepository(
vaccinated = row.mapVaccinated(),
dataCorrect = row.mapDataCorrect(),
vaccinationSlotDtoOut = row.mapVaccinationSlot(),
isinId = row[Patients.isinId]
isinId = row[Patients.isinId],
isinReady = row[Patients.isinReady]
)

private fun ResultRow.mapVaccinationSlot() = getOrNull(VaccinationSlots.id)?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package blue.mild.covid.vaxx.dto.request

data class IsinJobDtoIn(
val validatePatients: Boolean = false,
val checkVaccinations: Boolean = false,
val exportPatientsInfo: Boolean = false,
val exportVaccinations: Boolean = false,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ data class IsinJobDtoOut(
val validatedPatientsSuccess: Int,
val validatedPatientsErrors: Int,

val checkedVaccinationsSuccess: Int,
val checkedVaccinationsErrors: Int,

val exportedPatientsInfoSuccess: Int,
val exportedPatientsInfoErrors: Int,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ data class PatientDtoOut(
val vaccinated: VaccinationDtoOut? = null,
val dataCorrect: DataCorrectnessConfirmationDtoOut? = null,
val vaccinationSlotDtoOut: VaccinationSlotDtoOut? = null,
val isinId: String? = null
val isinId: String? = null,
val isinReady: Boolean? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class IsinRetryService(
var validatedPatientsSuccess: Int = 0
var validatedPatientsErrors: Int = 0

var checkedVaccinationsSuccess: Int = 0
var checkedVaccinationsErrors: Int = 0

var exportedPatientsInfoSuccess: Int = 0
var exportedPatientsInfoErrors: Int = 0

Expand Down Expand Up @@ -68,7 +71,24 @@ class IsinRetryService(

if (updatedPatient.isinId.isNullOrBlank()) continue

// 2. If data are correct but not exported to ISIN -> try export to isin
// 2. If isinReady is not set -> try to check vaccinations in ISIN
if (
isinJobDto.checkVaccinations &&
updatedPatient.isinReady == null
) {
logger.debug("Retrying to check ISIN vaccinations of patient ${updatedPatient.id} in ISIN")
val wasChecked = retryPatientIsReadyForVaccination(updatedPatient)

logger.info("Vaccinations of patient ${patient.id} was checked in ISIN with result: ${wasChecked}")

if (wasChecked) {
checkedVaccinationsSuccess++
} else {
checkedVaccinationsErrors++
}
}

// 3. If data are correct but not exported to ISIN -> try export to isin
logger.info("Checking correctness exported to ISIN of patient ${updatedPatient.id}")
if (
isinJobDto.exportPatientsInfo &&
Expand All @@ -88,7 +108,7 @@ class IsinRetryService(
}
}

// 3. If vaccinated but vaccination is not exported to ISIN -> try export vaccination to ISIN
// 4. If vaccinated but vaccination is not exported to ISIN -> try export vaccination to ISIN
if (isinJobDto.exportVaccinations && updatedPatient.vaccinated != null && updatedPatient.vaccinated.exportedToIsinOn == null) {
logger.info("Retrying to create vaccination of patient ${updatedPatient.id} in ISIN")
val wasCreated = retryPatientVaccinationCreation(updatedPatient)
Expand All @@ -106,6 +126,8 @@ class IsinRetryService(
return IsinJobDtoOut(
validatedPatientsSuccess = validatedPatientsSuccess,
validatedPatientsErrors = validatedPatientsErrors,
checkedVaccinationsSuccess = checkedVaccinationsSuccess,
checkedVaccinationsErrors = checkedVaccinationsErrors,
exportedPatientsInfoSuccess = exportedPatientsInfoSuccess,
exportedPatientsInfoErrors = exportedPatientsInfoErrors,
exportedVaccinationsSuccess = exportedVaccinationsSuccess,
Expand Down Expand Up @@ -143,6 +165,21 @@ class IsinRetryService(
return newIsinPatientId
}

private suspend fun retryPatientIsReadyForVaccination(patient: PatientDtoOut): Boolean {
requireNotNull(patient.isinId) { "ISIN id of patient ${patient.id} cannot be null." }

val isinReady = isinService.tryPatientIsReadyForVaccination(patient.isinId)

return if (isinReady != null) {
logger.debug { "Updating ISIN ready of patient ${patient.id} to value ${isinReady}"}
patientService.updateIsinReady(patient.id, isinReady)
true
} else {
logger.debug { "NOT updating ISIN ready of patient ${patient.id}"}
false
}
}

private suspend fun retryPatientContactInfoExport(patient: PatientDtoOut): Boolean {
requireNotNull(patient.dataCorrect) { "Data correctness of patient ${patient.id} cannot be null." }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class IsinService(
private companion object : KLogging() {
const val URL_GET_PATIENT_BY_PARAMETERS = "pacienti/VyhledatDleJmenoPrijmeniRc";
const val URL_GET_FOREIGNER_BY_INSURANCE_NUMBER = "pacienti/VyhledatCizinceDleCislaPojistence";
const val URL_GET_VACCINATIONS_BY_PATIENT_ID = "vakcinace/NacistVakcinacePacienta";
const val URL_UPDATE_PATIENT_INFO = "pacienti/AktualizujKontaktniUdajePacienta";
const val URL_CREATE_OR_CHANGE_VACCINATION = "vakcinace/VytvorNeboZmenVakcinaci";
const val URL_CREATE_OR_CHANGE_DOSE = "vakcinace/VytvorNeboZmenDavku";
Expand Down Expand Up @@ -92,6 +93,49 @@ class IsinService(
return result
}

private suspend fun getPatientVaccinations(isinId: String): List<IsinVaccinationDto> {
val url = createIsinURL(URL_GET_VACCINATIONS_BY_PATIENT_ID, parameters = listOf(
isinId.trim()
))
logger.info { "Executing ISIN HTTP call ${URL_GET_VACCINATIONS_BY_PATIENT_ID}." }
return isinClient.get<HttpResponse>(url) {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
}.receive()
}

override suspend fun tryPatientIsReadyForVaccination(isinId: String): Boolean? =
runCatching {
val allVaccinations = getPatientVaccinations(isinId)
logger.info(
"Getting vaccination from ISIN for patient ${isinId } was successful. " +
"${allVaccinations.count()} vaccinations were found."
)

val problematicVaccinations = allVaccinations.filter {
vaccination -> vaccination.typOckovaniKod == "CO19" && vaccination.stav != "Zruseno"
}

if (problematicVaccinations.count() > 0) {
logger.info(
"${problematicVaccinations.count()} problematic vaccinations of patient ${isinId} were found in ISIN. " +
"Patient is not ready for vaccination: ${problematicVaccinations}"
)
false
} else {
logger.info(
"No problematic vaccination of patient ${isinId} were found in ISIN. " +
"Patient is ready for vaccination."
)
true
}
}.getOrElse {
logger.error(it) {
"Getting vaccinations from ISIN failed for patient with ISIN ID ${isinId}."
}
null
}

override suspend fun tryExportPatientContactInfo(patient: PatientDtoOut, notes: String?): Boolean {
return if (patient.isinId != null) {
runCatching {
Expand Down Expand Up @@ -142,7 +186,7 @@ class IsinService(
}.receive()
}

@Suppress("ReturnCount")
@Suppress("ReturnCount", "LongMethod")
override suspend fun tryCreateVaccinationAndDose(
vaccination: StoreVaccinationRequestDto,
patient: PatientDtoOut
Expand All @@ -155,6 +199,10 @@ class IsinService(
logger.info("No vaccine expiration provided for vaccination ${vaccination.vaccinationId}. Skipping vaccination creating in ISIN.")
return false
}
if (patient.isinReady != true) {
logger.info("Patient ${patient.id} is not ISIN ready. Skipping vaccination creating in ISIN.")
return false
}

val vaccinationExpirationInstant = vaccination.vaccineExpiration.atTime(LocalTime.MIDNIGHT).atZone(ZoneId.systemDefault()).toInstant();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ interface IsinServiceInterface {
insuranceNumber: String
): IsinGetPatientByParametersResultDto

suspend fun tryPatientIsReadyForVaccination(isinId: String): Boolean?

suspend fun tryExportPatientContactInfo(
patient: PatientDtoOut,
notes: String?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ class PatientService(
).whenFalse { throw entityNotFound<Patients>(Patients::id, patientId) }
}

/**
* Updates patient ISIN ready.
*/
suspend fun updateIsinReady(patientId: UUID, isinReady: Boolean) {
patientRepository.updatePatientChangeSet(
id = patientId,
isinReady = isinReady
).whenFalse { throw entityNotFound<Patients>(Patients::id, patientId) }
}

/**
* Saves patient to the database and return its id.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,35 @@ class DummyIsinService : IsinServiceInterface {
lastName: String,
personalNumber: String
): IsinGetPatientByParametersResultDto {
logger.warn { "NOT GETTING patient ${firstName}/${lastName}/${personalNumber} from ISIN. This should not be in the production." }
logger.warn { "NOT GETTING patient ${firstName}/${lastName}/${personalNumber} from ISIN. This should not be used in production." }
return IsinGetPatientByParametersResultDto("UsingDummyISIN", null, null)
}

override suspend fun getForeignerByInsuranceNumber(
insuranceNumber: String
): IsinGetPatientByParametersResultDto {
logger.warn { "NOT GETTING foreigner ${insuranceNumber} from ISIN. This should not be in the production." }
logger.warn { "NOT GETTING foreigner ${insuranceNumber} from ISIN. This should not be used in production." }
return IsinGetPatientByParametersResultDto("UsingDummyISIN", null, null)
}

override suspend fun tryPatientIsReadyForVaccination(isinId: String): Boolean? {
logger.warn { "NOT GETTING vaccinations of patient ${isinId} from ISIN. This should not be used in production." }
return null
}

override suspend fun tryExportPatientContactInfo(
patient: PatientDtoOut,
notes: String?
): Boolean {
logger.warn { "NOT EXPORTING patient ${patient.personalNumber} to ISIN. This should not be in the production." }
logger.warn { "NOT EXPORTING patient ${patient.personalNumber} to ISIN. This should not be used in production." }
return false
}

override suspend fun tryCreateVaccinationAndDose(
vaccination: StoreVaccinationRequestDto,
patient: PatientDtoOut
): Boolean {
logger.warn { "NOT EXPORTING vaccination ${vaccination.vaccinationId} to ISIN. This should not be in the production." }
logger.warn { "NOT EXPORTING vaccination ${vaccination.vaccinationId} to ISIN. This should not be used in production." }
return false
}
}
3 changes: 3 additions & 0 deletions backend/src/main/resources/db/migration/V11__isin_ready.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- add patients.isin_ready column
ALTER TABLE patients
ADD COLUMN isin_ready BOOL DEFAULT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
POST {{url}}/admin/login?captcha=1234
Content-Type: application/json

{
"credentials": {
"email": "{{email}}",
"password": "{{password}}"
},
"vaccineSerialNumber": "test",
"vaccineExpiration": "2024-06-04T00:00:00.000Z"
}
> {% client.global.set("auth_token", response.body["token"]); %}

###
POST {{url}}/admin/run-isin-job
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{
"exportPatientsInfo": true,
"exportVaccinations": true,
"validatePatients": true,
"patientsCount": 6000,
"patientsOffset": 0
}

###
POST {{url}}/admin/run-isin-job
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{
"exportPatientsInfo": true,
"exportVaccinations": true,
"validatePatients": true,
"patientsCount": 6000,
"patientsOffset": 5900
}

###
POST {{url}}/admin/run-isin-job
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{
"exportPatientsInfo": true,
"exportVaccinations": true,
"validatePatients": true,
"patientsCount": 6000,
"patientsOffset": 11800
}

###
POST {{url}}/admin/run-isin-job
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{
"exportPatientsInfo": true,
"exportVaccinations": true,
"validatePatients": true,
"patientsCount": 6000,
"patientsOffset": 17700
}

###
POST {{url}}/admin/run-isin-job
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{
"exportPatientsInfo": true,
"exportVaccinations": true,
"validatePatients": true,
"patientsCount": 6000,
"patientsOffset": 23600
}

###
POST {{url}}/admin/run-isin-job
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{
"exportPatientsInfo": true,
"exportVaccinations": true,
"validatePatients": true,
"patientsCount": 6000,
"patientsOffset": 200
}
Loading

0 comments on commit 896a55a

Please sign in to comment.