Alpha surface

Analytics serves the report-ready derived facts.

Use this API when a Learning Report needs active, inactive, or waste time, minutes per enrolled school day, XP totals, mastery deltas, MAP RIT and Growth X, or progress evidence. Reports read Analytics rows for evidence and use Results course-progress / grade-level-progress for the app-facing progress answer. They do not rebuild platform math from Events, Results, Curriculum, calendars, or source exports.

13 approved tables 20 endpoint contracts 8 Analytics Problems + 2 auth branches

Quickstart

Make the first authenticated call

The docs-hosted demo API implements the approved customer contract with representative rows. The implementation deliverable must publish the same endpoint shapes at https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/implementation/api.

  1. Set the base URL.
    export ANALYTICS_BASE_URL="https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/customer_website/api"
  2. Mint a demo token.
    curl -sS -X POST "$ANALYTICS_BASE_URL/dev/mint?tenantId=demo"
    export ANALYTICS_TOKEN="$(curl -sS -X POST "$ANALYTICS_BASE_URL/dev/mint?tenantId=demo" | node -pe 'JSON.parse(fs.readFileSync(0,"utf8")).token')"
  3. Read a Learning Report tile.
    curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/school-day-minutes?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&subjectId=math&startDate=2026-05-01&endDate=2026-06-01&includeUnavailable=true" \
      -H "Authorization: Bearer $ANALYTICS_TOKEN"
If a response contains null_reason=source_missing or policy_pending, render that typed status. Do not substitute app-side calendar math, XP constants, or source-table queries.

Boundary

What the app never computes

Analytics owns these measures and reference translations. A Learning Report client renders the returned fields and provenance; it does not keep a second copy of the platform in app code.

Trace: aitd-000-extend-only-storage aitd-001-report-source-ingestion aitd-015-norms-r90-readable-resources aitd-016-scale-translation-apis

Recipe

Build one student's Learning Report

Call the report-ready collections below. Each row carries the policy/version/source refs needed to audit the number later.

XP goals

API path: /alpha/analytics/v1/xp-rollups

Raw DB path: Read alpha.analytics_xp_rollup. Do not SUM reporting.processed_facts, cap goal percent, or reconstruct policy rows in the report client.

Accuracy

API path: /alpha/analytics/v1/accuracy-rollups

Raw DB path: Read alpha.analytics_accuracy for correct questions, total questions, incorrect questions, and accuracy percent. Do not divide reporting.processed_facts in the report client.

Mastery changes

API path: /alpha/analytics/v1/mastery-deltas

Raw DB path: Read alpha.analytics_mastery_delta for changes in the window. Current mastery state remains a Results object; the report does not recompute mastery.

MAP RIT and Growth X

API path: /alpha/analytics/v1/map-growth-rollups

Raw DB path: Read alpha.analytics_map_growth_rollup by canonical_term_id, growth_window, and norms_set. Do not parse NWEA terms, choose retakes, or compute Growth X.

RIT, percentile, and R90 translations

API path: /alpha/analytics/v1/norms, /alpha/analytics/v1/norms/table, /alpha/analytics/v1/norms/rit, /alpha/analytics/v1/norms/percentile, /alpha/analytics/v1/r90/table, /alpha/analytics/v1/r90, /alpha/analytics/v1/school-days-remaining, /alpha/analytics/v1/grade-level-status

Raw DB path: Read alpha.analytics_norms_achievement and alpha.analytics_r90_table by norms_set/current version and exact requested RIT. For remaining school days, read alpha.school_calendar under alpha.policy.school_day over the documented half-open [asOf, endDate) window. Never ship a private norms/R90 table or a private school-day calendar.

Course and grade-level progress

API path: /alpha/analytics/v1/completion-rollups

Raw DB path: Read alpha.analytics_completion_rollup by completion_scope and scope_id. Course rows prefer app-reported Caliper percent, then XP remaining; grade_level rows average main-course rows only. Do not derive actual progress from MAP/RIT/R90 or count Results client-side.

One client loop

const base = process.env.ANALYTICS_BASE_URL;
const studentId = "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9";
let token = process.env.ANALYTICS_TOKEN || await mintDemoToken();

async function mintDemoToken() {
  const response = await fetch(`${base}/dev/mint?tenantId=demo`, { method: "POST" });
  if (!response.ok) throw await response.json();
  return (await response.json()).token;
}

async function analyticsJson(url, retriedAuth = false) {
  const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
  if (response.ok) return response.json();

  const problem = await response.json();
  const fieldCodes = new Set((problem.fieldErrors || []).map((item) => item.code));

  if (response.status === 401 && !retriedAuth) {
    // Missing, malformed, invalid, or expired bearer token. The rejected request did not run.
    token = await mintDemoToken();
    return analyticsJson(url, true);
  }

  if (response.status === 403) {
    throw new Error(`Abort credential path: ${problem.detail}. Get analytics:read for this tenant/student grant.`);
  }

  if (problem.code === "analytics:source_missing") {
    throw new Error(`Retry only after source repair or replay: ${problem.detail}`);
  }

  if (problem.code === "analytics:validation_failed" || problem.code === "analytics:unsupported_parameter") {
    throw new Error(`Abort and fix request shape: ${problem.detail}; field codes=${[...fieldCodes].join(",")}`);
  }

  throw problem;
}

async function readAllPages(path, checkpoint) {
  let cursor;
  let maxModifiedAt = checkpoint;

  do {
    const url = new URL(base + path);
    if (cursor) url.searchParams.set("cursor", cursor);
    if (!cursor && checkpoint) url.searchParams.set("modifiedSince", checkpoint);

    const page = await analyticsJson(url);
    render(page.data);

    for (const row of page.data || []) {
      if (row.modified_at && (!maxModifiedAt || row.modified_at > maxModifiedAt)) {
        maxModifiedAt = row.modified_at;
      }
    }
    cursor = page.nextCursor;
  } while (cursor);

  // Persist only after the entire page walk succeeds. 401 retries reuse the same URL.
  saveCheckpoint(path, maxModifiedAt);
}

const common = new URLSearchParams({ studentId, startDate: "2026-05-01", endDate: "2026-06-01", limit: "100" });
for (const path of [
  "/alpha/analytics/v1/school-day-minutes?" + common + "&includeUnavailable=true",
  "/alpha/analytics/v1/xp-rollups?" + common + "&windowKind=term",
  "/alpha/analytics/v1/accuracy-rollups?" + common + "&windowKind=term",
  "/alpha/analytics/v1/mastery-deltas?" + common,
  "/alpha/analytics/v1/map-growth-rollups?studentId=" + studentId + "&termId=term_2026_winter&growthWindow=winter_to_winter&normsSet=2025",
  "/alpha/analytics/v1/completion-rollups?studentId=" + studentId + "&completionScope=course"
]) {
  await readAllPages(path, loadCheckpoint(path));
}

GOALS recipe

GOALS percentile -> RIT -> R90 track position -> progress -> school days remaining

GOALS asks Analytics for the versioned reference tables and calculators to turn a percentile target into RIT, then R90 grade position, then actual grade-level progress and remaining school-day pacing. The current R90 selector is analytics.rit_to_grade.powerpath.v2026-06-15 per AITD-017. The app may cache the returned tables by table_version, but apps never maintain their own norms/R90 tables, carry a private norms or R90 copy, infer the current table by sorting version names, derive actual progress from MAP/RIT/R90, or count instructional days locally.

1. Read the NWEA norms resource

/alpha/analytics/v1/norms

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms?subject=math&grade=5&season=winter&normsSet=2025&limit=1" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "list",
  "table": "analytics.norms_table",
  "table_version": "analytics.norms.achievement.v2026-06-12",
  "data": [
    {
      "id": "norms_2025_math_student_5_winter",
      "norms_set": "2025",
      "subject_id": "math",
      "source_subject_name": "Math",
      "role": "student",
      "grade_key": "5",
      "grade_level": 5,
      "season": "winter",
      "mean_rit": 211.82,
      "sd_rit": 17.42,
      "calculator_version": "analytics.norms.achievement.v2026-06-12",
      "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)"
    }
  ],
  "limit": 1,
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/norms?subject=math&grade=5&season=winter&normsSet=2025&limit=1"
  }
}

2. Read the filterable NWEA norms table

/alpha/analytics/v1/norms/table

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&limit=1" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "list",
  "table": "analytics.norms_table",
  "table_version": "analytics.norms.achievement.v2026-06-12",
  "data": [
    {
      "id": "norms_2025_math_student_5_winter",
      "norms_set": "2025",
      "subject_id": "math",
      "source_subject_name": "Math",
      "role": "student",
      "grade_key": "5",
      "grade_level": 5,
      "season": "winter",
      "mean_rit": 211.82,
      "sd_rit": 17.42,
      "calculator_version": "analytics.norms.achievement.v2026-06-12",
      "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)"
    }
  ],
  "limit": 1,
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&limit=1"
  }
}

3. Translate percentile to RIT

/alpha/analytics/v1/norms/rit

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms/rit?subject=math&grade=5&season=winter&percentile=99&normsSet=2025" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "analytics.norms_rit_translation",
  "direction": "percentile_to_rit",
  "norms_set": "2025",
  "subject_id": "math",
  "grade_key": "5",
  "grade_level": 5,
  "season": "winter",
  "role": "student",
  "percentile": 99,
  "rit_score": 252,
  "output_rit": 252,
  "raw_rit": 252.345,
  "calculator_version": "analytics.norms.achievement.v2026-06-12",
  "mean_rit": 211.82,
  "sd_rit": 17.42,
  "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)",
  "links": {
    "table": "/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&role=student"
  }
}

4. Translate RIT to percentile

/alpha/analytics/v1/norms/percentile

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms/percentile?subject=math&grade=5&season=winter&rit=247&normsSet=2025" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "analytics.norms_percentile_translation",
  "direction": "rit_to_percentile",
  "norms_set": "2025",
  "subject_id": "math",
  "grade_key": "5",
  "grade_level": 5,
  "season": "winter",
  "role": "student",
  "rit_score": 247,
  "input_rit": 247,
  "percentile": 98,
  "output_percentile": 98,
  "raw_percentile": 97.828,
  "calculator_version": "analytics.norms.achievement.v2026-06-12",
  "mean_rit": 211.82,
  "sd_rit": 17.42,
  "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)",
  "links": {
    "table": "/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&role=student"
  }
}

5. Read the R90 table

/alpha/analytics/v1/r90/table

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/r90/table?subject=math&normsSet=2025&limit=1" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "list",
  "table": "analytics.r90_table",
  "table_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
  "data": [
    {
      "id": "r90_math_172",
      "table_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
      "table_subject_id": "math",
      "subject_id": "math",
      "source_subject_name": "Math",
      "rit_score": 172,
      "r90_grade": 0,
      "effective_grade": 1,
      "r90_grade_level": 0,
      "r90_percent_complete": 0,
      "rit90_grade_band_percent": 0,
      "observation_count": 1,
      "source_ref": "powerpath:/powerpath/rit-to-grade",
      "ownerModule": "nweamap"
    }
  ],
  "limit": 1,
  "hasMore": true,
  "nextCursor": "eyJvZmZzZXQiOjF9",
  "links": {
    "self": "/alpha/analytics/v1/r90/table?subject=math&normsSet=2025&limit=1"
  }
}

6. Translate RIT to R90 and grade position

/alpha/analytics/v1/r90

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/r90?subject=math&rit=239&normsSet=2025" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "analytics.r90_lookup",
  "table_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
  "requested_subject_id": "math",
  "table_subject_id": "math",
  "subject_id": "math",
  "rit_score": 239,
  "table_rit_score": 239,
  "r90_grade": 4.8,
  "effective_grade": 5,
  "r90_grade_level": 4,
  "r90_percent_complete": 80,
  "rit90_grade_band_percent": 80,
  "source_point_kind": "exact",
  "calculator_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
  "source_ref": "powerpath:/powerpath/rit-to-grade",
  "ownerModule": "nweamap",
  "links": {
    "table": "/alpha/analytics/v1/r90/table?subject=math"
  }
}

7. Read school days remaining

/alpha/analytics/v1/school-days-remaining

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/school-days-remaining?studentId=student-ada-001&asOf=2026-06-14&endDate=2026-07-24&xpRemaining=600&hoursPerSchoolDay=1.5&targetDate=2026-07-24" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "analytics.school_days_remaining",
  "student_id": "student-ada-001",
  "school_id": "school_alpha_demo",
  "as_of": "2026-06-14",
  "end_date": "2026-07-24",
  "school_day_policy_ref": "alpha.policy.school_day.v2026-06-10",
  "instructional_days_remaining": 29,
  "calendar_ref_count": 40,
  "first_instructional_date": "2026-06-15",
  "last_instructional_date": "2026-07-23",
  "instructional_dates": [
    "2026-06-15",
    "2026-06-16",
    "2026-06-17"
  ],
  "xp_unit": {
    "policy_ref": "alpha.policy.analytics.xp_expected_minute.v2026-06-14",
    "xp_per_expected_minute": 1,
    "xp_per_hour": 60,
    "meaning": "1 XP = 1 expected minute; XP hours = XP / 60."
  },
  "effort": {
    "xp_remaining": 600,
    "expected_minutes_remaining": 600,
    "hours_remaining": 10,
    "target_date": "2026-07-24",
    "school_days_until_target_date": 29,
    "required_hours_per_school_day_to_target_date": 0.345,
    "hours_per_school_day": 1.5,
    "school_days_needed_at_hours_per_school_day": 7,
    "target_date_at_hours_per_school_day": "2026-06-23",
    "can_finish_by_end_date_at_hours_per_school_day": true
  },
  "provenance": {
    "source": "alpha.school_calendar",
    "source_ref_sample": [
      "ed_fi.CalendarDate:school_alpha_demo:2026-06-15"
    ],
    "policy_ref": "alpha.policy.school_day.v2026-06-10",
    "xp_unit_policy_ref": "alpha.policy.analytics.xp_expected_minute.v2026-06-14"
  },
  "links": {
    "self": "/alpha/analytics/v1/school-days-remaining?studentId=student-ada-001&asOf=2026-06-14&endDate=2026-07-24",
    "schoolDayMinutes": "/alpha/analytics/v1/school-day-minutes?studentId=student-ada-001",
    "xpRollups": "/alpha/analytics/v1/xp-rollups?studentId=student-ada-001&windowKind=school_year"
  }
}

8. Read working and mastered age-grade status

/alpha/analytics/v1/grade-level-status

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/grade-level-status?studentId=b6fa7128-f641-4efd-9075-375411fd6c39&subject=math&asOfDate=2026-05-20" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"
{
  "object": "analytics.grade_level_status",
  "student_id": "b6fa7128-f641-4efd-9075-375411fd6c39",
  "subject_id": "math",
  "as_of_date": "2026-05-20",
  "age_grade": 5,
  "working_grade": 6,
  "highest_mastered_grade": 5,
  "working_age_grade_delta": 1,
  "working_age_grade_status": "working_above_age_grade",
  "mastered_age_grade_delta": 0,
  "mastered_age_grade_status": "mastered_at_age_grade",
  "source_refs": {
    "age_grade": "people_and_orgs.alpha.age_grade_history",
    "working_grade": "results.student_track_state.working_grade",
    "highest_mastered_grade": "results.highest_mastered_grade"
  },
  "deprecated_aliases": {
    "instructional_level_status": "working_age_grade_status",
    "strict_mastery_status": "mastered_age_grade_status"
  }
}
A GOALS client renders the returned output_rit, rit_score, r90_grade, effective_grade, r90_grade_level, rit90_grade_band_percent, completion_percent from completion_scope=grade_level, table_version, calculator_version, instructional_days_remaining, and effort.*. It does not interpolate norms, extrapolate R90, derive progress from MAP/RIT/R90, switch tables locally, count school days, or carry an XP-hours constant.

Trace: aitd-015-norms-r90-readable-resources aitd-016-scale-translation-apis aitd-017-r90-version-supersession aitd-018-goals-school-days-remaining aitd-112-axis-list-endpoints

