Skip to Content

Reporting & Parent PDF

Two modules compose the read-side reporting surface: modules/reporting (WP-06-BE) produces teacher- and admin-facing sub-skill reports, and modules/parent-pdf (WP-09-BE) produces a growth-language parent PDF. Both are read-only: they compose already-persisted engine outputs and write nothing.

Who sees what

AudienceSurfaceWhat is shownWhat is never shown
TeacherSub-skill report + class summaryProfile narrative, macro-domain statuses, per-skill status sorted by need for support, active bundle plan, open alerts, class-level status countsθ score, profile codes (e.g. PR2-04), benchmark numbers, internal IDs
Manager / PrincipalClass summary (aggregate-only), school overview, heatmapStatus counts per skill and macro domain across classes/schoolPer-student detail, θ score, any single overall percentage
ParentGrowth PDF (teacher-triggered, downloaded, shared off-platform)Child’s growth described in Arabic growth language, macro-domain status words (categorical only), teacher name, school nameAll numbers and percentages, θ score, profile codes, internal IDs, benchmark comparisons
StudentNeverEverything. Students never receive a report surface.

θ scores and internal profile codes (such as PR2-04) are developer-facing context. They must never appear in any teacher, parent, or student-facing surface.

Sub-skill report (WP-06-BE)

GET /api/reports/students/{id}/sub-skill-report composes five sections for a teacher:

  1. Profile section: the student’s primary profile narrative (pre-rendered Arabic text from the SPOT template library), plus a dataIncomplete: true flag when no assignment has been made yet.
  2. Macro tiles: the student’s status on each of the 6 macro domains plus the GLOBAL meta. Statuses are categorical (Meets, Approaching, Below, Severe, Not_Assessed), never a percentage, never an average. Not_Assessed and Partial states are excluded from worst-case-wins rollup.
  3. Per-skill rows: one row per sub-skill, sorted with “needs support” skills first (the Arabic tier ordering يحتاج تدخّل is applied by the reporting engine’s sort layer).
  4. Active plan: the student’s current bundle assignment and its status.
  5. Open alerts: active acute-regression alerts and monitoring flags.

No measurement logic runs here. All statuses come from already-persisted snapshots produced by the skill-status engine and profile resolution modules.

If the student has no assignment yet, profile.dataIncomplete is true and the response is still HTTP 200 (fail-closed, never a 404 for missing data).

Class summary (WP-06-BE)

GET /api/reports/classes/{id}/class-summary returns:

  • Per-skill distributions: counts of students at each status for every sub-skill, plus the Arabic action-tier distribution.
  • Class macro: worst-case-wins status rollup across the class for each macro domain. This is a status-level operation: it returns the most concerning status observed, not an average.

The audience query parameter controls the response shape:

audience valuePer-student rowsUse case
teacherIncluded (per-student macro summary)Teacher’s own class dashboard
managernull (aggregate only)District manager or non-class-owner view

A teacher can only call this for classes they are assigned to (ClassTeacher junction). A manager or admin always receives the aggregate-only shape.

Band descriptions (WP-06-BE)

GET /api/reports/band-descriptions returns the 5-band Mizan reference catalog. Each band has Arabic and English labels plus an Arabic description. This is reference data for the teacher UI; it explains what each status band means in growth language, without numbers or comparisons.

These labels are working defaults pending partner sign-off; the partnerOwed flag on each row signals this.

Parent PDF (WP-09-BE)

POST /api/parent/pdf/{childId} is teacher-triggered. It returns a binary PDF stream (application/pdf), A4 format, Arabic-RTL layout with embedded Noto Naskh Arabic font. The PDF is generated deterministically: byte-identical content across runs for identical inputs (V-6).

Parent-stricter language filter

Every string in the parent PDF passes two layers of language safety:

  1. applyParentStricterFilter: the canonical parent-stricter belt from modules/language-safety (FR-LANG-6). It blocks internal profile codes, numeric scores, benchmark comparisons, and clinical/framework labels.
  2. ParentSummaryDTO field strip: typed discipline that removes internal fields before the PDF layout engine ever sees them. θ, profile codes, and internal IDs are structurally absent, not just filtered at the string level.

What the parent PDF contains

  • Child’s first name and school name.
  • Macro-domain status words in Arabic, categorical descriptions only (e.g. “يحتاج دعمًا”, “needs support”), never a number.
  • Not_Assessed is rendered as “نحتاج بيانات إضافية” (we need more data); it is never hidden and never treated as a low result.
  • Growth-language narrative drawn from the SPOT profile template, with sensitive-language guardrails applied.

Halt page

If no resolved parent narrative exists for the student (for example, the student’s profile is DATA_INCOMPLETE), the PDF renders a safe GL-005 halt page. It never fabricates a profile or falls back to a generic statement. No active language-safety rule set causes the endpoint to return 503 rather than ship unfiltered text.

No single overall score

Neither the sub-skill report, the class summary, nor the parent PDF contains a single number or percentage representing overall Arabic literacy. Every status is per-measure or per-macro-domain. This is a hard platform rule (V-3) enforced in both the reporting engine and the parent PDF composition layer.

Last updated on