SegOps AIDocs

Product Segments

Dynamic product collections defined by layered rules. Product segments let tenants create named groups of products that automatically update when new products are ingested or when segment rules change.

Product segments are the product-side counterpart of user segments (Module 1). They use the same DSL, compute infrastructure, and visual builder — adapted for products instead of users.


Architecture#

ProductSegment.definition (JSON DSL)
        │
        ▼
build_product_preview_query()   ← apps/product_segments/preview.py
        │
        ▼
SELECT sku FROM segmentation.products FINAL
WHERE tenant_id = ... AND (
  JSONExtractString(data, 'category') = 'shoes'
  AND sku IN (subquery for user_behavior_signal)
)
        │
        ▼
segmentation.product_memberships (sku list per segment)
        │
        ▼
ProductSegment.computed_count + last_computed_at updated

Key difference from user segments: Product segment DSL compiler returns WHERE clause strings (not (select, having) tuples). No GROUP BY/HAVING needed because product attributes are direct properties, not event aggregates.


DSL Format#

json
{
  "logic": "AND",
  "conditions": [
    {
      "type": "product_attribute",
      "property": "category",
      "operator": "eq",
      "value": "shoes",
      "value_type": "string"
    },
    {
      "type": "product_attribute",
      "property": "price",
      "operator": "between",
      "value": 80,
      "value2": 200,
      "value_type": "number"
    },
    {
      "type": "user_behavior_signal",
      "signal": "purchased_by_segment",
      "segment_id": "123",
      "event_type": "order_completed",
      "product_id_property": "product_id"
    },
    {
      "type": "ai_visibility_metric"
    }
  ]
}

Condition Types#

product_attribute#

Filters products via JSONExtract* functions on the data JSON column in ClickHouse.

FieldTypeRequiredDescription
propertystringyesSchema field name (from ProductSchema.fields)
operatorstringyesSee operator table below
valueanydependsThe comparison value
value2numberfor between/not_betweenUpper bound
value_typestringnostring, number, boolean, array, url, price

Operators by value_type:

value_typeAvailable operators
stringeq, neq, contains, does_not_contain, starts_with, ends_with, in, not_in, is_set, is_not_set
number, priceeq, neq, gt, gte, lt, lte, between, not_between, in, not_in, is_set, is_not_set
booleanis_true, is_false
arraycontains, does_not_contain, is_set (not empty), is_not_set (empty)
urleq, is_set, is_not_set

user_behavior_signal#

Matches products that appear in behavioral events from users in a given user segment.

FieldTypeRequiredDescription
signalstringyespurchased_by_segment or viewed_by_segment
segment_idstringyesID of a user segment (Module 1)
event_typestringnoDefaults to order_completed / product_viewed
product_id_propertystringnoEvent payload field holding the product ID. Default: product_id

Compiled to a subquery:

sql
sku IN (
  SELECT DISTINCT JSONExtractString(payload, 'product_id')
  FROM segmentation.events
  WHERE tenant_slug = ? AND event_type = ?
    AND user_id IN (
      SELECT user_id FROM segmentation.segment_memberships FINAL
      WHERE tenant_id = ? AND segment_id = ?
    )
)

ai_visibility_metric#

Stub condition type. Always compiles to 1=0 (returns no matches) until M14 (AI Visibility Tracking) is live.


Models#

ProductSegment (apps/product_segments/models.py)#

FieldTypeDescription
tenantFKOwning tenant
namestringUnique per tenant
descriptiontextOptional
definitionJSONFieldDSL object (logic + conditions)
is_activeboolActive segments auto-recompute every 60s
computed_countint?Number of matched products after last compute
last_computed_atdatetime?Timestamp of last successful compute
refresh_intervalintSeconds between recomputes (default 60)

ClickHouse Table#

sql
CREATE TABLE IF NOT EXISTS segmentation.product_memberships (
    tenant_id          String,
    product_segment_id String,
    sku                String,
    computed_at        DateTime
) ENGINE = ReplacingMergeTree(computed_at)
ORDER BY (tenant_id, product_segment_id, sku)

Compute Pipeline#

  1. compute_product_segment_memberships(product_segment_id) Celery task
  2. Loads ProductSegment + injects tenant_id/tenant_slug into definition
  3. build_product_preview_query() compiles definition → SQL + parameters
  4. Executes on ClickHouse → list of matching SKUs
  5. Inserts rows to product_memberships
  6. Updates segment.computed_count and segment.last_computed_at in Postgres

Scheduled: schedule_product_segment_recomputation fires every 60s (Celery beat), enqueues tasks for all active segments due for refresh.


UI#

  • /pim/segments — List all product segments with computed count, status, last computed time, inline Recompute button
  • /pim/segments/new — Builder in create mode; redirects to /pim/segments/[id] after save
  • /pim/segments/[id] — Builder in edit mode + members table showing matched products with schema fields

Integration with Other Modules#

  • M13 (PIM): Reads ProductSchema.fields to populate the attribute picker in the rule builder
  • M1 (User Segments): user_behavior_signal conditions reference user segment IDs; membership data comes from segmentation.segment_memberships
  • M14 (AI Visibility, upcoming): ai_visibility_metric conditions activate when M14 is live
  • M10 (Copilot): list_product_segments and get_product_segment_detail tools expose segment data to the AI copilot

Code Paths#

ConcernPath
DSL compilerapps/api/apps/product_segments/preview.py
Celery tasksapps/api/apps/product_segments/tasks.py
API viewsapps/api/apps/product_segments/views.py
Frontend builderapps/web/app/(app)/pim/segments/builder.tsx
Frontend listapps/web/app/(app)/pim/segments/page.tsx
Frontend detailapps/web/app/(app)/pim/segments/[id]/page.tsx