---
title: "Student Profiles"
url: https://docs.growamal.com/en/guides/student-profiles/
language: en
---

# Student Profiles

The student profile is the synthesis layer of the RTI loop. After the
[Skill-Status Engine](https://docs.growamal.com/en/guides/skill-status-engine/) produces skill and domain
status for a student, the `modules/profile` module composes those statuses into
one educational profile per student per assessment window: a structured
object that drives the intervention recommendation and the teacher-facing
narrative.

Profiles are entirely developer-facing internal representations. The codes
on this page (primary profile IDs, modifier keys, sub-flags) are identifiers
in the system's data model. They are never shown to students or families.

## What a profile contains

A resolved profile has five layers, each more specific than the last.

| Layer | What it answers |
|---|---|
| **Primary profile** | Which broad instructional pattern best describes this student right now? |
| **Access driver** | For comprehension-adjacent profiles, is the bottleneck decoding, accuracy, or fluency? |
| **Comprehension subprofile** | If comprehension is the primary gap, is it literal, inferential, or mixed? |
| **Modifier profiles** (up to 5) | Which co-occurring patterns need to be layered onto the primary recommendation? |
| **Sub-flags** (up to 8) | Which fine-grained skill signals need to be surfaced to an educator reviewing the plan? |

### Primary profiles

The primary profile is selected from a closed catalog of ~12 codes.
`PR2-00 / DATA_INCOMPLETE` is a system guard that indicates there is not yet
enough evidence to assign any instructional profile. It is never a "real"
educational profile and blocks the intervention recommendation path entirely.

When `do_not_decide_yet` is returned by the Skill-Status Engine (the tie-rule
sentinel described in [The Decision Engine](https://docs.growamal.com/en/how-it-works/decision-engine/)),
or when the student is in `DATA_INCOMPLETE` state, the profile module writes
no assignment row. It records a structured decline in the audit log and
the read endpoint returns the guard state with a 200 status. This is intentional:
the system has nothing meaningful to say yet, and honesty is preferable to a
placeholder guess.

### Access driver

Refines reading-comprehension-adjacent primaries. Values: `decoding_driven`,
`accuracy_driven`, `fluency_driven`, `mixed_access`.

### Comprehension subprofile

Further refines comprehension gap profiles. Values: `literal_gap`,
`inferential_gap`, `mixed_comprehension_gap`.

### Modifier profiles

Up to 5 modifier profiles can co-occur with a primary. They represent secondary
patterns that an intervention plan must address alongside the anchor:

| Modifier key | What it signals |
|---|---|
| `VOCAB_SYNTAX_MORPH_GAP` | Vocabulary, syntax, or morphology weakness co-occurring with the primary |
| `ARABIC_ACCURACY_ONLY_MONITOR` | Arabic-feature accuracy indicators need monitoring (not a standalone profile) |
| `WRITING_SPELLING_SUPPORT` | Writing and spelling support layer needed |
| `LISTENING_LANGUAGE_SUPPORT` | Listening/language comprehension support needed |
| `ON_TRACK_WITH_SKILL_ALERT` | Generally on track but one or more skill alerts are open |

### Sub-flags

The 8 fine-grained sub-flags are the most granular signal in the profile:

| Sub-flag | Scope constraint |
|---|---|
| `AR_MADD_FLAG` | Written/visual items only; never derived from fluency probes |
| `AR_SHORT_VOWEL_FLAG` | Written/visual items only |
| `AR_ORTHOGRAPHIC_CONFUSION_FLAG` | Visual/orthographic confusion only (not auditory) |
| `AR_SHADDA_FLAG` | Disabled in the current phase (`enabled=false`, never advances) |
| `VOCAB_ALERT` | Vocabulary signal |
| `SYNTAX_ALERT` | Syntax signal |
| `MORPH_ALERT` | Morphology signal |
| `WR_SP_ALERT` | Writing and spelling signal |

> **Warning:** Arabic-feature sub-flags (`AR_MADD_FLAG`, `AR_SHORT_VOWEL_FLAG`,
> `AR_ORTHOGRAPHIC_CONFUSION_FLAG`, `AR_SHADDA_FLAG`) may **only** be derived from
> written/visual assessment items, never from oral reading fluency (ORF) or CBM
> probes. `AR_ORTHOGRAPHIC_CONFUSION_FLAG` is visual/orthographic only, not auditory.
> `AR_SHADDA_FLAG` is explicitly disabled (`enabled=false`) and never advances.
> Hamza is out of scope in the current phase.

#### R12: sub-flags are a stateful training-time pattern engine

R12 (Q4) reframed sub-flags from a flat, assessment-time "roll up into modifier
profiles" mapping into a **stateful training-time micro-pattern engine** (new
`modules/subflag`; PRD `23-student-profiles.md` §4b, FR-SPR-4). The two phases are
deliberately different:

- **During assessment** there is **no error-count sub-flag alert**. A confirmed
  skill/domain weakness *is* the evidence and flows straight to the profile and
  bundle; the sub-flag engine is never called on an assessment response.
- **During training (practice)** a per-(student × `micro_pattern`) ladder runs:

| Level | Condition | Effect |
|---|---|---|
| **L1** (`candidate`) | First valid occurrence | Log only — no indicator, no support, no teacher alert |
| **L2** (`automatic_support_active`) | ≥ 2 occurrences across ≥ 2 distinct valid items, same session | **Automatic** scaffold / hint / micro-bridge fires — **no teacher alert** |
| **L3** (`teacher_alert_active`) | ≥ 3 valid occurrences across ≥ 2 sessions, support already delivered, pattern persists | A **light, informational** teacher alert |

> **Warning:** **V-12 load-bearing invariant.** The L3 alert is informational only: it MUST NOT
> change the raw score, domain status, primary profile, RTI level, or Tier. The
> engine drives **automatic support first** and a non-score-changing alert only on
> persistence. It writes only `sub_flag_indicator_state` +
> `sub_flag_indicator_occurrence` + the emit-only `platform_alert_log`, enforced by
> `lint:subflag-alert-no-mutation`. ORF/CBM errors never fire these indicators.

## Profile assignment lifecycle

Every profile assignment starts in `pending_teacher_review`. It becomes `active`
only when the teacher explicitly confirms it. This is the V-12 principle that
the teacher decides at every step. The lifecycle states are:

| Status | Meaning |
|---|---|
| `pending_teacher_review` | Computed; awaiting teacher confirmation |
| `active` | Teacher confirmed; drives intervention recommendation |
| `superseded` | A recompute produced a newer assignment; this one is archived |
| `withdrawn` | Teacher indicated more data is needed; no active assignment |

When a recompute runs, any prior `active` assignment moves to `superseded` and a
fresh `pending_teacher_review` row is written. The student has no active profile
until the teacher confirms the new one. The database enforces a partial unique
constraint on `(org, student, window) WHERE status='active'`.

## Narratives

Each active profile produces a **pre-written narrative** rendered per audience.
The three supported audience values for the API parameter are `teacher`, `parent`, and `admin`.
(`student_tasks_only` is an internal rendering mode; it is not a valid `audience` query-parameter
value. The endpoint returns 422 for any value outside `teacher`, `parent`, and `admin`.) No
student-facing copy is served through this endpoint.

Narratives are sourced from a library of 15 SPOT templates × 4 grade levels
(60 rendered variants). They are rule-authored. No AI is in the narrative
path. Every narrative string passes through the
[Language Safety](https://docs.growamal.com/en/guides/language-safety/) filter before being written or
returned. The parent-audience variant applies the stricter parent filter: no
numeric scores, no internal label substrings, growth language only.

> **Note:** Profile narratives are developer-facing in the sense that they are returned by
> the API, but their content is designed for educator and family audiences.
> Never expose a `teacher` or `admin` narrative directly to a parent. Always
> request `audience=parent` for parent-facing surfaces.

## Determinism and replay

Profile resolution is fully deterministic (V-6): the same SSE snapshot +
the same pinned catalog version always produces the same profile assignment.
The admin replay endpoint (`POST /api/admin/profile-replay`) runs the entire
resolution dry (writing zero rows), so you can preview how a catalog version
change would affect existing assignments before publishing.

## Key endpoints

The profile module exposes 6 routes, all teacher/admin only and never
student-facing (FR-SPR-10).

| Endpoint | Purpose |
|---|---|
| `GET /api/profile/:studentId` | Active or pending assignment, or the DATA_INCOMPLETE guard state |
| `GET /api/profile/:studentId/narrative` | Filtered narrative (`audience=teacher\|parent\|admin`) |
| `GET /api/profiles/class/:classId` | Class distribution (ClassTeacher-scoped) |
| `POST /api/profile/:studentId/review-resolution` | Teacher confirms, withdraws, or maintains |
| `POST /api/admin/profile-catalog/publish` | Publish a new catalog version (completeness gate) |
| `POST /api/admin/profile-replay` | V-6 dry-run replay; writes nothing |

## Connections

- **Upstream:** [Skill-Status Engine](https://docs.growamal.com/en/guides/skill-status-engine/): the 5-layer
  decision engine whose skill and domain status snapshots feed profile resolution.
- **Downstream:** [Intervention Bundles](https://docs.growamal.com/en/guides/intervention-bundles/): the active
  profile is the key that selects a bundle from the 21-row profile-to-bundle map.
- **Workflow:** [Teacher Activation Workflow](https://docs.growamal.com/en/guides/teacher-activation/): the
  teacher review step that moves a profile from `pending_teacher_review` to `active`.
- **Language rules:** [Language Safety](https://docs.growamal.com/en/guides/language-safety/): the filter applied
  at every narrative write and render.
