Skip to main content
Concept Clusters group a tenant’s creatives into a handful of named, summarized concepts based on cosine similarity over the analysis-text embedding. Each cluster has a human-readable name (e.g. “UGC founder testimonial — vertical, fast-paced”), a one-sentence summary, and 30-day spend / ROAS aggregates.
Replaces the earlier creative_concept_clusters Bob tool that returned anonymous integer cluster IDs. Materialized weekly so cluster names stay stable across calls in the same week.

How it works

Re-running on the same tenant deletes the prior rows first — names refresh as the underlying creative library evolves. Clusters that no longer have ≥ 3 members are dropped.

Naming pass

For each cluster the task asks gpt-4o-mini (cheap, fast):
The prompt is short on purpose — the LLM gets enough signal from tag counts + 3 sample summaries to produce a meaningful name. Cost is about $0.003 per cluster. If the naming call fails for any reason, the cluster is still persisted with name="Cluster N" and summary="". The page still renders it — you just lose the readable label until the next refresh.

What’s in a cluster

Endpoints

Read-only list of materialized concepts for the tenant.Query params:
  • client_id — required (or implicit for single-tenant users).
Returns { "client_id": "...", "concepts": [...] } ordered by (spend_30d desc, roas_30d desc).
Force a recluster outside the weekly schedule. Useful after a sweep of new creatives lands.Body: { "client_id": "..." }. Returns 202 Accepted with a Celery task id. The page polls for completion and reloads.
Both endpoints are tenant-scoped via shared.auth.get_accessible_client_ids.

Bob tool

Reads materialized rows — never recomputes. If no clusters exist for the tenant yet (e.g. first run before the Monday refresh), the tool returns:
Bob is configured to chain that hint into a friendly suggestion to the user: “I don’t see any concept clusters yet — let me kick off a refresh.”

Bubble chart UI

The Ad Creative Analytics page renders concepts as a Highcharts bubble chart: A grid of cards below the chart shows each cluster with its name, summary, and stats. Clicking a bubble (or a card — wired in v2) opens a drawer with the member creative thumbnails so the user can quickly scan the actual content of a cluster.

Worked example

After a tenant’s Monday refresh, the page might show: The Studio hero and Before / after clusters are clearly underperforming relative to the others — that’s a useful prompt for the daily brief to recommend leaning into the founder talking heads concept next.

Cost & cadence

  • Embedding the corpus is a one-time cost (~$0.0002 per creative; shared with similarity / search).
  • k-means is local CPU compute (sklearn).
  • Naming pass is the only ongoing cost: ~0.003percluster× 5clusters×weekly= 0.003 per cluster × ~5 clusters × weekly = ~0.07 per tenant per month.
For a 100-tenant prod, weekly concept refresh costs about $7 / month.

Tunable knobs

Where the code lives

  • marketing_resources/tasks/refresh_creative_concepts.py — the cron task + naming pass.
  • marketing_resources/models/creative_concept.py — the row.
  • marketing_resources/views/creative_concepts.py — the two endpoints.
  • ai_chat/tools_creative.py:creative_concept_clusters — the Bob tool.