The path a prompt takes
1
A batch is dispatched
weekly:prompt-batch collects every active prompt across active campaigns and
dispatches WeeklyPromptBatchJob. Despite the name, the schedule runs daily at
03:00 UTC — the command name was kept for compatibility when the cadence changed.2
Each provider is fetched
One
Fetch* job per provider issues the prompt: OpenAI, Gemini, Claude, Perplexity and
Grok through their APIs, and Google AI Overview / AI Mode through SerpApi
(ProcessSerpApiOverviewJob, ProcessSerpApiAiModeJob). Every response is written as a
PromptRun tagged with a shared batch_id.3
Answers are parsed
The matching
Process*AnswersJob runs VisibilityDetector over the answer text to
decide whether the brand appeared and in what form, setting the run’s visibility fields.
Brand variants generated during onboarding are what make this robust to the brand being
referred to by a shortened or alternative name.4
Competitors are extracted
Domains mentioned alongside the brand become
DiscoveredCompetitor and
CompetitorAppearance rows. ClassifyBatchCompetitorsJob (or competitors:classify)
then asks an LLM which of them are genuine direct competitors.5
Scores are calculated
competitors:score runs IndustryVisibilityScorer, applying the weighted formula in
config/scoring.php to produce the industry visibility score shown on /visibility.6
Screenshots are captured
TakeSearchScreenshotJob stores a SERP screenshot per run through ScreenshotOne, exposed
on the model as screenshot_url.Running it by hand
Waiting for 03:00 UTC is rarely what you want while developing. Each stage has a manual entry point:POST /campaigns/{campaign}/generate-prompts, and
GET /campaigns/{campaign}/generation-prompt-preview shows the exact generation prompt that
would be sent — useful when tuning prompt generation without paying for a run.
Sentiment pipeline
Sentiment is a separate daily flow (sentiment:fetch-daily, 01:00 UTC) that dispatches
FetchCampaignSentiment and per-provider jobs (FetchOpenAiSentimentJob,
FetchGeminiSentimentJob, FetchGrokSentimentJob, FetchPerplexitySentimentJob). It runs
through Services/BrandSentiment:
TopicSelectorpicks which topics to evaluate this cycleTopicDataRetrievergathers answers usingRetrievalPromptWriterTopicSentimentScorerscores mentions usingEvaluationPromptWriterBrandSentimentFinalizerwritesSentimentRecordandSentimentMentionrows
GET /api/sentiment/status while this is in flight.
Site intelligence
Independent of prompts, several jobs profile the tracked domain itself:Cost tracking
Every provider call goes throughTokenUsageTracker, which writes an LlmUsage row.
config/ai.php carries cost_per_1k_tokens per model, so spend is derived from recorded
token counts rather than fetched from the providers.