Portal Community

Completion Rate Definition

For a given time window, completion rate is calculated as:

completion_rate = interaction_responses_total / interaction_published_total * 100

The complementary rate — timeout rate — is:

timeout_rate = interaction_timeout_total / interaction_published_total * 100

Blocked interactions (rejected by pre-send hooks) are counted separately in interaction_blocked_total and are excluded from the completion rate denominator, since they were never delivered.

Example Breakdown

87%
Completed (responded)
9%
Timed Out
4%
Blocked (pre-send hooks)

Metrics Involved

MetricTypeCounts
interaction_published_totalCounterEvery interaction that passed pre-send hooks and was delivered to the pipeline
interaction_responses_totalCounterEvery interaction that received a user response (any outcome)
interaction_timeout_totalCounterEvery interaction that expired without a response
interaction_blocked_totalCounterInteractions rejected before delivery by pre-send hooks

PromQL Queries for BizFirst Observe

// Completion rate (%) over 1h sliding window — all types
100 * (
  sum(increase(interaction_responses_total[1h]))
  /
  sum(increase(interaction_published_total[1h]))
)

// Timeout rate (%) — approval interactions only
100 * (
  sum(increase(interaction_timeout_total{type="approval"}[1h]))
  /
  sum(increase(interaction_published_total{type="approval"}[1h]))
)

// Blocked rate (%) — segmented by hook name
100 * (
  sum by (blocked_by) (increase(interaction_blocked_total[1h]))
  /
  sum(increase(interaction_published_total[1h]) + increase(interaction_blocked_total[1h]))
)

Alert Thresholds

ConditionSeverityDefault Threshold
Timeout rate > 5% over 5 minutesWarning5%
Timeout rate > 15% over 5 minutesCritical15%
Completion rate < 80% over 1 hourWarning80%
Blocked rate > 10% over 5 minutesWarning10%

Querying Completion Rate in Code

// Admin API endpoint — completion rate by type for last 24h
[HttpGet("stats/completion-rate")]
[Authorize(Roles = "admin")]
public async Task<CompletionRateReport> GetCompletionRateAsync(
    [FromQuery] string? type,
    [FromQuery] int windowHours = 24,
    CancellationToken ct = default)
{
    var since = DateTime.UtcNow.AddHours(-windowHours);
    var query = new AuditQuery { Type = type, CreatedAfter = since };

    var all    = await _auditStore.CountAsync(query, ct);
    var responded = await _auditStore.CountAsync(
        query with { Statuses = [InteractionStatus.Responded] }, ct);
    var timedOut  = await _auditStore.CountAsync(
        query with { Statuses = [InteractionStatus.TimedOut] }, ct);
    var blocked   = await _auditStore.CountAsync(
        query with { Statuses = [InteractionStatus.Blocked] }, ct);

    return new CompletionRateReport
    {
        TotalPublished    = all,
        Responded         = responded,
        TimedOut          = timedOut,
        Blocked           = blocked,
        CompletionRatePct = all > 0 ? (double)responded / all * 100 : 0,
        TimeoutRatePct    = all > 0 ? (double)timedOut  / all * 100 : 0
    };
}

Interpreting Low Completion Rates

PatternLikely CauseInvestigation
Sudden drop in completion rate for all typesDelivery failure (SignalR disconnects, EdgeStream outage)Check interaction_in_flight — is it growing without responses?
Low completion for one specific typeUI rendering issue for that interaction typeCheck browser console for render errors; review InteractionContainer renderer map
Gradually declining completion rateUser disengagement or notification fatigueReview volume per user; consider reducing frequency or increasing priority of critical interactions
High blocked rateRate limit hook is too aggressiveReview RateLimitInteractionHook configuration; check interaction_blocked_total by blocked_by label
First-to-Respond Interactions For role-targeted interactions (first-to-respond wins), the interaction is counted as one response in interaction_responses_total regardless of how many users received it. The interaction_published_total is also counted once per interaction, not once per recipient.