Runtime

Authentication

Bearer JWT

Every non-demo endpoint requires Authorization: Bearer <token>.

Tenant from token

Do not send tenantId in a query string or body. Tenant routing comes from the JWT claim.

Scopes

Read endpoints require analytics:read. Source imports require analytics:write.

Auth-failure contract

This is the complete 401/403 contract for long-running polling clients. Analytics inherits the shared Platform status meanings, while the response body uses the approved analytics:validation_failed carrier code. Do not collapse these into generic validation failures: branch on status and fieldErrors[].code.

StatusProblem codeWhen it appearsClient actionField-error codesTrace
401 Unauthorized analytics:validation_failed The Authorization header is missing, not a compact JWT, has an invalid signature or payload, or the token is expired. Mint or refresh the token, then retry the exact same request. For polling clients, keep the last completed modifiedSince checkpoint; the rejected request did not run. headers.Authorization code=required
headers.Authorization code=invalid_jwt
headers.Authorization code=invalid_signature
headers.Authorization code=invalid_payload
headers.Authorization code=expired
Analytics Problem
Platform 401 status
403 Forbidden analytics:validation_failed The token is valid, but it has no tenantId claim, the x-timeback-tenant header does not match the token tenant, or the token lacks the required Analytics read/write authority. Abort under the current token. Use a token with analytics:read for GET endpoints, analytics:write or analytics:import for source imports, and the correct student/cohort/tenant grant. jwt.tenantId code=required
headers.x-timeback-tenant code=tenant_mismatch
jwt.scopes code=missing_scope required=analytics:read
jwt.scopes code=missing_scope required=analytics:write
Analytics Problem
Platform 403 status

Trace: aitd-106-axis-auth-shape aitd-109-axis-tenant-routing

Runtime

Pagination, polling, and retries

Pagination

ItemClient contract
EnvelopeEvery GET list endpoint returns data, hasMore, nextCursor, and links.self.
Page sizelimit defaults to 100 and has a maximum of 1000. Invalid limits return analytics:validation_failed.
Cursorcursor is opaque and bound to tenant, endpoint, typed filters, and sort. Use it only to finish the active page walk.
Sort ordermodifiedSince page walks order by modified_at then stable id.
CheckpointPersist modifiedSince only after a complete page walk. If interrupted, restart from the last complete checkpoint.

modifiedSince polling

ItemClient contract
ParametermodifiedSince is a UTC ISO-8601 timestamp over modified_at.
Source missinganalytics:source_missing means the source Event, Result, calendar, enrollment, Curriculum row, or source import was not available for materialization. Repair or wait for the source and replay; do not fill the gap from source modules.
Freshness modelPoll plus modifiedSince is the shipped eventing model. Webhooks are deferred.
Clock ruleUse server-returned modified_at values for checkpoints, never the client clock.

Retry decision

ItemClient contract
401 Unauthorized (analytics:validation_failed)Authentication branch: missing, malformed, invalid-signature, invalid-payload, or expired Bearer token. Mint or refresh the token, retry the exact request, and do not advance a modifiedSince checkpoint until a 2xx page succeeds.
403 Forbidden (analytics:validation_failed)Authorization branch: valid token but missing tenant, mismatched x-timeback-tenant, or missing analytics:read/write authority. Abort this credential path or obtain a token with the right scope/student/cohort grant; do not keep retrying.
400 analytics:validation_failedAbort and correct the request shape or enum value.
400 analytics:unsupported_parameterAbort and use only the endpoint's typed filters.
409 analytics:source_missingRetry only after the source close/import/calendar prerequisite changes. Keep filters stable and use modifiedSince for sync.
409 analytics:idempotency_conflictAbort the replay or reuse the exact original import body for that key.
422 adapter/policy problemsRepair the source mapping or policy/config row, then replay. Do not hard-code the missing value in the client.
500 analytics:schema_migration_failedOperator fix only. Do not work around it by recomputing from source tables.

Operational contract

ItemClient contract
Tenant routingTenant comes from the JWT. The API never accepts tenantId in query or body.
IdempotencySource imports require Idempotency-Key. Reads do not require it.
RetentionCurrent reads exclude superseded and source-deleted rows unless an authorized audit endpoint explicitly includes them.

Trace: aitd-103-axis-query-model aitd-107-axis-eventing aitd-108-axis-error-envelope aitd-105-axis-idempotency

Runtime

Errors

Every error is a typed RFC 7807 Problem. The first two cards are the status-specific auth-failure contract. For Analytics-owned failures, classify by code. For auth failures, code remains analytics:validation_failed, and clients branch by status plus fieldErrors[].code. The documented examples match the docs-hosted demo handler and implementation envelope: type, title, status, code, detail, fieldErrors, requestId, and traceId.

401 Unauthorized analytics:validation_failed

The Authorization header is missing, not a compact JWT, has an invalid signature or payload, or the token is expired.

Client action: Mint or refresh the token, then retry the exact same request. For polling clients, keep the last completed modifiedSince checkpoint; the rejected request did not run.

Analytics validation Problem Inherited Platform 401 status aitd-106-axis-auth-shape aitd-108-axis-error-envelope

cURL repro

curl -i "$ANALYTICS_BASE_URL/alpha/analytics/v1/school-day-minutes?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&startDate=2026-05-01&endDate=2026-06-01"

# Fix: mint or refresh a token, then retry the same URL without advancing modifiedSince.

