diff --git a/skills/iblai-analytics/SKILL.md b/skills/iblai-analytics/SKILL.md index 1cc6149..531bb64 100644 --- a/skills/iblai-analytics/SKILL.md +++ b/skills/iblai-analytics/SKILL.md @@ -1,134 +1,215 @@ --- name: iblai-analytics -description: Read ibl.ai analytics via the platform API across agents, content, and users — overview KPIs, users, topics, transcripts, costs, courses, programs, and audit — plus generate and download Data Reports. Scope per-agent or organization-wide. Use to pull usage, engagement, cost, catalog, or per-user learning data. +description: Read ibl.ai analytics via the platform API — overview KPIs, users, topics, transcripts, costs, and catalog performance for courses, programs, pathways, and skills — plus per-learner drill-downs, time-on-platform, and async Data Reports (generate, poll, download). Scope per-agent or platform-wide. Use to pull usage, engagement, cost, catalog, or per-user learning data. --- # iblai-analytics -Read ibl.ai analytics from the API across all three scopes: +Read ibl.ai analytics and generate Data Reports from the platform API. Every endpoint lives under `https://api.iblai.app/dm/api/analytics/…` or `https://api.iblai.app/dm/api/reports/platforms/{platform_key}/…`. -- **Agent analytics** — include `mentor_unique_id` to scope any metric to one agent. -- **Organization / content analytics** — omit `mentor_unique_id` for org-wide - metrics, plus the catalog **Courses** and **Programs** breakdowns. -- **User analytics** — a single user's enrollments, grades, time spent, engagement. - -Reads are read-only; the only writes are Data Reports. +Reads are read-only. Reports are the only writes: kick one off, poll until it finishes, download the CSV or JSON. ## Auth & conventions - **Base URL:** `https://api.iblai.app` - **Header:** `Authorization: Api-Token $IBLAI_API_KEY` on every request. -- **Path vars:** `{org}` = `$IBLAI_ORG`, `{username}` = `$IBLAI_USERNAME`, - `{mentor}` = an agent's unique id (**optional** — include for agent scope, omit - for org-wide). -- **Host:** analytics live on **DM** at `…/dm/api/analytics/…`; **Audit** is on - DM under `…/dm/api/ai-mentor/…`. -- **Chart params:** every chart passes `date_filter` (`today` | `7d` | `30d` | - `90d` | `custom`; `custom` adds `start_date` / `end_date`), `platform_key={org}`, - optional `mentor_unique_id={mentor}` (agent scope), and optional `usergroup_ids`. - Below, `…` = `https://api.iblai.app/dm/api` and a trailing `&platform_key={org}` - (plus `&mentor_unique_id={mentor}` when scoping to an agent) is implied as `&…`. -- Not connected yet? Run **`/iblai-login`** first. +- **Path vars:** `{platform_key}` = `$IBLAI_ORG` (the platform key on the wire — Reports uses this). +- **Anchor:** `{dm_url}` = `https://api.iblai.app/dm` throughout the skill. Every URL below is written `{dm_url}/api/analytics/…` or `{dm_url}/api/reports/…` and substitutes to the full https URL at call time. Matches the anchor used in vibe's iblai-* skills. +- Not connected yet? Run **`/iblai-login`** first to populate `IBLAI_ORG` and `IBLAI_API_KEY`. +- Confirm with the user before creating a Data Report — the job runs against the platform and its output may be shared. + +## Ground source: OpenAPI schema (priority) + +**The OpenAPI schema is the contract, not this file.** Every URL, parameter, enum, and response shape below is orientation only — consult the schema before writing any request. If this file disagrees with the schema, the schema wins. + +Two sources, in priority order: + +1. **Live schema (authoritative)** — `https://api.iblai.app/dm/api/docs/schema/?format=json`. Browse at `https://api.iblai.app/dm/api/docs/`. +2. **Local snapshot (offline bootstrap)** — [`references/analytics-schema.json`](references/analytics-schema.json). Filtered to `/api/analytics/*` and `/api/reports/*`. Refresh with the routine in [`references/schema.md`](references/schema.md). Reference only; the live schema wins on any disagreement. + +See [`references/schema.md`](references/schema.md) for fetch, filter, refresh, and drift-check commands. Run the drift check before shipping any code that hits these endpoints — every URL cited here must appear in the live schema. + +## Concepts + +### Scope: agent vs platform + +Every `/api/analytics/*` endpoint accepts an optional `mentor_unique_id` query param. Include it to scope a metric to one agent; omit it for platform-wide. Same endpoints, same shapes — one param toggles scope. + +### Common query parameters + +| Parameter | Meaning | +|---|---| +| `date_filter` | `today` \| `7d` \| `30d` \| `90d` \| `all_time` \| `custom`. With `custom`, also send `start_date` and `end_date` (`yyyy-MM-dd`). | +| `platform_key` | Optional on most endpoints; **required** on `learners/list/`. Defaults to the platform on the token. | +| `mentor_unique_id` | Optional. Agent scope when present. | +| `usergroup_ids` | Optional. Narrows any read (and any Report) to specific user groups. | +| `granularity` | `day` \| `week` \| `month` (where the endpoint supports timeseries). | +| `page`, `limit` | Pagination for listing endpoints. | +| `search` | Free-text filter (users, topics, transcripts). | + +### RBAC + +Every read requires the `Ibl.Analytics/CanViewAnalytics/action` role (page-access gate) **plus** one of: + +- `Ibl.Analytics/Core/read` — user or usergroup analytics. +- `Ibl.Analytics/Mentors/read` — mentor analytics (grant on a usergroup or user to row-filter to specific users). + +Mentor analytics pages also require `Ibl.Analytics/CanViewMentorAnalytics/action`. Reports also require `Ibl.Analytics/Reports/read`. A learner may read their own analytics without the page gate. + +### Data Reports lifecycle + +Reports run asynchronously. `POST /new` returns a `report_id` and a `state`. Poll the detail endpoint until `state` is `completed`, then hit `download`. Terminal states: `completed` (download at `url`), `cancelled`, `error`, `expired` (output evicted — regenerate). Intermediate states: `pending → running → accumulating → processing → storing`. ## Reads -### Overview / Users / Topics / Transcripts / Costs +Every URL below sits under `{dm_url}/api/analytics/`. All accept `platform_key`, `mentor_unique_id`, `usergroup_ids`, `date_filter`, `start_date`, `end_date` unless noted. Query strings elide trailing repeated params for brevity. + +### Overview + +Headline KPIs for the landing page. + +| Intent | Endpoint | +|---|---| +| Messages / topics KPIs | **GET** `{dm_url}/api/analytics/topics/?date_filter=30d` | +| Conversations chart | **GET** `{dm_url}/api/analytics/conversations/?date_filter=30d` | +| Active users headline | **GET** `{dm_url}/api/analytics/users/?metric=active_users_last_30d&date_filter=30d` | +| Sessions timeseries | **GET** `{dm_url}/api/analytics/sessions/?date_filter=30d` | + +`topics/` and `sessions/` accept `metric` to switch between the overview KPI and a headline value. -These endpoints serve both **agent** scope and **organization-wide** scope; -include `mentor_unique_id` for the former, omit it for the latter. +### Costs (LLM & financial) -#### Overview +Aggregates and breakdowns. -- **GET** `…/analytics/topics/?date_filter=30d&…` — Messages / Topics / Conversations KPIs. -- **GET** `…/analytics/users/?metric=active_users_last_30d&date_filter=30d&…` — Active Users KPI. -- **GET** `…/analytics/sessions/?date_filter={r}&…` — Sessions line chart. -- **GET** `…/analytics/topics/details/?date_filter={r}&…` — Topics bar chart. +| Intent | Endpoint | +|---|---| +| Total / weekly / monthly cost headline | **GET** `{dm_url}/api/analytics/financial/?metric={total_costs\|weekly_costs\|monthly_costs}&date_filter=all_time` | +| Cost per day chart | **GET** `{dm_url}/api/analytics/financial/?metric=total_costs&date_filter=30d` | +| Cost breakdown | **GET** `{dm_url}/api/analytics/financial/details/?group_by={provider\|llm_model\|username\|mentor\|platform\|action}&date_filter=30d` | +| Cost breakdown, paginated | append `&metrics=total_costs,sessions&page=1&limit=25&search=jane` | +| Invoice CSV/JSON | **GET** `{dm_url}/api/analytics/financial/invoice/?date_filter=30d` | -#### Users +`financial/` requires `metric`. `financial/details/` requires `group_by`. -- **GET** `…/analytics/users/?metric=registered_users&date_filter=30d&…` -- **GET** `…/analytics/users/?metric=active_users&date_filter={r}&…` -- **GET** `…/analytics/users/?metric=currently_active&date_filter=today&…` -- **GET** `…/analytics/time/?date_filter={r}&…` — access-time heatmap. -- **GET** `…/analytics/users/details/?date_filter={r}&page={n}&limit=5&search={q}&…` — user table. +### Users & engagement -#### Topics +Registered and active users, plus time-of-day patterns. -- **GET** `…/analytics/conversations?metric=conversations&date_filter={r}&…` -- **GET** `…/analytics/topics/?date_filter=30d&…` -- **GET** `…/analytics/ratings/?date_filter={r}&…` -- **GET** `…/analytics/topics/details/?date_filter={r}&…` +| Intent | Endpoint | +|---|---| +| Registered users | **GET** `{dm_url}/api/analytics/users/?metric=registered_users&date_filter=30d` | +| Active users | **GET** `{dm_url}/api/analytics/users/?metric=active_users&date_filter=30d` | +| Currently active | **GET** `{dm_url}/api/analytics/users/?metric=currently_active&date_filter=today` | +| Time-of-day heatmap | **GET** `{dm_url}/api/analytics/time/?date_filter=30d` | +| User table (search + paginate) | **GET** `{dm_url}/api/analytics/users/details/?search=jane&page=1&limit=25&date_filter=30d` | -#### Transcripts +### Topics & conversations -- **GET** `…/analytics/conversations?metric=headline&…` -- **GET** `…/analytics/messages/?search={user}&topic={topic}&page={n}&limit=20&…` — transcript list. -- **GET** `…/analytics/messages/details/?session_id={id}&…` — one transcript. +| Intent | Endpoint | +|---|---| +| Topic overview | **GET** `{dm_url}/api/analytics/topics/?date_filter=30d` | +| Topic bar chart / details | **GET** `{dm_url}/api/analytics/topics/details/?search=&page=1&limit=25&date_filter=30d` | +| Conversation counts | **GET** `{dm_url}/api/analytics/conversations/?metric=conversations&date_filter=30d` | +| Ratings timeseries | **GET** `{dm_url}/api/analytics/ratings/?date_filter=30d` | -#### Costs +### Transcripts -- **GET** `…/analytics/financial/?metric=weekly_costs&date_filter=all_time&…` -- **GET** `…/analytics/financial/?metric=monthly_costs&date_filter=all_time&…` -- **GET** `…/analytics/financial/?metric=total_costs&all_time=true&…` -- **GET** `…/analytics/financial/?metric=total_costs&date_filter={r}&…` — cost/day chart. -- **GET** `…/analytics/financial/details/?group_by=provider&date_filter={r}&…` -- **GET** `…/analytics/financial/details/?metrics=total_costs,sessions&group_by=username&date_filter={r}&page={n}&limit={l}&search={q}&…` -- **GET** `…/analytics/financial/details/?metric=total_costs&group_by=llm_model&date_filter={r}&…` +| Intent | Endpoint | +|---|---| +| Transcript list | **GET** `{dm_url}/api/analytics/messages/?search={user_or_text}&sentiment={positive\|negative\|neutral}&topic={topic}&page=1&limit=20&date_filter=30d` | +| One transcript | **GET** `{dm_url}/api/analytics/messages/details/?session_id={id}` | -### Content (Courses & Programs) +`messages/details/` requires `session_id`. -Course-level and program-level analytics are org-wide catalog breakdowns -(no `mentor_unique_id`) on the same `…/dm/api/analytics/…` family, keyed -by course/program — analogous to the `details` grouping used by Costs (e.g. -`group_by`). Exact `courses` / `programs` sub-paths are platform-specific; use -the `…/analytics/…/details/` endpoints grouped by course/program, or capture the -precise sub-path from the live API responses. +### Sessions & ratings -### User analytics +`sessions/` and `ratings/` both take `metric` and `date_filter` and return timeseries for line charts. See the schema for the exact response shape. -A single user's own learning data (enrollments, grades, time spent, engagement, -last access). RBAC-gated (`IsPlatformAdminOfUser | IsSelfAccess`). +### Course analytics -- **GET** `…/analytics/learners/?username={username}&limit={n}&page={n}&start_date=&end_date=&date_filter={r}` — unified user analytics (user info, summary metrics, per-platform results). -- **GET** `…/analytics/learner/details?username={username}&start_date=&end_date=&date_filter={r}&metrics={…}` — detailed per-course analytics across catalog, agent, and credential data. +Course-level analytics use `content/` with `metric=courses` (or `metric=course`). -(`…/analytics/learners/` and `…/analytics/learner/details` keep the platform's -wire spelling.) +| Intent | Endpoint | +|---|---| +| Course roster with enrolments / completions / ratings | **GET** `{dm_url}/api/analytics/content/?metric=courses&date_filter=30d` | +| Per-course drill-down | **GET** `{dm_url}/api/analytics/content/details/{course_id}/?metric=courses&time_metric={enrollments\|completions\|ratings\|time_spent}&date_filter=30d` | -### Audit +`{course_id}` is the catalog identifier (the same `course_id` `/api/catalog/` returns). `content/details/` requires `metric`. -- **GET** `https://api.iblai.app/dm/api/ai-mentor/orgs/{org}/users/{username}/mentors/audit-logs/?limit=20&offset={n}&mentor={mentor}[&action=0|1|2&actor_email=&from_date=&to_date=]` +### Program analytics -### Data Reports +Program-level analytics use the same family, with `metric=programs` (or `metric=program`). -Data Reports (`…/reports`) are the only writes: kick off a report, poll until -complete, then download the finished CSV from the presigned URL. +| Intent | Endpoint | +|---|---| +| Program roster | **GET** `{dm_url}/api/analytics/content/?metric=programs&date_filter=30d` | +| Per-program drill-down | **GET** `{dm_url}/api/analytics/content/details/{program_id}/?metric=programs&time_metric={enrollments\|completions\|ratings\|time_spent}` | -- **GET** `…/reports/platforms/{org}/?mentor_id={mentorDbId}` — list reports + status. -- **GET** `…/reports/platforms/{org}/{report_name}?mentor_unique_id={mentor}` — poll status until complete. -- **GET** `{status.url}` — download the finished CSV. +### Pathway & skill analytics + +Same shape as course/program. Use `metric=pathways` or `metric=skills` on `content/`, and `metric=pathway` / `metric=skill` on `content/details/`. + +### Per-learner drill-down + +One learner's learning data. + +| Intent | Endpoint | +|---|---| +| Cross-platform learner summary | **GET** `{dm_url}/api/analytics/learners/?username={username}&date_filter=30d&page=1&limit=25` | +| Sortable, filterable learner roster | **GET** `{dm_url}/api/analytics/learners/list/?platform_key={platform_key}&search=&sort_by={username\|name\|last_activity\|total_points\|total_time_spent_seconds\|total_enrollments\|total_skills_count}&sort_order={asc\|desc}&page=1&limit=25` | +| Detailed learner profile | **GET** `{dm_url}/api/analytics/learner/details?username={username}&metrics={comma-separated}&include_edx_progress=true&course_id={id}&program_id={id}&pathway_id={id}&date_filter=30d` | + +`learners/list/` requires `platform_key`. `learner/details` takes **no trailing slash** — Django URL registration. A learner may read their own record with the same endpoints; self-access needs no admin role. + +### Time-on-platform + +| Intent | Endpoint | +|---|---| +| Time spent per user | **GET** `{dm_url}/api/analytics/time-spent/user/` | + +### Data Reports (list, poll, download) + +Report reads. See `## Writes → Data Reports` below for report creation. + +| Intent | Endpoint | +|---|---| +| List reports on the platform | **GET** `{dm_url}/api/reports/platforms/{platform_key}/?mentor_id={optional_pk}` — inlines the last-run status per report where one exists. | +| Poll report status | **GET** `{dm_url}/api/reports/platforms/{platform_key}/{report_name}?mentor_unique_id={optional_uuid}` — poll until `state == "completed"`; `url` then holds the download link. | +| Download a completed report | **GET** `{dm_url}/api/reports/platforms/{platform_key}/{task_id}/download?format={csv\|json}&columns={comma-separated}` — streams the finished output. `format` defaults to `csv`. `columns` reorders columns; leave empty for the default order. | ## Writes -### Data Reports +### Push time-on-platform (client beacon) -- **POST** `…/reports/platforms/{org}/new` — generate a report: +- **POST** `{dm_url}/api/analytics/orgs/{org}/time/update/` (the wire segments are literal — `{org}` holds the platform key value). ```json { - "report_name": "string (e.g. ai-mentor-chat-history)", - "mentor": "uuid", - "usergroup_ids": "number[]", - "start_date": "yyyy-MM-dd", - "end_date": "yyyy-MM-dd", - "course_id": "string", - "query": "string" + "time_spent_seconds": 120, + "session_id": "…", + "url": "…" } ``` +### Data Reports (create) + +- **POST** `{dm_url}/api/reports/platforms/{platform_key}/new` — kick off a report. **Confirm with the user first.** + ```json + { + "report_name": "ai-mentor-chat-history", + "mentor": "9e3b5c17-…", + "usergroup_ids": [12, 34], + "start_date": "2026-06-01", + "end_date": "2026-06-30", + "course_id": "course-v1:iblx+cs101+2026", + "source": "", + "query": "" + } + ``` + All fields are optional; supply what the target report expects. Mentor-scoped reports include `ai-mentor-chat-history`, `my-chat-history`, and `recommendation-history-report`. The response is a status object with `report_id`, `report_name`, `state`, `started_on`, `owner`, `expires`, and `poll_timeout`. See `## Reads → Data Reports` for poll and download. + ## Example -Org-wide Messages/Topics/Conversations KPIs (omit `mentor_unique_id`), then the -same scoped to one agent: +Platform-wide messages/topics KPIs, then the same metric scoped to one agent: ```bash curl -s "https://api.iblai.app/dm/api/analytics/topics/?date_filter=30d&platform_key=$IBLAI_ORG" \ @@ -138,11 +219,40 @@ curl -s "https://api.iblai.app/dm/api/analytics/topics/?date_filter=30d&platform -H "Authorization: Api-Token $IBLAI_API_KEY" ``` +End-to-end chat-history Report — generate, poll, download: + +```bash +# 1. Kick off +REPORT=$(curl -s -X POST "https://api.iblai.app/dm/api/reports/platforms/$IBLAI_ORG/new" \ + -H "Authorization: Api-Token $IBLAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"report_name":"ai-mentor-chat-history","mentor":"'"$MENTOR"'","start_date":"2026-06-01","end_date":"2026-06-30"}') +NAME=$(echo "$REPORT" | jq -r '.data.status.report_name') + +# 2. Poll +while true; do + STATUS=$(curl -s "https://api.iblai.app/dm/api/reports/platforms/$IBLAI_ORG/$NAME?mentor_unique_id=$MENTOR" \ + -H "Authorization: Api-Token $IBLAI_API_KEY") + STATE=$(echo "$STATUS" | jq -r '.data.state') + [ "$STATE" = "completed" ] && break + [ "$STATE" = "error" ] || [ "$STATE" = "cancelled" ] || [ "$STATE" = "expired" ] && { echo "failed: $STATE"; exit 1; } + sleep 5 +done + +# 3. Download +TASK=$(echo "$STATUS" | jq -r '.data.report_id') +curl -s "https://api.iblai.app/dm/api/reports/platforms/$IBLAI_ORG/$TASK/download?format=csv" \ + -H "Authorization: Api-Token $IBLAI_API_KEY" \ + -o chat-history.csv +``` + ## Notes -- Same endpoints serve agent and org/content scope — the presence of - `mentor_unique_id` is the only difference. +- `mentor_unique_id` alone toggles agent scope versus platform scope. - `date_filter=custom` requires both `start_date` and `end_date` (`yyyy-MM-dd`). -- `usergroup_ids` narrows any chart (and a report) to specific user groups. -- For *finding* agents/content use `/iblai-search`; for recommendations use - `/iblai-search`. +- `learner/details` takes no trailing slash — Django's URL registration matches exactly. +- `learners/list/` requires `platform_key` in the query string. +- `financial/`, `financial/details/`, `content/details/`, and `messages/details/` each require one parameter (see the tables). The schema returns `400` if omitted. +- Report state `expired` means the download is gone; re-`POST` `/new` to regenerate. +- The download endpoint streams — expect a large body on chat-history reports. +- To discover agents, courses, programs, or users, see `/iblai-search`. For a user's own profile view of their analytics, use `/iblai-profile`. diff --git a/skills/iblai-analytics/references/analytics-schema.json b/skills/iblai-analytics/references/analytics-schema.json new file mode 100644 index 0000000..40153f7 --- /dev/null +++ b/skills/iblai-analytics/references/analytics-schema.json @@ -0,0 +1 @@ +{"openapi":"3.0.3","info":{"title":"ibl-data-manager","version":"4.294.1-ai-plus","note":"Snapshot filtered to /api/analytics/* and /api/reports/* paths. Component $refs point back to the live schema at https://api.iblai.app/dm/api/docs/schema/?format=json (authoritative)."},"paths":{"/api/analytics/content/":{"get":{"operationId":"get_content_analytics","description":"\n Retrieve aggregated analytics for catalog content (courses, programs, pathways, skills).\n \n Returns both summary statistics and paginated list of content items with individual analytics.\n When a platform_key is provided, results are filtered to show only content consumed by \n learners associated with that platform.\n \n **RBAC Requirements:**\n - Requires: Analytics Viewer role (Ibl.Analytics/Core/read) or Tenant Admin role (Ibl.*)\n \n **Metrics supported:**\n - `course` or `courses`: Course analytics with time spent\n - `program` or `programs`: Program analytics \n - `pathway` or `pathways`: Pathway analytics\n - `skill` or `skills`: Skill analytics\n \n **Platform Filtering:**\n - Without platform_key: Shows global analytics across all platforms\n - With platform_key: Shows analytics filtered by platform learners only\n \n **Time Spent Analytics:**\n - Platform-level: Total time spent across all content and average per learner\n - Course-level: Total time spent per course and average per enrolled learner\n - Time values are provided in seconds for precision\n - Overtime: Time series data showing platform time spent over last 7 days (courses only, and include_overtime=true)\n \n **External Content:**\n - Content not owned by the requesting platform but used by its learners is marked as \"external\"\n - External content has limited metadata exposure for privacy\n ","summary":"Get Content Analytics","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"include_overtime","schema":{"type":"boolean","default":false},"description":"Include time spent over time data"},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100,"minimum":1,"default":20},"description":"Number of items per page (max 100)"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["course","courses","program","programs","pathway","pathways","skill","skills"],"type":"string","minLength":1},"description":"The type of content to retrieve (course, program, pathway, skill)\n\n* `course` - course\n* `courses` - courses\n* `program` - program\n* `programs` - programs\n* `pathway` - pathway\n* `pathways` - pathways\n* `skill` - skill\n* `skills` - skills","required":true},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1},"description":"Page number for pagination"},{"in":"query","name":"platform_key","schema":{"type":"string","nullable":true},"description":"Optional platform key to filter results by platform"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentResponse"}}},"description":""},"400":{"content":{"application/json":{"schema":{"description":"Invalid metric or parameters"}}},"description":""},"404":{"content":{"application/json":{"schema":{"description":"Page out of range"}}},"description":""},"500":{"content":{"application/json":{"schema":{"description":"Internal server error"}}},"description":""}}}},"/api/analytics/content/details/{content_id}/":{"get":{"operationId":"get_content_details","description":"Retrieve detailed analytics for a specific content item including summary statistics, learner-level data, and optional time series information.","summary":"Get Content Details","parameters":[{"in":"path","name":"content_id","schema":{"type":"string"},"required":true},{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"limit","schema":{"type":"integer","maximum":30,"minimum":1,"default":10},"description":"Number of learner records per page"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["course","courses","program","programs","pathway","pathways","skill","skills"],"type":"string","minLength":1},"description":"Content type to fetch (course, program, pathway, skill)\n\n* `course` - course\n* `courses` - courses\n* `program` - program\n* `programs` - programs\n* `pathway` - pathway\n* `pathways` - pathways\n* `skill` - skill\n* `skills` - skills","required":true},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"time_metric","schema":{"type":"string","nullable":true},"description":"Optional time series metric (enrollments, completions, ratings, time_spent)"},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentDetailsResponse"}}},"description":""},"400":{"content":{"application/json":{"schema":{"description":"Invalid request parameters"}}},"description":""},"404":{"content":{"application/json":{"schema":{"description":"Content not found or page out of range"}}},"description":""},"500":{"content":{"application/json":{"schema":{"description":"Internal server error"}}},"description":""}}}},"/api/analytics/conversations/":{"get":{"operationId":"analytics_conversations_retrieve","description":"Mixin for RBAC validation for mentor analytics views.\n\nMentor analytics endpoints enforce RBAC in two phases:\n1. Endpoint authorization (_perform_mentor_analytics_validation):\n - Aggregate (no mentor_unique_id): requires\n \"Ibl.Analytics/CanViewAnalytics/action\" on /platforms/{pk}/ AND\n \"Ibl.Analytics/Mentors/read\" on /platforms/{pk}/mentors/.\n - Per-mentor (mentor_unique_id provided): requires\n \"Ibl.Analytics/CanViewMentorAnalytics/action\" on\n /platforms/{pk}/mentors/{mentor_id}/. The mentor-owner bypass\n (is_owner) applies via the MENTOR_OWNER well-known role.\n2. Row-level filter (_get_accessible_user_ids_for_mentor_analytics):\n - Enumerates accessible users under\n /platforms/{pk}/mentors/{mentor_id}/users/ using\n \"Ibl.Analytics/Mentors/read\". Grants on the mentor resource itself\n cascade to its users (full mentor access). Usergroup authorization\n is resolved at platform-level (/platforms/{pk}/usergroups/) —\n mentor-level grants do NOT cascade to usergroup access.\n\nSpecial case - Mentor owner unfiltered access:\nIf the requesting user owns the mentor AND no usergroup_ids are\nspecified, returns None (no filtering) — the analytics query layer is\nalready scoped to the mentor. With usergroup_ids provided, the owner\nfalls through to normal RBAC filtering and still needs explicit access\n(ownership or grant) to the requested groups.\n\nNote: Mentor analytics APIs work with platform_key (from query params).","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["conversations","headline"],"type":"string","default":"conversations","minLength":1},"description":"* `conversations` - Conversations over time\n* `headline` - Headline metrics for conversations (avg messages per conversation, avg rating)"},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationsChart"}}},"description":""}}}},"/api/analytics/financial/":{"get":{"operationId":"analytics_financial_retrieve","description":"Financial Analytics API - Get comprehensive cost metrics with comparison analysis.\n\nThis endpoint provides period-based cost analysis (not cumulative) with support for:\n- Multiple time granularities and metrics\n- Cross-dimensional filtering\n- Percentage change vs comparison periods\n- Forward-filled time series\n\n**Examples:**\n\n**Basic Weekly Costs:**\n```\nGET /api/analytics/financial/?metric=weekly_costs&comparison_days=10\n```\n\n**Platform & Mentor Filtered:**\n```\n# Get total costs for a specific platform and mentor\nGET /api/analytics/financial/?metric=total_costs&platform_key=web-app&mentor_unique_id=mentor-123&comparison_days=14\n```\n\n**Monthly Costs by Provider:**\n```\nGET /api/analytics/financial/?metric=monthly_costs&provider=openai&granularity=month&comparison_days=30\n```\n\n**Daily Costs for Specific User:**\n```\nGET /api/analytics/financial/?metric=total_costs&username=user-456&granularity=day&start_date=2025-01-15&end_date=2025-01-21&comparison_days=7\n```\n\n\n**Response Structure:**\n```json\n{\n \"metric\": \"weekly_costs\",\n \"value\": 12.47,\n \"percentage_change\": 8.5,\n \"overtime\": [\n {\"date\": \"2025-01-06\", \"value\": 2.89},\n {\"date\": \"2025-01-13\", \"value\": 3.12}\n ],\n \"period_info\": {\n \"start_date\": \"2025-01-01\",\n \"end_date\": \"2025-01-31\",\n \"period_days\": 31\n },\n \"comparison_info\": {\n \"previous_period_value\": 11.50,\n ...\n }\n}\n```","parameters":[{"in":"query","name":"comparison_days","schema":{"type":"integer","minimum":1},"description":"Number of days for comparison period to calculate percentage change (e.g., 10 for 10-day comparison)"},{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"fill_method","schema":{"enum":["zero","previous"],"type":"string","default":"zero","minLength":1},"description":"Method for filling missing time periods in overtime data\n\n* `zero` - Fill missing periods with zero\n* `previous` - Fill missing periods with previous value"},{"in":"query","name":"granularity","schema":{"enum":["day","week","month"],"type":"string","default":"day","minLength":1},"description":"Time granularity for overtime series data\n\n* `day` - Daily data points\n* `week` - Weekly data points\n* `month` - Monthly data points"},{"in":"query","name":"llm_model","schema":{"type":"string","minLength":1},"description":"Filter by specific LLM model (e.g., gpt-4o, claude-3-5-sonnet)"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["total_costs","weekly_costs","monthly_costs"],"type":"string","minLength":1},"description":"Type of financial metric to retrieve\n\n* `total_costs` - Total costs for selected timeframe\n* `weekly_costs` - Costs for current/selected week\n* `monthly_costs` - Costs for current/selected month","required":true},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"provider","schema":{"enum":["openai","anthropic","azure","google","meta","other"],"type":"string","minLength":1},"description":"Filter by AI provider\n\n* `openai` - OpenAI\n* `anthropic` - Anthropic\n* `azure` - Azure\n* `google` - Google\n* `meta` - Meta\n* `other` - Other"},{"in":"query","name":"show_overtime","schema":{"type":"boolean","default":true},"description":"Whether to include overtime series data in response"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"},{"in":"query","name":"username","schema":{"type":"string","minLength":1},"description":"Filter by specific username - returns costs for this user only"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseFinanceResponse"},"examples":{"TotalCostsResponse":{"value":{"metric":"total_costs","filters":{"start_date":"2025-01-01","end_date":"2025-01-31","granularity":"day","comparison_days":10},"value":45.789,"percentage_change":15.2,"overtime":[{"date":"2025-01-01","value":1.23},{"date":"2025-01-02","value":1.45},{"date":"2025-01-03","value":1.67},{"date":"2025-01-04","value":1.34},{"date":"2025-01-05","value":1.89}],"period_info":{"start_date":"2025-01-01","end_date":"2025-01-31","period_days":31,"range_type":"custom"},"comparison_info":{"previous_period_count":10,"previous_period_value":39.654,"comparison_start":"2024-12-22","comparison_end":"2024-12-31","comparison_days":10,"recent_period_value":45.789}},"summary":"Total Costs Response","description":"Response for total_costs metric."},"WeeklyCostsResponse":{"value":{"metric":"weekly_costs","filters":{"granularity":"week","comparison_days":10},"value":12.47,"percentage_change":8.5,"overtime":[{"date":"2025-01-06","value":2.89},{"date":"2025-01-13","value":3.12},{"date":"2025-01-20","value":3.46},{"date":"2025-01-27","value":3.0}],"period_info":{"start_date":"2025-01-01","end_date":"2025-01-31","period_days":31,"range_type":"default"},"comparison_info":{"previous_period_count":10,"previous_period_value":11.5,"comparison_start":"2024-12-22","comparison_end":"2024-12-31","comparison_days":10,"recent_period_value":12.47}},"summary":"Weekly Costs Response","description":"Response for weekly_costs metric."},"MonthlyCostsResponse":{"value":{"metric":"monthly_costs","filters":{"granularity":"month","comparison_days":30,"provider":"openai"},"value":156.23,"percentage_change":-5.2,"overtime":[{"date":"2025-01-01","value":45.67},{"date":"2025-02-01","value":52.34},{"date":"2025-03-01","value":58.22}],"period_info":{"start_date":"2025-01-01","end_date":"2025-03-31","period_days":90,"range_type":"custom"},"comparison_info":{"previous_period_count":30,"previous_period_value":164.78,"comparison_start":"2024-12-01","comparison_end":"2024-12-31","comparison_days":30,"recent_period_value":156.23}},"summary":"Monthly Costs Response","description":"Response for monthly_costs metric."}}}},"description":"Successful financial metrics response with period-based cost data"},"400":{"description":"Bad Request - Invalid query parameters"},"403":{"description":"Forbidden - Insufficient permissions"},"500":{"description":"Internal Server Error"}}}},"/api/analytics/financial/details/":{"get":{"operationId":"analytics_financial_details_retrieve","description":"Financial Details Analytics API – paginated cost tables with flexible grouping.\n\nThis endpoint returns tabular cost metrics aggregated by the dimension\nspecified via the `group_by` query parameter. One or more KPI columns\ncan be requested through the comma-separated `metrics` list while\ntypical filters (date range, provider, platform_key, user, etc.) narrow the\ndataset. Results are paginated with `page` / `limit`.\n\n**Required query parameters**\n- group_by – provider | llm_model | username | mentor | platform\n- metrics – csv list of KPI names, e.g. total_cost, sessions\n\n**Shared optional filters**\n- start_date, end_date – ISO yyyy-mm-dd (ignored when all_time=true)\n- platform_key – tenant isolation\n- mentor_unique_id – filter to one mentor\n- username – filter to a learner\n- provider / llm_model – filter to LLM provider / model\n- all_time – true → lifetime totals\n- page (default 1), limit (default 50)\n\n**Examples**\n--------\n1. Cost by provider for the last week\n```\n GET /api/v2/analytics/financial/details?\n group_by=provider&\n metrics=total_cost&\n start_date=2025-01-01&\n end_date=2025-01-07&\n page=1&limit=10\n```\n\n2. Lifetime cost per user with extra KPIs\n ```\n GET /api/v2/analytics/financial/details?\n group_by=username&\n metrics=total_cost,sessions&\n all_time=true&page=1&limit=50\n ```\n3. Cost by LLM model with tenant filter\n```\n GET /api/v2/analytics/financial/details?\n group_by=llm_model&\n metrics=total_cost&\n platform_key=web-app&\n start_date=2025-01-01&end_date=2025-01-31\n```\nResponse structure\n-------------------\n```\n{\n \"page\": 1,\n \"limit\": 10,\n \"total_pages\": 1,\n \"total_records\": 3,\n \"rows\": [\n {\"provider\": \"openai\", \"total_cost\": \"2.50000\"},\n {\"provider\": \"anthropic\", \"total_cost\": \"1.00000\"},\n {\"provider\": \"azure\", \"total_cost\": \"0.50000\"}\n ],\n \"metrics\": [\n {\n \"name\": \"total_cost\",\n \"unit\": \"$\",\n \"description\": \"Cost for this entity in period\"\n }\n ],\n \"total_cost\": \"4.00000\" // optional grand-total when available\n}\n``","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"group_by","schema":{"enum":["provider","llm_model","username","mentor","platform","action"],"type":"string","minLength":1},"description":"Dimension to group by\n\n* `provider` - Group by provider\n* `llm_model` - Group by LLM model\n* `username` - Group by username\n* `mentor` - Group by mentor\n* `platform` - Group by platform\n* `action` - Group by actions","required":true},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100,"minimum":1,"default":20}},{"in":"query","name":"llm_model","schema":{"type":"string","minLength":1}},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metrics","schema":{"type":"string","default":"total_cost","minLength":1},"description":"Comma-separated list of metrics (e.g. total_cost,sessions, last_active)"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"provider","schema":{"type":"string","minLength":1}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"},{"in":"query","name":"username","schema":{"type":"string","minLength":1}}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FinanceDetailsResponse"}}},"description":""},"400":{"description":"Bad Request – invalid query params"},"500":{"description":"Internal Server Error"}}}},"/api/analytics/financial/invoice/":{"get":{"operationId":"analytics_financial_invoice_retrieve","description":"Flexible Invoice Report API – Billing summary with username and platform filtering.\n\nThis endpoint generates invoice reports with flexible filtering options:\n- Platform admins can view their platform's data and filter by username within their platform\n- Super admins can view any combination of username/platform or global summaries\n\n**Key Features:**\n- Flexible filtering by username and/or platform_key\n- Essential metrics: total cost, sessions, usage period\n- Provider breakdown (OpenAI, Anthropic, etc.)\n- Top mentors/actions by cost\n- Clean, invoice-ready format\n\n**Query Parameters:**\n- username: Filter by specific username (optional)\n- platform_key: Filter by platform (optional, but required for platform admins)\n- start_date, end_date: billing period (defaults to last 30 days)\n- include_breakdown: show provider/mentor details (default: true)\n\n**Permission Logic:**\n- Platform admins: Must include platform_key matching their permission scope\n- Super admins: Can use any combination of filters or none (global summary)\n\n**Examples:**\n```\n# Platform admin viewing their platform\nGET /api/analytics/financial/invoice?platform_key=web-app\n\n# Platform admin viewing specific user in their platform\nGET /api/analytics/financial/invoice?platform_key=web-app&username=john.doe\n\n# Super admin viewing specific user across all platforms\nGET /api/analytics/financial/invoice?username=john.doe\n\n# Super admin viewing global summary\nGET /api/analytics/financial/invoice\n```\n\n**Response Structure:**\n```json\n{\n \"entity\": {\n \"type\": \"user|platform|global\",\n \"username\": \"john.doe\",\n \"platform_key\": \"web-app\",\n \"platform_name\": \"Web Application\",\n \"display_name\": \"John Doe on Web Application\"\n },\n \"billing_period\": {\n \"start_date\": \"2025-01-01\",\n \"end_date\": \"2025-01-31\",\n \"days\": 31\n },\n \"summary\": {\n \"total_cost\": 245.750,\n \"total_sessions\": 1250,\n \"active_users\": 85,\n \"cost_per_session\": 0.196\n },\n \"breakdown\": {\n \"by_provider\": [...],\n \"by_mentor\": [...],\n \"by_action\": [...]\n }\n}\n```","parameters":[{"in":"query","name":"end_date","schema":{"type":"string","format":"date"},"description":"End date for billing period (defaults to today)"},{"in":"query","name":"include_breakdown","schema":{"type":"boolean","default":true},"description":"Whether to include provider and mentor breakdown"},{"in":"query","name":"platform_key","schema":{"type":"string","maxLength":255,"minLength":1},"description":"Platform key to filter by (optional, required for platform admins)"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"},"description":"Start date for billing period (defaults to 30 days ago)"},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"},{"in":"query","name":"username","schema":{"type":"string","maxLength":255,"minLength":1},"description":"Username to generate invoice for (optional)"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceReportResponse"},"examples":{"PlatformInvoiceReport":{"value":{"entity":{"type":"platform","platform_key":"web-app","platform_name":"Web Application","display_name":"Web Application"},"billing_period":{"start_date":"2025-01-01","end_date":"2025-01-31","days":31},"summary":{"total_cost":"245.750","total_sessions":1250,"active_users":85,"cost_per_session":"0.196"},"breakdown":{"by_provider":[{"provider":"openai","cost":"180.250","percentage":"73.3"},{"provider":"anthropic","cost":"65.500","percentage":"26.7"}],"by_mentor":[{"mentor":"AI Tutor","cost":"120.500","sessions":650},{"mentor":"Code Helper","cost":"85.250","sessions":420}]}},"summary":"Platform Invoice Report"}}}},"description":""},"400":{"description":"Bad Request – invalid parameters"},"403":{"description":"Forbidden – insufficient permissions"},"404":{"description":"No data found"},"500":{"description":"Internal Server Error"}}}},"/api/analytics/learner/details":{"get":{"operationId":"analytics_learner_details_retrieve","description":"\n Retrieve a holistic snapshot of a learner across catalog enrollments, mentor\n engagement, skills, credentials, and time spent data. The `metrics` query parameter controls\n which sections are returned and defaults to `courses` when omitted.\n\n Optional `course_id` / `program_id` / `pathway_id` scope the matching section to a\n single item (empty list if the learner is not enrolled — never a 404); omitting them\n returns the full list unchanged. When `course_id` is set, the course block is also\n enriched with the learner's live edX Progress data under `edx_progress` (completion\n summary, grade, grade summary) unless `include_edx_progress=false`. The edX call is\n best-effort: on failure `edx_progress` is null and the DM data is still returned.\n `program_id` / `pathway_id` get scoping only — no edX enrichment.\n ","parameters":[{"in":"query","name":"course_id","schema":{"type":"string"},"description":"Scope the `courses` and `time_spent` sections to a single course. When set, also enriches the course with live edX progress (see `include_edx_progress`)."},{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"include_edx_progress","schema":{"type":"boolean","default":true},"description":"When `course_id` is set, fold the learner's live edX Progress-page data (completion summary, grade, grade summary) into the course block. Defaults to true; set false to skip the edX round-trip."},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metrics","schema":{"type":"string"}},{"in":"query","name":"overtime","schema":{"type":"boolean","default":false},"description":"Include overtime metrics for time_spent data (default: false)"},{"in":"query","name":"pathway_id","schema":{"type":"string"},"description":"Scope the `pathways` section to a single pathway."},{"in":"query","name":"platform_key","schema":{"type":"string"}},{"in":"query","name":"program_id","schema":{"type":"string"},"description":"Scope the `programs` section to a single program."},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"},{"in":"query","name":"username","schema":{"type":"string","minLength":1},"required":true}],"tags":["ai-analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnerDetailsResponse"},"examples":{"LearnerDetails":{"value":{"user":{"user_id":42,"username":"learner123","email":"learner@example.com","name":"Learner Example","date_joined":"2024-01-01T00:00:00Z","last_active":"2024-02-01T12:30:00Z"},"data":{"courses":[{"course_id":"course-v1:edX+Demo+2024","name":"Demo Course","platform":"key_org1","enrollment":{"created":"2024-01-05T00:00:00Z","started":"2024-01-06T00:00:00Z","ended":null,"active":true},"completion":{"completed":true,"completion_percentage":92.5,"passed":true,"passed_date":"2024-01-18T00:00:00Z"}}],"mentors":[{"mentor_id":"b451624c-ef13-4f9f-8cc6-2a0e8c781f12","name":"AI Math Tutor","slug":"ai-math-tutor","platform_key":"key_org1","categories":["Mathematics"],"subjects":["Algebra"],"interactions":{"total_messages":12,"latest_interaction":"2024-02-01T12:30:00Z","rating":4.5}}],"time_spent":{"courses":[{"course_id":"course-v1:edX+Demo+2024","course_name":"Demo Course","platform":"key_org1","time_spent":"2h 30m","time_spent_secs":9000}],"total_time_spent":"2h 30m","total_time_spent_secs":9000}}},"summary":"Learner details","description":"Example learner detail payload"}}}},"description":"Learner profile with requested analytics data"},"400":{"description":"Bad Request - Invalid parameters supplied"},"404":{"description":"Learner not found"},"500":{"description":"Unexpected error"}}}},"/api/analytics/learners/":{"get":{"operationId":"analytics_learners_retrieve","description":"Unified API endpoint for learner analytics.\n\nThis endpoint provides either:\n1. Cross-platform summary (when only username is provided)\n2. Platform-specific detailed data (when username + platform_key are provided)\n\nQuery params:\n- username (required): Username of the learner\n- platform_key (optional): Platform key for platform-specific data\n- page (optional): Page number (default: 1)\n- limit (optional): Records per page (default: 20, max: 100)\n\nReturns:\n- If platform_key provided: Detailed platform metrics\n- If no platform_key: Cross-platform summary with pagination","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100,"minimum":1,"default":20},"description":"Number of records per page (default: 20, max: 100)"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"overtime","schema":{"type":"boolean","default":false},"description":"Include overtime metrics for the user in the platform (default: false)"},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1},"description":"Page number (default: 1)"},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1},"description":"Optional platform key - if provided, returns platform-specific detailed data"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"},{"in":"query","name":"username","schema":{"type":"string","minLength":1},"description":"Username of the learner to get analytics for. Defaults to self if not provided."}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnerAnalyticsResponse"}}},"description":""}}}},"/api/analytics/learners/list/":{"get":{"operationId":"analytics_learners_list_retrieve","description":"\n Retrieve a paginated list of learners for a specific platform with their comprehensive\n metrics from the UserPlatformSummary materialized view. This endpoint is accessible only\n to platform administrators and supports search, sorting, and pagination.\n ","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100,"minimum":1,"default":20},"description":"Number of learners per page (default: 20, max: 100)"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1},"description":"Page number for pagination (default: 1)"},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1},"description":"Platform key to filter learners by platform","required":true},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term to filter learners by username, email, or name"},{"in":"query","name":"sort_by","schema":{"enum":["username","name","last_activity","total_points","total_time_spent_seconds","total_enrollments","total_skills_count"],"type":"string","default":"last_activity","minLength":1},"description":"Field to sort learners by (default: last_activity)\n\n* `username` - Username\n* `name` - Name\n* `last_activity` - Last Activity\n* `total_points` - Total Points\n* `total_time_spent_seconds` - Time Spent\n* `total_enrollments` - Enrollments\n* `total_skills_count` - Skills Count"},{"in":"query","name":"sort_order","schema":{"enum":["asc","desc"],"type":"string","default":"desc","minLength":1},"description":"Sort order (default: desc)\n\n* `asc` - Ascending\n* `desc` - Descending"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnerListResponse"},"examples":{"LearnerList":{"value":{"platform":{"platform_id":1,"platform_name":"Test Platform","platform_key":"test-platform"},"learners":[{"user_id":42,"username":"learner123","email":"learner@example.com","name":"Learner Example","is_active":true,"platform_id":1,"platform_name":"Test Platform","platform_key":"test-platform","metrics":{"enrollments":5,"programs":2,"pathways":1,"resources":10,"reported_skills":3,"desired_skills":2,"assigned_skills":4,"total_skills":9,"credentials":2,"points":150.5,"total_time_spent_seconds":7200,"top_content":{"course_id":1,"course_identifier":"course-v1:edX+Demo+2024","course_name":"Demo Course","time_spent_seconds":3600}},"first_activity":"2024-01-01T00:00:00Z","last_activity":"2024-02-01T12:30:00Z"}],"pagination":{"page":1,"limit":20,"total_pages":1,"total_records":1,"has_next":false,"has_previous":false,"next_page":null,"previous_page":null},"generated_at":"2024-02-01T12:30:00Z"},"summary":"Learner list","description":"Example learner list payload"}}}},"description":"Paginated list of learners with their metrics"},"400":{"description":"Bad Request - Invalid parameters supplied"},"403":{"description":"Forbidden - Platform admin access required"},"500":{"description":"Unexpected error"}}}},"/api/analytics/messages/":{"get":{"operationId":"analytics_messages_retrieve","description":"Conversation list endpoint for analytics.\n\nQuery params (all optional unless specified by permissions):\n- platform_key: filter by platform\n- mentor_unique_id: filter by mentor\n- page: page number (default 1)\n- limit: page size (default 20, max 100)\n- search: search in user name and first user message\n- min_messages, max_messages: message_count range\n- sentiment: positive|negative|neutral\n- topic: topic name contains\n- start_date, end_date: date filter on conversation date\n\nReturns: summary totals, results list (paginated), and pagination metadata.","parameters":[{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100,"minimum":1,"default":20}},{"in":"query","name":"max_messages","schema":{"type":"integer","minimum":0}},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"min_messages","schema":{"type":"integer","minimum":0}},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"sentiment","schema":{"enum":["positive","negative","neutral"],"type":"string","minLength":1},"description":"* `positive` - positive\n* `negative` - negative\n* `neutral` - neutral"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"topic","schema":{"type":"string","minLength":1}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationListResponse"}}},"description":""}}}},"/api/analytics/messages/details/":{"get":{"operationId":"analytics_messages_details_retrieve","description":"Conversation detail endpoint for analytics.\n\nQuery params:\n- session_id (required): UUID of the session to fetch\n- platform_key, mentor_unique_id (optional): further scope\n- start_date, end_date (optional): date filter on message timestamps\n\nReturns: summary metadata from conversation_list MV, and a list of\nhuman/ai message pairs in chronological order.","parameters":[{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"session_id","schema":{"type":"string","minLength":1},"required":true},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDetailResponse"}}},"description":""}}}},"/api/analytics/orgs/{org}/time/update/":{"post":{"operationId":"analytics_orgs_time_update_create","description":"Update time spent tracking data from client-side events.\n\nThis endpoint receives time spent data collected on the client side and\nstores it in the analytics database. Authentication is optional - the API\nsupports both authenticated and anonymous requests.\n\nMethods:\n POST: Submit time spent tracking data\n\nAuthentication:\n Optional - supports both authenticated and anonymous requests.\n - Authenticated requests: User ID is associated with the record\n - Anonymous requests: Records are stored with null user_id\n\nReturns:\n A response indicating success or failure:\n {\n \"success\": true|false,\n \"message\": \"Error message if failed\" (optional)\n }\n\nError Responses:\n 400 Bad Request: If the request data is invalid or the API is disabled\n\nNotes:\n This API must be enabled via the ENABLE_TIME_SPENT_UPDATE_API setting.","parameters":[{"in":"path","name":"org","schema":{"type":"string"},"required":true}],"tags":["ai-analytics"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeSpentUpdateRequest"}},"application/scim+json":{"schema":{"$ref":"#/components/schemas/TimeSpentUpdateRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TimeSpentUpdateRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/TimeSpentUpdateRequest"}},"*/*":{"schema":{"$ref":"#/components/schemas/TimeSpentUpdateRequest"}}},"required":true},"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeSpentUpdateResponse"}}},"description":""}}}},"/api/analytics/ratings/":{"get":{"operationId":"analytics_ratings_retrieve","description":"Ratings overtime endpoint.\n\nQuery params:\n- metric: only 'ratings' is supported (default)\n- platform_key, mentor_unique_id: optional filters\n- granularity: 'day' (default) or 'hour' (hour only for today)\n- start_date, end_date: optional date range; defaults applied if not provided\n\nReturns: { metric: 'ratings', points: [{date, value}, ...] }","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["ratings"],"type":"string","default":"ratings","minLength":1},"description":"* `ratings` - Ratings over time"},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RatingsOvertime"}}},"description":""}}}},"/api/analytics/sessions/":{"get":{"operationId":"analytics_sessions_retrieve","description":"Mixin for RBAC validation for mentor analytics views.\n\nMentor analytics endpoints enforce RBAC in two phases:\n1. Endpoint authorization (_perform_mentor_analytics_validation):\n - Aggregate (no mentor_unique_id): requires\n \"Ibl.Analytics/CanViewAnalytics/action\" on /platforms/{pk}/ AND\n \"Ibl.Analytics/Mentors/read\" on /platforms/{pk}/mentors/.\n - Per-mentor (mentor_unique_id provided): requires\n \"Ibl.Analytics/CanViewMentorAnalytics/action\" on\n /platforms/{pk}/mentors/{mentor_id}/. The mentor-owner bypass\n (is_owner) applies via the MENTOR_OWNER well-known role.\n2. Row-level filter (_get_accessible_user_ids_for_mentor_analytics):\n - Enumerates accessible users under\n /platforms/{pk}/mentors/{mentor_id}/users/ using\n \"Ibl.Analytics/Mentors/read\". Grants on the mentor resource itself\n cascade to its users (full mentor access). Usergroup authorization\n is resolved at platform-level (/platforms/{pk}/usergroups/) —\n mentor-level grants do NOT cascade to usergroup access.\n\nSpecial case - Mentor owner unfiltered access:\nIf the requesting user owns the mentor AND no usergroup_ids are\nspecified, returns None (no filtering) — the analytics query layer is\nalready scoped to the mentor. With usergroup_ids provided, the owner\nfalls through to normal RBAC filtering and still needs explicit access\n(ownership or grant) to the requested groups.\n\nNote: Mentor analytics APIs work with platform_key (from query params).","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["sessions","headline"],"type":"string","default":"sessions","minLength":1},"description":"* `sessions` - Sessions over time\n* `headline` - Headline metrics for sessions (avg messages per session, avg rating)"},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionsChart"}}},"description":""}}}},"/api/analytics/time/":{"get":{"operationId":"analytics_time_retrieve","description":"\n Time Analytics API - User activity patterns by time of day and day of week.\n \n Provides heatmap data showing when users are most active, useful for:\n - Understanding peak usage times\n - Capacity planning and resource allocation\n - User behavior analysis\n - Support scheduling optimization\n \n **Key Features:**\n - Day of week patterns (0=Sunday through 6=Saturday)\n - Hour of day activity levels (0-23)\n - Flexible date range filtering\n - Platform and mentor-specific filtering\n - Message count aggregation\n \n **Data Structure:**\n - `day_of_week`: 0-6 (Sunday-Saturday)\n - `hour`: 0-23 (24-hour format)\n - `value`: Message count for that time slot\n ","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessTimesHeatmap"},"examples":{"AccessTimesHeatmap":{"value":{"metric":"access_times","data":[{"day_of_week":1,"hour":9,"value":45},{"day_of_week":1,"hour":10,"value":52},{"day_of_week":1,"hour":11,"value":48},{"day_of_week":2,"hour":9,"value":38},{"day_of_week":2,"hour":10,"value":44}]},"summary":"Access Times Heatmap","description":"Hourly activity patterns by day of week (0=Sunday, 1=Monday, etc.)"}}}},"description":"Access times heatmap data with day/hour patterns"},"400":{"description":"Bad Request - Invalid parameters"},"403":{"description":"Forbidden - Insufficient permissions"},"500":{"description":"Internal Server Error"}}}},"/api/analytics/time-spent/user/":{"get":{"operationId":"analytics_time_spent_user_retrieve","description":"\n Returns the total time spent (in seconds) for the current authenticated user.\n Can be filtered by platform, date range, course ID, URL, mentor UUID, and session UUID.\n ","summary":"Get total time spent for current user","parameters":[{"in":"query","name":"course_id","schema":{"type":"string","minLength":1},"description":"Course ID to filter by (can be partial)"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"},"description":"End date for time range (YYYY-MM-DD)"},{"in":"query","name":"include_main_platform","schema":{"type":"boolean","default":true},"description":"Whether to include main platform data"},{"in":"query","name":"mentor_uuid","schema":{"type":"string","minLength":1},"description":"Mentor UUID to filter by"},{"in":"query","name":"platform_key","schema":{"type":"string","nullable":true,"minLength":1},"description":"Platform key to filter by"},{"in":"query","name":"session_uuid","schema":{"type":"string","minLength":1},"description":"Session UUID to filter by"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"},"description":"Start date for time range (YYYY-MM-DD)"},{"in":"query","name":"url","schema":{"type":"string","minLength":1},"description":"URL to filter by (can be partial)"},{"in":"query","name":"username","schema":{"type":"string","minLength":1},"description":"Username to get data for (admin users only)"}],"tags":["ai-analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeSpentPerUserResponse"}}},"description":""},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"}}}},"/api/analytics/topics/":{"get":{"operationId":"analytics_topics_retrieve","description":"Returns topics overview analytics for a given platform and mentor.\n\nPermission Required:\n- Platform Admin\n- Ibl.Analytics/Mentors/read","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["overview","sessions","ratings","highlighted"],"type":"string","default":"overview","minLength":1},"description":"* `overview` - Overall topic metrics\n* `sessions` - Sessions over time\n* `ratings` - Ratings over time\n* `highlighted` - Highlighted topics"},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopicsOverview"}}},"description":""}}}},"/api/analytics/topics/details/":{"get":{"operationId":"analytics_topics_details_retrieve","description":"Returns topic details Analytics for a given platform and mentor.\n\nPermission Required:\n- Platform Admin\n- Ibl.Analytics/Mentors/read","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100,"minimum":1,"default":20}},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by topic name"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopicDetails"}}},"description":""}}}},"/api/analytics/users/":{"get":{"operationId":"analytics_users_retrieve","description":"\n User Analytics API - Comprehensive user activity metrics and trends.\n \n Provides real-time and historical user analytics including:\n - Currently active users (last hour)\n - Active users over time periods (7d, 30d, 90d)\n - Registered user counts and growth\n - Time series charts with customizable granularity\n \n **Key Features:**\n - Real-time active user counting\n - Percentage change calculations vs previous periods\n - Flexible date filtering and granularity\n - Platform and mentor-specific filtering\n - Forward-filled time series data\n \n **Supported Metrics:**\n - `currently_active`: Users active in last hour\n - `active_users`: Unique users in specified period\n - `registered_users`: Total and new user counts\n ","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"granularity","schema":{"enum":["day","hour","week","month"],"type":"string","default":"hour","minLength":1},"description":"* `day` - day\n* `hour` - hour\n* `week` - week\n* `month` - month"},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"metric","schema":{"enum":["currently_active","active_users","registered_users","active_users_last_30d"],"type":"string","minLength":1},"description":"* `currently_active` - Users logged in right now\n* `active_users` - Active users in a period\n* `registered_users` - Registered users\n* `active_users_last_30d` - Active users in the last 30 days","required":true},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurrentUsersResponse"},"examples":{"CurrentlyActiveUsers":{"value":{"metric":"currently_active","count":45,"change":12},"summary":"Currently Active Users","description":"Users currently active in the last hour"},"ActiveUsers30Days":{"value":{"metric":"active_users","count":1250,"change":150,"percentage_change":13.6,"prev_count":1100},"summary":"Active Users 30 Days","description":"Unique active users in the last 30 days with comparison"},"RegisteredUsers":{"value":{"metric":"registered_users","count":5000,"new_users":125},"summary":"Registered Users","description":"Total registered users and new users this month"},"ActiveUsersChart":{"value":{"metric":"active_users","points":[{"date":"2025-01-01T10:00:00Z","value":45},{"date":"2025-01-01T11:00:00Z","value":52},{"date":"2025-01-01T12:00:00Z","value":48}]},"summary":"Active Users Chart","description":"Time series of active users with forward-filled gaps"}}}},"description":"User analytics metrics with comparison data"},"400":{"description":"Bad Request - Invalid parameters"},"403":{"description":"Forbidden - Insufficient permissions"},"500":{"description":"Internal Server Error"}}}},"/api/analytics/users/details/":{"get":{"operationId":"analytics_users_details_retrieve","description":"\n User Details API - Comprehensive user activity details with search and filtering.\n \n Provides detailed user information including:\n - User contact information (email, full name)\n - Activity metrics (message count, last activity)\n - Search functionality across multiple fields\n - Flexible date range filtering\n - CSV export capability\n \n **Key Features:**\n - Full-text search across email, name, and user ID\n - Date range filtering for activity periods\n - Platform and mentor-specific filtering\n - Comprehensive pagination with metadata\n - CSV export for data analysis\n - User aggregation across platforms/mentors\n \n **Search Capabilities:**\n - Email address matching\n - Full name search\n - User ID lookup\n - Partial string matching (case-insensitive)\n \n **Export Options:**\n - JSON response (default)\n - CSV export (?export=csv)\n - Includes all user fields in export\n ","parameters":[{"in":"query","name":"date_filter","schema":{"enum":["today","7d","30d","90d","all_time","custom"],"type":"string","default":"today","minLength":1},"description":"* `today` - Today only\n* `7d` - Last 7 days\n* `30d` - Last 30 days\n* `90d` - Last 90 days\n* `all_time` - All time\n* `custom` - Custom date range"},{"in":"query","name":"end_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100,"minimum":1,"default":20}},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"page","schema":{"type":"integer","minimum":1,"default":1}},{"in":"query","name":"platform_key","schema":{"type":"string","minLength":1}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by email, full name, or user ID"},{"in":"query","name":"start_date","schema":{"type":"string","format":"date"}},{"in":"query","name":"usergroup_ids","schema":{"type":"array","items":{"type":"integer"},"nullable":true},"description":"Optional list of usergroup IDs to filter results"}],"tags":["ai-analytics","analytics"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetail"},"examples":{"UserDetailsResponse":{"value":{"results":[{"email":"john.doe@example.com","full_name":"John Doe","messages":1250,"last_activity":"2025-01-15T14:30:00Z"},{"email":"jane.smith@example.com","full_name":"Jane Smith","messages":890,"last_activity":"2025-01-15T13:45:00Z"}],"total":1250,"pagination":{"page":1,"limit":50,"total_pages":25,"has_next":true,"has_previous":false,"start_index":1,"end_index":50}},"summary":"User Details Response","description":"Paginated list of users with activity details"},"CSVExport":{"value":"email,full_name,messages,last_activity\njohn.doe@example.com,John Doe,1250,2025-01-15T14:30:00Z\njane.smith@example.com,Jane Smith,890,2025-01-15T13:45:00Z","summary":"CSV Export","description":"CSV export format when ?export=csv is specified"}}}},"description":"Paginated user details with activity metrics"},"400":{"description":"Bad Request - Invalid parameters"},"403":{"description":"Forbidden - Insufficient permissions"},"500":{"description":"Internal Server Error"}}}},"/api/reports/platforms/{key}/":{"get":{"operationId":"reports_platforms_retrieve","description":"Return the reports available on the platform along with the latest status for each report previously requested by the caller. When ``mentor_id`` is supplied, access validation is scoped to that mentor.","summary":"List available reports","parameters":[{"in":"path","name":"key","schema":{"type":"string"},"required":true},{"in":"query","name":"mentor_id","schema":{"type":"integer","minimum":1},"description":"Mentor primary key. When provided, access validation is scoped to that mentor."}],"tags":["reports"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportList"}}},"description":""}}}},"/api/reports/platforms/{key}/{report_name}":{"get":{"operationId":"reports_platforms_retrieve_2","description":"Returns details of a specific report type including its status if previously requested.\n\nRequired RBAC Permissions:\n 1. CanViewAnalytics (kill switch):\n - Action: \"Ibl.Analytics/CanViewAnalytics/action\"\n - Resource: /platforms/{platform_pk}/\n\n 2. Reports access to at least one user or usergroup:\n - Action: \"Ibl.Analytics/Reports/read\"\n - Resource: /platforms/{platform_pk}/users/{user_pk}/ OR\n /platforms/{platform_pk}/usergroups/{group_pk}/\n - Note: User must have access to at least one user or usergroup resource\n with Reports/read permission. Unrestricted access to all users\n (/platforms/{platform_pk}/users/) also satisfies this requirement.","parameters":[{"in":"path","name":"key","schema":{"type":"string"},"required":true},{"in":"query","name":"mentor_unique_id","schema":{"type":"string","format":"uuid"},"description":"Mentor unique ID for mentor-specific reports (e.g., ai-mentor-chat-history). When provided, ownership is validated - mentor owners can access without explicit RBAC permissions."},{"in":"path","name":"report_name","schema":{"type":"string"},"required":true}],"tags":["reports"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDetail"}}},"description":""}}}},"/api/reports/platforms/{key}/{task_id}/download":{"get":{"operationId":"reports_platforms_download_retrieve","description":"Download a completed report as CSV or JSON. Use the columns parameter to control column order.","summary":"Download report","parameters":[{"in":"query","name":"columns","schema":{"type":"string"},"description":"Comma-separated column names to control output order. Must be a subset of the report's allowed_result_fields. Omit to use the report's default order."},{"in":"query","name":"format","schema":{"type":"string","enum":["csv","json"],"default":"csv"},"description":"Download format"},{"in":"path","name":"key","schema":{"type":"string"},"required":true},{"in":"path","name":"task_id","schema":{"type":"string"},"required":true}],"tags":["reports"],"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"description":"No response body"}}}},"/api/reports/platforms/{key}/new":{"post":{"operationId":"reports_platforms_new_create","description":"Triggers a new report generation.\n\nIf the report has been previously requested, it returns the status of the report.\nReports expire after a configured duration.\n\nThe request body should include:\n- report_name: Name of the report to generate\n- learner_id: (optional) ID of the learner to filter by\n- course_id: (optional) ID of the course to filter by\n- force: (optional) Force generation of a new report even if one exists\n- filters: (optional) Additional filters for the report\n- departments: (optional) Department IDs to filter by\n- mentor: (optional) Mentor unique_id for mentor-specific reports (e.g., ai-mentor-chat-history)\n- usergroup_ids: (optional) List of usergroup IDs to filter report data\n\nRequired RBAC Permissions:\n 1. CanViewAnalytics (kill switch):\n - Action: \"Ibl.Analytics/CanViewAnalytics/action\"\n - Resource: /platforms/{platform_pk}/\n\n 2. Reports access to at least one user or usergroup:\n - Action: \"Ibl.Analytics/Reports/read\"\n - Resource: /platforms/{platform_pk}/users/{user_pk}/ OR\n /platforms/{platform_pk}/usergroups/{group_pk}/\n - Note: User must have access to at least one user or usergroup resource\n with Reports/read permission. Unrestricted access to all users\n (/platforms/{platform_pk}/users/) also satisfies this requirement.\n\n 3. For mentor-specific reports (e.g., ai-mentor-chat-history):\n - Action: \"Ibl.Analytics/Reports/read\"\n - Resource: /platforms/{platform_pk}/mentors/{mentor_id}/\n - Note: User must have access to at least one mentor via RBAC OR own at least\n one mentor (created_by == username). If a specific mentor_unique_id is\n provided, user must have access to that specific mentor.\n - Raises PermissionDenied if user has no access to any mentors.","parameters":[{"in":"path","name":"key","schema":{"type":"string"},"required":true}],"tags":["reports"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRequest"}},"application/scim+json":{"schema":{"$ref":"#/components/schemas/ReportRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/ReportRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/ReportRequest"}},"*/*":{"schema":{"$ref":"#/components/schemas/ReportRequest"}}}},"security":[{"PlatformApiKeyAuthentication":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportCreate"}}},"description":""}}}}}} diff --git a/skills/iblai-analytics/references/schema.md b/skills/iblai-analytics/references/schema.md new file mode 100644 index 0000000..0eeeb33 --- /dev/null +++ b/skills/iblai-analytics/references/schema.md @@ -0,0 +1,105 @@ +# Analytics schema — priority, fetch, refresh, drift check + +The OpenAPI schema is the contract for every endpoint the `iblai-analytics` skill references. Parameter names, enums, and response shapes come from the schema — not from prose in `SKILL.md`. + +## Priority + +1. **Live schema (authoritative):** `https://api.iblai.app/dm/api/docs/schema/?format=json`. +2. **Local snapshot (offline bootstrap):** `references/analytics-schema.json`. Filtered to `/api/analytics/*` and `/api/reports/*`. Refresh on demand (see below). The live schema wins on any disagreement. + +## Fetch the live schema + +```bash +curl -s -H "Accept: application/json" \ + "https://api.iblai.app/dm/api/docs/schema/?format=json" \ + > /tmp/iblai-schema.json +``` + +Filter to analytics + reports paths: + +```bash +jq '.paths | with_entries(select(.key | test("^/api/(analytics|reports)/")))' \ + /tmp/iblai-schema.json \ + > /tmp/iblai-analytics-schema.json +``` + +## Refresh the local snapshot + +Run from the repo root to regenerate `skills/iblai-analytics/references/analytics-schema.json`: + +```bash +curl -s -H "Accept: application/json" \ + "https://api.iblai.app/dm/api/docs/schema/?format=json" \ + | jq -c '{ + openapi: .openapi, + info: { + title: .info.title, + version: .info.version, + note: "Snapshot filtered to /api/analytics/* and /api/reports/* paths. Component $refs point back to the live schema at https://api.iblai.app/dm/api/docs/schema/?format=json (authoritative)." + }, + paths: (.paths | with_entries(select(.key | test("^/api/(analytics|reports)/")))) + }' \ + > skills/iblai-analytics/references/analytics-schema.json +``` + +The snapshot omits `components.schemas` to stay small. Any `$ref` in the snapshot points to the live schema — resolve it there. + +## Inspect one endpoint + +Query parameters: + +```bash +jq '.["/api/analytics/financial/"].get.parameters' \ + skills/iblai-analytics/references/analytics-schema.json +``` + +Response shape (200): + +```bash +jq '.["/api/analytics/financial/"].get.responses["200"]' \ + skills/iblai-analytics/references/analytics-schema.json +``` + +Required request body for POST (Reports): + +```bash +jq '.["/api/reports/platforms/{key}/new"].post.requestBody' \ + skills/iblai-analytics/references/analytics-schema.json +``` + +## List every documented path + +```bash +jq -r '.paths | keys[]' \ + skills/iblai-analytics/references/analytics-schema.json \ + | sort +``` + +## Drift check before shipping + +Every URL cited in `SKILL.md` must appear in the schema. The check has to account for two spelling differences between the skill's prose and the schema's paths: + +1. The skill uses the `{dm_url}` anchor (= `https://api.iblai.app/dm`) plus `/api/…` — rewrite `{dm_url}/api` to `/api` before comparing. Legacy `…` shorthand also supported for older skills. +2. Some placeholders differ: the skill says `{platform_key}` where the schema uses `{key}`; the skill uses domain nouns like `{course_id}` and `{program_id}` where the schema uses the generic `{content_id}`. Normalize before comparing. + +```bash +grep -oE '(\{dm_url\}|…|\.\.\.|https://api\.iblai\.app/dm)/api/(analytics|reports)/[a-zA-Z0-9/_<>{}-]*' \ + skills/iblai-analytics/SKILL.md \ + | sed -E 's#^\{dm_url\}#/dm#; s#^…#/api#; s#^\.\.\.#/api#; s#^https://api\.iblai\.app/dm/api#/api#; s#^/dm/api#/api#; s/\{platform_key\}/{key}/g; s/\{course_id\}/{content_id}/g; s/\{program_id\}/{content_id}/g' \ + | sort -u \ + | while read -r url; do + # skip incomplete stubs like /api/analytics/ on its own + case "$url" in "/api/analytics/"|"/api/reports/platforms/") continue ;; esac + jq -e --arg u "$url" '.paths | has($u)' \ + skills/iblai-analytics/references/analytics-schema.json > /dev/null \ + || echo "MISSING: $url" + done +``` + +Any `MISSING:` line is a bug in the skill, not the schema. Fix `SKILL.md`; do not work around a schema mismatch client-side. + +Also compare the local snapshot to the live schema — a `MISSING:` on the snapshot side means the snapshot is stale. Refresh it. + +## When the schema disagrees with the skill + +The schema wins. Update `SKILL.md` to match. If the change is behavioural (a new required parameter, a renamed enum value, a removed endpoint), also update the `## Notes` section so callers know.