Skip to content

Commit

Permalink
Merge pull request #321 from mild-blue/validate-foreigners
Browse files Browse the repository at this point in the history
Validate foreigners
  • Loading branch information
tomaskourim authored Jun 16, 2021
2 parents e55bffc + 4e6866f commit ca4500e
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import blue.mild.covid.vaxx.dto.response.Ok
import blue.mild.covid.vaxx.dto.response.PatientDtoOut
import blue.mild.covid.vaxx.dto.response.PatientRegistrationResponseDtoOut
import blue.mild.covid.vaxx.error.IsinValidationException
import blue.mild.covid.vaxx.error.NoPersonalAndInsuranceNumberException
import blue.mild.covid.vaxx.extensions.asContextAware
import blue.mild.covid.vaxx.extensions.closestDI
import blue.mild.covid.vaxx.extensions.createLogger
Expand Down Expand Up @@ -64,29 +65,34 @@ fun NormalOpenAPIRoute.patientRoutes() {
captchaService.verify(recaptchaToken, request.determineRealIp())
logger.info { "Captcha token verified." }

val patientIsinId: String? = if (patientRegistration.personalNumber != null) {
logger.info { "Validating patient in the ISIN." }
// TODO maybe validate received input before actually using ISIN
val patientValidationResult = patientValidation.validatePatient(
patientRegistration.firstName,
patientRegistration.lastName,
patientRegistration.personalNumber
)
logger.info { "Validation completed." }
logger.info { "Validating data." }
if (patientRegistration.personalNumber == null && patientRegistration.insuranceNumber == null) {
throw NoPersonalAndInsuranceNumberException()
}

logger.info { "Validating patient in the ISIN." }
val patientValidationResult = patientValidation.validatePatient(
patientRegistration.firstName,
patientRegistration.lastName,
patientRegistration.personalNumber,
patientRegistration.insuranceNumber
)
logger.info { "Validation completed." }

when (patientValidationResult.status) {
PatientValidationResult.PATIENT_FOUND ->
patientValidationResult.patientId
PatientValidationResult.PATIENT_NOT_FOUND ->
val patientIsinId: String? = when (patientValidationResult.status) {
PatientValidationResult.PATIENT_FOUND ->
patientValidationResult.patientId
PatientValidationResult.PATIENT_NOT_FOUND ->
if (patientRegistration.personalNumber != null) {
throw IsinValidationException(patientValidationResult)
PatientValidationResult.WAS_NOT_VERIFIED -> {
logger.warn { "Patient was not validated in isin due to some problem. Skipping ISIN validation." }
} else {
logger.warn { "Patient was not found in ISIN. Not raising error because the patient was a foreigner. Skipping ISIN validation." }
null
}
PatientValidationResult.WAS_NOT_VERIFIED -> {
logger.warn { "Patient was not validated in isin due to some problem. Skipping ISIN validation." }
null
}
} else {
logger.info { "Personal number not set. Skipping ISIN validation" }
null
}

logger.info { "Saving patient to the database." }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ class IsinRetryService(
for (patient in patients) {
logger.info("Checking ISIN id of patient ${patient.id}")

// 1. If patient has personal number but ISIN id is not set -> try ISIN validation
// 1. If patient ISIN id is not set -> try ISIN validation
val updatedPatient = if (!patient.isinId.isNullOrBlank()) {
logger.info("ISIN id of patient ${patient.id} is already set, skipping ISIN validation.")
patient
} else if (isinJobDto.validatePatients && !patient.personalNumber.isNullOrBlank() ) {
logger.info("Patient ${patient.id} has personal number but no ISIN id. Validating in ISIN.")
} else if (isinJobDto.validatePatients) {
logger.info("Patient ${patient.id} has no ISIN id. Validating in ISIN.")

val newIsinPatientId = retryPatientValidation(patient)

Expand Down Expand Up @@ -117,7 +117,8 @@ class IsinRetryService(
val patientValidationResult = patientValidation.validatePatient(
firstName = patient.firstName,
lastName = patient.lastName,
personalNumber = patient.personalNumber ?: throw AssertionError { "Personal number cannot be null."},
personalNumber = patient.personalNumber,
insuranceNumber = patient.insuranceNumber
)

logger.info {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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_UPDATE_PATIENT_INFO = "pacienti/AktualizujKontaktniUdajePacienta";
const val URL_CREATE_OR_CHANGE_VACCINATION = "vakcinace/VytvorNeboZmenVakcinaci";
const val URL_CREATE_OR_CHANGE_DOSE = "vakcinace/VytvorNeboZmenDavku";
Expand All @@ -56,14 +57,39 @@ class IsinService(
personalNumber.normalizePersonalNumber()
))
logger.info { "Executing ISIN HTTP call ${URL_GET_PATIENT_BY_PARAMETERS}." }
val response = isinClient.get<HttpResponse>(url)
val json = response.receive<JsonNode>()
val json = isinClient.get<JsonNode>(url)

return IsinGetPatientByParametersResultDto(
val result = IsinGetPatientByParametersResultDto(
result = json.get("vysledek").textValue(),
resultMessage = json.get("vysledekZprava")?.textValue(),
patientId = json.get("pacient")?.get("id")?.textValue()
)
logger.info {
"Data from ISIN for patient ${firstName} ${lastName}, personalNumber=${personalNumber}: " +
"result=${result.result}, resultMessage=${result.resultMessage}, patientId=${result.patientId}."
}
return result
}

override suspend fun getForeignerByInsuranceNumber(
insuranceNumber: String
): IsinGetPatientByParametersResultDto {
val url = createIsinURL(URL_GET_FOREIGNER_BY_INSURANCE_NUMBER, parameters = listOf(
insuranceNumber.trim()
))
logger.info { "Executing ISIN HTTP call ${URL_GET_FOREIGNER_BY_INSURANCE_NUMBER}." }
val json = isinClient.get<JsonNode>(url)

val result = IsinGetPatientByParametersResultDto(
result = json.get("vysledek").textValue(),
resultMessage = json.get("vysledekZprava")?.textValue(),
patientId = json.get("pacient")?.get("id")?.textValue()
)
logger.info {
"Data from ISIN for foreigner insuranceNumber=${insuranceNumber}: " +
"result=${result.result}, resultMessage=${result.resultMessage}, patientId=${result.patientId}."
}
return result
}

override suspend fun tryExportPatientContactInfo(patient: PatientDtoOut, notes: String?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ interface IsinServiceInterface {
personalNumber: String
): IsinGetPatientByParametersResultDto

suspend fun getForeignerByInsuranceNumber(
insuranceNumber: String
): IsinGetPatientByParametersResultDto

suspend fun tryExportPatientContactInfo(
patient: PatientDtoOut,
notes: String?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package blue.mild.covid.vaxx.service

import blue.mild.covid.vaxx.dto.internal.IsinGetPatientByParametersResultDto
import blue.mild.covid.vaxx.dto.internal.IsinValidationResultDto
import blue.mild.covid.vaxx.dto.internal.PatientValidationResult
import mu.KLogging
Expand All @@ -23,14 +24,43 @@ class IsinValidationService(
override suspend fun validatePatient(
firstName: String,
lastName: String,
personalNumber: String
personalNumber: String?,
insuranceNumber: String?
): IsinValidationResultDto {
val result = runCatching {
isinService.getPatientByParameters(
firstName = firstName,
lastName = lastName,
personalNumber = personalNumber
)
return runCatching {
if (personalNumber != null) {
val result = isinService.getPatientByParameters(
firstName = firstName,
lastName = lastName,
personalNumber = personalNumber
)
convertResult(result)
} else if (insuranceNumber != null){
val byInsuranceNumberResult = isinService.getForeignerByInsuranceNumber(
insuranceNumber = insuranceNumber
)
val validationResult = convertResult(byInsuranceNumberResult)
if (validationResult.status == PatientValidationResult.PATIENT_FOUND) {
validationResult
} else {
logger.info {
"Foreigner was not found in ISIN by insurance number ${insuranceNumber}. Trying to find the " +
"patient by first name, last name and personal number "
}
val byPersonalNumberResult = isinService.getPatientByParameters(
firstName = firstName,
lastName = lastName,
personalNumber = insuranceNumber
)
convertResult(byPersonalNumberResult)
}
} else {
logger.error {
"Both personal and insurance numbers are not set for patient ${firstName} ${lastName}. " +
"This should not happen. Skipping ISIN validation."
}
IsinValidationResultDto(status = PatientValidationResult.WAS_NOT_VERIFIED)
}
}.onSuccess {
logger.info { "Data retrieval from ISIN - success." }
}.onFailure {
Expand All @@ -39,16 +69,15 @@ class IsinValidationService(
val wrappingException =
Exception("An exception ${it.javaClass.canonicalName} was thrown! - ${it.message}\n${it.stackTraceToString()}")
logger.error(wrappingException) {
"Getting data from ISIN server failed for patient ${firstName}/${lastName}/${personalNumber}"
"Getting data from ISIN server failed for patient ${firstName} ${lastName}, " +
"personalNumber=${personalNumber}, insuranceNumber=${insuranceNumber}"
}
}.getOrNull() ?: return IsinValidationResultDto(status = PatientValidationResult.WAS_NOT_VERIFIED)
}.getOrNull() ?: IsinValidationResultDto(status = PatientValidationResult.WAS_NOT_VERIFIED)

logger.info {
"Data from ISIN for patient ${firstName}/${lastName}/${personalNumber}: " +
"result=${result.result}, resultMessage=${result.resultMessage}, patientId=${result.patientId}."
}
}

return when (result.result) {
private fun convertResult(result: IsinGetPatientByParametersResultDto): IsinValidationResultDto =
when (result.result) {
VyhledaniPacientaResult.PacientNalezen.name,
VyhledaniPacientaResult.NalezenoVicePacientu.name ->
IsinValidationResultDto(
Expand All @@ -65,5 +94,4 @@ class IsinValidationService(
status = PatientValidationResult.WAS_NOT_VERIFIED
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ fun interface PatientValidationService {
/**
* Validates patient against medical system service.
*/
suspend fun validatePatient(firstName: String, lastName: String, personalNumber: String): PatientValidationResultDto
suspend fun validatePatient(
firstName: String,
lastName: String,
personalNumber: String?,
insuranceNumber: String?
): PatientValidationResultDto
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class ValidationService(private val questionService: QuestionService) {
}
}

fun requireValidPersonalOrInsuranceNumber(personalNumber: String?, insuranceNumber: String?) {
private fun requireValidPersonalOrInsuranceNumber(personalNumber: String?, insuranceNumber: String?) {
if (personalNumber != null && personalNumber.isNotBlank())
requireValidPersonalNumber(personalNumber)
else if (insuranceNumber != null && insuranceNumber.isNotBlank()){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ class DummyIsinService : IsinServiceInterface {
personalNumber: String
): IsinGetPatientByParametersResultDto {
logger.warn { "NOT GETTING patient ${firstName}/${lastName}/${personalNumber} from ISIN. This should not be in the production." }
return IsinGetPatientByParametersResultDto("PouzitiDummyISIN", null, null)
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." }
return IsinGetPatientByParametersResultDto("UsingDummyISIN", null, null)
}

override suspend fun tryExportPatientContactInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class DummyPatientValidationService : PatientValidationService {
override suspend fun validatePatient(
firstName: String,
lastName: String,
personalNumber: String
personalNumber: String?,
insuranceNumber: String?
): PatientValidationResultDto {
logger.warn { "NOT VERIFYING patient ${personalNumber}. This should not be in the production." }
return IsinValidationResultDto(PatientValidationResult.WAS_NOT_VERIFIED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
"password": "bluemild"
},
"test": {
"url": "https://covid-vaxx.test.mild.blue/api"
"url": "https://covid-vaxx.test.mild.blue/api",
"email": "mail",
"password": "pass"
},
"production": {
"url": "https://ockovani.praha7.cz/api"
"url": "https://ockovani.praha7.cz/api",
"email": "mail",
"password": "pass"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ POST {{url}}/patient?captcha=1234
Content-Type: application/json

{
"firstName": "Tomáš",
"lastName": "Kos",
"insuranceNumber": "1234567890",
"firstName": "Adéla",
"lastName": "Kostičková",
"personalNumber": "9151010385",
"email": "kubant.jan@example.com",
"phoneNumber": {
"countryCode": "CZ",
Expand Down Expand Up @@ -129,7 +129,6 @@ Authorization: Bearer {{auth_token}}
"patientId":"{{patient_id}}"
}


###

// register user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class PatientRoutesTest : ServerTestBase() {
bind<PatientValidationService>() with singleton {
val service = mockk<PatientValidationService>()
coEvery {
service.validatePatient(any(), any(), any())
service.validatePatient(any(), any(), any(), any())
} returns IsinValidationResultDto(PatientValidationResult.PATIENT_FOUND, "10")

service
Expand Down Expand Up @@ -315,7 +315,8 @@ class PatientRoutesTest : ServerTestBase() {
validationService.validatePatient(
validRegistration.firstName,
validRegistration.lastName,
validRegistration.personalNumber ?: ""
validRegistration.personalNumber,
validRegistration.insuranceNumber
)
} returns IsinValidationResultDto(PatientValidationResult.PATIENT_NOT_FOUND)

Expand Down

0 comments on commit ca4500e

Please sign in to comment.