Analytics
Phase 12 adds a read-only analytics layer that turns the data the workflow engine already produces — executions, approvals, the append-only audit trail, events and versions — into operational information. Analytics never write to the database and never run a second engine: every number derives from the existing tables through the normal ORM.
The public API lives in workflow_kit.analytics:
| What it answers | Entry points |
|---|---|
| What is happening | execution_metrics, version_analytics |
| How long it takes | completion_metrics, state_durations, bottlenecks |
| Where approvals slow down | approval_metrics, approval_totals |
| How SLAs are met | sla_metrics, escalation_analytics |
| How to scrape it | prometheus_metrics_text, collect_metrics |
All querying functions share the same scope parameters:
workflow— restrict to one workflow by name;workflow_version— restrict to a definition version number;start/end— a timezone-aware range (ISO strings accepted);state— for execution queries the current state; for approval queries the approval step name.
Invalid arguments (an unparsable date, a non-integer version, an unknown
grouping) raise workflow_kit.analytics.AnalyticsError, a ValueError
subclass. Dates without a timezone are interpreted in the current Django time
zone.
Execution metrics
from workflow_kit.analytics import execution_metrics
metrics = execution_metrics(workflow="invoice_approval")
print(metrics.started, metrics.active, metrics.completed)
print(metrics.to_dict()) # JSON-safe
print(metrics.summary()) # human-readable one-liner
Counts returned: started, active, completed, rejected, cancelled,
failed, escalated and sla_breached.
Outcome classification uses the audit trail. The engine records every terminal
transition as a workflow_completed event whose action is the transition
name, so:
completed= aworkflow_completedevent whose action is notrejectorcancel;rejected= anapproval_rejectedevent or arejecttransition;cancelled= acanceltransition (or an explicitworkflow_cancelledevent);failed= reached a terminal state without any of the markers above;escalated= anapproval_escalatedevent;sla_breached= an approval that is still overdue while pending, or was decided after itsdue_atdeadline.
Counts use database aggregations and Exists subqueries, so their query cost
is constant regardless of how many executions are in scope. version_analytics
reports each persisted WorkflowVersion once with its own metrics and average
completion time.
Duration and turnaround
from workflow_kit.analytics import completion_metrics, state_durations, bottlenecks
completion = completion_metrics(workflow="invoice_approval")
# completion.total, completion.completed, completion.duration (seconds)
for row in state_durations(workflow="invoice_approval", start="2024-01-01", end="2024-06-30"):
print(row.state, row.count, row.duration.average)
for row in bottlenecks(workflow="invoice_approval", threshold=1.25):
if row.is_bottleneck:
print("stall:", row.state)
DurationStats summarises durations in seconds: count, average, median,
minimum, maximum, p95 and p99. state_durations reconstructs how long
executions spend in each state from the started_at timestamp, the ordered
transition_executed audit events and completed_at (running executions count
up to "now"). bottlenecks flags the states whose average duration exceeds the
overall average by threshold times — it identifies where workflows stall
without claiming a cause.
Approval analytics
from workflow_kit.analytics import approval_metrics, approval_totals
metrics = approval_metrics(workflow="invoice_approval")
# requested, approved, rejected, pending, cancelled, escalated, sla_breached,
# approved_duration, rejected_duration
for row in approval_totals(workflow="invoice_approval", group_by="step"):
print(row)
approval_totals groups the counts with a single aggregation by workflow,
version, step, approver or status.
SLA and escalations
from workflow_kit.analytics import sla_metrics, escalation_analytics
sla = sla_metrics(workflow="invoice_approval")
# tracked, decided, on_time, breached, pending_overdue, compliance_rate,
# average_overdue, escalation_count, average_completion_time
escalations = escalation_analytics(workflow="invoice_approval")
# total, by_workflow, by_state, by_reason, average_delay_seconds
compliance_rate is on_time / decided * 100 (None when nothing has been
decided). Escalation breakdowns are derived from the approval_escalated
audit events — no separate escalation table is consulted.
Prometheus
prometheus_metrics_text renders the same aggregates as Prometheus text format
without requiring prometheus_client:
from workflow_kit.analytics import prometheus_metrics_text
exposition = prometheus_metrics_text(workflow="invoice_approval")
Metrics exposed include workflow_executions_started_total,
workflow_executions_active, workflow_executions_completed_total,
workflow_execution_completion_duration_seconds (summary),
workflow_approval_* counters, workflow_sla_compliance_ratio and
workflow_sla_breaches_total. Labels use only low-cardinality values such as
the workflow name. collect_metrics returns the numbers as a plain nested
dict if you prefer to feed them into prometheus_client yourself.
HTTP and admin access
The same analytics are available as read-only REST endpoints under
/api/analytics/ (see REST API) and as an admin summary
page on the execution admin (see Django Admin).
Both are authorization-gated.
Performance notes
- Counts are computed on the database; only the small timestamp/status columns needed for duration statistics are materialised in Python.
version_analyticsand the admin summary are bound by the number of versions / workflows, never by execution volume.- State-duration analysis loads one timestamp/value row per execution plus the
transition_executedevents of those executions — two queries in total.