ULDA Lead-360 — Enterprise Filtering System Design

Scalable multi-criteria search & filter architecture for millions of records

Data Architecture — InternetSP / ULDA

2026-07-16

1. Design Goals & Architecture

A production filter system over 1M+ Lead-360 records must be fast (sub-second), composable (AND/OR across groups), and intuitive. The core architectural rule:

Filter on the materialized, indexed base (mv_lead_360); display the assembled 75-field record only for the page returned.

The 75-column v_lead_master_flat is perfect for reading one lead but too expensive to filter across millions (its LATERAL joins compute per row). So filtering runs against the materialized, indexed mv_lead_360 (identity/geo/source/score — the high-selectivity fields), with EXISTS sub-filters against the indexed event tables for journey/order criteria. Results (the page’s IDs) are then hydrated into the full flat record for display.

flowchart LR
  UI[Filter panel<br/>groups · AND/OR · presets] --> API[POST /leads/search<br/>filter JSON]
  API --> B[Query builder → WHERE on mv_lead_360<br/>+ EXISTS on interaction/order]
  B --> PG[(indexed mv_lead_360)]
  PG --> IDS[page of master_lead_ids<br/>keyset pagination]
  IDS --> HYD[hydrate → v_lead_master_flat]
  HYD --> UI

2. UI / UX Layout

Pattern: collapsible left filter rail + results grid + active-filter chips bar.

Why this layout: it is the proven CRM/CDP pattern (Salesforce list views, HubSpot, Segment) — discoverable, supports many simultaneous filters without clutter, and keeps the active state transparent.


3. Filter Type per Field

Group Field Filter type Operators
Lead Info Unique Lead ID search box (exact) =
First / Last Name search box (trigram) contains / starts-with
Email search box (trigram) contains / =
Direct Number search box (normalized) contains / =
Company search box (trigram) contains
Street search box contains
City search box + autocomplete contains / in
State multi-select dropdown in
ZIP search box / range = / prefix
Date Added date-range picker between / before / after
Source Source ID search box =
Source Name multi-select (Apollo/Instantly/…) in
Source Type multi-select (database/broker/API/CSV) in
Address & Serviceability Fiber Availability checkbox group per carrier (AT&T/Frontier/Kinetic/Spectrum) is-true
Address type dropdown (SFU/MDU/business) in
Max speed range slider (Mbps)
Enrichment Last Enrichment Date date-range picker between
Last Enrichment Type multi-select in
Last Enrichment Source multi-select in
Journey 1st–4th Channel Interaction multi-select (SMS/Email/Voice/Web/Door) in
First / Last Interaction Date date-range picker between
Last Interaction Channel multi-select in
Campaign Name search box + multi-select in / contains
Agent Activity Agents Interacted With multi-select (agent list) any-of
Last Agent Interaction dropdown (agent) =
Sales & Orders Product Offered multi-select in
Order Details / Opportunity dropdown (status) in
Revenue (MRR) range slider between
Commission range slider between
Signals (recommended) Lead Score range slider (0–100) between
DNC / Litigator / Contactable toggle switches is

Rules of thumb: low-cardinality → multi-select/dropdown; high-cardinality text → trigram search box; numeric/temporal → range slider / date picker; boolean → toggle/checkbox.


4. PostgreSQL Indexes (implemented)

Filtering runs on mv_lead_360 (1,074,518 rows, materialized). Indexes now in place / added:

Index Type Serves
ux_mv360_master btree PK / keyset pagination
ix_mv360_name/email/phone_trgm GIN trigram fuzzy text search
ix_mv360_email, _phone, _lower_email, _phone10 btree exact contact lookup
ix_mv360_state btree State multi-select
ix_mv360_zip btree ZIP filter
ix_mv360_company_trgm, _city_trgm GIN trigram Company/City search
ix_mv360_completeness btree quality range
ix_int_lead_time, ix_int_channel btree journey EXISTS sub-filters
ix_serv_carrier_avail btree fiber carrier filter
source_crosswalk(source_system, source_id) unique btree source filters

All built CONCURRENTLY (no lock, zero downtime). Composite indexes on the hottest combos (e.g. (state, completeness)) can be added as query patterns emerge.


5. Laravel Backend Strategy

Pattern: a declarative filter pipeline (Spatie Query Builder or a custom FilterCriteria chain).

// sketch
$q = DB::connection('ulda_ro')->table('ulda_serve.mv_lead_360 as m');
foreach ($groups as $g) {
  $method = $g->logic === 'or' ? 'orWhere' : 'where';
  $q->$method(fn($sub) => (new Lead360Filter($sub))->apply($g->conditions));
}
$ids = $q->orderBy('master_lead_id')->limit($size)->pluck('master_lead_id');
$rows = DB::select('SELECT * FROM ulda_serve.v_lead_master_flat WHERE unique_lead_id = ANY(?)', [$ids]);

6. API Design (dynamic filtering)

POST /api/leads/search — filter as a structured JSON body (not query string), so complex AND/OR is expressible:

{
  "logic": "AND",
  "groups": [
    { "logic": "AND", "conditions": [
      { "field": "state", "op": "in", "value": ["tx","ca"] },
      { "field": "lead_score", "op": "between", "value": [70,100] },
      { "field": "fiber_att", "op": "is", "value": true }
    ]},
    { "logic": "OR", "conditions": [
      { "field": "last_interaction_channel", "op": "in", "value": ["SMS","Voice"] },
      { "field": "date_added", "op": "after", "value": "2026-06-01" }
    ]}
  ],
  "sort": [{ "field": "lead_score", "dir": "desc" }],
  "page": { "cursor": null, "size": 50 },
  "select": "grid"
}

Response: { rows, next_cursor, approx_total, applied }. Companion endpoints: GET /api/filters/options/{field} (dropdown values, cached), GET/POST/DELETE /api/filters/presets (saved filters), POST /api/leads/export.


7. Pagination, Sorting, Export


8. Additional Enterprise Filters (recommended)

Sales: lead score/tier band · propensity-to-convert · days-since-last-touch · # attempts per channel · reply/engagement (total_replies, open_count) · fiber speed tier. Operations / Compliance: DNC / litigator / consent status · timezone / within-calling-hours · phone type (mobile/landline) · phone tier (T1–T4) · email deliverability · data-quality score. Management: by owner agent / team / territory · by source ROI (once orders integrate) · by campaign performance · duplicate/merge status (merged_from) · freshness (updated_at) · geography (county / census tract). Smart/saved: “My open leads”, “TX fiber + high score + contactable now”, “No touch in 14 days” — one-click presets stored in ulda_serve.saved_filters.


9. What was added to the ULDA DB

Net: the database is now indexed for fast multi-criteria filtering at 1M+ scale; the application layer (Laravel filter pipeline + POST /leads/search + keyset pagination + async export) sits on top of this foundation exactly as specified above.