Skip to content

Commit

Permalink
feat: add default values for new medical settings
Browse files Browse the repository at this point in the history
* feature: updated setings

- Updated Settings variables
- Added new settings variables in Constant file
- Updated function getDate in ValidityCheck

* feature: updated settings

- Fixed broken tests due to major breaking changes in codebase
- Majorly refactored VaccineValidityCheck business rules
- Added first implementation of HCertGenerator for unit testing
- WIP

* feature: updated settings

- Removed commented WIP code

* feature: updated settings

- Fix VaccineCheck condition for JJ

* feature: updated settings

- Fix Booster condition

* feature: updated settings

- Fix Booster condition

* feature: update settings

- Fix precondition priority

* feature: updated settings

- Minor fixes in booster logic

* feat: new rules

change version number to 1.2.3

Co-authored-by: eapuzzo <[email protected]>
Co-authored-by: Johnny Bueti <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2022
1 parent 75be86d commit e5750f3
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 119 deletions.
51 changes: 46 additions & 5 deletions DGCAVerifier/BusinessRules/Internal/RecoveryValidityCheck.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,26 @@ struct RecoveryValidityCheck {
guard let recoveryValidFromDate = validFrom.toRecoveryDate else { return .notValid }
guard let recoveryValidUntilDate = validUntil.toRecoveryDate else { return .notValid }

guard let recoveryStartDays = getStartDays(from: hcert ) else { return .notGreenPass }
guard let recoveryEndDays = getEndDays(from: hcert) else { return .notGreenPass }
var start: Int?
var end: Int?

let scanMode: String = Store.get(key: .scanMode) ?? ""
switch scanMode {
case Constants.scanMode2G:
start = getStartDays(from: hcert)
end = getEndDays(from: hcert)
case Constants.scanMode3G:
start = getStartDays3G(from: hcert)
end = getEndDays3G(from: hcert)
case Constants.scanModeBooster:
start = getStartDays(from: hcert)
end = getEndDays(from: hcert)
default:
return .notValid
}

guard let recoveryStartDays = start else { return .notGreenPass }
guard let recoveryEndDays = end else { return .notGreenPass }

guard let validityStart = recoveryValidFromDate.add(recoveryStartDays, ofType: .day) else { return .notValid }
let validityEnd = recoveryValidUntilDate
Expand All @@ -48,19 +66,42 @@ struct RecoveryValidityCheck {

let recoveryStatus = Validator.validate(currentDate, from: validityStart, to: validityEnd, extendedTo: validityExtension)

let scanMode: String = Store.get(key: .scanMode) ?? ""
guard scanMode != Constants.scanModeBooster else { return recoveryStatus == .valid ? .verificationIsNeeded : recoveryStatus }

return recoveryStatus
}

private func getStartDays3G(from hcert: HCert) -> Int? {
let isITCode = hcert.countryCode == Constants.ItalyCountryCode
let startDaysConfig: String
if isSpecialRecovery(hcert: hcert){
startDaysConfig = Constants.recoverySpecialStartDays
}
else {
startDaysConfig = isITCode ? Constants.recoveryStartDays_IT : Constants.recoveryStartDays_NOT_IT
}
return getValue(for: startDaysConfig)?.intValue
}

private func getEndDays3G(from hcert: HCert) -> Int? {
let isITCode = hcert.countryCode == Constants.ItalyCountryCode
let startDaysConfig: String
if isSpecialRecovery(hcert: hcert){
startDaysConfig = Constants.recoverySpecialEndDays
}
else {
startDaysConfig = isITCode ? Constants.recoveryEndDays_IT : Constants.recoveryEndDays_NOT_IT
}
return getValue(for: startDaysConfig)?.intValue
}

private func getStartDays(from hcert: HCert) -> Int? {
let startDaysConfig = isSpecialRecovery(hcert: hcert) ? Constants.recoverySpecialStartDays : Constants.recoveryStartDays
let startDaysConfig = isSpecialRecovery(hcert: hcert) ? Constants.recoverySpecialStartDays : Constants.recoveryStartDays_IT
return getValue(for: startDaysConfig)?.intValue
}

private func getEndDays(from hcert: HCert) -> Int? {
let endDaysConfig = isSpecialRecovery(hcert: hcert) ? Constants.recoverySpecialEndDays : Constants.recoveryEndDays
let endDaysConfig = isSpecialRecovery(hcert: hcert) ? Constants.recoverySpecialEndDays : Constants.recoveryEndDays_IT
return getValue(for: endDaysConfig)?.intValue
}

Expand Down
300 changes: 217 additions & 83 deletions DGCAVerifier/BusinessRules/Internal/VaccineValidityCheck.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
/*
* license-start
*
*
* Copyright (C) 2021 Ministero della Salute and all other contributors
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
*/

//
// VaccineValidityCheck.swift
Expand All @@ -27,84 +27,218 @@ import Foundation
import SwiftDGC

struct VaccineValidityCheck {

typealias Validator = MedicalRulesValidator

private var allowedVaccinationInCountry: [String: [String]] {
[Constants.SputnikVacineCode: [Constants.sanMarinoCode]]
}

func isVaccineDateValid(_ hcert: HCert) -> Status {
guard let currentDoses = hcert.currentDosesNumber else { return .notValid }
guard let totalDoses = hcert.totalDosesNumber else { return .notValid }
guard currentDoses > 0 else { return .notValid }
guard totalDoses > 0 else { return .notValid }
let lastDose = currentDoses >= totalDoses

guard let product = hcert.medicalProduct else { return .notValid }
guard isValid(for: product) else { return .notValid }
guard let countryCode = hcert.countryCode else { return .notValid }
guard isAllowedVaccination(for: product, fromCountryWithCode: countryCode) else { return .notValid }

guard let start = getStartDays(for: product, lastDose) else { return .notGreenPass }
guard let end = getEndDays(for: product, lastDose) else { return .notGreenPass }

guard let dateString = hcert.vaccineDate else { return .notValid }
guard let date = dateString.toVaccineDate else { return .notValid }
guard let validityStart = date.add(start, ofType: .day) else { return .notValid }
guard let validityEnd = date.add(end, ofType: .day)?.startOfDay else { return .notValid }

guard let currentDate = Date.startOfDay else { return .notValid }

let isJJ = hcert.medicalProduct == Constants.JeJVacineCode
let isJJBooster = isJJ && isaJJBoosterDose(current: currentDoses, total: totalDoses)
let fromDate = isJJBooster ? date : validityStart

let result = Validator.validate(currentDate, from: fromDate, to: validityEnd)

guard result == .valid else { return result }

let scanMode: String = Store.get(key: .scanMode) ?? ""
if scanMode == Constants.scanModeBooster {
let isaBoosterDose = currentDoses > totalDoses ||
currentDoses >= Constants.boosterMinimumDosesNumber || isJJBooster

typealias Validator = MedicalRulesValidator

private var allowedVaccinationInCountry: [String: [String]] {
[Constants.SputnikVacineCode: [Constants.sanMarinoCode]]
}

struct CertificatePreconditions {
let currentDoses: Int
let totalDoses: Int
let medicalProduct: String
let vaccineDate: Date
let countryCode: String

var isIT: Bool {
return self.countryCode.uppercased() == Constants.ItalyCountryCode
}

var isJJ: Bool {
return self.medicalProduct == Constants.JeJVacineCode
}

var isJJBooster: Bool {
return self.isJJ && (self.currentDoses >= Constants.jjBoosterMinimumDosesNumber)
}

var isNonJJBooster: Bool {
return !self.isJJ && (self.currentDoses >= Constants.boosterMinimumDosesNumber)
}

var isCurrentDoseIncomplete: Bool {
return self.currentDoses < self.totalDoses
}

var isCurrentDoseComplete: Bool {
return self.currentDoses == self.totalDoses
}

/// Valid booster dose JJ or any other
var isCurrentDoseBooster: Bool {
return (self.currentDoses > self.totalDoses) || (isJJBooster || self.isNonJJBooster)
}
}

func checkPreconditions(_ hcert: HCert) -> CertificatePreconditions? {
guard let currentDoses = hcert.currentDosesNumber, currentDoses > 0 else { return nil }
guard let totalDoses = hcert.totalDosesNumber, totalDoses > 0 else { return nil }
guard let vaccineDate = hcert.vaccineDate?.toVaccineDate else { return nil }
guard let medicalProduct = hcert.medicalProduct else { return nil }
guard isValid(for: medicalProduct) else { return nil }
guard let countryCode = hcert.countryCode else { return nil }
guard isAllowedVaccination(for: medicalProduct, fromCountryWithCode: countryCode) else { return nil }

return CertificatePreconditions(currentDoses: currentDoses, totalDoses: totalDoses, medicalProduct: medicalProduct, vaccineDate: vaccineDate, countryCode: countryCode)
}

func checkCertificateDate(_ preconditions: CertificatePreconditions) -> Status {
let scanMode: String = Store.get(key: .scanMode) ?? ""

guard let start = getStartDays(scanMode: scanMode, preconditions: preconditions) else { return .notValid }
guard let end = getEndDays(scanMode: scanMode, preconditions: preconditions) else { return .notValid }

guard let validityStart = preconditions.vaccineDate.add(start, ofType: .day) else { return .notValid }
guard let validityEnd = preconditions.vaccineDate.add(end, ofType: .day)?.startOfDay else { return .notValid }

guard let currentDate = Date.startOfDay else { return .notValid }

// J&J booster is immediately valid
let fromDate = preconditions.isJJBooster ? preconditions.vaccineDate : validityStart

return Validator.validate(currentDate, from: fromDate, to: validityEnd)
}

func isScanModeBooster() -> Bool {
let scanMode: String = Store.get(key: .scanMode) ?? ""
return scanMode == Constants.scanModeBooster
}

func checkBooster(_ preconditions: CertificatePreconditions) -> Status {
if preconditions.isCurrentDoseBooster { return . valid }

return preconditions.isCurrentDoseComplete ? .verificationIsNeeded : .notValid
}

func isVaccineValid(_ hcert: HCert) -> Status {
guard let preconditions = checkPreconditions(hcert) else { return .notValid }
let result = checkCertificateDate(preconditions)

guard result == .valid else { return result }

guard !isScanModeBooster() else {
return checkBooster(preconditions)
}

return result
}

private func isaJJBoosterDose(current: Int, total: Int) -> Bool {
return current >= Constants.jjBoosterMinimumDosesNumber
}

private func isAllowedVaccination(for medicalProduct: String, fromCountryWithCode countryCode: String) -> Bool {
if let allowedCountries = allowedVaccinationInCountry[medicalProduct] {
return allowedCountries.contains(countryCode)
}
return true
}

private func isValid(for medicalProduct: String) -> Bool {
// Vaccine code not included in settings -> not a valid vaccine for Italy
let name = Constants.vaccineCompleteEndDays
return getValue(for: name, type: medicalProduct) != nil
}

private func getStartDays(scanMode: String, preconditions: CertificatePreconditions) -> Int? {
switch(scanMode) {
case Constants.scanMode3G:
if preconditions.isCurrentDoseBooster {
let settingName: String = preconditions.isIT ? Constants.vaccineBoosterStartDays_IT : Constants.vaccineBoosterStartDays_NOT_IT
return self.getValue(for: settingName)?.intValue
}

if preconditions.isCurrentDoseIncomplete {
return self.getValue(for: Constants.vaccineIncompleteStartDays, type: preconditions.medicalProduct)?.intValue
}

if isaBoosterDose { return . valid }
return lastDose ? .verificationIsNeeded : .notValid
}
if preconditions.isJJ {
let settingName = Constants.vaccineCompleteStartDays
return self.getValue(for: settingName, type: preconditions.medicalProduct)?.intValue
}
let settingName = preconditions.isIT ? Constants.vaccineCompleteStartDays_IT : Constants.vaccineCompleteStartDays_NOT_IT
return self.getValue(for: settingName)?.intValue

case Constants.scanMode2G:
if preconditions.isCurrentDoseBooster {
return self.getValue(for: Constants.vaccineBoosterStartDays_IT)?.intValue
}

if preconditions.isCurrentDoseIncomplete {
return self.getValue(for: Constants.vaccineIncompleteStartDays, type: preconditions.medicalProduct)?.intValue
}

if preconditions.isJJ {
let settingName = Constants.vaccineCompleteStartDays
return self.getValue(for: settingName, type: preconditions.medicalProduct)?.intValue
}
return self.getValue(for: Constants.vaccineCompleteStartDays_IT)?.intValue
case Constants.scanModeBooster:
if preconditions.isCurrentDoseBooster {
return self.getValue(for: Constants.vaccineBoosterStartDays_IT)?.intValue
}

if preconditions.isCurrentDoseIncomplete {
return self.getValue(for: Constants.vaccineIncompleteStartDays, type: preconditions.medicalProduct)?.intValue
}

if preconditions.isJJ {
let settingName = Constants.vaccineCompleteStartDays
return self.getValue(for: settingName, type: preconditions.medicalProduct)?.intValue
}
return self.getValue(for: Constants.vaccineCompleteStartDays_IT)?.intValue
default:
return nil
}
}

private func getEndDays(scanMode: String, preconditions: CertificatePreconditions) -> Int? {
switch(scanMode) {
case Constants.scanMode3G:
if preconditions.isCurrentDoseBooster {
let settingName: String = preconditions.isIT ? Constants.vaccineBoosterEndDays_IT : Constants.vaccineBoosterEndDays_NOT_IT
return self.getValue(for: settingName)?.intValue
}

if preconditions.isCurrentDoseIncomplete {
return self.getValue(for: Constants.vaccineIncompleteEndDays, type: preconditions.medicalProduct)?.intValue
}

let settingName = preconditions.isIT ? Constants.vaccineCompleteEndDays_IT : Constants.vaccineCompleteEndDays_NOT_IT
return self.getValue(for: settingName)?.intValue
case Constants.scanMode2G:
if preconditions.isCurrentDoseBooster {
return self.getValue(for: Constants.vaccineBoosterEndDays_IT)?.intValue
}

if preconditions.isCurrentDoseIncomplete {
return self.getValue(for: Constants.vaccineIncompleteEndDays, type: preconditions.medicalProduct)?.intValue
}

return self.getValue(for: Constants.vaccineCompleteEndDays_IT)?.intValue
case Constants.scanModeBooster:
if preconditions.isCurrentDoseBooster {
return self.getValue(for: Constants.vaccineBoosterEndDays_IT)?.intValue
}

if preconditions.isCurrentDoseIncomplete {
return self.getValue(for: Constants.vaccineIncompleteEndDays, type: preconditions.medicalProduct)?.intValue
}

return self.getValue(for: Constants.vaccineCompleteEndDays_IT)?.intValue
default:
return nil
}
}

private func getValue(for name: String, type: String) -> String? {
return LocalData.getSetting(from: name, type: type)
}

private func getValue(for name: String) -> String? {
return LocalData.getSetting(from: name)
}

return result
}

private func isaJJBoosterDose(current: Int, total: Int) -> Bool {
return current > total || (current == total && current >= Constants.jjBoosterMinimumDosesNumber)
}

private func isAllowedVaccination(for medicalProduct: String, fromCountryWithCode countryCode: String) -> Bool {
if let allowedCountries = allowedVaccinationInCountry[medicalProduct] {
return allowedCountries.contains(countryCode)
}
return true
}

private func isValid(for medicalProduct: String) -> Bool {
// Vaccine code not included in settings -> not a valid vaccine for Italy
let name = Constants.vaccineCompleteEndDays
return getValue(for: name, type: medicalProduct) != nil
}

private func getStartDays(for medicalProduct: String, _ isLastDose: Bool) -> Int? {
let name = isLastDose ? Constants.vaccineCompleteStartDays : Constants.vaccineIncompleteStartDays
return getValue(for: name, type: medicalProduct)?.intValue
}

private func getEndDays(for medicalProduct: String, _ isLastDose: Bool) -> Int? {
let name = isLastDose ? Constants.vaccineCompleteEndDays : Constants.vaccineIncompleteEndDays
return getValue(for: name, type: medicalProduct)?.intValue
}

private func getValue(for name: String, type: String) -> String? {
return LocalData.getSetting(from: name, type: type)
}

}

Loading

0 comments on commit e5750f3

Please sign in to comment.