Skip to content

AI-coach SSE stream for a dashboard

POST
/api/dashboard/coaching/
curl --request POST \
--url 'http://localhost:8000/api/dashboard/coaching/?format=json' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "context": "overview", "lang": "en", "payload": { "range_days": 14, "total_clients": 42, "active_clients": 18, "pending_review": 3, "failed_7d": 0, "roster_avg": 78, "at_risk": 8, "streaks": 9, "movers_up": [ { "client": "Anna Greco", "activity": "running", "delta": 12 } ], "movers_down": [] } }'

Streams a richer DeepSeek narrative on top of the rule-based recommendations field of every dashboard response. POST a small payload summarising the dashboard context — the server hands it to DeepSeek with a coach-facing system prompt and streams tokens back via Server-Sent Events.

Response is Content-Type: text/event-stream. The view returns StreamingHttpResponse directly (no CSRF — DRF APIView is CSRF-exempt).

Wire format — four event types:

  • event: meta (1 frame, first) — {context, lang, model, provider, mode}. mode="live" means live DeepSeek; mode="stub" means deterministic local fallback (dev or no LLM_API_KEY).
  • event: token (N frames) — {text: "<chunk>"} — concatenate text across frames to assemble the markdown.
  • event: done (1 frame, last) — {ok: true}. After this the connection closes.
  • event: warn (rare) — emitted on LLM errors before falling through to the stub: {detail: "<reason>"}.

Context-payload contracts — what the backend expects in payload per context:

  • overview{range_days, total_clients, active_clients, pending_review, failed_7d, roster_avg, at_risk, streaks, movers_up:[{client,activity,delta}], movers_down:[...]}.
  • activity{activity, range_days, metric_label, metric_unit, cohort_median, cohort_p25, cohort_p75, leaderboard_count, outliers:[{client_name,metric,value,direction}]}.
  • client{activity, metric_label, metric_unit, target_min, target_max, series_tail:[number|null]*, zone_tail:[bool|null]*} (last 6).
  • business{payment_status, plan_name, quota_remaining, quota_granted, coach_productivity:[{name,role,sessions,pending}], activity_mix:[{activity,count,fraction}]}.

Source-of-truth: apps/api/aikynetix/dashboard/coaching.py::_user_prompt_for_context.

format
string
Allowed values: json sse
object
context
required

Which dashboard the prompt is built for.

  • overview - overview
  • activity - activity
  • client - client
  • business - business
string
Allowed values: overview activity client business
payload
required

Context-specific dict — see the description for the per-context contract. Pre-aggregated client-side from the dashboard response, so the server doesn’t re-fetch.

lang

Output language for the narrative.

  • en - en
  • ru - ru
string
Allowed values: en ru
Examples
ExampleOverviewBody

Overview body

{
"context": "overview",
"lang": "en",
"payload": {
"range_days": 14,
"total_clients": 42,
"active_clients": 18,
"pending_review": 3,
"failed_7d": 0,
"roster_avg": 78,
"at_risk": 8,
"streaks": 9,
"movers_up": [
{
"client": "Anna Greco",
"activity": "running",
"delta": 12
}
],
"movers_down": []
}
}

A text/event-stream (SSE) response. Frames: one meta, N token ({text}), a final done {ok: true}, and a rare warn {detail} if the LLM falls back to the stub.

string
Examples
ExampleSSEWire(excerpt)

SSE wire (excerpt)

event: meta
data: {"context":"overview","lang":"en","model":"deepseek-v4-flash","provider":"deepseek","mode":"live"}
event: token
data: {"text":"- Triage 3 sessions waiting "}
event: token
data: {"text":"in review.\n"}
event: done
data: {"ok":true}
object
context
Array<string>
Examplegenerated
{
"context": [
"example"
]
}
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}