Add Info Broker Removal service (FRE-5402)

New service for helping clients remove personal listings from data broker sites.

Service features:
- BrokerRegistry: Catalog of 20+ data brokers with removal methods
- RemoveBrokersService: Core service for scanning, creating removal requests,
  submitting removals, and verifying completions
- RemoveBrokersScheduler: Automated processing of pending removals and
  verification of completed removals
- BrokerAlertPipeline: Alert integration for listing discoveries and removal status

API endpoints (/removebrokers):
- GET /brokers - List available data brokers
- GET /status - Get removal request status and stats
- POST /scan - Scan for personal listings across brokers
- POST /request - Create a new removal request
- GET /request/:id - Get specific removal request details
- DELETE /request/:id - Cancel a removal request
- POST /process - Trigger processing of pending removals
- POST /verify/:id - Manually verify a removal completion

DB models: InfoBroker, RemovalRequest, BrokerListing
Types: BrokerStatus, RemovalStatus, RemovalMethod, and related interfaces
This commit is contained in:
Founding Engineer
2026-05-17 00:58:23 -04:00
committed by Michael Freno
parent 590e15e66e
commit bd881045f4
14 changed files with 1808 additions and 0 deletions

View File

@@ -25,6 +25,7 @@ model User {
// Relationships
accounts Account[]
sessions Session[]
deviceTokens DeviceToken[]
familyGroups FamilyGroupMember[]
familyGroupOwned FamilyGroup[] @relation("FamilyGroupOwner")
subscriptions Subscription[]
@@ -37,6 +38,8 @@ model User {
correlationGroups CorrelationGroup[]
securityReports SecurityReport[]
analysisJobs AnalysisJob[]
removalRequests RemovalRequest[]
brokerListings BrokerListing[]
// Audit
createdAt DateTime @default(now())
@@ -107,6 +110,42 @@ model Session {
@@index([userId])
}
model DeviceToken {
id String @id @default(uuid())
userId String
deviceType DeviceType
token String @unique
platform Platform
appName String?
appVersion String?
osVersion String?
model String?
isActive Boolean @default(true)
lastUsedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([deviceType])
@@index([platform])
@@index([isActive])
}
enum DeviceType {
mobile
web
desktop
}
enum Platform {
ios
android
web
}
// ============================================
// Family & Subscription Models
// ============================================
@@ -167,6 +206,9 @@ model Subscription {
watchlistItems WatchlistItem[]
exposures Exposure[]
alerts Alert[]
propertyWatchlistItems PropertyWatchlistItem[]
removalRequests RemovalRequest[]
brokerListings BrokerListing[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -506,6 +548,8 @@ enum AlertSource {
SPAMSHIELD
VOICEPRINT
CALL_ANALYSIS
HOME_TITLE
INFO_BROKER
}
enum AlertCategory {
@@ -517,6 +561,9 @@ enum AlertCategory {
CALL_ANOMALY
CALL_QUALITY
CALL_EVENT
HOME_TITLE
INFO_BROKER_LISTING
INFO_BROKER_REMOVAL
}
enum NormalizedAlertSeverity {
@@ -588,6 +635,7 @@ model CorrelationGroup {
enum ReportType {
MONTHLY_PLUS
ANNUAL_PREMIUM
WEEKLY_DIGEST
}
enum ReportStatus {
@@ -676,3 +724,192 @@ model BlogPost {
@@index([published, publishedAt])
@@index([tags])
}
// ============================================
// Home Title Service Models
// ============================================
enum PropertyChangeType {
tax_change
deed_change
ownership_transfer
lien_filing
metadata_change
}
enum PropertyChangeSeverity {
info
warning
critical
}
model PropertyWatchlistItem {
id String @id @default(uuid())
subscriptionId String
address String
parcelId String?
ownerName String?
streetAddress String
city String? @default("")
state String? @default("")
zipCode String? @default("")
latitude Float?
longitude Float?
isActive Boolean @default(true)
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
snapshots PropertySnapshot[]
changes PropertyChange[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([subscriptionId, parcelId])
@@index([subscriptionId])
@@index([parcelId])
@@index([address])
}
model PropertySnapshot {
id String @id @default(uuid())
propertyWatchlistItemId String
subscriptionId String
capturedAt DateTime
ownerName String
address Json // { streetNumber, streetName, streetType, unit, city, state, zip, latitude?, longitude? }
deedDate String?
taxId String?
propertyType String @default("residential")
taxAmount Float?
lienCount Int @default(0)
propertyWatchlistItem PropertyWatchlistItem @relation(fields: [propertyWatchlistItemId], references: [id], onDelete: Cascade)
changes PropertyChange[] @relation("SnapshotChanges")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([propertyWatchlistItemId])
@@index([subscriptionId])
@@index([capturedAt])
}
model PropertyChange {
id String @id @default(uuid())
propertyWatchlistItemId String
snapshotId String?
changeType PropertyChangeType
severity PropertyChangeSeverity @default(info)
details Json?
detectedAt DateTime @default(now())
propertyWatchlistItem PropertyWatchlistItem @relation(fields: [propertyWatchlistItemId], references: [id], onDelete: Cascade)
snapshot PropertySnapshot? @relation("SnapshotChanges", fields: [snapshotId], references: [id])
@@index([propertyWatchlistItemId])
@@index([snapshotId])
@@index([changeType])
}
// ============================================
// Info Broker Removal Models
// ============================================
enum BrokerCategory {
people_search
background_check
public_records
reverse_lookup
social_media
}
enum RemovalMethod {
automated
manual_form
email
phone
mail
none
}
enum RemovalStatus {
pending
submitted
in_progress
completed
failed
rejected
}
model InfoBroker {
id String @id @default(uuid())
name String
domain String @unique
category BrokerCategory
removalMethod RemovalMethod
removalUrl String?
requiresAccount Boolean @default(false)
requiresVerification Boolean @default(false)
estimatedDays Int @default(14)
isActive Boolean @default(true)
removalRequests RemovalRequest[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@index([category])
@@index([isActive])
@@index([removalMethod])
}
model RemovalRequest {
id String @id @default(uuid())
subscriptionId String
brokerId String
status RemovalStatus @default(pending)
personalInfo Json // { fullName, email?, phone?, address?, dob? }
method RemovalMethod
attempts Int @default(0)
nextRetryAt DateTime?
submittedAt DateTime?
completedAt DateTime?
error String?
notes String?
metadata Json? // Broker response data, tracking info
broker InfoBroker @relation(fields: [brokerId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@index([subscriptionId])
@@index([brokerId])
@@index([status])
@@index([submittedAt])
@@index([subscriptionId, status])
}
model BrokerListing {
id String @id @default(uuid())
subscriptionId String
brokerId String
removalRequestId String?
url String
dataFound Json // Fields found on the listing
screenshotUrl String?
isRemoved Boolean @default(false)
removedAt DateTime?
removalRequest RemovalRequest? @relation(fields: [removalRequestId], references: [id])
scannedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@index([subscriptionId])
@@index([brokerId])
@@index([removalRequestId])
@@index([isRemoved])
@@index([subscriptionId, isRemoved])
}