Analysis, purchase and utility workflows

nooksCallAnalysis

File

lib/workflows/functions/nooks-call-analysis.ts

Event

nooks/call.logged

Config

retries: 0, concurrency: { limit: 10 }, rateLimit: { key: "event.data.callId", limit: 1, period: "24h" }

Payload

{ callId, subscriptionId, disposition }

A single run-ai-analysis step calls CallAnalysisService.runAndSave(callId, null). The null campaign id matters: a Nooks call has no campaign, so the workspace’s default analysis agent is used and the result is persisted with campaignId = null.

Because there is no caller agent either, the system prompt is DEFAULT_HUMAN_CALL_ANALYSIS_SYSTEM_PROMPT — a sales-coaching frame that asks whether the logged disposition matches the conversation, how objections and rapport were handled, and whether there are compliance or tone issues.

The event is only emitted for dispositions outside NOOKS_ANALYSIS_SKIP_DISPOSITIONS ("No Answer", "Voicemail", "Busy") — the handler filters before emitting. See Nooks events.

purchasePhoneNumber

File

lib/workflows/functions/purchase-phone-number.ts

Event

phone-number/purchase.requested

Config

retries: 0, concurrency: { limit: 1 }, idempotency: 'event.data.subscriptionId + "-" + event.data.state + "-" + event.data.areaCode'

Payload

{ state, areaCode, subscriptionId, campaignId, targetId }

The function body is entirely commented out — phone-number purchasing is currently manual. The function is still registered, so events are consumed and complete without doing anything.

The intended design, preserved in the comments:

  1. check-existing — decide whether a purchase is needed:

    • the state already has 3 or more active numbers → skip (enough_numbers);

    • the state has none → purchase (no_numbers_in_state);

    • some numbers exist and at least one has never called this target → skip (unused_number_available), because the selector can use that fresh number on the next tick;

    • all existing numbers in the state have already called this target → purchase (all_numbers_used_for_target).

  2. purchase — buy from MessagingApp and register with Retell; throws on failure.

  3. store — persist to the database; on failure, clean up at both Retell and the phone service.

The idempotency key stops two targets in the same state and area code from triggering two purchases, and concurrency: { limit: 1 } serialises purchasing globally so a burst cannot overspend.

To re-enable: uncomment the body, then enable both Configs keys — purchase_new_phone_numbers and daily_phone_number_purchase_limit. The selector’s skipForPurchase branch and CallSkipReason.NO_PHONE_NUMBER_AVAILABLE are still in place for that path.

batchCallAnalysisWorkflow

File

lib/workflows/batch-call-analysis-workflow.ts

Not an Inngest function. It uses the 'use workflow' / 'use step' directive runtime (the workflow package) and is started fire-and-forget from POST /api/calls/batch-analyze.

export interface BatchCallAnalysisFilters {
  campaign_id?: string | null
  status?: string | null
  date_from?: string | null
  date_to?: string | null
  hubspot_sync_status?: string | null
}

export interface BatchCallAnalysisWorkflowResult {
  total: number
  saved: number
  errors: Array<{ callId: string; error: string }>
}

Steps:

  1. getCallsToAnalyzeStep(filters) — CallRepository.findCallsForBatchAnalysis(…​), then filters out calls with a null campaignId and maps to { callId, campaignId }. The workflow never touches Prisma; the repository is imported dynamically inside the step.

  2. runCallAnalysisAndSaveStep(callId, campaignId) — CallAnalysisService.runAndSave, the same code path as per-call analysis, so there is no second implementation to drift.

The loop accumulates saved and pushes { callId, error } for failures, so a partial run reports exactly which calls could not be analysed.

findCallsForBatchAnalysis and countCallsForBatchAnalysis share the private buildBatchAnalysisWhere(filters) helper, which is why the GET preview count and the executed set cannot disagree.

Calls whose campaignId is null — Nooks calls — are excluded from batch re-analysis. Re-run those individually with POST /api/calls/{id}/analyze.

Scaffolding functions

Function File

processRequest

lib/workflows/functions/test-function.ts

testContextCollection

lib/workflows/functions/test-context-collection.ts

Registered so the Inngest wiring can be exercised end to end. lib/workflows/call-target-workflow.ts is an older directive-runtime variant of the dial pipeline, superseded by the callTarget Inngest function.