Problem body

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-validation-failed",
  "title": "Bearer token is expired.",
  "status": 401,
  "code": "analytics:validation_failed",
  "detail": "Bearer token is expired.",
  "fieldErrors": [
    {
      "path": "headers.Authorization",
      "code": "required"
    },
    {
      "path": "headers.Authorization",
      "code": "invalid_jwt"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

403 Forbidden analytics:validation_failed

The token is valid, but it has no tenantId claim, the x-timeback-tenant header does not match the token tenant, or the token lacks the required Analytics read/write authority.

Client action: Abort under the current token. Use a token with analytics:read for GET endpoints, analytics:write or analytics:import for source imports, and the correct student/cohort/tenant grant.

Analytics validation Problem Inherited Platform 403 status aitd-106-axis-auth-shape aitd-108-axis-error-envelope

cURL repro

curl -i "$ANALYTICS_BASE_URL/alpha/analytics/v1/school-day-minutes?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&startDate=2026-05-01&endDate=2026-06-01" \
  -H "Authorization: Bearer forbidden.demo.jwt"

# Fix: abort this credential path and obtain a token with analytics:read for the right tenant/student grant.

Problem body

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-validation-failed",
  "title": "Analytics reads require analytics:read authority.",
  "status": 403,
  "code": "analytics:validation_failed",
  "detail": "Analytics reads require analytics:read authority.",
  "fieldErrors": [
    {
      "path": "jwt.tenantId",
      "code": "required"
    },
    {
      "path": "headers.x-timeback-tenant",
      "code": "tenant_mismatch"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:validation_failed 400

The request envelope, query string, dates, enum values, or required fields are invalid before an adapter/materializer can run. Carries field-level codes, including the norms/R90 reference codes norms_subject_not_found, r90_subject_not_found, norms_point_not_found, r90_point_not_found (aitd-015/016). Also the status carrier for 401/403/404/405/415 envelope errors.

Client action: Correct the request shape or query parameter. Do not retry the same body under a new idempotency key.

Data dictionary Problem entry aitd-103-axis-query-model aitd-108-axis-error-envelope

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-validation-failed",
  "title": "normsSet=2026, completionScope=course_component, window_end before window_start, or tenantId supplied in the query string.",
  "status": 400,
  "code": "analytics:validation_failed",
  "detail": "normsSet=2026, completionScope=course_component, window_end before window_start, or tenantId supplied in the query string.",
  "fieldErrors": [
    {
      "path": "query.normsSet",
      "code": "invalid_enum"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:unsupported_parameter 400

A query parameter is not part of the typed query model (Vercel routing params such as path are stripped before validation).

Client action: Use only documented filters from the table grain and endpoint contract; strip routing params before validation.

Data dictionary Problem entry aitd-103-axis-query-model aitd-108-axis-error-envelope

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-unsupported-parameter",
  "title": "Filtering completion-rollups by raw course_component_kind instead of completion_scope/scope_id.",
  "status": 400,
  "code": "analytics:unsupported_parameter",
  "detail": "Filtering completion-rollups by raw course_component_kind instead of completion_scope/scope_id.",
  "fieldErrors": [
    {
      "path": "query.course_component_kind",
      "code": "unsupported"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:adapter_rejected 422

A valid source-shaped row cannot be normalized, OR an expected report-source row is missing — a reconciliation error, never a silent default.

Client action: Repair the source mapping or upstream row, then replay the same source-shaped row idempotently.

Data dictionary Problem entry aitd-001-report-source-ingestion aitd-011-policy-and-enum-normalization aitd-108-axis-error-envelope

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-adapter-rejected",
  "title": "A processed_facts row names an unknown subject, or a completion row points at a course the Curriculum surface cannot resolve.",
  "status": 422,
  "code": "analytics:adapter_rejected",
  "detail": "A processed_facts row names an unknown subject, or a completion row points at a course the Curriculum surface cannot resolve.",
  "fieldErrors": [
    {
      "path": "body.rows[0].subject",
      "code": "unknown_subject"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:idempotency_conflict 409

The same Idempotency-Key or source_fact_key was replayed with different content.

Client action: Use the original body for replay, or submit a correction through the correction/reversal pathway.

Data dictionary Problem entry aitd-105-axis-idempotency aitd-012-corrections-and-reversals

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-idempotency-conflict",
  "title": "A migration replay keeps the same source_fact_key but changes active_seconds from 300 to 360.",
  "status": 409,
  "code": "analytics:idempotency_conflict",
  "detail": "A migration replay keeps the same source_fact_key but changes active_seconds from 300 to 360.",
  "fieldErrors": [
    {
      "path": "headers.Idempotency-Key",
      "code": "body_mismatch"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:schema_migration_failed 500

An internal/migration failure the surface could not recover from — surfaced honestly as 500, never a lying 200.

Client action: Follow the endpoint contract and retry only after the documented input or source condition changes.

Data dictionary Problem entry aitd-108-axis-error-envelope

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-schema-migration-failed",
  "title": "See the endpoint-specific Problem response.",
  "status": 500,
  "code": "analytics:schema_migration_failed",
  "detail": "See the endpoint-specific Problem response.",
  "fieldErrors": [],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:source_missing 409

Analytics expected an Event, Result, calendar, enrollment, or Curriculum source row, but it was not available or not closed.

Client action: Wait for the source close/import to finish or repair the upstream source row, then replay the affected source/window. For school-day minutes, the fix is to surface the upstream Alpha calendar source and replay the window.

Data dictionary Problem entry aitd-003-close-time-materialization aitd-111-axis-privacy-retention aitd-108-axis-error-envelope

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-source-missing",
  "title": "A TimeSpentEvent close notification references an event id that Events has already tombstoned.",
  "status": 409,
  "code": "analytics:source_missing",
  "detail": "A TimeSpentEvent close notification references an event id that Events has already tombstoned.",
  "fieldErrors": [
    {
      "path": "source.event_id",
      "code": "not_available"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:source_unlinked 422

The source row exists, but Analytics cannot link it to the required Alpha student, subject, KC, course, school calendar, or policy ref.

Client action: Fix the upstream id mapping or governed alias/policy row. Do not supply a caller-normalized replacement id just for this import.

Data dictionary Problem entry aitd-011-policy-and-enum-normalization aitd-001-report-source-ingestion aitd-108-axis-error-envelope

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-source-unlinked",
  "title": "A processed_facts row has subject=Science-notqced and no accepted subject fold.",
  "status": 422,
  "code": "analytics:source_unlinked",
  "detail": "A processed_facts row has subject=Science-notqced and no accepted subject fold.",
  "fieldErrors": [
    {
      "path": "body.rows[0].studentid",
      "code": "unresolved_student"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

analytics:policy_pending 422

A required alpha.policy.* row is missing, inactive for the effective date, or lacks a governed value needed to compute the fact.

Client action: Seed or activate the policy row and replay. Reports and skill packs must not hard-code the missing value.

Data dictionary Problem entry aitd-011-policy-and-enum-normalization aitd-108-axis-error-envelope

{
  "type": "https://platform3-andymontgomery-9773s-projects.vercel.app/analytics/alpha/data_dictionary#problem-analytics-policy-pending",
  "title": "alpha.policy.school_day has no reason mapping for a real Ed-Fi CalendarEventDescriptor.",
  "status": 422,
  "code": "analytics:policy_pending",
  "detail": "alpha.policy.school_day has no reason mapping for a real Ed-Fi CalendarEventDescriptor.",
  "fieldErrors": [
    {
      "path": "alpha.policy.school_day",
      "code": "policy_missing"
    }
  ],
  "requestId": "req_01J0ANALYTICS",
  "traceId": "trace_01J0ANALYTICS"
}

Two first-class paths

API-only and raw-DB-plus-dictionary reach the same answer

The raw path reads Analytics tables documented in the data dictionary. It does not reproduce source-module materializers or hidden report logic.

Time commitment

API-only: GET /school-day-minutes and GET /time-windows

Raw DB plus dictionary: Read alpha.analytics_school_day_minutes and alpha.analytics_time_window at the documented grain. Keep source_missing or policy_pending rows visible when includeUnavailable=true. Do not count school days in the client.

XP goals

API-only: GET /xp-rollups

Raw DB plus dictionary: Read alpha.analytics_xp_rollup. Do not SUM reporting.processed_facts, cap goal percent, or reconstruct policy rows in the report client.

Accuracy

API-only: GET /accuracy-rollups

Raw DB plus dictionary: Read alpha.analytics_accuracy for correct questions, total questions, incorrect questions, and accuracy percent. Do not divide reporting.processed_facts in the report client.

Mastery changes

API-only: GET /mastery-deltas

Raw DB plus dictionary: Read alpha.analytics_mastery_delta for changes in the window. Current mastery state remains a Results object; the report does not recompute mastery.

MAP RIT and Growth X

API-only: GET /map-growth-rollups

Raw DB plus dictionary: Read alpha.analytics_map_growth_rollup by canonical_term_id, growth_window, and norms_set. Do not parse NWEA terms, choose retakes, or compute Growth X.

RIT, percentile, and R90 translations

API-only: GET /norms, GET /norms/table, GET /norms/rit, GET /norms/percentile, GET /r90/table, GET /r90, and GET /school-days-remaining

Raw DB plus dictionary: Read alpha.analytics_norms_achievement and alpha.analytics_r90_table by norms_set/current version and exact requested RIT. For remaining school days, read alpha.school_calendar under alpha.policy.school_day over the documented half-open [asOf, endDate) window. Never ship a private norms/R90 table or a private school-day calendar.

Course and grade-level progress

API-only: GET /completion-rollups

Raw DB plus dictionary: Read alpha.analytics_completion_rollup by completion_scope and scope_id. Course rows prefer app-reported Caliper percent, then XP remaining; grade_level rows average main-course rows only. Do not derive actual progress from MAP/RIT/R90 or count Results client-side.

Guardrails mirrored in the raw path

tenant_scope

Question: Which rows belong to this tenant?

API-only rule: The API takes tenant_id only from the JWT claim; tenantId in URL/query/body is rejected or ignored according to the endpoint contract.

Raw DB plus dictionary rule: Every raw query must filter alpha.analytics_* by tenant_id before joins, windows, counts, sorting, or paging.

Failure mode: A raw report mixes demo/reviewer/production tenants and returns plausible but wrong totals.

ITDs: aitd-109-axis-tenant-routing aitd-106-axis-auth-shape

read_analytics_not_sources

Question: How should a report answer a metric question?

API-only rule: Reports call the named Analytics endpoint for the metric.

Raw DB plus dictionary rule: Raw readers query the matching alpha.analytics_* table. They do not recompute from Events, Results, processed_facts, Curriculum trees, calendars, or private norms/R90 tables; for scale questions they read alpha.analytics_norms_achievement and the alpha.analytics_r90_table mirror sourced from the NWEAMAP-owned PowerPath RIT-to-grade master.

Failure mode: Client-side math leaks platform-owned logic into skill packs and fails three-way convergence.

ITDs: aitd-003-close-time-materialization aitd-110-axis-conformance-evidence

source_ref_not_source_copy

Question: How do I audit a derived fact?

API-only rule: Detail/sourceRefs subcollections expose authorized source ids and provenance summaries.

Raw DB plus dictionary rule: Join through source_event_id, source_state_ref, source_result_refs, source_ledger_refs, map refs, or curriculum_scope_refs; do not expect copied source columns in Analytics tables.

Failure mode: A raw query looks for Caliper payload, NWEA raw fields, Results scores, or Curriculum trees duplicated in Analytics.

ITDs: aitd-000-extend-only-storage aitd-102-axis-read-shape

current_rows_only

Question: Which derived rows are reportable by default?

API-only rule: Ordinary list/detail endpoints exclude superseded/tombstoned rows unless includeSuperseded=true and the caller is authorized.

Raw DB plus dictionary rule: Filter superseded_at IS NULL AND source_deleted_at IS NULL for ordinary reports, then apply table-specific quality_status rules.

Failure mode: A raw report double-counts corrections or invalidated source rows.

ITDs: aitd-111-axis-privacy-retention aitd-012-corrections-and-reversals

signed_corrections

Question: How do corrections affect totals?

API-only rule: Analytics exposes signed source contributions and report-ready net/display totals.

Raw DB plus dictionary rule: Use signed seconds_delta/negative_xp/net fields exactly as documented; do not clamp, drop, or recompute corrections locally.

Failure mode: A report silently removes negative gaming XP or reversal seconds and stops reconciling.

ITDs: aitd-012-corrections-and-reversals

typed_subjects

Question: How are source subjects handled?

API-only rule: Materializers write canonical subject_id or typed null_reason/adapter Problem at write time.

Raw DB plus dictionary rule: Filter/group by subject_id, not source strings such as FastMath, Vocabulary, Science-notqced, ELA, or ISEE variants.

Failure mode: A raw report creates extra subjects or mixes MAP proxy subjects after policy changes.

ITDs: aitd-011-policy-and-enum-normalization

minutes_school_day

Question: Is the school-day denominator available, and if so how many active minutes per enrolled school day did this student have?

API-only rule: GET /school-day-minutes returns active_minutes, enrolled_school_day_count, excluded_school_day_count, minutes_per_enrolled_school_day, quality_status, and null_reason at the report grain.

Raw DB plus dictionary rule: Query alpha.analytics_school_day_minutes at the documented grain with quality_status in ('ok','corrected') and null_reason='none' for ordinary reports. source_missing rows are repair states; do not intersect Events with alpha.school_calendar, enrollment windows, processed_facts active dates, or attendance dates in the consumer.

Failure mode: A raw path returns an empty list for a known student, includes weekends/MAP testing days/current-only school assignment, fabricates a denominator from active dates, or renders a source_missing repair row as a completed time-commitment value.

ITDs: aitd-006-school-day-minutes

xp_totals

Question: How much XP did a student earn, how much remains, and what percent of the XP goal is complete?

API-only rule: GET /xp-rollups returns xp_total, daily_xp_goal, enrolled_school_day_count, xp_goal, xp_remaining, xp_goal_percent, on_track, signed audit subtotals, source_import provenance, and null_reason.

Raw DB plus dictionary rule: Query alpha.analytics_xp_rollup; use reporting.processed_facts and Results xp_ledger only through authorized source refs, not for report totals, remaining XP, or XP goal percent. Ordinary report rows carry stored denominator-derived goal fields; source_missing means repair/replay is required. Never cap xp_goal_percent at 100 when it is populated.

Failure mode: A client sums TimeBack processed_facts.xp_earned or xp_ledger, then computes goals, remaining XP, or a capped percent locally.

ITDs: aitd-007-xp-rollups aitd-001-report-source-ingestion aitd-004-provenance-no-literals

accuracy_rollup

Question: What percent of reportable questions did the student answer correctly?

API-only rule: GET /accuracy-rollups returns correct_question_count, total_question_count, incorrect_question_count, accuracy_percent, source_import_id, quality_status, and null_reason at the documented student/subject/window grain.

Raw DB plus dictionary rule: Query alpha.analytics_accuracy; use reporting.processed_facts only through source_import/source-ref audit views, never as report math. If total_question_count is zero, read null_reason=no_questions and do not substitute 0% or 100%.

Failure mode: A report computes accuracy by summing raw processed_facts, counting QTI/Events attempts, keeping a private app/source allowlist, or dividing and rounding locally.

ITDs: aitd-001-report-source-ingestion aitd-004-provenance-no-literals aitd-011-policy-and-enum-normalization

mastery_delta_not_state

Question: Which KCs changed between dates?

API-only rule: GET /mastery-deltas filters by effective_at and state_dimension.

Raw DB plus dictionary rule: Query alpha.analytics_mastery_delta; current state remains Results student_kc_state and mastery math remains Results/Policy.

Failure mode: A report rebuilds mastery/decay/HMG from attempts or treats Analytics as current state of record.

ITDs: aitd-008-mastery-and-grade-levels

map_norms_windows

Question: What RIT and Growth X did the student have under a norms set?

API-only rule: GET /map-growth-rollups accepts typed normsSet/growthWindow/term filters and returns report-ready values; the Learning Report "vs 1yr ago" panel requests growthWindow=winter_to_winter.

Raw DB plus dictionary rule: Query alpha.analytics_map_growth_rollup by canonical_term_id, growth_window, and norms_set; for the Learning Report MAP panel read growth_percentile and growth_x from growth_window=winter_to_winter, not fall_to_winter. Do not parse terms, choose retakes, maintain a private norms table, or compute Growth X as observed/typical.

Failure mode: A raw report mixes 2020/2025 norms, selects fall_to_winter for a one-year panel, computes observed/typical, ships a private norms/R90 table, or disagrees with Results/NWEAMap test-of-record.

ITDs: aitd-009-map-growth-rollups aitd-001-report-source-ingestion

scale_reference_resources

Question: What RIT maps to a percentile, and what R90/effective grade maps to a RIT?

API-only rule: GET /norms and /r90/table return versioned readable resources; GET /norms/rit, /norms/percentile, and /r90 return translated values with norms_set, table_version, calculator_version, and source_point_kind where applicable.

Raw DB plus dictionary rule: Read alpha.analytics_norms_achievement by norms_set/table_version/subject_id/role/grade_key/season and apply the documented normal model. Read alpha.analytics_r90_table by table_version='analytics.rit_to_grade.powerpath.v2026-06-15' and table_subject_id, then choose the row whose rit_score equals the requested RIT. Missing subjects/table points return typed missing/validation behavior; missing RIT rows return source_missing rather than a fabricated value.

Failure mode: GOALS, Learning Report, and skill-pack examples disagree because one app cached a private norms table or R90 copy, silently changed versions, chose a nearest row, used an old between-row method, or skipped the current table_version filter.

ITDs: aitd-015-norms-r90-readable-resources aitd-016-scale-translation-apis aitd-017-r90-version-supersession

goals_school_days_remaining

Question: How many instructional days remain, and what daily effort is needed to finish by the target date?

API-only rule: GET /school-days-remaining returns analytics.school_days_remaining with instructional_days_remaining, instructional_dates, xp_unit.*, effort.*, provenance.*, and links.*. The endpoint uses the half-open [asOf,endDate) window and alpha.policy.analytics.xp_expected_minute.v2026-06-14.

Raw DB plus dictionary rule: Resolve the student's school through People and Orgs effective-dated enrollment, read alpha.school_calendar where calendar_date >= :asOf and calendar_date < :endDate, filter to instructional school days under alpha.policy.school_day.v2026-06-10, exclude MAP-testing days by policy, and apply alpha.policy.analytics.xp_expected_minute.v2026-06-14: 1 XP = 1 expected minute and 60 XP = 1 expected hour. Do not read consumed-day rollups for future dates, count weekdays, use attendance days, parse school_period labels, or hard-code XP_PER_HOUR.

Failure mode: GOALS or a skill pack computes a different target date because it counts weekdays/summer gaps locally, includes MAP days, uses current-only enrollment, treats endDate as inclusive, or carries a private XP_PER_HOUR constant.

ITDs: aitd-018-goals-school-days-remaining aitd-006-school-day-minutes aitd-007-xp-rollups

completion_scope

Question: What progress evidence exists for a course/subject/track?

API-only rule: GET /completion-rollups returns xp_earned, xp_remaining, completion_percent, source_import_id, and null_reason as Analytics evidence.

Raw DB plus dictionary rule: Query alpha.analytics_completion_rollup by completion_scope and scope_id for evidence. Course rows prefer app-reported Caliper percent, then XP remaining; grade_level rows preserve main-course evidence only. Student-facing progress reads come from Results. Do not derive progress from MAP/RIT/R90, walk Curriculum trees, count Results client-side, or use activity flags.

Failure mode: The raw path diverges when a client treats Analytics evidence as the final app-facing answer, treats RIT grade-band percent as actual progress, includes hole-filling courses in grade-level progress, or invents 1-if-active completion.

ITDs: aitd-010-completion-rollups aitd-001-report-source-ingestion aitd-004-provenance-no-literals

source_import_status

Question: Did source-shaped import materialize rows?

API-only rule: POST /source-imports/{adapter} accepts only named adapters; timeback-map accepts full source-shaped hp_map_results rows and ignores unknown extra columns; validation failures return analytics:validation_failed with HTTP 400; adapter rejections return analytics:adapter_rejected with HTTP 422; successful imports report accepted/materialized counts.

Raw DB plus dictionary rule: Migration checks count readable alpha.analytics_* rows plus analytics_source_import.materialized_row_count, not submitted source rows or Problem bodies.

Failure mode: A lying HTTP 200 with zero readable rows, or a timeback-map rejection caused only by unknown extra hp_map_results columns, recreates the migration failure Analytics exists to fix.

ITDs: aitd-001-report-source-ingestion aitd-108-axis-error-envelope aitd-110-axis-conformance-evidence

Reference

Endpoint reference

POST

Mint a demo token

/dev/mint?tenantId=demo

Create a short-lived demo JWT. This utility is unauthenticated and only accepts tenantId=demo.

Auth
No Authorization header.
Scopes
demo utility
Source object
Endpoint catalog runtime utility
No Analytics table is written. The demo token follows the approved endpoint catalog plus the auth and tenant-routing decisions.
Architecture
aitd-106-axis-auth-shape aitd-109-axis-tenant-routing

Request and query parameters

NameTypeRequiredDescription
tenantIdTEXT enumRequiredMust be demo.

Response fields

FieldTypeNullabilityMeaningSource
tokenJWT stringrequiredBearer token scoped to the demo tenant. dictionary
tenantIdTEXTrequiredTenant claim inside the token. dictionary
expiresInINTEGERrequiredSeconds until the token expires. dictionary

cURL

curl -sS -X POST "$ANALYTICS_BASE_URL/dev/mint?tenantId=demo"

JavaScript

const response = await fetch(`${base}/dev/mint?tenantId=demo`, { method: "POST" });
const { token } = await response.json();

Example response

{
  "token": "demo.analytics.jwt",
  "tenantId": "demo",
  "expiresIn": 3600
}

GET

List event time facts

/alpha/analytics/v1/event-time-facts

Read signed active, inactive, and waste-second contributions written when source Events close. Use this for audit and low-level time tracing.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_event_time_fact
Stores signed active, inactive, and waste second contributions for the DERIVED Events axis, so reports can audit time-on-task without classifying raw Caliper events themselves.
Architecture
aitd-005-time-facts-and-windows aitd-003-close-time-materialization aitd-012-corrections-and-reversals

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id. Omit only for authorized cohort reads.
subjectIdsubject_id enumOptionalCanonical subject after server-side subject normalization.
startDateDATEOptionalInclusive start date in the school/reporting timezone.
endDateDATEOptionalExclusive end date. Must be later than startDate.
modifiedSinceTIMESTAMPTZOptionalUTC ISO-8601 polling checkpoint over modified_at.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor returned by the previous page.
factKindfact_kind enumOptionalactive_seconds, inactive_seconds, or waste_seconds.
sourceEventIdTEXTOptionalEvents Alpha event id that produced the fact.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
source_event_idTEXTrequiredEvents Alpha event id this contribution derives from. Must reference one closed Events row in the same tenant.dictionary
fact_kindTEXT enumrequiredWhich time bucket this signed contribution belongs to. Allowed values: fact_kind enum. Set by the versioned time-classification policy named in policy_ref, for example alpha.policy.analytics.time_classification.v2026-06-10.dictionary
seconds_deltaNUMERIC(14,3)requiredSigned seconds contributed by this source event to the fact_kind bucket. May be negative for correction/reversal rows; ordinary display measures use rollup fields, not client-side clamping.dictionary
subject_idTEXTnullable where the row is all-subject or the source cannot validly resolve a subjectCanonical Alpha subject used for reporting and grouping. Closed Alpha subject enum after write-time alias folds; unknown source subject becomes an adapter finding or null_reason, not a new string.dictionary
application_refTEXTnullableApplication/activity-source reference resolved from the source event when available. Reference only; application identity/lifecycle belongs to Applications.dictionary
policy_refTEXTrequiredNamed policy/config version used to compute the measure or rollup. Must point at alpha.policy.analytics.* or an inherited Curriculum/Results policy active for the source effective date.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this fact. Allowed values: quality_status enum. Ordinary report reads keep ok, corrected, and signed reversed rows according to the table rule; audit reads may include source_missing/source_unlinked/policy_pending/adapter_rejected.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/event-time-facts?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&factKind=active_seconds&startDate=2026-05-01&endDate=2026-06-01" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/event-time-facts`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/event-time-facts",
  "data": [
    {
      "id": "8e9c0ef2-bc75-4f53-8124-df77df49f25f",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "source_event_id": "evt_alpha_000891",
      "fact_kind": "active_seconds",
      "seconds_delta": 420,
      "subject_id": "math",
      "application_ref": "app_math_academy",
      "policy_ref": "alpha.policy.analytics.time_classification.v2026-06-10",
      "quality_status": "ok",
      "modified_at": "2026-05-14T18:32:22Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/event-time-facts"
  }
}

GET

List time windows

/alpha/analytics/v1/time-windows

Read active, inactive, waste, and display minutes by student, subject, and reporting window.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_time_window
Report-ready active, inactive, and waste time totals for the DERIVED Events axis. These complement the ingested Time Commitment tile and support audit/Waste views.
Architecture
aitd-005-time-facts-and-windows aitd-011-policy-and-enum-normalization

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id. Omit only for authorized cohort reads.
subjectIdsubject_id enumOptionalCanonical subject after server-side subject normalization.
startDateDATEOptionalInclusive start date in the school/reporting timezone.
endDateDATEOptionalExclusive end date. Must be later than startDate.
modifiedSinceTIMESTAMPTZOptionalUTC ISO-8601 polling checkpoint over modified_at.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor returned by the previous page.
windowKindwindow_kind enumOptionalday, week, term, school_year, or custom.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
subject_idTEXTnullable where the row is all-subject or the source cannot validly resolve a subjectCanonical Alpha subject used for reporting and grouping. Closed Alpha subject enum after write-time alias folds; unknown source subject becomes an adapter finding or null_reason, not a new string.dictionary
window_kindTEXT enumrequiredNamed window family for this rollup. Allowed values: window_kind enum; custom requires explicit policy/window metadata.dictionary
window_startDATErequiredInclusive start date of the reporting window in the school timezone. Must be before window_end; ISO date.dictionary
window_endDATErequiredExclusive end date of the reporting window in the school timezone. Must be after window_start; ISO date.dictionary
active_seconds_totalNUMERIC(14,3)requiredNet active seconds from current event_time_fact rows in this window. Can include signed corrections; display fields apply policy presentation.dictionary
inactive_seconds_totalNUMERIC(14,3)requiredNet inactive seconds from current event_time_fact rows in this window. Can include signed corrections.dictionary
waste_seconds_totalNUMERIC(14,3)requiredNet waste/gaming seconds from current event_time_fact rows in this window. Can be positive or negative after corrections.dictionary
display_active_minutesNUMERIC(12,3)requiredPolicy-defined active minutes shown in reports for this time window. Computed by Analytics from active_seconds_total under policy_ref; consumers read it directly.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this fact. Allowed values: quality_status enum. Ordinary report reads keep ok, corrected, and signed reversed rows according to the table rule; audit reads may include source_missing/source_unlinked/policy_pending/adapter_rejected.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/time-windows?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&subjectId=math&windowKind=day&startDate=2026-05-01&endDate=2026-06-01" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/time-windows`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/time-windows",
  "data": [
    {
      "id": "1b0d98de-8f58-4ec3-9f2b-5cfbfa3d0001",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "subject_id": "math",
      "window_kind": "day",
      "window_start": "2026-05-14",
      "window_end": "2026-05-15",
      "active_seconds_total": 3240,
      "inactive_seconds_total": 120,
      "waste_seconds_total": 0,
      "display_active_minutes": 54,
      "quality_status": "ok",
      "modified_at": "2026-05-14T23:59:59Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/time-windows"
  }
}

GET

List minutes per enrolled school day

/alpha/analytics/v1/school-day-minutes

Read active minutes, enrolled-school-day denominator, MAP-day exclusions, and null_reason when the calendar prerequisite is not yet materialized.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_school_day_minutes
Minutes per enrolled school day for the Time Commitment tile. Numerator is ingested from reporting.processed_facts.active_seconds; denominator is alpha.school_calendar intersected with enrollment/subject assignment, with MAP-day exclusion.
Architecture
aitd-006-school-day-minutes aitd-014-source-ownership-and-prerequisites

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id. Omit only for authorized cohort reads.
subjectIdsubject_id enumOptionalCanonical subject after server-side subject normalization.
startDateDATEOptionalInclusive start date in the school/reporting timezone.
endDateDATEOptionalExclusive end date. Must be later than startDate.
modifiedSinceTIMESTAMPTZOptionalUTC ISO-8601 polling checkpoint over modified_at.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor returned by the previous page.
schoolIdTEXTOptionalPeople and Orgs school id used for the calendar denominator.
includeUnavailableBOOLEANOptionalWhen true, include current source_missing or policy_pending rows for UI explanation.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
school_idTEXTrequiredPeople and Orgs school whose calendar/enrollment intersection defines the denominator. Must resolve as the student school for the effective range; no current-only shortcut.dictionary
subject_idTEXTnullable; null means all reportable subjectsCanonical Alpha subject used for reporting and grouping. Closed Alpha subject enum after write-time alias folds; unknown source subject becomes an adapter finding or null_reason, not a new string.dictionary
window_startDATErequiredInclusive start date for the denominator/numerator window. ISO date; half-open range with window_end.dictionary
window_endDATErequiredExclusive end date for the denominator/numerator window. ISO date; must be after window_start.dictionary
active_minutesNUMERIC(12,3)requiredPolicy-defined active minutes for the same window and subject grain. Normalized by the timeback-xp-time-accuracy adapter from reporting.processed_facts.active_seconds, then stored as minutes under policy_ref.dictionary
enrolled_school_day_countINTEGERrequired when null_reason=none; nullable only for source_missing repair rowsDenominator: school days intersecting the student effective-dated enrollment and subject assignment when subject_id is present. Nonnegative; excludes weekends, holidays, teacher-workshop days, MAP-testing days, and non-time-locatable enrollments. Null only with a typed repair reason such as source_missing.dictionary
excluded_school_day_countINTEGERrequired when null_reason=none; nullable only for source_missing repair rowsCalendar dates inside the requested window that the school-day policy excluded from the denominator. Nonnegative; includes weekend, holiday_break, teacher_workshop, nwea_map_testing, and governed other exclusions. Null only with a typed repair reason such as source_missing.dictionary
minutes_per_enrolled_school_dayNUMERIC(12,3)nullable when denominator is zero or policy/source is missingReport-ready average active minutes per enrolled school day when the school-day denominator source is available. Formula: round(active_minutes / enrolled_school_day_count, 3) when enrolled_school_day_count > 0 and null_reason=none. Null requires null_reason other than none; consumers never recompute the denominator or substitute active days.dictionary
school_day_policy_refTEXTrequiredNamed policy/config version used to compute the measure or rollup. Must point at alpha.policy.analytics.* or an inherited Curriculum/Results policy active for the source effective date.dictionary
null_reasonTEXT enumrequiredWhy minutes_per_enrolled_school_day and denominator counts are null or not reportable. Allowed values: null_reason enum. none means the metric is populated; source_missing means the required calendar source does not exist or is unavailable and the row is not reportable.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this school-day denominator row. Allowed values: quality_status enum. Ordinary Learning Report rows are ok or corrected; source_missing is a readable repair state when the calendar source is unavailable.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/school-day-minutes?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&subjectId=math&startDate=2026-05-01&endDate=2026-06-01&includeUnavailable=true" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/school-day-minutes`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/school-day-minutes",
  "data": [
    {
      "id": "2d4b32cd-c4d8-4e45-8a3a-9de8ce3b0002",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "school_id": "school_alpha_austin",
      "subject_id": "math",
      "window_start": "2026-05-01",
      "window_end": "2026-06-01",
      "active_minutes": 1080,
      "enrolled_school_day_count": 20,
      "excluded_school_day_count": 2,
      "minutes_per_enrolled_school_day": 54,
      "school_day_policy_ref": "alpha.policy.school_day.v2026-06-10",
      "null_reason": "none",
      "quality_status": "ok",
      "modified_at": "2026-06-01T01:00:00Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/school-day-minutes"
  }
}

GET

List XP rollups

/alpha/analytics/v1/xp-rollups

Read report-ready XP totals, goals, remaining XP, and signed correction subtotals while Results remains the immutable XP ledger.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_xp_rollup
Report-ready XP total plus goal progress fields. The goal fields use the same materialized school-day denominator as time commitment.
Architecture
aitd-007-xp-rollups aitd-001-report-source-ingestion aitd-004-provenance-no-literals

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id. Omit only for authorized cohort reads.
subjectIdsubject_id enumOptionalCanonical subject after server-side subject normalization.
startDateDATEOptionalInclusive start date in the school/reporting timezone.
endDateDATEOptionalExclusive end date. Must be later than startDate.
modifiedSinceTIMESTAMPTZOptionalUTC ISO-8601 polling checkpoint over modified_at.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor returned by the previous page.
windowKindwindow_kind enumOptionalday, week, term, school_year, or custom.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
subject_idTEXTnullable where the row is all-subject or the source cannot validly resolve a subjectCanonical Alpha subject used for reporting and grouping. Closed Alpha subject enum after write-time alias folds; unknown source subject becomes an adapter finding or null_reason, not a new string.dictionary
window_kindTEXT enumrequiredNamed window family for the XP rollup. Allowed values: window_kind enum.dictionary
window_startDATErequiredInclusive start date for included XP ledger effective dates. ISO date; half-open with window_end.dictionary
window_endDATErequiredExclusive end date for included XP ledger effective dates. ISO date; must be after window_start.dictionary
positive_xpNUMERIC(14,3)requiredPositive XP contribution subtotal retained for audit/explanation. Nonnegative; normalized by the adapter/materializer, with Results xp_ledger refs when available.dictionary
negative_xpNUMERIC(14,3)requiredSum of negative XP penalties/reversals in this rollup. Zero or negative; gaming/cheating signals and reversals stay signed.dictionary
xp_totalNUMERIC(14,3)requiredPolicy-defined report XP total after the report-source adapter applies subject folds, exclusions, corrections, and signed contributions. Non-null values require source_import_id provenance; should equal the report value for this student/subject/window.dictionary
net_xpNUMERIC(14,3)requiredSigned audit total after positive and negative XP contributions; equal to xp_total for ordinary report rows under the current policy. positive_xp + negative_xp under policy_ref; retained for correction/reversal reconciliation.dictionary
xp_goalNUMERIC(14,3)required when null_reason=none; nullable only when enrolled_school_day_count or XP goal policy is missingWindow XP target for this student, subject, and window, used by the Learning Report XP Remaining and XP Goal Percent panels. Nonnegative when populated; equals daily_xp_goal multiplied by enrolled_school_day_count when both are present. Null requires a typed repair reason such as source_missing or policy_pending.dictionary
daily_xp_goalNUMERIC(14,3)nullable when daily XP policy is missingDaily XP target used in the auditor-pinned XP goal percent denominator. Nonnegative; read from alpha.policy.analytics.xp_goal_percent.v1 / Curriculum Policy, never from a consumer constant.dictionary
enrolled_school_day_countINTEGERrequired when null_reason=none; nullable only when school-day denominator source is missingNumber of days in the XP percent denominator: days the student was enrolled and school was in session. Nonnegative integer when populated. Enrollment begin/end bound the count; the student's school calendar governs it; MAP testing days are excluded; per-subject rows count only days with a subject assignment. Null requires a typed repair reason such as source_missing.dictionary
xp_remainingNUMERIC(14,3)required when null_reason=none; nullable when xp_goal is null/source_missingReport-ready remaining XP under policy_ref. max(xp_goal - xp_total, 0) under policy_ref when xp_goal is present; stored by Analytics so reports do not subtract locally. Null requires a typed repair reason.dictionary
xp_goal_percentNUMERIC(9,3)nullable when daily_xp_goal or enrolled_school_day_count is null or zeroUncapped percent of the XP goal reached in this window when the school-day denominator is available. Formula under alpha.policy.analytics.xp_goal_percent.v1 when populated: xp_total / (daily_xp_goal x enrolled_school_day_count) x 100. Values above 100 are valid and must remain visible. Null requires a typed repair reason.dictionary
xp_completion_percentNUMERIC(9,3)deprecated compatibility alias; nullability matches xp_goal_percentDeprecated compatibility alias for xp_goal_percent. Must equal xp_goal_percent exactly and remains uncapped. It has no independent capped-completion meaning. Nullability matches xp_goal_percent.dictionary
on_trackBOOLEANnullable when xp_goal_percent is nullWhether the student is on track against the XP goal policy for this window. Computed under policy_ref; null means the goal policy or denominator is missing.dictionary
award_countINTEGERrequiredNumber of positive or neutral XP ledger rows contributing to this rollup. Nonnegative; excludes superseded rows unless audit query requests them.dictionary
reversal_countINTEGERrequiredNumber of reversal/correction XP ledger rows included. Nonnegative.dictionary
source_import_idTEXTrequired for report-tile rows; nullable for purely derived event-axis rowsAnalytics source-import receipt that proves which named report-source adapter produced this report-grade fact. Must reference alpha.analytics_source_import for non-null Learning Report tile values; no literal or fixture value may be reportable without this provenance.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this fact. Allowed values: quality_status enum. Ordinary report reads keep ok, corrected, and signed reversed rows according to the table rule; audit reads may include source_missing/source_unlinked/policy_pending/adapter_rejected.dictionary
null_reasonTEXT enumrequiredWhy XP goal fields are null or unavailable while xp_total itself may still be reportable. Allowed values: null_reason enum. none means xp_goal, xp_remaining, xp_goal_percent, and on_track are populated; source_missing means the shared school-day denominator source is absent and the goal fields are not reportable.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/xp-rollups?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&subjectId=math&windowKind=term&startDate=2026-01-01&endDate=2026-06-01" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/xp-rollups`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/xp-rollups",
  "data": [
    {
      "id": "3f2b79e1-6182-4e21-a0b6-7d6cf7530003",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "subject_id": "math",
      "window_kind": "term",
      "window_start": "2026-01-01",
      "window_end": "2026-06-01",
      "positive_xp": 11850,
      "negative_xp": -50,
      "xp_total": 11800,
      "net_xp": 11800,
      "xp_goal": 9000,
      "daily_xp_goal": 100,
      "enrolled_school_day_count": 90,
      "xp_remaining": 0,
      "xp_goal_percent": 131.111,
      "xp_completion_percent": 131.111,
      "on_track": true,
      "award_count": 24,
      "reversal_count": 1,
      "source_import_id": "imp_01HT8XPTIME_2026_SPRING",
      "quality_status": "ok",
      "null_reason": "none",
      "modified_at": "2026-06-01T01:02:00Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/xp-rollups"
  }
}

GET

List accuracy rollups

/alpha/analytics/v1/accuracy-rollups

Read report-ready correct questions, total questions, incorrect questions, and accuracy percent without client-side processed_facts math.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_accuracy
Report-ready Accuracy panel facts: correct questions, total questions, incorrect questions, and percent correct without client-side processed_facts math.
Architecture
aitd-001-report-source-ingestion aitd-004-provenance-no-literals aitd-011-policy-and-enum-normalization

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id. Omit only for authorized cohort reads.
subjectIdsubject_id enumOptionalCanonical subject after server-side subject normalization.
startDateDATEOptionalInclusive start date in the school/reporting timezone.
endDateDATEOptionalExclusive end date. Must be later than startDate.
modifiedSinceTIMESTAMPTZOptionalUTC ISO-8601 polling checkpoint over modified_at.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor returned by the previous page.
windowKindwindow_kind enumOptionalday, week, term, school_year, or custom.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
subject_idTEXTnullable where the row is all-subject or the source cannot validly resolve a subjectCanonical Alpha subject used for reporting and grouping. Closed Alpha subject enum after write-time alias folds; unknown source subject becomes an adapter finding or null_reason, not a new string.dictionary
window_kindTEXT enumrequiredNamed window family for the Accuracy rollup. Allowed values: window_kind enum.dictionary
window_startDATErequiredInclusive start date for source question rows included in this rollup. ISO date; half-open with window_end.dictionary
window_endDATErequiredExclusive end date for source question rows included in this rollup. ISO date; must be after window_start.dictionary
correct_question_countINTEGERrequiredReportable correct-question numerator after the adapter applies subject, app/source, score_type, correction, and real-student rules. Nonnegative integer; cannot exceed total_question_count when total_question_count is present. Normalized from source-shaped reporting.processed_facts.correct_questions, not read by reports from the source table.dictionary
total_question_countINTEGERrequiredReportable question-attempt denominator after the adapter applies the same policy filters as correct_question_count. Nonnegative integer. Zero is allowed only with null_reason=no_questions and null accuracy_percent.dictionary
incorrect_question_countINTEGERrequiredReportable incorrect-question count retained for explanations. Nonnegative integer; equals total_question_count - correct_question_count under the policy version.dictionary
accuracy_percentNUMERIC(9,3)nullable when total_question_count is zero or source/policy is missingReport-ready percent of reportable questions answered correctly. 0 through 100 when populated; null requires a typed null_reason. Formula and rounding are owned by Analytics under policy_ref.dictionary
source_import_idTEXTrequired for report-tile rows; nullable for purely derived event-axis rowsAnalytics source-import receipt that proves which named report-source adapter produced this report-grade fact. Must reference alpha.analytics_source_import for non-null Learning Report tile values; no literal or fixture value may be reportable without this provenance.dictionary
source_fact_refsJSONBrequiredCompact ids/hashes/cursor for source-shaped processed_facts rows accepted by the adapter. Stores refs/hashes only; never copies raw processed_facts rows or student identifiers.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this fact. Allowed values: quality_status enum. Ordinary report reads keep ok, corrected, and signed reversed rows according to the table rule; audit reads may include source_missing/source_unlinked/policy_pending/adapter_rejected.dictionary
null_reasonTEXT enumrequiredWhy accuracy_percent is null or not reportable. Allowed values: null_reason enum. no_questions means the source window had zero reportable question attempts; none means the metric is populated.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/accuracy-rollups?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&subjectId=math&windowKind=term&startDate=2026-01-01&endDate=2026-06-01" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/accuracy-rollups`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/accuracy-rollups",
  "data": [
    {
      "id": "9c3f3e4f-1fd8-4c28-8a72-cbbf2e6d0006",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "subject_id": "math",
      "window_kind": "term",
      "window_start": "2026-01-01",
      "window_end": "2026-06-01",
      "correct_question_count": 480,
      "total_question_count": 600,
      "incorrect_question_count": 120,
      "accuracy_percent": 80,
      "source_import_id": "imp_01HT8XPTIME_2026_SPRING",
      "source_fact_refs": [
        "processed_facts_hash_01HT7Q"
      ],
      "quality_status": "ok",
      "null_reason": "none",
      "modified_at": "2026-06-01T01:02:30Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/accuracy-rollups"
  }
}

GET

List mastery deltas

/alpha/analytics/v1/mastery-deltas

Read dated changes in Results-owned KC mastery state. The current state remains in Results; Analytics reports what changed in the window.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_mastery_delta
Grade Levels Mastered facts for the Learning Report plus a typed change log of mastery state movements; Results remains the current mastery state of record.
Architecture
aitd-008-mastery-and-grade-levels aitd-014-source-ownership-and-prerequisites

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id.
kcIdTEXTOptionalKnowledge Component id.
stateDimensionstate_dimension enumOptionaldurable mastery, working grade, highest mastered grade, or another governed dimension.
transitionKindtransition_kind enumOptionalHow the state changed.
startDateDATEOptionalInclusive boundary for effective_at.
endDateDATEOptionalExclusive boundary for effective_at.
modifiedSinceTIMESTAMPTZOptionalPoll for changed rows.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
kc_idTEXTnullable when the row is a grade-level mastered fact without a single KCKnowledge Component whose student state changed. Must reference a Curriculum KC; never a standard or lesson id.dictionary
grade_subjectTEXT enumnullable except grade-level mastered rowsSubject for a Grade Levels Mastered row shown in the Learning Report. Closed Alpha subject enum after write-time normalization; required when grade_level/completed_on are populated.dictionary
grade_levelINTEGERnullable except grade-level mastered rowsGrade level the student has mastered in the subject. Positive integer; set by the grade-mastery adapter from passed gate/assessment evidence.dictionary
state_dimensionTEXT enumrequiredWhich Results mastery-state dimension changed. Allowed values: state_dimension enum.dictionary
effective_atTIMESTAMPTZrequiredWhen the state transition is effective for as-of reporting. UTC timestamp; comes from Results state transition, not Analytics computed_at.dictionary
previous_valueJSONBnullableTyped previous value for the changed state dimension. JSON shape must match state_dimension; null allowed for first known state.dictionary
new_valueJSONBrequiredTyped new value for the changed state dimension. JSON shape must match state_dimension.dictionary
delta_valueNUMERIC(12,6)nullableNumeric difference where the state dimension has a numeric value. Null for non-numeric dimensions such as next_due_at or fluency_state.dictionary
transition_kindTEXT enumrequiredHuman-readable category of the state transition. Allowed values: acquired, improved, decayed, review_due, fluency_changed, corrected, blocked. Set by Results/Analytics materializer policy; reports group by this value instead of classifying attempts.dictionary
source_result_refsJSONBrequiredResult/evidence ids that caused or explain the state transition or grade-level mastery fact. Stores ids/hashes only; no raw scored payload copies.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this fact. Allowed values: quality_status enum. Ordinary report reads keep ok, corrected, and signed reversed rows according to the table rule; audit reads may include source_missing/source_unlinked/policy_pending/adapter_rejected.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/mastery-deltas?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&stateDimension=durable_mastery&startDate=2026-05-01&endDate=2026-06-01" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/mastery-deltas`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/mastery-deltas",
  "data": [
    {
      "id": "4e9268b7-e76a-46d9-9b6e-899aa2df0004",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "kc_id": "kc_math_fraction_addition_unlike_denominators",
      "grade_subject": "math",
      "grade_level": 5,
      "state_dimension": "durable_mastery",
      "effective_at": "2026-05-14T18:45:00Z",
      "previous_value": {
        "value": 0.82
      },
      "new_value": {
        "value": 0.91
      },
      "delta_value": 0.09,
      "transition_kind": "acquired",
      "source_result_refs": [
        "result_01HT7K8BR3",
        "result_kc_evidence_01HT7K8BR3_KC"
      ],
      "quality_status": "ok",
      "modified_at": "2026-05-14T18:45:04Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/mastery-deltas"
  }
}

GET

List MAP growth rollups

/alpha/analytics/v1/map-growth-rollups

Read report-ready RIT, achievement percentile, growth percentile, Growth X, target, on-track state, sitting count, and retake count.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_map_growth_rollup
Report-ready RIT, achievement percentile, growth percentile, Growth X, MAP window, sitting-count, retake, and on-track facts without client-side norms tables, sitting selection, or term parsing.
Architecture
aitd-009-map-growth-rollups aitd-001-report-source-ingestion aitd-011-policy-and-enum-normalization

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id.
subjectIdsubject_id enumOptionalCanonical Alpha subject id.
termIdTEXTOptionalPublic API alias for canonical_term_id.
growthWindowgrowth_window enumOptionalUse winter_to_winter for the Learning Report vs one-year panel.
normsSetnorms_set enumOptional2020 or 2025. This selects stored rollup rows; clients do not carry norm tables.
modifiedSinceTIMESTAMPTZOptionalPoll for changed rows.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
subject_idTEXTrequired for MAP rollupsCanonical Alpha subject used for reporting and grouping. Closed Alpha subject enum after write-time alias folds; unknown source subject becomes an adapter finding or null_reason, not a new string.dictionary
canonical_term_idTEXTrequiredAlpha canonical term id for the MAP observation/window. Set by Results/NWEAMap term normalization; not parsed by Analytics clients.dictionary
growth_windowTEXT enumrequiredMAP growth interval represented by this row. Allowed values: growth_window enum.dictionary
norms_setTEXT enumrequiredNWEA norms family used for projected growth and normed outputs. Allowed values: norms_set enum; the same report can be regenerated by changing this filter.dictionary
rit_scoreNUMERIC(6,2)requiredReport-ready RIT value from Results MAP test-of-record for this subject/term. Positive numeric; no local test-of-record choice in Analytics clients.dictionary
achievement_percentileNUMERIC(6,2)nullable when the MAP source row has no achievement percentileAchievement percentile for the test-of-record sitting. 0 to 100 when present; for hp_map_results this is normalized from testpercentile.dictionary
growth_percentileNUMERIC(6,2)nullable when the selected growth_window has no conditional growth percentileConditional growth percentile for the selected MAP window; the Learning Report "vs 1yr ago" panel reads this from the winter_to_winter row. 0 to 100 when present; null requires null_reason for report windows that should have growth.dictionary
observed_growthNUMERIC(7,3)nullable when no paired window existsActual RIT growth for the growth_window. Null for term-only observations; otherwise computed by Results MAP views.dictionary
projected_growthNUMERIC(7,3)nullable when no projected growth existsNWEA projected-growth value for the selected window. Must be greater than 0 when growth_x is present; comes from Results/NWEAMap normalized projections and never from a client norms table.dictionary
growth_xNUMERIC(8,4)nullable when observed or projected growth is unavailableObserved growth divided by projected_growth, using the named norms_set and growth_window. Null requires null_reason; target comparisons use growth_x_target.dictionary
growth_x_targetNUMERIC(8,4)requiredAlpha target Growth X for intervention/on-track decisions. Normally 2 under current Alpha policy, but read from policy_ref.dictionary
on_trackBOOLEANnullable when growth_x or target is nullWhether the student is meeting the Growth X target for this row. Computed under policy_ref; null requires null_reason.dictionary
sitting_countINTEGERrequiredNumber of MAP sittings Results observed for this subject/term before test-of-record selection. Nonnegative; includes source-visible sittings according to Results policy.dictionary
retake_countINTEGERrequiredNumber of non-test-of-record sittings represented by the sitting_count. Nonnegative; computed by Results/NWEAMap policy.dictionary
source_import_idTEXTrequired for report-tile rows; nullable for purely derived event-axis rowsAnalytics source-import receipt that proves which named report-source adapter produced this report-grade fact. Must reference alpha.analytics_source_import for non-null Learning Report tile values; no literal or fixture value may be reportable without this provenance.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this fact. Allowed values: quality_status enum. Ordinary report reads keep ok, corrected, and signed reversed rows according to the table rule; audit reads may include source_missing/source_unlinked/policy_pending/adapter_rejected.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/map-growth-rollups?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&subjectId=math&termId=term_2026_winter&growthWindow=winter_to_winter&normsSet=2025" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/map-growth-rollups`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/map-growth-rollups",
  "data": [
    {
      "id": "5a41fb2a-5d08-45fd-a62b-58a518060005",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "subject_id": "math",
      "canonical_term_id": "term_2026_winter",
      "growth_window": "winter_to_winter",
      "norms_set": "2025",
      "rit_score": 239,
      "achievement_percentile": 91,
      "growth_percentile": 43,
      "observed_growth": 6,
      "projected_growth": 7,
      "growth_x": 0.8571,
      "growth_x_target": 2,
      "on_track": false,
      "sitting_count": 2,
      "retake_count": 1,
      "source_import_id": "imp_01HT8MAP_2026_WINTER",
      "quality_status": "ok",
      "modified_at": "2026-05-14T19:00:00Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/map-growth-rollups"
  }
}

GET

Read the NWEA norms resource

/alpha/analytics/v1/norms

Read the versioned achievement-status norms resource that powers percentile-to-RIT and RIT-to-percentile translation. Same contract as /norms/table; cache by table_version and never ship a private norms table.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_norms_achievement
Readable NWEA achievement-status norms resource for percentile-to-RIT and RIT-to-percentile translation. Apps may read/cache the surface table by table_version or call /norms/rit and /norms/percentile; they never maintain their own norms table.
Architecture
aitd-015-norms-r90-readable-resources aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
subjectsubject_id enumOptionalmath, reading, language, or science. subjectId is accepted as an alias.
gradeTEXTOptionalK or 1 through 12. gradeLevel is accepted as an alias.
seasonTEXT enumOptionalfall, winter, or spring. termSeason is accepted as an alias.
roleTEXTOptionalNWEA role/population label. Defaults to student for public student translations.
normsSetnorms_set enumOptional2020 or 2025. Omit to list both published sets.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredAlways list. dictionary
tableTEXTrequiredanalytics.norms_table list resource over alpha.analytics_norms_achievement. dictionary
table_versionTEXTrequiredVersion of the readable norms table. dictionary
data[].idTEXTrequiredStable Analytics id for this norms row. dictionary
data[].norms_setTEXT enumrequiredNorms family for the row. dictionary
data[].subject_idTEXTrequiredCanonical subject for the norms row. dictionary
data[].source_subject_nameTEXTrequiredPublished source subject name for audit display. dictionary
data[].roleTEXTrequiredNWEA role/population label for the row. dictionary
data[].grade_keyTEXTrequiredNWEA grade key for the row. dictionary
data[].grade_levelINTEGERrequiredNumeric grade level for sorting and display. dictionary
data[].seasonTEXTrequiredMAP testing season for the row. dictionary
data[].mean_ritNUMERIC(7,2)requiredPublished mean RIT for the row. dictionary
data[].sd_ritNUMERIC(6,3)requiredPublished RIT standard deviation for the row. dictionary
data[].calculator_versionTEXTrequiredVersion of the surface norms calculator/table. dictionary
data[].source_refTEXTrequiredOpaque provenance pointer to NWEA-published norms. dictionary
limitINTEGERrequiredRows returned in this page. dictionary
hasMoreBOOLEANrequiredWhether another page exists. dictionary
nextCursorTEXT nullablerequiredOpaque cursor for the next page. dictionary
links.selfURLrequiredThe request URL for this page. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms?subject=math&grade=5&season=winter&normsSet=2025&limit=1" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/norms`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "table": "analytics.norms_table",
  "table_version": "analytics.norms.achievement.v2026-06-12",
  "data": [
    {
      "id": "norms_2025_math_student_5_winter",
      "norms_set": "2025",
      "subject_id": "math",
      "source_subject_name": "Math",
      "role": "student",
      "grade_key": "5",
      "grade_level": 5,
      "season": "winter",
      "mean_rit": 211.82,
      "sd_rit": 17.42,
      "calculator_version": "analytics.norms.achievement.v2026-06-12",
      "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)"
    }
  ],
  "limit": 1,
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/norms?subject=math&grade=5&season=winter&normsSet=2025&limit=1"
  }
}

GET

Read the filterable NWEA norms table

/alpha/analytics/v1/norms/table

Read the paginated table rows for the current or requested NWEA achievement-status norms table version.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_norms_achievement
Readable NWEA achievement-status norms resource for percentile-to-RIT and RIT-to-percentile translation. Apps may read/cache the surface table by table_version or call /norms/rit and /norms/percentile; they never maintain their own norms table.
Architecture
aitd-015-norms-r90-readable-resources aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
subjectsubject_id enumOptionalmath, reading, language, or science. subjectId is accepted as an alias.
gradeTEXTOptionalK or 1 through 12. gradeLevel is accepted as an alias.
seasonTEXT enumOptionalfall, winter, or spring. termSeason is accepted as an alias.
roleTEXTOptionalNWEA role/population label. Defaults to student for public student translations.
normsSetnorms_set enumOptional2020 or 2025. Omit to list both published sets.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredAlways list. dictionary
tableTEXTrequiredanalytics.norms_table list resource over alpha.analytics_norms_achievement. dictionary
table_versionTEXTrequiredVersion of the readable norms table. dictionary
data[].idTEXTrequiredStable Analytics id for this norms row. dictionary
data[].norms_setTEXT enumrequiredNorms family for the row. dictionary
data[].subject_idTEXTrequiredCanonical subject for the norms row. dictionary
data[].source_subject_nameTEXTrequiredPublished source subject name for audit display. dictionary
data[].roleTEXTrequiredNWEA role/population label for the row. dictionary
data[].grade_keyTEXTrequiredNWEA grade key for the row. dictionary
data[].grade_levelINTEGERrequiredNumeric grade level for sorting and display. dictionary
data[].seasonTEXTrequiredMAP testing season for the row. dictionary
data[].mean_ritNUMERIC(7,2)requiredPublished mean RIT for the row. dictionary
data[].sd_ritNUMERIC(6,3)requiredPublished RIT standard deviation for the row. dictionary
data[].calculator_versionTEXTrequiredVersion of the surface norms calculator/table. dictionary
data[].source_refTEXTrequiredOpaque provenance pointer to NWEA-published norms. dictionary
limitINTEGERrequiredRows returned in this page. dictionary
hasMoreBOOLEANrequiredWhether another page exists. dictionary
nextCursorTEXT nullablerequiredOpaque cursor for the next page. dictionary
links.selfURLrequiredThe request URL for this page. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&limit=1" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/norms/table`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "table": "analytics.norms_table",
  "table_version": "analytics.norms.achievement.v2026-06-12",
  "data": [
    {
      "id": "norms_2025_math_student_5_winter",
      "norms_set": "2025",
      "subject_id": "math",
      "source_subject_name": "Math",
      "role": "student",
      "grade_key": "5",
      "grade_level": 5,
      "season": "winter",
      "mean_rit": 211.82,
      "sd_rit": 17.42,
      "calculator_version": "analytics.norms.achievement.v2026-06-12",
      "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)"
    }
  ],
  "limit": 1,
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&limit=1"
  }
}

GET

Translate percentile to RIT

/alpha/analytics/v1/norms/rit

Convert a requested achievement percentile to a RIT score using the surface-owned achievement norms table for subject, grade, season, role, and norms set.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_norms_achievement
Readable NWEA achievement-status norms resource for percentile-to-RIT and RIT-to-percentile translation. Apps may read/cache the surface table by table_version or call /norms/rit and /norms/percentile; they never maintain their own norms table.
Architecture
aitd-016-scale-translation-apis aitd-015-norms-r90-readable-resources aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
subjectsubject_id enumRequiredmath, reading, language, or science. subjectId is accepted as an alias.
gradeTEXTRequiredK or 1 through 12. gradeLevel is accepted as an alias.
seasonTEXT enumRequiredfall, winter, or spring. termSeason is accepted as an alias.
roleTEXTOptionalNWEA role/population label. Defaults to student.
percentileNUMERICRequiredPercentile to translate. Must be greater than 0 and less than 100.
normsSetnorms_set enumRequired2020 or 2025.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredanalytics.norms_rit_translation. dictionary
directionTEXT enumrequiredpercentile_to_rit. dictionary
subject_idTEXTrequiredCanonical subject used for the lookup. dictionary
grade_keyTEXTrequiredGrade key used by the published norms resource. dictionary
grade_levelINTEGERrequiredNumeric grade level used by the calculator. dictionary
seasonTEXTrequiredfall, winter, spring, or another published NWEA season label. dictionary
roleTEXTrequiredNWEA role/population label used for the lookup. dictionary
percentileNUMERIC(5,2)requiredRequested percentile. dictionary
rit_scoreNUMERICrequiredRounded RIT score returned by the surface calculator. dictionary
output_ritNUMERICrequiredStable output alias for the rounded RIT score. dictionary
raw_ritNUMERICrequiredUnrounded calculator output, rounded to three decimals. dictionary
norms_setTEXT enumrequiredNorms family used for the lookup. dictionary
calculator_versionTEXTrequiredVersion of the surface norms table and calculator used for the normal-model translation. dictionary
mean_ritNUMERIC(7,2)requiredPublished mean RIT used by the calculator. dictionary
sd_ritNUMERIC(6,3)requiredPublished RIT standard deviation used by the calculator. dictionary
source_refTEXTrequiredProvenance pointer for the published norms row. dictionary
links.tableURLrequiredThe /norms/table resource row used for the lookup. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms/rit?subject=math&grade=5&season=winter&percentile=99&normsSet=2025" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/norms/rit`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "analytics.norms_rit_translation",
  "direction": "percentile_to_rit",
  "norms_set": "2025",
  "subject_id": "math",
  "grade_key": "5",
  "grade_level": 5,
  "season": "winter",
  "role": "student",
  "percentile": 99,
  "rit_score": 252,
  "output_rit": 252,
  "raw_rit": 252.345,
  "calculator_version": "analytics.norms.achievement.v2026-06-12",
  "mean_rit": 211.82,
  "sd_rit": 17.42,
  "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)",
  "links": {
    "table": "/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&role=student"
  }
}

GET

Translate RIT to percentile

/alpha/analytics/v1/norms/percentile

Convert a RIT score to an achievement percentile using the same surface-owned norms table row.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_norms_achievement
Readable NWEA achievement-status norms resource for percentile-to-RIT and RIT-to-percentile translation. Apps may read/cache the surface table by table_version or call /norms/rit and /norms/percentile; they never maintain their own norms table.
Architecture
aitd-016-scale-translation-apis aitd-015-norms-r90-readable-resources aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
subjectsubject_id enumRequiredmath, reading, language, or science. subjectId is accepted as an alias.
gradeTEXTRequiredK or 1 through 12. gradeLevel is accepted as an alias.
seasonTEXT enumRequiredfall, winter, or spring. termSeason is accepted as an alias.
roleTEXTOptionalNWEA role/population label. Defaults to student.
ritNUMERICRequiredRIT score to translate.
normsSetnorms_set enumRequired2020 or 2025.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredanalytics.norms_percentile_translation. dictionary
directionTEXT enumrequiredrit_to_percentile. dictionary
subject_idTEXTrequiredCanonical subject used for the lookup. dictionary
grade_keyTEXTrequiredGrade key used by the published norms resource. dictionary
grade_levelINTEGERrequiredNumeric grade level used by the calculator. dictionary
seasonTEXTrequiredfall, winter, spring, or another published NWEA season label. dictionary
roleTEXTrequiredNWEA role/population label used for the lookup. dictionary
rit_scoreNUMERICrequiredRequested RIT score, rounded for response stability. dictionary
input_ritNUMERICrequiredRequested RIT score. dictionary
percentileNUMERIC(5,2)requiredAchievement percentile returned by the surface calculator. dictionary
output_percentileNUMERIC(5,2)requiredStable output alias for the achievement percentile. dictionary
raw_percentileNUMERICrequiredUnrounded normal-model percentile, rounded to three decimals. dictionary
norms_setTEXT enumrequiredNorms family used for the lookup. dictionary
calculator_versionTEXTrequiredVersion of the surface norms table and calculator used for the normal-model translation. dictionary
mean_ritNUMERIC(7,2)requiredPublished mean RIT used by the calculator. dictionary
sd_ritNUMERIC(6,3)requiredPublished RIT standard deviation used by the calculator. dictionary
source_refTEXTrequiredProvenance pointer for the published norms row. dictionary
links.tableURLrequiredThe /norms/table resource row used for the lookup. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/norms/percentile?subject=math&grade=5&season=winter&rit=247&normsSet=2025" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/norms/percentile`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "analytics.norms_percentile_translation",
  "direction": "rit_to_percentile",
  "norms_set": "2025",
  "subject_id": "math",
  "grade_key": "5",
  "grade_level": 5,
  "season": "winter",
  "role": "student",
  "rit_score": 247,
  "input_rit": 247,
  "percentile": 98,
  "output_percentile": 98,
  "raw_percentile": 97.828,
  "calculator_version": "analytics.norms.achievement.v2026-06-12",
  "mean_rit": 211.82,
  "sd_rit": 17.42,
  "source_ref": "NWEA 2025 MAP Growth Norms Technical Manual (Hawthorne, Velazquez, Peng, Hall, Newburn, 2025)",
  "links": {
    "table": "/alpha/analytics/v1/norms/table?subject=math&grade=5&season=winter&normsSet=2025&role=student"
  }
}

GET

Read the R90 table

/alpha/analytics/v1/r90/table

Read the single versioned Alpha R90 table used for RIT-to-grade and GOALS track-position work.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_r90_table
Readable RIT-to-R90 and grade-position reference for GOALS target conversion, Learning Report grade-position display, and screener starting-grade hints. Final PowerPath placement comes from bottom-up grade-level mastery tests, not this table. Actual course and grade-level progress comes from completion-rollups. Analytics serves an Alpha-compatible mirror; the PowerPath RIT-to-grade master source is owned by NWEAMAP.
Architecture
aitd-015-norms-r90-readable-resources aitd-017-r90-version-supersession aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
subjectsubject_id enumOptionalmath, reading, language, science, or a locked proxy subject such as vocabulary, writing, or fastmath. subjectId is accepted as an alias.
normsSetnorms_set enumOptional2020 or 2025. Accepted for caller symmetry; the current R90 table version is returned.
tableVersionTEXTOptionalImmutable R90 table_version. Omit for the current version of the requested normsSet.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredAlways list. dictionary
tableTEXTrequiredanalytics.r90_table list resource over alpha.analytics_r90_table. dictionary
table_versionTEXTrequiredVersion of the readable R90 table. dictionary
data[].idTEXTrequiredStable Analytics id for this R90 row. dictionary
data[].table_versionTEXTrequiredImmutable version id for the row. dictionary
data[].table_subject_idTEXTrequiredCanonical table subject for this R90 row. dictionary
data[].subject_idTEXTrequiredSubject returned by the table endpoint. dictionary
data[].source_subject_nameTEXTrequiredPublished source subject name for audit display. dictionary
data[].rit_scoreNUMERIC(7,2)requiredRIT score represented by this row. dictionary
data[].r90_gradeTEXTrequiredPlain grade-position value for this row. dictionary
data[].effective_gradeTEXTrequiredReport-ready grade/effective-grade label. dictionary
data[].r90_grade_levelTEXTrequiredGoverned grade-level bucket for this row. dictionary
data[].r90_percent_completeNUMERIC(7,3)requiredCompatibility field for the MAP-inferred percent through the R90 grade band. dictionary
data[].rit90_grade_band_percentNUMERIC(7,3)requiredClear field name for the MAP-inferred percent through the R90 grade band. This is not actual course or grade-level progress. dictionary
data[].observation_countINTEGERrequiredCount carried by the Alpha R90 mirror for audit. dictionary
data[].source_refTEXTrequiredRuntime provenance pointer for the NWEAMAP-owned seeded grade2rit source backing this current table row. It is not a route and clients do not read it to answer reports; cache and reproduce by table_version. dictionary
limitINTEGERrequiredRows returned in this page. dictionary
hasMoreBOOLEANrequiredWhether another page exists. dictionary
nextCursorTEXT nullablerequiredOpaque cursor for the next page. dictionary
links.selfURLrequiredThe request URL for this page. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/r90/table?subject=math&normsSet=2025&limit=1" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/r90/table`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "table": "analytics.r90_table",
  "table_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
  "data": [
    {
      "id": "r90_math_172",
      "table_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
      "table_subject_id": "math",
      "subject_id": "math",
      "source_subject_name": "Math",
      "rit_score": 172,
      "r90_grade": 0,
      "effective_grade": 1,
      "r90_grade_level": 0,
      "r90_percent_complete": 0,
      "rit90_grade_band_percent": 0,
      "observation_count": 1,
      "source_ref": "powerpath:/powerpath/rit-to-grade",
      "ownerModule": "nweamap"
    }
  ],
  "limit": 1,
  "hasMore": true,
  "nextCursor": "eyJvZmZzZXQiOjF9",
  "links": {
    "self": "/alpha/analytics/v1/r90/table?subject=math&normsSet=2025&limit=1"
  }
}

GET

Translate RIT to R90 and grade position

/alpha/analytics/v1/r90

Return the R90 grade, effective grade, grade level, and MAP-inferred grade-band percent for a RIT score using the current exact PowerPath RIT-to-grade mirror.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_r90_table
Readable RIT-to-R90 and grade-position reference for GOALS target conversion, Learning Report grade-position display, and screener starting-grade hints. Final PowerPath placement comes from bottom-up grade-level mastery tests, not this table. Actual course and grade-level progress comes from completion-rollups. Analytics serves an Alpha-compatible mirror; the PowerPath RIT-to-grade master source is owned by NWEAMAP.
Architecture
aitd-016-scale-translation-apis aitd-015-norms-r90-readable-resources aitd-017-r90-version-supersession aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
subjectsubject_id enumRequiredmath, reading, language, science, or a locked proxy subject such as vocabulary, writing, or fastmath. subjectId is accepted as an alias.
ritNUMERICRequiredRIT score to translate. Exact table hits return source_point_kind=exact; missing rows return source_point_kind=source_missing rather than interpolation, step-flooring, upward clamping, or extrapolation.
normsSetnorms_set enumOptional2020 or 2025. Accepted for caller symmetry; R90 table_version is returned in the body.
tableVersionTEXTOptionalImmutable R90 table_version. Omit for the current version of the requested normsSet.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredanalytics.r90_lookup. dictionary
table_versionTEXTrequiredVersion of the Alpha R90 mirror table. dictionary
requested_subject_idTEXTrequiredSubject requested by the caller before locked proxy folding. dictionary
table_subject_idTEXTrequiredCanonical table subject used for the lookup. dictionary
subject_idTEXTrequiredSubject returned to the caller after validation. dictionary
rit_scoreNUMERICrequiredRIT table point used for the response. dictionary
table_rit_scoreNUMERIC nullablerequiredSelected table RIT. Exact lookups return the current-version row whose rit_score equals the requested RIT; missing rows return source_missing. dictionary
r90_gradeTEXTrequiredPlain grade-position value for the RIT row. dictionary
effective_gradeTEXTrequiredReport-ready grade/effective-grade label. dictionary
r90_grade_levelTEXTrequiredGoverned grade-level bucket for this R90 row. dictionary
r90_percent_completeNUMERIC(7,3)requiredCompatibility field for the MAP-inferred percent through the R90 grade band. dictionary
rit90_grade_band_percentNUMERIC(7,3)requiredClear field name for the MAP-inferred percent through the R90 grade band. This is not actual course or grade-level progress. dictionary
source_point_kindTEXT enumrequiredexact or source_missing. The current R90 lookup never interpolates, step-floors, clamps, or extrapolates. dictionary
calculator_versionTEXTrequiredVersion of the Alpha R90 mirror table and calculator. dictionary
source_refTEXTrequiredRuntime provenance pointer for the NWEAMAP-owned seeded grade2rit source row used by this lookup. It is not a route and clients do not read it to answer reports; cache and reproduce by table_version. dictionary
links.tableURLrequiredThe /r90/table resource family used for the lookup. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/r90?subject=math&rit=239&normsSet=2025" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/r90`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "analytics.r90_lookup",
  "table_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
  "requested_subject_id": "math",
  "table_subject_id": "math",
  "subject_id": "math",
  "rit_score": 239,
  "table_rit_score": 239,
  "r90_grade": 4.8,
  "effective_grade": 5,
  "r90_grade_level": 4,
  "r90_percent_complete": 80,
  "rit90_grade_band_percent": 80,
  "source_point_kind": "exact",
  "calculator_version": "analytics.rit_to_grade.powerpath.v2026-06-15",
  "source_ref": "powerpath:/powerpath/rit-to-grade",
  "ownerModule": "nweamap",
  "links": {
    "table": "/alpha/analytics/v1/r90/table?subject=math"
  }
}

GET

Read school days remaining

/alpha/analytics/v1/school-days-remaining

Return the forward instructional-day count and XP-to-time effort projection GOALS needs for its target-date column. GOALS asks Analytics for this read instead of doing calendar arithmetic locally.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
analytics.school_days_remaining
GOALS response object over alpha.school_calendar, alpha.policy.school_day, and alpha.policy.analytics.xp_expected_minute.v2026-06-14; no app-side calendar table or XP-hour constant.
Architecture
aitd-018-goals-school-days-remaining aitd-006-school-day-minutes aitd-007-xp-rollups aitd-014-source-ownership-and-prerequisites aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTRequiredCanonical Alpha student id.
schoolIdTEXTOptionalSchool id for the calendar denominator. Omit to let Analytics infer the student's enrolled school.
asOfDATERequiredStart date for the forward school-day count. startDate is accepted as an alias.
endDateDATEOptionalExclusive end date for the forward window. schoolYearEnd is accepted as an alias; omit to use the surface's current GOALS school-year end.
targetDateDATEOptionalDate GOALS wants to test for the target-date column. Must be after asOf and on or before endDate.
xpRemainingNUMERICOptionalRemaining XP to convert into expected minutes by the surface unit 1 XP = 1 expected minute. Mutually exclusive with minutesRemaining and hoursRemaining.
minutesRemainingNUMERICOptionalExpected minutes remaining. Mutually exclusive with xpRemaining and hoursRemaining.
hoursRemainingNUMERICOptionalExpected hours remaining. Mutually exclusive with xpRemaining and minutesRemaining.
hoursPerSchoolDayNUMERICOptionalPlanned hours per instructional day. When present, Analytics returns how many school days are needed and the date reached at that pace.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredanalytics.school_days_remaining. dictionary
student_idTEXTrequiredCanonical Alpha student id used for the calendar/enrollment lookup. dictionary
school_idTEXTrequiredSchool id whose alpha.school_calendar rows were read. dictionary
as_ofDATErequiredInclusive start date for the forward count. dictionary
end_dateDATErequiredExclusive end date for the forward count. dictionary
school_day_policy_refTEXTrequiredPolicy ref used to decide which calendar dates count as instructional days and which MAP days are excluded. dictionary
instructional_days_remainingINTEGERrequiredCount of school days after applying alpha.policy.school_day over the half-open [asOf, endDate) window. dictionary
calendar_ref_countINTEGERrequiredCount of calendar rows inspected before filtering to instructional days. dictionary
first_instructional_dateDATE nullablerequiredFirst remaining instructional day, or null when no school day remains. dictionary
last_instructional_dateDATE nullablerequiredLast remaining instructional day in the requested window. dictionary
instructional_datesDATE[]requiredInstructional dates in the requested window. Render or cache these only as the surface-returned answer; do not recreate them from a private calendar. dictionary
xp_unit.policy_refTEXTrequiredVersioned policy that defines XP-to-expected-minute conversion. dictionary
xp_unit.xp_per_expected_minuteNUMERICrequiredSurface-owned unit convention: 1 XP equals 1 expected minute. dictionary
xp_unit.xp_per_hourNUMERICrequiredSurface-owned conversion: 60 XP equals one expected hour. dictionary
xp_unit.meaningTEXTrequiredPlain-language statement of the XP time conversion. dictionary
effort.xp_remainingNUMERIC nullablerequiredCaller-supplied remaining XP when xpRemaining was used. dictionary
effort.expected_minutes_remainingNUMERIC nullablerequiredRemaining effort in expected minutes after applying the surface XP/minutes/hours conversion. dictionary
effort.hours_remainingNUMERIC nullablerequiredRemaining effort in hours. dictionary
effort.target_dateDATE nullablerequiredRequested target date, if supplied. dictionary
effort.school_days_until_target_dateINTEGER nullablerequiredInstructional days between asOf and targetDate. dictionary
effort.required_hours_per_school_day_to_target_dateNUMERIC nullablerequiredHours per remaining school day needed to finish by targetDate. dictionary
effort.hours_per_school_dayNUMERIC nullablerequiredCaller-supplied pace assumption. dictionary
effort.school_days_needed_at_hours_per_school_dayINTEGER nullablerequiredInstructional days needed at the supplied pace. dictionary
effort.target_date_at_hours_per_school_dayDATE nullablerequiredInstructional date reached at the supplied pace, or null when the window is too short. dictionary
effort.can_finish_by_end_date_at_hours_per_school_dayBOOLEAN nullablerequiredWhether the supplied pace finishes within the requested forward window. dictionary
provenance.sourceTEXTrequiredCalendar source read by Analytics. dictionary
provenance.source_ref_sampleTEXT[]requiredSample Ed-Fi CalendarDate-derived refs used for audit. dictionary
provenance.policy_refTEXTrequiredPolicy ref used for the school-day decision. dictionary
provenance.xp_unit_policy_refTEXTrequiredXP expected-minute policy ref used for effort projection. dictionary
links.selfURLrequiredThe request URL for this read. dictionary
links.schoolDayMinutesURLrequiredRelated school-day-minutes rollup endpoint. dictionary
links.xpRollupsURLrequiredRelated XP rollup endpoint. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/school-days-remaining?studentId=student-ada-001&asOf=2026-06-14&endDate=2026-07-24&xpRemaining=600&hoursPerSchoolDay=1.5&targetDate=2026-07-24" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/school-days-remaining`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "analytics.school_days_remaining",
  "student_id": "student-ada-001",
  "school_id": "school_alpha_demo",
  "as_of": "2026-06-14",
  "end_date": "2026-07-24",
  "school_day_policy_ref": "alpha.policy.school_day.v2026-06-10",
  "instructional_days_remaining": 29,
  "calendar_ref_count": 40,
  "first_instructional_date": "2026-06-15",
  "last_instructional_date": "2026-07-23",
  "instructional_dates": [
    "2026-06-15",
    "2026-06-16",
    "2026-06-17"
  ],
  "xp_unit": {
    "policy_ref": "alpha.policy.analytics.xp_expected_minute.v2026-06-14",
    "xp_per_expected_minute": 1,
    "xp_per_hour": 60,
    "meaning": "1 XP = 1 expected minute; XP hours = XP / 60."
  },
  "effort": {
    "xp_remaining": 600,
    "expected_minutes_remaining": 600,
    "hours_remaining": 10,
    "target_date": "2026-07-24",
    "school_days_until_target_date": 29,
    "required_hours_per_school_day_to_target_date": 0.345,
    "hours_per_school_day": 1.5,
    "school_days_needed_at_hours_per_school_day": 7,
    "target_date_at_hours_per_school_day": "2026-06-23",
    "can_finish_by_end_date_at_hours_per_school_day": true
  },
  "provenance": {
    "source": "alpha.school_calendar",
    "source_ref_sample": [
      "ed_fi.CalendarDate:school_alpha_demo:2026-06-15"
    ],
    "policy_ref": "alpha.policy.school_day.v2026-06-10",
    "xp_unit_policy_ref": "alpha.policy.analytics.xp_expected_minute.v2026-06-14"
  },
  "links": {
    "self": "/alpha/analytics/v1/school-days-remaining?studentId=student-ada-001&asOf=2026-06-14&endDate=2026-07-24",
    "schoolDayMinutes": "/alpha/analytics/v1/school-day-minutes?studentId=student-ada-001",
    "xpRollups": "/alpha/analytics/v1/xp-rollups?studentId=student-ada-001&windowKind=school_year"
  }
}

GET

Read working and mastered age-grade status

/alpha/analytics/v1/grade-level-status

Return the two named GOALS age-grade comparisons. Analytics reads P&O age grade and Results grade positions, then returns working-vs-age and mastered-vs-age labels so clients do not recompute or rename ahead/at/behind.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
analytics.grade_level_status
Read-time response over People & Orgs alpha.age_grade_history and Results working_grade/highest_mastered_grade; no Analytics-owned age grade table.
Architecture
aitd-008-mastery-and-grade-levels aitd-011-policy-and-enum-normalization aitd-014-source-ownership-and-prerequisites aitd-112-axis-list-endpoints

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTRequiredCanonical Alpha student id.
subjectsubject_id enumRequiredCanonical subject id. subjectId is also accepted by the runtime.
asOfDateDATERequiredPoint-in-time date for P&O age grade and Results grade state.

Response fields

FieldTypeNullabilityMeaningSource
objectTEXTrequiredanalytics.grade_level_status. dictionary
student_idTEXTrequiredCanonical Alpha student id. dictionary
subject_idTEXTrequiredCanonical subject id used for Results working/HMG lookup. dictionary
as_of_dateDATErequiredPoint-in-time date for P&O age grade and Results state. dictionary
age_gradeINTEGER nullablerequiredSchool-assigned cohort grade from People & Orgs alpha.age_grade_history. dictionary
working_gradeINTEGER nullablerequiredResults-owned working grade. dictionary
highest_mastered_gradeINTEGER nullablerequiredResults-owned highest mastered grade. dictionary
working_age_grade_deltaINTEGER nullablerequiredworking_grade - age_grade. dictionary
working_age_grade_statusTEXT enumrequiredworking_behind_age_grade, working_at_age_grade, working_above_age_grade, or unknown. dictionary
mastered_age_grade_deltaINTEGER nullablerequiredhighest_mastered_grade - age_grade. dictionary
mastered_age_grade_statusTEXT enumrequiredmastered_behind_age_grade, mastered_at_age_grade, mastered_above_age_grade, or unknown. dictionary
source_refsJSON objectrequiredOwner pointers for P&O age grade and Results grade positions. dictionary
deprecated_aliasesJSON objectrequiredCompatibility mapping only; not the preferred public contract. dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/grade-level-status?studentId=b6fa7128-f641-4efd-9075-375411fd6c39&subject=math&asOfDate=2026-05-20" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/grade-level-status`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "analytics.grade_level_status",
  "student_id": "b6fa7128-f641-4efd-9075-375411fd6c39",
  "subject_id": "math",
  "as_of_date": "2026-05-20",
  "age_grade": 5,
  "working_grade": 6,
  "highest_mastered_grade": 5,
  "working_age_grade_delta": 1,
  "working_age_grade_status": "working_above_age_grade",
  "mastered_age_grade_delta": 0,
  "mastered_age_grade_status": "mastered_at_age_grade",
  "source_refs": {
    "age_grade": "people_and_orgs.alpha.age_grade_history",
    "working_grade": "results.student_track_state.working_grade",
    "highest_mastered_grade": "results.highest_mastered_grade"
  },
  "deprecated_aliases": {
    "instructional_level_status": "working_age_grade_status",
    "strict_mastery_status": "mastered_age_grade_status"
  }
}

GET

List completion rollups

/alpha/analytics/v1/completion-rollups

Read progress evidence used by Results and audit views. Course rows preserve app-reported Caliper percent and XP remaining evidence; student-facing course and grade-level progress comes from Results course-progress and grade-level-progress.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_completion_rollup
Progress evidence for Results and Analytics audits without MAP/RIT/R90 inference, client-side Curriculum tree walking, or Results-row counting.
Architecture
aitd-010-completion-rollups aitd-001-report-source-ingestion aitd-004-provenance-no-literals

Request and query parameters

NameTypeRequiredDescription
studentIdTEXTOptionalCanonical Alpha student id.
completionScopecompletion_scope enumOptionalcourse, subject, track, track_level, grade_level, or segment.
scopeIdTEXTOptionalCourse, subject, track, track-level, grade-level, or segment scope id. Grade-level scope ids are grade_level:{subject}:{grade}.
subjectIdsubject_id enumOptionalCanonical Alpha subject id.
startDateDATEOptionalInclusive reporting window start.
endDateDATEOptionalExclusive reporting window end.
modifiedSinceTIMESTAMPTZOptionalPoll for changed rows.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor.

Response fields

FieldTypeNullabilityMeaningSource
idUUIDrequiredAnalytics-owned stable row id for this derived fact. Generated by the platform; globally unique within alpha.analytics_* objects.dictionary
student_idTEXTrequiredCanonical Alpha student id the source Event or Result resolved to at materialization time. Must resolve through People and Orgs as a real student for the source timestamp/effective date.dictionary
completion_scopeTEXT enumrequiredCurriculum/reporting scope for this completion measure. Allowed values: completion_scope enum.dictionary
scope_idTEXTrequiredIdentifier of the Curriculum or subject scope being measured. Must match completion_scope: course id for course, subject id for subject, track id for track, etc.dictionary
subject_idTEXTnullable; required when completion_scope=subject or the scope resolves to one subjectCanonical Alpha subject used for reporting and grouping. Closed Alpha subject enum after write-time alias folds; unknown source subject becomes an adapter finding or null_reason, not a new string.dictionary
grade_levelINTEGERnullable for non-grade-level scopes without a course gradeWorking grade level associated with the course progress row, or the grade represented by a grade_level aggregate. Must come from the course/Caliper/Curriculum scope metadata, not from MAP/RIT/R90.dictionary
is_main_courseBOOLEANnullable when not applicable; true for main course and grade_level aggregate rowsWhether the course row counts toward grade-level progress. Main grade-level courses count; remediation, hole-filling, catalog, practice, and review courses do not.dictionary
progress_source_kindTEXT enumrequired for reportable progress rowsWhich evidence source produced completion_percent. Allowed values include app_reported_percent, xp_remaining, lesson_count, subject_aggregate, and main_course_aggregate.dictionary
xp_earnedNUMERIC(14,3)nullable when progress_source_kind is app_reported_percent or an aggregate without XP evidenceEarned XP for this completion scope when the row uses the XP fallback. Nonnegative when present; source corrections are reflected through quality_status/corrections before this value is reportable.dictionary
xp_remainingNUMERIC(14,3)nullable when progress_source_kind is app_reported_percent or an aggregate without XP evidenceRemaining expected XP for this completion scope when the row uses the XP fallback. Nonnegative when present; computed from Curriculum expected XP refs under denominator_policy_ref.dictionary
completion_percentNUMERIC(7,3)nullable when no direct percent, XP denominator, lesson denominator, or source/policy is availableReport-ready completion measure for the scope. Direct app percent wins. If absent, fallback formula is xp_earned / (xp_earned + xp_remaining) * 100. Normally 0 to 100; null requires null_reason.dictionary
denominator_policy_refTEXTrequiredPolicy that decides which Curriculum expected-XP refs belong in the remaining-XP denominator. Named versioned progress policy, for example alpha.policy.analytics.course_progress.v2026-06-15 or alpha.policy.curriculum.main_course_grade_level.v2026-06-15, or inherited Curriculum policy.dictionary
curriculum_scope_refsJSONBrequiredCompact ids/cursor for Curriculum refs used to form the denominator. Refs/hashes only; never copies Curriculum trees or Content rows.dictionary
source_import_idTEXTrequired for report-tile rows; nullable for purely derived event-axis rowsAnalytics source-import receipt that proves which named report-source adapter produced this report-grade fact. Must reference alpha.analytics_source_import for non-null Learning Report tile values; no literal or fixture value may be reportable without this provenance.dictionary
null_reasonTEXT enumrequiredWhy completion_percent is null or not reportable. Allowed values: null_reason enum; none means the metric is populated.dictionary
quality_statusTEXT enumrequiredCurrent materialization quality state for this fact. Allowed values: quality_status enum. Ordinary report reads keep ok, corrected, and signed reversed rows according to the table rule; audit reads may include source_missing/source_unlinked/policy_pending/adapter_rejected.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/completion-rollups?studentId=student_01HT7G3YZV7QB5N4YKQ1K0Z9A9&completionScope=course&scopeId=course_math_grade_4_powerpath" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/completion-rollups`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/completion-rollups",
  "data": [
    {
      "id": "6d7d3b01-6b70-44ce-90dd-e7cf2f690006",
      "student_id": "student_01HT7G3YZV7QB5N4YKQ1K0Z9A9",
      "completion_scope": "course",
      "scope_id": "course_math_grade_4_powerpath",
      "subject_id": "math",
      "grade_level": 4,
      "is_main_course": true,
      "progress_source_kind": "app_reported_percent",
      "xp_earned": null,
      "xp_remaining": null,
      "completion_percent": 75,
      "denominator_policy_ref": "alpha.policy.analytics.course_progress.v2026-06-15",
      "curriculum_scope_refs": [
        "course_component_01HT8ROOT",
        "progress_source:app_reported_percent",
        "course_role:main",
        "grade_level:4"
      ],
      "source_import_id": "imp_01HT8COMPLETION_2026_SPRING",
      "null_reason": "none",
      "quality_status": "ok",
      "modified_at": "2026-06-01T01:03:00Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/completion-rollups"
  }
}

POST

Import TimeBack XP, time, and accuracy facts

/alpha/analytics/v1/source-imports/timeback-xp-time-accuracy

Submit source-shaped reporting.processed_facts rows. Analytics normalizes subject, time, XP, and source refs server-side.

Auth
Bearer JWT plus Idempotency-Key header.
Scopes
analytics:write
Source object
alpha.analytics_source_import
Provenance spine for every ingested report-grade fact: adapter, source system/table, idempotency key, source-row natural-key/hash set, materialized counts, and per-row Problem summaries.
Architecture
aitd-001-report-source-ingestion aitd-007-xp-rollups aitd-105-axis-idempotency

Request and query parameters

NameTypeRequiredDescription
Idempotency-KeyHTTP headerRequiredRequired for source imports. Same key plus different body returns analytics:idempotency_conflict.
sourceBatchRefTEXTRequiredOperator-controlled batch reference for audit and reconciliation.
rowsJSON arrayRequiredRaw source-shaped processed_facts rows. Do not pre-normalize subject, time, XP, or student identity.

Response fields

FieldTypeNullabilityMeaningSource
import_idTEXTrequiredStable id for this source import request. Generated by platform; unique per tenant.dictionary
statusTEXT enumrequiredProcessing state of the source import. Allowed values: import_status enum.dictionary
submitted_row_countINTEGERrequiredNumber of source-shaped rows submitted in the batch. Nonnegative.dictionary
accepted_row_countINTEGERrequiredRows accepted by request validation and adapter mapping. Nonnegative and not greater than submitted_row_count.dictionary
materialized_row_countINTEGERrequiredRows that produced readable Analytics facts. Nonnegative; successful nonempty imports must produce readable rows unless status explains otherwise.dictionary
rejected_row_countINTEGERrequiredRows rejected by validation or adapter mapping. Nonnegative; submitted = accepted + rejected for completed imports.dictionary
problem_countsJSONBrequiredCounts of typed Problem codes produced by the import. Keys must be stable analytics:* codes; values nonnegative integers.dictionary
completed_atTIMESTAMPTZnullable until processing completesWhen import processing reached a terminal status. UTC timestamp; null while status is received/running.dictionary

cURL

curl -sS -X POST "$ANALYTICS_BASE_URL/alpha/analytics/v1/source-imports/timeback-xp-time-accuracy" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN" \
  -H "Idempotency-Key: pfacts-demo-2026-05-01" \
  -H "Content-Type: application/json" \
  --data '{"sourceBatchRef":"migration-demo-2026-05","rows":[{"date":"2026-05-14","subject":"Math","app":"Math Academy","active_seconds":420,"inactive_seconds":0,"waste_seconds":0,"xp_earned":18}]}'

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/source-imports/timeback-xp-time-accuracy`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Idempotency-Key": "demo-import-001", "Content-Type": "application/json" }, body: JSON.stringify({ sourceBatchRef: "demo", rows: [] }) });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "import_id": "import_01J0ANALYTICS",
  "status": "materialized",
  "submitted_row_count": 250,
  "accepted_row_count": 249,
  "materialized_row_count": 249,
  "rejected_row_count": 1,
  "problem_counts": {
    "analytics:source_unlinked": 1
  },
  "completed_at": "2026-06-12T17:05:00Z"
}

POST

Import TimeBack MAP results

/alpha/analytics/v1/source-imports/timeback-map

Submit source-shaped reporting.hp_map_results rows. Analytics normalizes student, subject, term, norms, test-of-record, Growth X, and R90 position server-side.

Auth
Bearer JWT plus Idempotency-Key header.
Scopes
analytics:write
Source object
alpha.analytics_source_import
Provenance spine for every ingested report-grade fact: adapter, source system/table, idempotency key, source-row natural-key/hash set, materialized counts, and per-row Problem summaries.
Architecture
aitd-001-report-source-ingestion aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization aitd-105-axis-idempotency

Request and query parameters

NameTypeRequiredDescription
Idempotency-KeyHTTP headerRequiredRequired for source imports. Same key plus different body returns analytics:idempotency_conflict.
sourceBatchRefTEXTRequiredOperator-controlled batch reference for audit and reconciliation.
rowsJSON arrayRequiredFull source-shaped reporting.hp_map_results rows. Submit the row as exported; do not pre-dedupe, parse terms, normalize subjects, compute Growth X, or apply norms/R90 locally.
rows[].studentidTEXTRequiredTimeBack/NWEA student id from hp_map_results. student_sourced_id is accepted only for migration compatibility; the adapter resolves the canonical Alpha student id.
rows[].course or rows[].subjectTEXTRequiredSource MAP course/subject label such as Math K-12. The adapter writes the canonical subject_id.
rows[].termnameTEXTRequiredSource MAP term label such as Winter 2025-2026. The adapter writes canonical_term_id; callers do not parse term strings.
rows[].testritscoreINTEGERRequiredMAP RIT score. Missing RIT returns analytics:adapter_rejected.
rows[].normsreferencedataINTEGER enumRequiredNWEA norms family, normally 2020 or 2025. The adapter writes norms_set.
rows[].wintertowinterobservedgrowthNUMERICRequired for winter_to_winterObserved growth for the Learning Report MAP window.
rows[].wintertowinterprojectedgrowthNUMERICRequired for winter_to_winterProjected-growth denominator for Growth X. The adapter never uses typical growth as the Learning Report denominator when projected growth is present.
rows[].testpercentileINTEGEROptionalAchievement percentile from hp_map_results. If omitted, Analytics derives it from the surface-owned norms table and stamps the calculator/table version.

Response fields

FieldTypeNullabilityMeaningSource
import_idTEXTrequiredStable id for this source import request. Generated by platform; unique per tenant.dictionary
import_kindTEXT enumrequiredWhich source/replay contract this batch follows. Allowed values: import_kind enum.dictionary
adapter_nameTEXTrequiredNamed server-side adapter that normalized this source batch. Must match one approved adapter: timeback-xp-time-accuracy, timeback-map, timeback-grade-mastery, or timeback-completion, plus internal replay kinds where applicable.dictionary
source_systemTEXTrequiredProducer system the adapter read from. Closed governed values for approved adapters: reporting, events, results, migration_reconcile.dictionary
source_tableTEXTrequired for report-source adaptersReport-source table or producer collection read by the adapter. For the four public report adapters: processed_facts, hp_map_results, assessment_results, or processed_facts+courses+course_components. For timeback-map, source_table=hp_map_results means the adapter accepts the full source-shaped row and ignores unknown extra columns.dictionary
statusTEXT enumrequiredProcessing state of the source import. Allowed values: import_status enum.dictionary
submitted_row_countINTEGERrequiredNumber of source-shaped rows submitted in the batch. Nonnegative.dictionary
accepted_row_countINTEGERrequiredRows accepted by request validation and adapter mapping. Nonnegative and not greater than submitted_row_count.dictionary
materialized_row_countINTEGERrequiredRows that produced readable Analytics facts. Nonnegative; successful nonempty imports must produce readable rows unless status explains otherwise.dictionary
rejected_row_countINTEGERrequiredRows rejected by validation or adapter mapping. Nonnegative; submitted = accepted + rejected for completed imports.dictionary
problem_countsJSONBrequiredCounts of typed Problem codes produced by the import. Keys must be stable analytics:* codes; values nonnegative integers.dictionary
completed_atTIMESTAMPTZnullable until processing completesWhen import processing reached a terminal status. UTC timestamp; null while status is received/running.dictionary

cURL

curl -sS -X POST "$ANALYTICS_BASE_URL/alpha/analytics/v1/source-imports/timeback-map" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN" \
  -H "Idempotency-Key: hp-map-demo-2026-winter" \
  -H "Content-Type: application/json" \
  --data '{"sourceBatchRef":"migration-demo-map-2026-winter","rows":[{"id":"hp-map-demo-001","studentid":"student_01HT7G3YZV7QB5N4YKQ1K0Z9A9","course":"Math K-12","termname":"Winter 2025-2026","teststartdate":"2026-01-28","testritscore":239,"testpercentile":91,"wintertowinterconditionalgrowthpercentile":43,"wintertowinterobservedgrowth":6,"wintertowinterprojectedgrowth":7,"typicalwintertowintergrowth":2,"normsreferencedata":2025,"growthmeasureyn":"true","goal1name":"Operations and Algebraic Thinking","goal1ritscore":241}]}'

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/source-imports/timeback-map`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Idempotency-Key": "demo-import-001", "Content-Type": "application/json" }, body: JSON.stringify({ sourceBatchRef: "demo", rows: [] }) });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "import_id": "import_01J0MAPRESULTS",
  "import_kind": "timeback-map",
  "adapter_name": "timeback-map",
  "source_system": "reporting",
  "source_table": "hp_map_results",
  "status": "materialized",
  "submitted_row_count": 12,
  "accepted_row_count": 12,
  "materialized_row_count": 12,
  "rejected_row_count": 0,
  "problem_counts": {},
  "completed_at": "2026-06-12T17:08:00Z"
}

GET

List source imports

/alpha/analytics/v1/source-imports

Inspect source-import status, materialized row counts, and typed problem counts. This is the audit trail for migration and backfill.

Auth
Bearer JWT.
Scopes
analytics:read
Source object
alpha.analytics_source_import
Provenance spine for every ingested report-grade fact: adapter, source system/table, idempotency key, source-row natural-key/hash set, materialized counts, and per-row Problem summaries.
Architecture
aitd-001-report-source-ingestion aitd-110-axis-conformance-evidence

Request and query parameters

NameTypeRequiredDescription
importKindimport_kind enumOptionalAdapter/import kind, such as timeback-xp-time-accuracy or timeback-map.
statusimport_status enumOptionalreceived, running, materialized, rejected, or another governed import status.
modifiedSinceTIMESTAMPTZOptionalPoll for import status changes.
limitINTEGEROptionalPage size. Default 100; maximum 1000.
cursorTEXTOptionalOpaque next-page cursor.

Response fields

FieldTypeNullabilityMeaningSource
import_idTEXTrequiredStable id for this source import request. Generated by platform; unique per tenant.dictionary
import_kindTEXT enumrequiredWhich source/replay contract this batch follows. Allowed values: import_kind enum.dictionary
statusTEXT enumrequiredProcessing state of the source import. Allowed values: import_status enum.dictionary
source_batch_refTEXTnullableOperator/source identifier for the imported file, replay, or reconciliation run. Opaque reference; no PII or raw row contents.dictionary
submitted_row_countINTEGERrequiredNumber of source-shaped rows submitted in the batch. Nonnegative.dictionary
accepted_row_countINTEGERrequiredRows accepted by request validation and adapter mapping. Nonnegative and not greater than submitted_row_count.dictionary
materialized_row_countINTEGERrequiredRows that produced readable Analytics facts. Nonnegative; successful nonempty imports must produce readable rows unless status explains otherwise.dictionary
rejected_row_countINTEGERrequiredRows rejected by validation or adapter mapping. Nonnegative; submitted = accepted + rejected for completed imports.dictionary
problem_countsJSONBrequiredCounts of typed Problem codes produced by the import. Keys must be stable analytics:* codes; values nonnegative integers.dictionary
modified_atTIMESTAMPTZrequiredLast time the Analytics row changed for polling and modifiedSince queries. UTC timestamp; list endpoints support modifiedSince against this field.dictionary

cURL

curl -sS "$ANALYTICS_BASE_URL/alpha/analytics/v1/source-imports?importKind=timeback-map&modifiedSince=2026-06-10T00:00:00Z" \
  -H "Authorization: Bearer $ANALYTICS_TOKEN"

JavaScript

const response = await fetch(`${base}/alpha/analytics/v1/source-imports`, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw await response.json();
const body = await response.json();

Example response

{
  "object": "list",
  "url": "/alpha/analytics/v1/source-imports",
  "data": [
    {
      "import_id": "imp_01HT8ANALYTICS",
      "import_kind": "timeback-xp-time-accuracy",
      "status": "materialized",
      "source_batch_ref": "migration-2026-06-10-reporting-processed-facts-001",
      "submitted_row_count": 5000,
      "accepted_row_count": 4970,
      "materialized_row_count": 4970,
      "rejected_row_count": 30,
      "problem_counts": {
        "analytics:adapter_rejected": 30
      },
      "modified_at": "2026-06-10T00:56:12Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "links": {
    "self": "/alpha/analytics/v1/source-imports"
  }
}

Objects

Approved Analytics tables

alpha.analytics_event_time_fact

Stores signed active, inactive, and waste second contributions for the DERIVED Events axis, so reports can audit time-on-task without classifying raw Caliper events themselves.

Grain: One source Event contribution per fact_kind, policy_ref, and calculation_version.

API: GET /alpha/analytics/v1/event-time-facts

Open data dictionary table

aitd-000-extend-only-storage aitd-002-derived-events-complement aitd-003-close-time-materialization aitd-005-time-facts-and-windows

alpha.analytics_school_day_minutes

Minutes per enrolled school day for the Time Commitment tile. Numerator is ingested from reporting.processed_facts.active_seconds; denominator is alpha.school_calendar intersected with enrollment/subject assignment, with MAP-day exclusion.

Grain: student_id x school_id x subject_id nullable x window_start x window_end x school_day_policy_ref.

API: GET /alpha/analytics/v1/school-day-minutes

Open data dictionary table

aitd-006-school-day-minutes aitd-001-report-source-ingestion aitd-004-provenance-no-literals aitd-005-time-facts-and-windows

alpha.analytics_mastery_delta

Grade Levels Mastered facts for the Learning Report plus a typed change log of mastery state movements; Results remains the current mastery state of record.

Grain: One state transition per student_id, kc_id|grade_scope, state_dimension, effective_at, policy_ref, and calculation_version.

API: GET /alpha/analytics/v1/mastery-deltas

Open data dictionary table

aitd-008-mastery-and-grade-levels aitd-001-report-source-ingestion aitd-004-provenance-no-literals aitd-003-close-time-materialization

alpha.analytics_map_growth_rollup

Report-ready RIT, achievement percentile, growth percentile, Growth X, MAP window, sitting-count, retake, and on-track facts without client-side norms tables, sitting selection, or term parsing.

Grain: student_id x subject_id x canonical_term_id x growth_window x norms_set x policy_ref.

API: GET /alpha/analytics/v1/map-growth-rollups

Open data dictionary table

aitd-009-map-growth-rollups aitd-001-report-source-ingestion aitd-004-provenance-no-literals aitd-003-close-time-materialization

alpha.analytics_norms_achievement

Readable NWEA achievement-status norms resource for percentile-to-RIT and RIT-to-percentile translation. Apps may read/cache the surface table by table_version or call /norms/rit and /norms/percentile; they never maintain their own norms table.

Grain: norms_set x table_version x subject_id x role x grade_key x season.

API: GET /alpha/analytics/v1/norms, GET /alpha/analytics/v1/norms/table, GET /alpha/analytics/v1/norms/rit, GET /alpha/analytics/v1/norms/percentile

Open data dictionary table

aitd-015-norms-r90-readable-resources aitd-016-scale-translation-apis aitd-009-map-growth-rollups aitd-011-policy-and-enum-normalization

alpha.analytics_r90_table

Readable RIT-to-R90 and grade-position reference for GOALS target conversion, Learning Report grade-position display, and screener starting-grade hints. Final PowerPath placement comes from bottom-up grade-level mastery tests, not this table. Actual course and grade-level progress comes from completion-rollups. Analytics serves an Alpha-compatible mirror; the PowerPath RIT-to-grade master source is owned by NWEAMAP.

Grain: table_version x table_subject_id x rit_score.

API: GET /alpha/analytics/v1/r90/table, GET /alpha/analytics/v1/r90

Open data dictionary table

aitd-015-norms-r90-readable-resources aitd-016-scale-translation-apis aitd-017-r90-version-supersession aitd-009-map-growth-rollups

analytics.school_days_remaining

Forward instructional-day count and XP-to-time effort projection for GOALS target-date columns. Apps call this endpoint or follow this raw path instead of counting weekdays, excluding MAP days, or carrying XP-hour constants locally.

Grain: One GOALS forward-calendar answer per student_id x school_id x half-open [as_of, end_date) request x school_day_policy_ref x XP expected-minute policy.

API: GET /alpha/analytics/v1/school-days-remaining

Open data dictionary table

aitd-018-goals-school-days-remaining aitd-006-school-day-minutes aitd-007-xp-rollups aitd-016-scale-translation-apis

alpha.analytics_source_import

Provenance spine for every ingested report-grade fact: adapter, source system/table, idempotency key, source-row natural-key/hash set, materialized counts, and per-row Problem summaries.

Grain: One named-adapter ingestion run from one report-source producer.

API: POST /alpha/analytics/v1/source-imports/{adapter} and GET /alpha/analytics/v1/source-imports

Open data dictionary table

aitd-001-report-source-ingestion aitd-004-provenance-no-literals aitd-101-axis-write-granularity aitd-105-axis-idempotency

Grounding

Source evidence used for examples

The page examples stay at the approved contract level. These local evidence sidecars explain why the architecture requires server-side normalization and forbids client-side rollups.

Processed facts subject summary

subjectnactive_secondsinactive_secondswaste_secondsxp_earned
Writing496040578952263.877.0801221796.13
Math4007104363185643.773812377.12452971.584385760.04
Language2211100158180469.14618341.92311330.74907040.48
FastMath200788470248577.86480024.938318762.11063480.88
Reading1789550179081105.5559883.94124753.452950557.92

Caliper event summary

typeactionndeleted_n
TimeSpentEventSpentTime130980880
ActivityEventCompleted41650130
EventCreated344810
ToolUseEventUsed36720
AnnotationEventTagged15830