Alert System

Notifier Service

The bridge between the database and end-users. Stateless, idempotent, and designed for reliable WhatsApp delivery.

Notification Workflow

1
TriggerCalled by scraper or runs as daemon polling database
2
SelectionQuery listings where notified = FALSE
3
Recipient LoadRecipientManager fetches active recipients from DB
4
FilteringApply hard filters + user subscription filters
5
Quiet HoursCheck recipient_quiet_hours table before sending
6
DeliverySend via Twilio Content Template, update notified flag

Lead Quality Classification

Gold

Private seller, low ad count, complete data, fresh listing

  • active_ads ≤ 5
  • total_ads ≤ 10
  • Complete profile
Good

Likely private seller with reasonable ad activity

  • active_ads ≤ 10
  • total_ads ≤ 30
  • Standard profile
Average

Ambiguous or high-volume private seller

  • active_ads ≤ 20
  • total_ads ≤ 100
  • Mixed signals
Bad

Probable agent with high activity

  • active_ads > 20
  • OR total_ads > 100
  • Agent patterns
Invalid

Commercial ad, holiday rental, or false positive

  • Holiday rental detected
  • Commercial keywords
  • Price < R1000

Hard Filters

System-level filters applied to all notifications

  • Exclude Holiday Rentals
  • Exclude commercial ads
  • Price minimum threshold

User Subscriptions

Database-driven filters from recipient_table_subscriptions

  • table_name: 'gumtree_property_listings'
  • min_lead_quality: 'good'
  • Location filters via joins

Database-Driven Recipients

recipient_manager.py
# RecipientManager - Database-driven recipients
class RecipientManager:
    """Manages notification recipients from database"""
    
    def get_active_recipients(self) -> list[Recipient]:
        """Fetch active, non-paused recipients"""
        return self.db.query(whatsapp_recipients)
            .filter(is_active=True, is_paused=False)
            .all()
    
    def check_quiet_hours(self, recipient_id: int) -> bool:
        """Check if current time falls within quiet hours"""
        quiet_hours = self.db.query(recipient_quiet_hours)
            .filter(recipient_id=recipient_id)
            .first()
        return self._is_within_quiet_period(quiet_hours)
    
# Twilio Content Template
CONTENT_SID = "HX57cf1aad109f3f5bca1593f250896c51"

Recipients are managed via the whatsapp_recipients table with subscriptions and location filters stored in related tables. Twilio Content Templates ensure consistent message formatting.