-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add required information to DBP NA<->FE API for removal timeline support #3268
Changes from 13 commits
1c82d0f
6388873
24d3fc9
19ba1c8
2f45004
9e326ef
fa5f675
59988dd
0865a78
9aa5b41
1af1ebe
29c8bb8
c2d4d9f
06b1020
88fb01b
608bd9f
b389b6b
cea7624
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,6 +76,15 @@ struct DBPUIUserProfileAddress: Codable { | |
let zipCode: String? | ||
} | ||
|
||
extension DBPUIUserProfileAddress { | ||
init(addressCityState: AddressCityState) { | ||
self.init(street: addressCityState.fullAddress, | ||
city: addressCityState.city, | ||
state: addressCityState.state, | ||
zipCode: nil) | ||
} | ||
} | ||
|
||
/// Message Object representing a user profile containing one or more names and addresses | ||
/// also contains the user profile's birth year | ||
struct DBPUIUserProfile: Codable { | ||
|
@@ -105,11 +114,13 @@ struct DBPUIDataBroker: Codable, Hashable { | |
let name: String | ||
let url: String | ||
let date: Double? | ||
let parentURL: String? | ||
|
||
init(name: String, url: String, date: Double? = nil) { | ||
init(name: String, url: String, date: Double? = nil, parentURL: String?) { | ||
self.name = name | ||
self.url = url | ||
self.date = date | ||
self.parentURL = parentURL | ||
} | ||
|
||
func hash(into hasher: inout Hasher) { | ||
|
@@ -135,7 +146,79 @@ struct DBPUIDataBrokerProfileMatch: Codable { | |
let addresses: [DBPUIUserProfileAddress] | ||
let alternativeNames: [String] | ||
let relatives: [String] | ||
let date: Double? // Used in some methods to set the removedDate or found date | ||
let foundDate: Double | ||
let optOutSubmittedDate: Double? | ||
let estimatedRemovalDate: Double? | ||
let removedDate: Double? | ||
let hasMatchingRecordOnParentBroker: Bool | ||
} | ||
|
||
extension DBPUIDataBrokerProfileMatch { | ||
init(optOutJobData: OptOutJobData, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like how long this construct got, but didn't have any nice ideas to simplify it, so taking suggestions! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tried rewriting this a different way, but it was mostly just different, not necessarily better. However, leaving one suggestion. |
||
dataBrokerName: String, | ||
dataBrokerURL: String, | ||
dataBrokerParentURL: String?, | ||
parentBrokerOptOutJobData: [OptOutJobData]?) { | ||
let extractedProfile = optOutJobData.extractedProfile | ||
|
||
/* | ||
createdDate used to not exist in the DB, so in the migration we defaulted it to Unix Epoch zero (i.e. 1970) | ||
If that's the case, we should rely on the events instead | ||
We don't do that all the time since it's unnecssarily expensive trawling through events, and | ||
this is involved in some already heavy endpoints | ||
|
||
optOutSubmittedDate also used to not exist, but instead defaults to nil | ||
However, it could be nil simply because the opt out hasn't been submitted yet. So since we don't want to | ||
look through events unneccesarily, we instead only look for it if the createdDate is 1970 | ||
*/ | ||
var foundDate = optOutJobData.createdDate | ||
var optOutSubmittedDate = optOutJobData.submittedSuccessfullyDate | ||
if foundDate == Date(timeIntervalSince1970: 0) { | ||
let foundEvents = optOutJobData.historyEvents.filter { $0.isMatchesFoundEvent() } | ||
let firstFoundEvent = foundEvents.min(by: { $0.date < $1.date }) | ||
if let firstFoundEventDate = firstFoundEvent?.date { | ||
foundDate = firstFoundEventDate | ||
} else { | ||
assertionFailure("No matching MatchFound event for an extract profile found") | ||
} | ||
|
||
let optOutSubmittedEvents = optOutJobData.historyEvents.filter { $0.type == .optOutRequested } | ||
let firstOptOutEvent = optOutSubmittedEvents.min(by: { $0.date < $1.date }) | ||
optOutSubmittedDate = firstOptOutEvent?.date | ||
} | ||
let estimatedRemovalDate = Calendar.current.date(byAdding: .day, value: 14, to: optOutSubmittedDate ?? foundDate) | ||
|
||
// Check for any matching records on the parent broker | ||
var hasFoundParentMatch = false | ||
if let parentBrokerOptOutJobData = parentBrokerOptOutJobData { | ||
for parentOptOut in parentBrokerOptOutJobData { | ||
let parentProfile = parentOptOut.extractedProfile | ||
if extractedProfile.doesMatchExtractedProfile(parentProfile) { | ||
hasFoundParentMatch = true | ||
break | ||
} | ||
} | ||
} | ||
THISISDINOSAUR marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
self.init(dataBroker: DBPUIDataBroker(name: dataBrokerName, url: dataBrokerURL, parentURL: dataBrokerParentURL), | ||
name: extractedProfile.fullName ?? "No name", | ||
addresses: extractedProfile.addresses?.map {DBPUIUserProfileAddress(addressCityState: $0) } ?? [], | ||
alternativeNames: extractedProfile.alternativeNames ?? [String](), | ||
relatives: extractedProfile.relatives ?? [String](), | ||
foundDate: foundDate.timeIntervalSince1970, | ||
optOutSubmittedDate: optOutSubmittedDate?.timeIntervalSince1970, | ||
estimatedRemovalDate: estimatedRemovalDate?.timeIntervalSince1970, | ||
removedDate: extractedProfile.removedDate?.timeIntervalSince1970, | ||
hasMatchingRecordOnParentBroker: hasFoundParentMatch) | ||
} | ||
|
||
init(optOutJobData: OptOutJobData, dataBroker: DataBroker, parentBrokerOptOutJobData: [OptOutJobData]?) { | ||
self.init(optOutJobData: optOutJobData, | ||
dataBrokerName: dataBroker.name, | ||
dataBrokerURL: dataBroker.url, | ||
dataBrokerParentURL: dataBroker.parent, | ||
parentBrokerOptOutJobData: parentBrokerOptOutJobData) | ||
} | ||
} | ||
|
||
/// Protocol to represent a message that can be passed from the host to the UI | ||
|
@@ -156,6 +239,27 @@ struct DBPUIOptOutMatch: DBPUISendableMessage { | |
let alternativeNames: [String] | ||
let addresses: [DBPUIUserProfileAddress] | ||
let date: Double | ||
let foundDate: Double | ||
let optOutSubmittedDate: Double? | ||
let estimatedRemovalDate: Double? | ||
let removedDate: Double? | ||
} | ||
|
||
extension DBPUIOptOutMatch { | ||
init?(profileMatch: DBPUIDataBrokerProfileMatch, matches: Int) { | ||
guard let removedDate = profileMatch.removedDate else { return nil } | ||
let dataBroker = profileMatch.dataBroker | ||
self.init(dataBroker: dataBroker, | ||
matches: matches, | ||
name: profileMatch.name, | ||
alternativeNames: profileMatch.alternativeNames, | ||
addresses: profileMatch.addresses, | ||
date: removedDate, | ||
foundDate: profileMatch.foundDate, | ||
optOutSubmittedDate: profileMatch.optOutSubmittedDate, | ||
estimatedRemovalDate: nil, | ||
removedDate: removedDate) | ||
} | ||
} | ||
|
||
/// Data representing the initial scan progress | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,7 +56,7 @@ struct ExtractProfileSelectors: Codable, Sendable { | |
} | ||
} | ||
|
||
struct AddressCityState: Codable { | ||
struct AddressCityState: Codable, Hashable { | ||
let city: String | ||
let state: String | ||
|
||
|
@@ -166,10 +166,51 @@ struct ExtractedProfile: Codable, Sendable { | |
identifier: self.identifier | ||
) | ||
} | ||
|
||
/* | ||
Matching records are: | ||
1/ Completely identical records (same name, addresses, ages, etc) | ||
2/ Records that overlap completely (record A has all the data of record B, but might have | ||
extra information as well (e.g. an extra address, a middle name where record B doesn't) | ||
I.e. B is a subset of A, or vice versa | ||
However, we ignore some of the properties | ||
So, basically age == age, we ignore phone numbers and email, and then everything else one should be a subset of the other | ||
*/ | ||
func doesMatchExtractedProfile(_ extractedProfile: ExtractedProfile) -> Bool { | ||
if age != extractedProfile.age { | ||
return false | ||
} | ||
|
||
if name != extractedProfile.name { | ||
return false | ||
} | ||
|
||
if !(alternativeNames ?? []).isASubSetOrSuperSetOf(extractedProfile.alternativeNames ?? []) { | ||
return false | ||
} | ||
|
||
if !(addresses ?? []).isASubSetOrSuperSetOf(extractedProfile.addresses ?? []) { | ||
return false | ||
} | ||
|
||
if !(relatives ?? []).isASubSetOrSuperSetOf(extractedProfile.relatives ?? []) { | ||
return false | ||
} | ||
Comment on lines
+180
to
+198
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prob just a preference, but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about this and I think I prefer not using guards because to me guard suggests something went wrong or some prerequisite weren't satisfied. I think that is a biased view on my part though and probably not shared universally There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair, one subjective view -vs- another 😅! Fine with me to stick with what you have. |
||
|
||
return true | ||
} | ||
} | ||
|
||
extension ExtractedProfile: Equatable { | ||
static func == (lhs: ExtractedProfile, rhs: ExtractedProfile) -> Bool { | ||
lhs.name == rhs.name | ||
} | ||
} | ||
|
||
private extension Sequence where Element: Hashable { | ||
func isASubSetOrSuperSetOf<Settable>(_ sequence: Settable) -> Bool where Settable: Sequence, Element == Settable.Element { | ||
let setA = Set(self) | ||
let setB = Set(sequence) | ||
return setA.isSubset(of: setB) || setB.isSubset(of: setA) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏼