Platform3 · Canonical architecture

Analytics Alpha Architecture

This page is rendered directly from the committed architecture decisions. Each stable section anchor is part of the public traceability contract.

Source-bound decision record

32 committed decisions · analytics/alpha Source generated 2026-06-15T23:37:06.595Z

#aitd-000-extend-only-storage

aitd 000 extend only storage

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics has no 1EdTech base and no alias map; it creates Alpha-owned extension tables for typed derived facts. Each row stores the normalized fact plus provenance (source_import_id, source_system, source_table, source_row_key) and references platform Events/Results/NWEAMap base rows by FK where one exists — never a verbatim copy of a base column, and never a re-derived value standing in for an ingested one.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Every alpha.analytics_* object is an extension table or a read view over one. Each row carries source_import_id (for ingested facts) and/or source_event_id/source_result_id/source_state_ref (for derived facts). No Analytics column copies a raw Caliper/Results/NWEA/Ed-Fi/Curriculum base column when an FK or provenance ref can be used. The data dictionary states this is not an alias surface and must not publish an Alpha-to-1EdTech alias map. Trace: all alpha.analytics_* tables | storage gate | module-placement QC.

Downstream rules

  • Every alpha.analytics_* object is an extension table or a read view over one.
  • Each row carries source_import_id (for ingested facts) and/or source_event_id/source_result_id/source_state_ref (for derived facts).
  • No Analytics column copies a raw Caliper/Results/NWEA/Ed-Fi/Curriculum base column when an FK or provenance ref can be used.
  • The data dictionary states this is not an alias surface and must not publish an Alpha-to-1EdTech alias map.

Trace

  • all alpha.analytics_* tables
  • storage gate
  • module-placement QC
#aitd-001-report-source-ingestion

aitd 001 report source ingestion

AcceptedOwner: TimeBack Platform Architecture

Decision

Each Learning-Report fact is produced by ONE named adapter that ingests the reporting-DB table the LIVE report reads (reporting.processed_facts for XP/minutes/accuracy; reporting.hp_map_results for MAP; reporting.assessment_results for grade-levels-mastered; processed_facts + Curriculum expected XP for completion), normalizes it server-side under the report's documented rule, writes the normalized value with full provenance to alpha.analytics_source_import, and treats a missing expected source row as a reconciliation error. Analytics never re-derives a report tile from raw events by a guessed formula.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Named adapters: timeback-xp-time-accuracy, timeback-map, timeback-grade-mastery, timeback-completion — one per report-source producer. Each adapter normalizes server-side (proxy-subject folds, test-app exclusion, test-of-record dedup, Growth X via the NWEAMap calculator); the caller never pre-computes (skill-pack leak guard, aitd-011). Every materialized report-fact row carries source_import_id → alpha.analytics_source_import (aitd-004). A missing expected source row is analytics:adapter_rejected (422) on import and null_reason=source_missing on read — never a literal or a guess. The named external source these adapters read is pinned in aitd-013; the per-value READ-vs-INGEST-vs-BLOCKED ownership and the prerequisites are pinned in aitd-014. The reporting probe (loop/scripts/reporting-probe.mjs) must be green from ingested rows seeded from REAL production reporting rows (via the MCP/token-file path), never from literals or inputs computed off the ground-truth tile; the migration reconcile validates the same. Trace: reportSources | aitd-013-reporting-export-source | aitd-014-source-ownership-and-prerequisites | alpha.analytics_source_import | source/evidence/report-source-grounding.json | loop/migration/feedback/alpha_reconcile.md | loop/scripts/reporting-probe.mjs.

Downstream rules

  • Named adapters: timeback-xp-time-accuracy, timeback-map, timeback-grade-mastery, timeback-completion — one per report-source producer.
  • Each adapter normalizes server-side (proxy-subject folds, test-app exclusion, test-of-record dedup, Growth X via the NWEAMap calculator); the caller never pre-computes (skill-pack leak guard, aitd-011).
  • Every materialized report-fact row carries source_import_id → alpha.analytics_source_import (aitd-004).
  • A missing expected source row is analytics:adapter_rejected (422) on import and null_reason=source_missing on read — never a literal or a guess.
  • The named external source these adapters read is pinned in aitd-013; the per-value READ-vs-INGEST-vs-BLOCKED ownership and the prerequisites are pinned in aitd-014.
  • The reporting probe (loop/scripts/reporting-probe.mjs) must be green from ingested rows seeded from REAL production reporting rows (via the MCP/token-file path), never from literals or inputs computed off the ground-truth tile; the migration reconcile validates the same.

Trace

  • reportSources
  • aitd-013-reporting-export-source
  • aitd-014-source-ownership-and-prerequisites
  • alpha.analytics_source_import
  • source/evidence/report-source-grounding.json
  • loop/migration/feedback/alpha_reconcile.md
  • loop/scripts/reporting-probe.mjs
#aitd-002-derived-events-complement

aitd 002 derived events complement

AcceptedOwner: TimeBack Platform Architecture

Decision

Active/inactive/waste seconds and their window rollups are genuinely DERIVED from Events (Caliper) and are the second fact axis. They power time-on-task audit and the Waste panel, and they cross-check the ingested minutes tile — but the report tile value is always read from the ingested fact (aitd-001), never silently replaced by the derived one.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: alpha.analytics_event_time_fact and alpha.analytics_time_window are the DERIVED axis. alpha.analytics_school_day_minutes exposes the INGESTED tile value; the derived window is available as an audit subcollection, not the tile. Where ingested and derived disagree beyond policy tolerance, the surface flags a reconciliation finding; it never overwrites the tile with the derived value. Trace: alpha.analytics_event_time_fact | alpha.analytics_time_window | alpha.analytics_school_day_minutes | metric_check minutes|Caliper events.

Downstream rules

  • alpha.analytics_event_time_fact and alpha.analytics_time_window are the DERIVED axis.
  • alpha.analytics_school_day_minutes exposes the INGESTED tile value; the derived window is available as an audit subcollection, not the tile.
  • Where ingested and derived disagree beyond policy tolerance, the surface flags a reconciliation finding; it never overwrites the tile with the derived value.

Trace

  • alpha.analytics_event_time_fact
  • alpha.analytics_time_window
  • alpha.analytics_school_day_minutes
  • metric_check minutes|Caliper events
#aitd-003-close-time-materialization

aitd 003 close time materialization

AcceptedOwner: TimeBack Platform Architecture

Decision

Derived facts are written when Events/Results close; ingested facts are written when an adapter import runs. Both are idempotent and replayable. Reports READ stored Analytics facts; they never recompute the math at read time.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Event/Result close writes or refreshes derived facts; adapter import writes or refreshes ingested facts. Rows carry computed_at, source_closed_at/imported_at, calculation_version, policy_ref, and idempotency_key. Recompute writes a new calculation_version or a signed correction; it never mutates a source row. Trace: all alpha.analytics_* tables.

Downstream rules

  • Event/Result close writes or refreshes derived facts; adapter import writes or refreshes ingested facts.
  • Rows carry computed_at, source_closed_at/imported_at, calculation_version, policy_ref, and idempotency_key.
  • Recompute writes a new calculation_version or a signed correction; it never mutates a source row.

Trace

  • all alpha.analytics_* tables
#aitd-004-provenance-no-literals

aitd 004 provenance no literals

AcceptedOwner: TimeBack Platform Architecture

Decision

A report-grade fact value is valid only if it traces to an ingested source row via source_import_id plus a resolvable source_row_key. A value with no resolvable source returns a typed null_reason=source_missing — never a literal, a fixture, or a guessed re-derivation. This is the probe-green provenance bar.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Every report-fact row has a non-null source_import_id; reads join it for provenance. Absent source → null_reason ∈ {source_missing, source_unlinked, policy_pending}; never a default numeric value. No materializer fallback may invent a value (the completion 1-if-active path is forbidden). No test/smoke fixture may compute an import input from the report answer (XP split-to-sum, observed=GrowthX×10/projected=10, expectedXp=earned×100/percent, activeSeconds=minutes×60 are all forbidden — aitd-014 seeding rule). Surface QC and the reporting probe assert non-null values are provenance-backed. Trace: alpha.analytics_source_import | all REPORT TILE tables | quality_status enum | loop/scripts/reporting-probe.mjs.

Downstream rules

  • Every report-fact row has a non-null source_import_id; reads join it for provenance.
  • Absent source → null_reason ∈ {source_missing, source_unlinked, policy_pending}; never a default numeric value.
  • No materializer fallback may invent a value (the completion 1-if-active path is forbidden).
  • No test/smoke fixture may compute an import input from the report answer (XP split-to-sum, observed=GrowthX×10/projected=10, expectedXp=earned×100/percent, activeSeconds=minutes×60 are all forbidden — aitd-014 seeding rule).
  • Surface QC and the reporting probe assert non-null values are provenance-backed.

Trace

  • alpha.analytics_source_import
  • all REPORT TILE tables
  • quality_status enum
  • loop/scripts/reporting-probe.mjs
#aitd-005-time-facts-and-windows

aitd 005 time facts and windows

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics stores signed active_seconds, inactive_seconds, and waste_seconds contributions per source event, then materializes day/week/term/window totals by student and subject — the derived axis (aitd-002).

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: fact_kind enum: active_seconds, inactive_seconds, waste_seconds. seconds_delta is signed so corrections reconcile; display measures may expose policy-clamped non-negative minutes separately. window_kind enum: day, week, term, school_year, custom. reportable subject_id uses the owner-approved Alpha report-subject subset after write-time alias folds; unknown source subjects outside the owning registry/subset become adapter findings, not ad-hoc values. Curriculum/Content authoring may store computer_science for AP CSA, but Analytics report reads require a dictionary-backed policy before adding it to report-subject outputs. Trace: alpha.analytics_event_time_fact | alpha.analytics_time_window | metric_check minutes|Caliper events.

Downstream rules

  • fact_kind enum: active_seconds, inactive_seconds, waste_seconds.
  • seconds_delta is signed so corrections reconcile; display measures may expose policy-clamped non-negative minutes separately.
  • window_kind enum: day, week, term, school_year, custom.
  • reportable subject_id uses the owner-approved Alpha report-subject subset after write-time alias folds; unknown source subjects outside the owning registry/subset become adapter findings, not ad-hoc values. Curriculum/Content authoring may store computer_science for AP CSA, but Analytics report reads require a dictionary-backed policy before adding it to report-subject outputs.

Trace

  • alpha.analytics_event_time_fact
  • alpha.analytics_time_window
  • metric_check minutes|Caliper events
#aitd-006-school-day-minutes

aitd 006 school day minutes

AcceptedOwner: TimeBack Platform Architecture

Decision

The Time Commitment tile is minutes-per-enrolled-school-day: numerator ingested from reporting.processed_facts.active_seconds (aitd-001), denominator = alpha.school_calendar instructional days intersected with the student's point-in-time enrollment window, excluding MAP-testing days, under alpha.policy.school_day.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: alpha.school_calendar.reason enum: instructional, weekend, teacher_workshop, holiday_break, nwea_map_testing, other; MAP days are not school days. A student's denominator = their school's instructional days ∩ their effective-dated enrollment. Subject denominator counts only days the student had a course/test assignment in that subject. Rows carry school_day_policy_ref, source_import_id (numerator), and calendar/enrollment FKs. Default GET /alpha/analytics/v1/school-day-minutes reads are report answers: only canonical report-subject rows with null_reason=none and non-null minutes_per_enrolled_school_day are returned unless the caller passes includeUnavailable=true. subject_id=null rows, source_missing/policy_pending repair rows, and impossible minutes/day rows are audit/repair rows. minutes_per_enrolled_school_day > 240 is non-reportable and must be superseded or marked quality_status=source_missing with null_reason=active_minutes_exceeds_school_day. Trace: alpha.analytics_school_day_minutes | loop/context/alpha-calendar-decision.md | alpha.school_calendar | metric_check minutes|Caliper events.

Downstream rules

  • alpha.school_calendar.reason enum: instructional, weekend, teacher_workshop, holiday_break, nwea_map_testing, other; MAP days are not school days.
  • A student's denominator = their school's instructional days ∩ their effective-dated enrollment.
  • Subject denominator counts only days the student had a course/test assignment in that subject.
  • Rows carry school_day_policy_ref, source_import_id (numerator), and calendar/enrollment FKs.
  • Default GET /alpha/analytics/v1/school-day-minutes reads are report answers: only canonical report-subject rows with null_reason=none and non-null minutes_per_enrolled_school_day are returned unless the caller passes includeUnavailable=true.
  • subject_id=null rows, source_missing/policy_pending repair rows, and impossible minutes/day rows are audit/repair rows visible only with includeUnavailable=true.
  • minutes_per_enrolled_school_day > 240 is non-reportable and must be superseded or marked quality_status=source_missing with null_reason=active_minutes_exceeds_school_day.

Trace

  • alpha.analytics_school_day_minutes
  • loop/context/alpha-calendar-decision.md
  • alpha.school_calendar
  • metric_check minutes|Caliper events
#aitd-007-xp-rollups

aitd 007 xp rollups

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics ingests XP from reporting.processed_facts under the report's normalization and materializes, per (student, subject, window): xp_total, daily xp_goal (from alpha.policy by level), xp_remaining, xp_completion_percent, and on_track. Results xp_ledger remains the canonical awarded-XP ledger.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Fields: xp_total, xp_goal, xp_remaining, xp_completion_percent, on_track, positive_xp, negative_xp, award_count, reversal_count, policy_ref, source_import_id. daily xp_goal comes from alpha.policy.analytics.xp_goal keyed by the student's level; never a magic number in a consumer. xp_remaining uses Curriculum expected XP for the scope; XP award math stays in Results/Policy. The data dictionary maps this table to the XP metric_check and lists every tile field. Trace: alpha.analytics_xp_rollup | reporting.processed_facts | alpha.xp_ledger | alpha.policy.analytics.xp_goal | metric_check XP.

Downstream rules

  • Fields: xp_total, xp_goal, xp_remaining, xp_completion_percent, on_track, positive_xp, negative_xp, award_count, reversal_count, policy_ref, source_import_id.
  • daily xp_goal comes from alpha.policy.analytics.xp_goal keyed by the student's level; never a magic number in a consumer.
  • xp_remaining uses Curriculum expected XP for the scope; XP award math stays in Results/Policy.
  • The data dictionary maps this table to the XP metric_check and lists every tile field.

Trace

  • alpha.analytics_xp_rollup
  • reporting.processed_facts
  • alpha.xp_ledger
  • alpha.policy.analytics.xp_goal
  • metric_check XP
#aitd-008-mastery-and-grade-levels

aitd 008 mastery and grade levels

AcceptedOwner: TimeBack Platform Architecture

Decision

Grade Levels Mastered (subject, grade, completed_on) are ingested from reporting.assessment_results passed gates via the timeback-grade-mastery adapter. Mastery deltas are written when Results student_kc_state changes, one row per state dimension, referencing the source result/state by FK and read as-of.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: grade-mastered rows: subject, grade, completed_on, source_import_id (assessment_results gate). state_dimension enum: initial_mastery, current_retention, durable_mastery, fluency_state, next_due_at. Delta rows carry previous_value, new_value, delta_value, transition_kind, effective_at, policy_ref, source_state_ref, source_result_refs. Analytics reads the mastery ledger/state as-of; it never implements mastery or decay math. Trace: alpha.analytics_mastery_delta | reporting.assessment_results | alpha.student_kc_state | metric_check mastery|student_kc_state.

Downstream rules

  • grade-mastered rows: subject, grade, completed_on, source_import_id (assessment_results gate).
  • state_dimension enum: initial_mastery, current_retention, durable_mastery, fluency_state, next_due_at.
  • Delta rows carry previous_value, new_value, delta_value, transition_kind, effective_at, policy_ref, source_state_ref, source_result_refs.
  • Analytics reads the mastery ledger/state as-of; it never implements mastery or decay math.

Trace

  • alpha.analytics_mastery_delta
  • reporting.assessment_results
  • alpha.student_kc_state
  • metric_check mastery|student_kc_state
#aitd-009-map-growth-rollups

aitd 009 map growth rollups

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics ingests reporting.hp_map_results via the timeback-map adapter, applies test-of-record dedup (NWEAMap GAP-3 highest RIT), and materializes per (student, subject, term, norms_set): rit, start_rit, achievement_percentile (testpercentile), conditional_growth_percentile (wintertowinterconditionalgrowthpercentile), compatibility growth_percentile mirroring conditional_growth_percentile, and growth_x = observed / PROJECTED growth read from the NWEAMap calculator (ALPHA-2). It never re-derives Growth X as observed/typical.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Ingested columns: testritscore, testpercentile, wintertowinter observed/projected/typical growth, wintertowinterconditionalgrowthpercentile, termname, growthmeasureyn. Test-of-record dedup (highest RIT) runs before reading the growth cell; sitting_count/retake_count are retained for audit. The public rollup exposes start_rit and canonical conditional_growth_percentile; growth_percentile is only a compatibility alias that mirrors conditional_growth_percentile. growth_x = observed / projected via the NWEAMap calculator; norms_set is typed and a query parameter (2020/2025), not a code path. The data dictionary maps this table to the RIT|GrowthX and MAP sittings metric_checks. Trace: alpha.analytics_map_growth_rollup | reporting.hp_map_results | NWEAMap GAP-3 / ALPHA-2 | metric_check RIT|GrowthX|MAP growth windows.

Downstream rules

  • Ingested columns: testritscore, testpercentile, wintertowinter observed/projected/typical growth, wintertowinterconditionalgrowthpercentile, termname, growthmeasureyn.
  • Test-of-record dedup (highest RIT) runs before reading the growth cell; sitting_count/retake_count are retained for audit.
  • Public fields include start_rit and canonical conditional_growth_percentile; growth_percentile is only a compatibility alias that mirrors conditional_growth_percentile.
  • growth_x = observed / projected via the NWEAMap calculator; norms_set is typed and a query parameter (2020/2025), not a code path.
  • The data dictionary maps this table to the RIT|GrowthX and MAP sittings metric_checks.

Trace

  • alpha.analytics_map_growth_rollup
  • reporting.hp_map_results
  • NWEAMap GAP-3 / ALPHA-2
  • metric_check RIT|GrowthX|MAP growth windows
#aitd-010-completion-rollups

aitd 010 completion rollups

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics computes completion per (student, course/subject/track) as xp_earned / (xp_earned + xp_remaining): earned XP ingested from reporting.processed_facts, the XP-remaining denominator from Curriculum expected XP. When the denominator is absent it returns null_reason=source_missing — never a 1-if-active fallback.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Fields: completion_scope ∈ {course, subject, track, track_level, segment}, xp_earned, xp_remaining, completion_percent, null_reason, source_import_id, curriculum_scope_refs. A missing/changing Curriculum denominator yields null_reason, never client fallback math. No materializer may default completion to a constant or derive it from an activity flag. The data dictionary maps this table to the completion|curriculum refs metric_check. Trace: alpha.analytics_completion_rollup | reporting.processed_facts | Curriculum expected-XP refs | metric_check completion|curriculum refs.

Downstream rules

  • Fields: completion_scope ∈ {course, subject, track, track_level, segment}, xp_earned, xp_remaining, completion_percent, null_reason, source_import_id, curriculum_scope_refs.
  • A missing/changing Curriculum denominator yields null_reason, never client fallback math.
  • No materializer may default completion to a constant or derive it from an activity flag.
  • The data dictionary maps this table to the completion|curriculum refs metric_check.

Trace

  • alpha.analytics_completion_rollup
  • reporting.processed_facts
  • Curriculum expected-XP refs
  • metric_check completion|curriculum refs
#aitd-011-policy-and-enum-normalization

aitd 011 policy and enum normalization

AcceptedOwner: TimeBack Platform Architecture

Decision

Adapters and materializers write closed enums and policy refs (subject_id, window_kind, fact_kind, metric_kind, quality_status, norms_set, null_reason) at write time. Subject alias folds preserve the canonical Alpha subjects `writing` and `vocabulary`; proxy-to-MAP mapping is limited to RIT/growth reporting. Test-app exclusion, dedup, and Growth X normalization all run server-side; the caller never pre-computes them.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: report/evidence subject_id uses the owner-approved six-value Alpha report subset: math, reading, language, science, vocabulary, writing. Curriculum/Content authoring uses the eight-value registry math, reading, language, science, computer_science, vocabulary, writing, social_studies, with Computer Science/AP CSA folded to computer_science and Social Studies/social studies folded to social_studies at write/import time. Economics, history, geography, and civics remain course-grain disciplines rather than Platform3 subject ids. Arts, world languages, engineering/technology, health/PE, financial literacy, CTE, SEL, and media literacy are out of scope until explicit owner approval adds them to the relevant registry/subset. Unknown/out-of-scope values become typed adapter findings or null_reason, never ad-hoc subject ids. No request body may carry a pre-computed metric value (skill-pack leak guard). Trace: all alpha.analytics_* tables | alpha.policy.analytics.* | source/evidence/report-source-grounding.json.

Downstream rules

  • report/evidence subject_id uses the owner-approved six-value Alpha report subset: math, reading, language, science, vocabulary, writing.
  • Curriculum and Content authoring/catalog use the eight-value registry math, reading, language, science, computer_science, vocabulary, writing, social_studies; Analytics maps vocabulary→language, writing→language, and fastmath→math only in MAP RIT/growth reporting paths, while the report subset remains six values.
  • Unknown/out-of-scope source values outside the owning registry/subset become typed adapter findings or null_reason, never ad-hoc subject ids.
  • policy_ref points to alpha.policy.analytics.* or inherited Curriculum/Results policy config; no magic numbers in consumers.
  • quality_status enum: ok, corrected, reversed, source_missing, source_unlinked, policy_pending, adapter_rejected.
  • No request body may carry a pre-computed metric value (skill-pack leak guard).

Trace

  • all alpha.analytics_* tables
  • alpha.policy.analytics.*
  • source/evidence/report-source-grounding.json
#aitd-012-corrections-and-reversals

aitd 012 corrections and reversals

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics preserves correction/reversal contributions as signed deltas with quality_status and source refs, while report-facing measures expose policy-defined net and display values.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: seconds_delta and xp_delta are signed contribution fields. Rollup tables carry net_* values plus optional display_* values computed under policy. Correction rows require source_ref, correction_of_ref when known, quality_status, and policy_ref. Public copy explains signed source contributions without asking apps to repair them. Trace: alpha.analytics_event_time_fact | alpha.analytics_xp_rollup | source/evidence/report-source-grounding.json.

Downstream rules

  • seconds_delta and xp_delta are signed contribution fields.
  • Rollup tables carry net_* values plus optional display_* values computed under policy.
  • Correction rows require source_ref, correction_of_ref when known, quality_status, and policy_ref.
  • Public copy explains signed source contributions without asking apps to repair them.

Trace

  • alpha.analytics_event_time_fact
  • alpha.analytics_xp_rollup
  • source/evidence/report-source-grounding.json
#aitd-013-reporting-export-source

aitd 013 reporting export source

AcceptedOwner: TimeBack Platform Architecture

Decision

The Learning-Report period facts have a single authoritative upstream source — the TimeBack Reporting Export (reporting.processed_facts, reporting.hp_map_results, reporting.assessment_results, reporting.courses/course_components), the same warehouse the live alpha.timeback.com Learning Report reads. It is a NEW named external source for Analytics, exactly as the NWEA export is for NWEAMap and the Ed-Fi model is for Ed-Fi — it is NOT one of the approved Alpha modules and Analytics does not alias it. Analytics ingests it source-shaped via one named adapter per producer (aitd-001); the migration app is the producer that POSTs the source rows; Analytics normalizes server-side and stores provenance. No report tile is re-derived from raw Events/Results.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Upstream source list (columns verified first-hand 2026-06-11): reporting.processed_facts, reporting.hp_map_results, reporting.assessment_results, reporting.courses + reporting.course_components. The reporting export is NOT an approved Alpha module; Analytics never aliases it. It is ingested via the aitd-001 named adapters into alpha.analytics_source_import plus the rollup tables. Producer = the migration app (and any future close-time sync). Analytics is the CONSUMER of POST /source-imports/{adapter}; it never reaches into the warehouse at read time. Per-value READ-vs-INGEST-vs-BLOCKED ownership is pinned in aitd-014; the data dictionary copies the exact column map per adapter. Trace: aitd-001-report-source-ingestion | aitd-014-source-ownership-and-prerequisites | reporting.processed_facts | reporting.hp_map_results | reporting.assessment_results | reporting.courses/course_components | NWEAMap/Ed-Fi external-source pattern | source/evidence/reporting-export-columns-verified.json.

Downstream rules

  • Upstream source list (columns verified first-hand 2026-06-11): reporting.processed_facts, reporting.hp_map_results, reporting.assessment_results, reporting.courses + reporting.course_components.
  • The reporting export is NOT an approved Alpha module; Analytics never aliases it. It is ingested via the aitd-001 named adapters into alpha.analytics_source_import plus the rollup tables.
  • Producer = the migration app (and any future close-time sync). Analytics is the CONSUMER of POST /source-imports/{adapter}; it never reaches into the warehouse at read time.
  • Per-value READ-vs-INGEST-vs-BLOCKED ownership is pinned in aitd-014; the data dictionary copies the exact column map per adapter.

Trace

  • aitd-001-report-source-ingestion
  • aitd-014-source-ownership-and-prerequisites
  • reporting.processed_facts
  • reporting.hp_map_results
  • reporting.assessment_results
  • reporting.courses/course_components
  • NWEAMap/Ed-Fi external-source pattern
  • source/evidence/reporting-export-columns-verified.json
#aitd-014-source-ownership-and-prerequisites

aitd 014 source ownership and prerequisites

AcceptedOwner: TimeBack Platform Architecture

Decision

Every Learning-Report value is pinned to one of three ownership verdicts in the Source & Ownership Ledger: (a) READ from an approved Alpha object (Results.student_kc_state, the NWEAMap dedup/Growth-X calculator, People & Orgs enrollment, Curriculum expected XP, alpha.policy.*); (b) INGEST source-shaped from the TimeBack Reporting Export (aitd-013) under a named adapter; or (c) BLOCKED on a named prerequisite not yet present in the deployed surface — in which case the tile reads its typed null_reason (source_missing / policy_pending), never a literal. The four prerequisites (alpha.school_calendar over ed_fi.CalendarDate; alpha.policy.analytics.xp_goal; Curriculum per-scope expected XP; the ingested reporting rows themselves) are each named with an owner and a re-open trigger. The reporting probe MUST be seeded from REAL reporting rows via the MCP/token-file path; computing import inputs from the ground-truth tile is a forbidden fabrication.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: The Source & Ownership Ledger on this page is binding: data dictionary and implementation copy the exact source.columns, normalization policy, owner, and verdict per metric. BLOCKED values read a typed null_reason (source_missing | policy_pending); no materializer may substitute a literal, a constant, an activity flag, or a value computed from the report answer. Probe/smoke seeding rule (anti-fabrication): the deployed reporting probe is fed by REAL reporting rows ingested via the documented adapter (seeded through the MCP/token-file path). Inputs computed from the ground-truth tile are a conformance failure, not a green. Each prerequisite carries an owner plus a re-open trigger; when an upstream canonical owner later exposes a value Analytics gap-fill-ingested, the read switches to the canonical object. Trace: Source & Ownership Ledger | Prerequisites And Owners | aitd-001-report-source-ingestion | aitd-004-provenance-no-literals | aitd-110-axis-conformance-evidence | loop/scripts/reporting-probe.mjs | loop/migration/feedback/alpha_reconcile.md.

Downstream rules

  • The Source & Ownership Ledger on this page is binding: data dictionary and implementation copy the exact source.columns, normalization policy, owner, and verdict per metric.
  • BLOCKED values read a typed null_reason (source_missing | policy_pending); no materializer may substitute a literal, a constant, an activity flag, or a value computed from the report answer.
  • Probe/smoke seeding rule (anti-fabrication): the deployed reporting probe is fed by REAL reporting rows ingested via the documented adapter (seeded through the MCP/token-file path). Inputs computed from the ground-truth tile are a conformance failure, not a green.
  • Each prerequisite carries an owner plus a re-open trigger; when an upstream canonical owner later exposes a value Analytics gap-fill-ingested, the read switches to the canonical object.

Trace

  • Source & Ownership Ledger
  • Prerequisites And Owners
  • aitd-001-report-source-ingestion
  • aitd-004-provenance-no-literals
  • aitd-110-axis-conformance-evidence
  • loop/scripts/reporting-probe.mjs
  • loop/migration/feedback/alpha_reconcile.md
#aitd-015-norms-r90-readable-resources

aitd 015 norms r90 readable resources

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics publishes the NWEA achievement-status norms (percentile<->RIT) and an Alpha-readable mirror of the NWEAMAP-owned PowerPath RIT-to-grade table as first-class, versioned, READABLE surface resources — the tables THEMSELVES, served at GET /alpha/analytics/v1/norms and /norms/table and GET /alpha/analytics/v1/r90/table — not just the APIs over them. Norms are Analytics-owned reference data versioned by table_version and parameterized by norms_set (2020/2025). R90/effective-grade is an Alpha EXTEND because NWEA does not publish it, but the platform master lives in NWEAMAP under R6; Analytics mirrors it with ownerModule=nweamap and sourceRef=powerpath:/powerpath/rit-to-grade. An app reads or caches the surface response; it never maintains its own table. Per ruling R1/R6, Analytics is the Alpha-side serving home and NWEAMAP is the PowerPath RIT-to-grade owner. (Deployed and probe-green at implementation a11; this ITD documents the served shape — it does not redesign it.)

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Two READ-only reference resources to apps: alpha.analytics_norms_achievement (NWEA achievement-status norms — one mean_rit/sd_rit row per norms_set x subject x role x grade x season), served at GET /alpha/analytics/v1/norms (default view) and /norms/table (filterable by subject/grade/season/role/normsSet, paginated); and alpha.analytics_r90_table, the Alpha mirror of NWEAMAP-owned PowerPath RIT-to-grade rows (r90_grade, effective_grade, r90_grade_level, rit90_grade_band_percent per RIT), served at GET /alpha/analytics/v1/r90/table (filterable by subject, paginated). Each table read carries its table_version string (active norms: analytics.norms.achievement.v2026-06-12; active R90 mirror: analytics.rit_to_grade.powerpath.v2026-06-15, the ONE current Alpha mirror — see aitd-017) and the norms_set parameter is in {2020, 2025} (ALPHA-5). A table is immutable per table_version — a legitimate new norms set or R90 owner revision is served under a NEW table_version string, never an in-place update, so a historical report reproduces exactly. Which table_version is active is named surface config read identically by the API and any raw-DB consumer (aitd-017), never inferred by sorting the version string. Provenance: each norms row source_refs the NWEA-published norms in the NWEAMap 1EdTech raw mirror (NormsReferenceData); each R90 row carries ownerModule=nweamap and sourceRef=powerpath:/powerpath/rit-to-grade, is marked extend_kind=alpha_extrapolation with the documented method, and reproduces the exact PowerPath RIT-to-grade row for the requested RIT. Cache/version semantics: every read carries its table_version (translations carry calculator_version) and responds cache-control: no-store; the version string IS the cache key — clients MAY cache by table_version and MUST NOT maintain their own copy; norms changes are owned by Analytics and R90 changes start in the NWEAMAP owner then mirror into Analytics. There is no separate version-lookup error code: a subject with no published table returns analytics:validation_failed (field code norms_subject_not_found / r90_subject_not_found). Ownership: per ruling R1 Analytics is the Alpha serving home for MAP report/reference reads; per ruling R6 NWEAMAP owns the platform PowerPath RIT-to-grade master. Results owns per-student MAP scores/growth and READS these tables, never forks them. These are reference tables, not per-student derived facts: the no-copy/FK storage gate (aitd-000) governs per-student rows; ALPHA-8 explicitly requires the platform to materialize the norms/R90 tables themselves as readable resources, and R6 narrows R90 master ownership to NWEAMAP while allowing Analytics to serve the Alpha mirror. Trace: alpha.analytics_norms_achievement | alpha.analytics_r90_table | GET /alpha/analytics/v1/norms | GET /alpha/analytics/v1/norms/table | GET /alpha/analytics/v1/r90/table | table_version | Brainlift ALPHA-8 (amended 2026-06-12) | Brainlift ALPHA-4 (R90) | Brainlift ALPHA-5 (norms discipline) | ruling R1 (loop/context/brainlift-contradiction-rulings.md) | aitd-016-scale-translation-apis | aitd-017-r90-version-supersession | aitd-009-map-growth-rollups.

Downstream rules

  • Two READ-only reference resources to apps: alpha.analytics_norms_achievement (NWEA achievement-status norms — one mean_rit/sd_rit row per norms_set x subject x role x grade x season), served at GET /alpha/analytics/v1/norms (default view) and /norms/table (filterable by subject/grade/season/role/normsSet, paginated); and alpha.analytics_r90_table, the Alpha mirror of NWEAMAP-owned PowerPath RIT-to-grade rows (r90_grade, effective_grade, r90_grade_level, rit90_grade_band_percent per RIT), served at GET /alpha/analytics/v1/r90/table (filterable by subject, paginated).
  • Each table read carries its table_version string (active norms: analytics.norms.achievement.v2026-06-12; active R90 mirror: analytics.rit_to_grade.powerpath.v2026-06-15, the ONE current Alpha mirror — see aitd-017) and the norms_set parameter is in {2020, 2025} (ALPHA-5). A table is immutable per table_version — a legitimate new norms set or R90 owner revision is served under a NEW table_version string, never an in-place update, so a historical report reproduces exactly. Which table_version is active is named surface config read identically by the API and any raw-DB consumer (aitd-017), never inferred by sorting the version string.
  • Provenance: each norms row source_refs the NWEA-published norms in the NWEAMap 1EdTech raw mirror (NormsReferenceData); each R90 row carries ownerModule=nweamap and sourceRef=powerpath:/powerpath/rit-to-grade, is marked extend_kind=alpha_extrapolation with the documented method, and reproduces the exact PowerPath RIT-to-grade row for the requested RIT.
  • Cache/version semantics: every read carries its table_version (translations carry calculator_version) and responds cache-control: no-store; the version string IS the cache key — clients MAY cache by table_version and MUST NOT maintain their own copy; norms changes are owned by Analytics and R90 changes start in the NWEAMAP owner then mirror into Analytics. There is no separate version-lookup error code: a subject with no published table returns analytics:validation_failed (field code norms_subject_not_found / r90_subject_not_found).
  • Ownership: per ruling R1 Analytics is the Alpha serving home for MAP report/reference reads; per ruling R6 NWEAMAP owns the platform PowerPath RIT-to-grade master. Results owns per-student MAP scores/growth and READS these tables, never forks them.
  • These are reference tables, not per-student derived facts: the no-copy/FK storage gate (aitd-000) governs per-student rows; ALPHA-8 explicitly requires the platform to materialize the norms/R90 tables themselves as readable resources, and R6 narrows R90 master ownership to NWEAMAP while allowing Analytics to serve the Alpha mirror.

Trace

  • alpha.analytics_norms_achievement
  • alpha.analytics_r90_table
  • GET /alpha/analytics/v1/norms
  • GET /alpha/analytics/v1/norms/table
  • GET /alpha/analytics/v1/r90/table
  • table_version
  • Brainlift ALPHA-8 (amended 2026-06-12)
  • Brainlift ALPHA-4 (R90)
  • Brainlift ALPHA-5 (norms discipline)
  • ruling R1 (loop/context/brainlift-contradiction-rulings.md)
  • aitd-016-scale-translation-apis
  • aitd-017-r90-version-supersession
  • aitd-009-map-growth-rollups
#aitd-016-scale-translation-apis

aitd 016 scale translation apis

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics exposes the translations that read those reference tables as server-side surface APIs: GET /alpha/analytics/v1/norms/rit (percentile->RIT) and /norms/percentile (RIT->percentile) — the ALPHA-8 translation, computed from the published norm row's mean_rit/sd_rit normal model — and GET /alpha/analytics/v1/r90 (RIT->R90, effective grade, and grade level — the ALPHA-4 conversion), which reads the current PowerPath RIT-to-grade table and chooses the row whose rit_score equals the requested score. Exact table hits return source_point_kind=exact; missing rows return source_missing. Each translation takes a norms_set parameter where applicable. An app calls the surface or reads the versioned table; it never maintains its own norms math.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Reads: GET /alpha/analytics/v1/norms/rit (percentile->RIT; inputs subject, grade, season, percentile, normsSet, role; response carries norms_set, calculator_version, mean_rit, sd_rit, rit_score), /norms/percentile (RIT->percentile; ...rit instead of percentile), and /r90 (RIT->R90; inputs subject, rit; response carries r90_grade, effective_grade, r90_grade_level, source_point_kind, table_version). percentile<->RIT is computed from the published norm row's mean_rit/sd_rit (a continuous normal model, exact to the published norm). RIT->R90 uses the current PowerPath RIT-to-grade table exactly: exact table hits return source_point_kind=exact, and missing RIT rows return source_missing. It never uses a nearest-row fallback, interpolates in app code, or fabricates an extrapolation. norms_set is a query parameter (2020/2025), never a code path; the same call under the other norms set regenerates the other report by flipping one parameter (ALPHA-5). Raw-DB convergence: a consumer may instead read alpha.analytics_norms_achievement (mean_rit/sd_rit) and apply the documented normal-model translation, or read alpha.analytics_r90_table — FILTERED to the one active table_version analytics.rit_to_grade.powerpath.v2026-06-15 (aitd-017) — and choose the exact row whose rit_score equals the requested RIT. The supersede removed the prior production.v2026-06-12 derivation, so that version is the only one present in the table and the raw-DB path equals the API path even before the filter; the data dictionary publishes both rules INCLUDING the active-table_version filter, so the API path and the raw-DB path return the same RIT/grade. The MAP growth rollup (aitd-009) and the GOALS goal-to-effort read consume these translations; no consumer re-implements percentile<->RIT or RIT->R90. Trace: GET /alpha/analytics/v1/norms/rit | GET /alpha/analytics/v1/norms/percentile | GET /alpha/analytics/v1/r90 | alpha.analytics_norms_achievement | alpha.analytics_r90_table | source_point_kind | Brainlift ALPHA-8 (amended 2026-06-12) | Brainlift ALPHA-4 | aitd-015-norms-r90-readable-resources | aitd-009-map-growth-rollups.

Downstream rules

  • Reads: GET /alpha/analytics/v1/norms/rit (percentile->RIT; inputs subject, grade, season, percentile, normsSet, role; response carries norms_set, calculator_version, mean_rit, sd_rit, rit_score), /norms/percentile (RIT->percentile; ...rit instead of percentile), and /r90 (RIT->R90; inputs subject, rit; response carries r90_grade, effective_grade, r90_grade_level, source_point_kind, table_version).
  • percentile<->RIT is computed from the published norm row's mean_rit/sd_rit (a continuous normal model, exact to the published norm). RIT->R90 uses the current PowerPath RIT-to-grade table exactly: exact table hits return source_point_kind=exact, and missing RIT rows return source_missing. It never uses a nearest-row fallback, interpolates in app code, or fabricates an extrapolation.
  • norms_set is a query parameter (2020/2025), never a code path; the same call under the other norms set regenerates the other report by flipping one parameter (ALPHA-5).
  • Raw-DB convergence: a consumer may instead read alpha.analytics_norms_achievement (mean_rit/sd_rit) and apply the documented normal-model translation, or read alpha.analytics_r90_table — FILTERED to the one active table_version analytics.rit_to_grade.powerpath.v2026-06-15 (aitd-017) — and choose the exact row whose rit_score equals the requested RIT. The supersede removed the prior production.v2026-06-12 derivation, so that version is the only one present in the table and the raw-DB path equals the API path even before the filter; the data dictionary publishes both rules INCLUDING the active-table_version filter, so the API path and the raw-DB path return the same RIT/grade.
  • The MAP growth rollup (aitd-009) and the GOALS goal-to-effort read consume these translations; no consumer re-implements percentile<->RIT or RIT->R90.

Trace

  • GET /alpha/analytics/v1/norms/rit
  • GET /alpha/analytics/v1/norms/percentile
  • GET /alpha/analytics/v1/r90
  • alpha.analytics_norms_achievement
  • alpha.analytics_r90_table
  • source_point_kind
  • Brainlift ALPHA-8 (amended 2026-06-12)
  • Brainlift ALPHA-4
  • aitd-015-norms-r90-readable-resources
  • aitd-009-map-growth-rollups
#aitd-017-r90-version-supersession

aitd 017 r90 version supersession

AcceptedOwner: TimeBack Platform Architecture

Decision

alpha.analytics_r90_table holds EXACTLY ONE active version: analytics.rit_to_grade.powerpath.v2026-06-15, an Alpha mirror of the NWEAMAP-owned PowerPath observed RIT-to-grade master. The lookup is exact-row only: Math RIT 239 returns RIT50 7.3, RIT90/r90_grade 4.8, highest mastered 4, working/effective 5, starting 3, maximum placement 6, and rit90_grade_band_percent 80. The one active version is named once as surface config (R90_TABLE_VERSION = analytics.rit_to_grade.powerpath.v2026-06-15): the API and every raw-DB reader filter alpha.analytics_r90_table.table_version = R90_TABLE_VERSION, so the API path, raw-DB path, data dictionary, and NWEAMAP owner stay convergent. The current version is NEVER chosen by sorting/parsing the table_version string; inferring a typed fact from a name violates the cross-cutting typed-at-write-time rule. norms (analytics.norms.achievement.v2026-06-12) is independent and keeps its own version.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Exactly one active version: alpha.analytics_r90_table is the Analytics mirror of NWEAMAP's PowerPath RIT-to-grade master under table_version analytics.rit_to_grade.powerpath.v2026-06-15. A WIRED convergence assertion (httpContracts.test.mjs) pins that the raw-DB table value equals the API value for Math RIT 239 -> RIT90 4.8/effective 5, so a conflicting version fails the build. The one version is surface config: R90_TABLE_VERSION = analytics.rit_to_grade.powerpath.v2026-06-15. The API and every raw-DB reader filter alpha.analytics_r90_table.table_version = R90_TABLE_VERSION; the data dictionary publishes this exact filter as the raw-DB rule for R90, and the /r90 + /r90/table responses echo table_version so a caller can confirm which version answered. The filter is correct-by-construction and, because only that version is present, robust even if omitted. Field semantics: rit90_grade_band_percent is MAP-inferred grade-band position inside r90_grade_level, not actual course or grade-level progress. Generic percent_complete is not emitted by the R90 API; student-facing course and grade-level progress is served by Results course-progress and grade-level-progress; Analytics completion rollups are progress evidence and audit rows. Defect-supersede vs norms-revision (reconciles with aitd-015 immutability): aitd-015's immutable-per-version promise covers a legitimate NEW publication (a new NWEA norms set, or a genuinely improved method) — that ships under a NEW table_version and the prior MAY be retained if a report was dated against it. A defective private derivation that contradicts the NWEAMAP/PowerPath owner is not a publication to reproduce; it is removed. No report should be reproducible against a wrong RIT-to-grade derivation. norms is unaffected: analytics.norms.achievement.v2026-06-12 was not revised by #81; only R90 advanced. The two reference tables version independently. Re-open trigger: if a genuinely-second, legitimate R90 version must ever coexist with analytics.rit_to_grade.powerpath.v2026-06-15 (e.g. a 2020-vs-2025 R90 split parallel to the norms sets, or a non-Analytics consumer needing to discover the current version without reading this doc), promote the pointer to a readable alpha.analytics_reference_version registry row (resource -> current table_version) plus a discovery read and retain both versions. Until a second LEGITIMATE version exists, exactly-one-version plus the named-config pointer is sufficient and avoids redesigning the deployed surface. Trace: alpha.analytics_r90_table | table_version | R90_TABLE_VERSION (surface config) | NWEAMAP PowerPath RIT-to-grade master | GET /alpha/analytics/v1/r90 | GET /alpha/analytics/v1/r90/table | httpContracts.test.mjs (raw-DB == API convergence assertion) | issue #81 (R90 conformance) | Brainlift ALPHA-4 (one R90 table) | cross-cutting: policy numbers are surface config | cross-cutting: typed at write time, never inferred from names | aitd-015-norms-r90-readable-resources | aitd-016-scale-translation-apis | aitd-009-map-growth-rollups.

Downstream rules

  • Exactly one active version: alpha.analytics_r90_table is the Analytics mirror of NWEAMAP's PowerPath RIT-to-grade master under table_version analytics.rit_to_grade.powerpath.v2026-06-15. A WIRED convergence assertion (httpContracts.test.mjs) pins that the raw-DB table value equals the API value for Math RIT 239 -> RIT90 4.8/effective 5, so a conflicting version fails the build.
  • The one version is surface config: R90_TABLE_VERSION = analytics.rit_to_grade.powerpath.v2026-06-15. The API and every raw-DB reader filter alpha.analytics_r90_table.table_version = R90_TABLE_VERSION; the data dictionary publishes this exact filter as the raw-DB rule for R90, and the /r90 + /r90/table responses echo table_version so a caller can confirm which version answered. The filter is correct-by-construction and, because only that version is present, robust even if omitted.
  • Field semantics: rit90_grade_band_percent is MAP-inferred grade-band position inside r90_grade_level, not actual course or grade-level progress. Generic percent_complete is not emitted by the R90 API; student-facing course and grade-level progress is served by Results course-progress and grade-level-progress; Analytics completion rollups are progress evidence and audit rows.
  • Defect-supersede vs norms-revision (reconciles with aitd-015 immutability): aitd-015's immutable-per-version promise covers a legitimate NEW publication (a new NWEA norms set, or a genuinely improved method) — that ships under a NEW table_version and the prior MAY be retained if a report was dated against it. A defective private derivation that contradicts the NWEAMAP/PowerPath owner is not a publication to reproduce; it is removed. No report should be reproducible against a wrong RIT-to-grade derivation.
  • norms is unaffected: analytics.norms.achievement.v2026-06-12 was not revised by #81; only R90 advanced. The two reference tables version independently.
  • Re-open trigger: if a genuinely-second, legitimate R90 version must ever coexist with analytics.rit_to_grade.powerpath.v2026-06-15 (e.g. a 2020-vs-2025 R90 split parallel to the norms sets, or a non-Analytics consumer needing to discover the current version without reading this doc), promote the pointer to a readable alpha.analytics_reference_version registry row (resource -> current table_version) plus a discovery read and retain both versions. Until a second LEGITIMATE version exists, exactly-one-version plus the named-config pointer is sufficient and avoids redesigning the deployed surface.

Trace

  • alpha.analytics_r90_table
  • table_version
  • R90_TABLE_VERSION (surface config)
  • NWEAMAP PowerPath RIT-to-grade master
  • GET /alpha/analytics/v1/r90
  • GET /alpha/analytics/v1/r90/table
  • httpContracts.test.mjs (raw-DB == API convergence assertion)
  • issue #81 (R90 conformance)
  • Brainlift ALPHA-4 (one R90 table)
  • cross-cutting: policy numbers are surface config
  • cross-cutting: typed at write time, never inferred from names
  • aitd-015-norms-r90-readable-resources
  • aitd-016-scale-translation-apis
  • aitd-009-map-growth-rollups
#aitd-018-goals-school-days-remaining

aitd 018 goals school days remaining

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics owns a forward-looking GOALS read, GET /alpha/analytics/v1/school-days-remaining, that counts remaining instructional days from alpha.school_calendar over a half-open [asOf, endDate) window, intersects the student's effective-dated enrollment when studentId is supplied, excludes MAP-testing days under alpha.policy.school_day, and returns the XP-to-time effort projection under alpha.policy.analytics.xp_expected_minute.v2026-06-14. The unit policy is 1 XP = 1 expected minute and 60 XP = 1 expected hour. The response shape is analytics.school_days_remaining: instructional_days_remaining, calendar_ref_count, first/last_instructional_date, instructional_dates, xp_unit.*, effort.*, provenance.*, and links.*. GOALS calls this read or follows the documented raw path; it does not count weekdays, maintain a private school calendar, or carry an XP-hours constant.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Endpoint: GET /alpha/analytics/v1/school-days-remaining?studentId=...&asOf=YYYY-MM-DD&endDate=YYYY-MM-DD, with optional targetDate, xpRemaining, minutesRemaining, hoursRemaining, and hoursPerSchoolDay. The date window is half-open: asOf is included and endDate is excluded. Raw-DB convergence: read alpha.school_calendar for the student's school/enrollment window, filter is_school_day=true under alpha.policy.school_day, exclude MAP-testing days, count dates in [asOf,endDate), and apply alpha.policy.analytics.xp_expected_minute.v2026-06-14 for effort fields. Do not read alpha.analytics_school_day_minutes for future days; those are consumed-day rollups. XP unit policy: 1 XP = 1 expected minute; 60 XP = 1 expected hour. expected_xp, daily_xp_goal, and xp_remaining values are therefore expected-minute units for GOALS effort projection, while XP awarding rules remain owned by Results/Policy. Response object: analytics.school_days_remaining with stable field anchors in the data dictionary. first_instructional_date and last_instructional_date are null when the window has zero instructional days; effort.* fields are null unless the caller supplies the needed effort inputs. The customer website and skill pack link to the data dictionary object and policy anchors; if the endpoint is missing, that is an implementation bug, not permission for clients to re-implement the calendar. Trace: GET /alpha/analytics/v1/school-days-remaining | analytics.school_days_remaining | alpha.school_calendar | alpha.policy.school_day | alpha.policy.analytics.xp_expected_minute.v2026-06-14 | GOALS target-date read | Brainlift GOALS goal-to-effort conversion | aitd-006-school-day-minutes | aitd-007-xp-rollups | aitd-016-scale-translation-apis.

Downstream rules

  • Endpoint: GET /alpha/analytics/v1/school-days-remaining?studentId=...&asOf=YYYY-MM-DD&endDate=YYYY-MM-DD, with optional targetDate, xpRemaining, minutesRemaining, hoursRemaining, and hoursPerSchoolDay. The date window is half-open: asOf is included and endDate is excluded.
  • Raw-DB convergence: read alpha.school_calendar for the student's school/enrollment window, filter is_school_day=true under alpha.policy.school_day, exclude MAP-testing days, count dates in [asOf,endDate), and apply alpha.policy.analytics.xp_expected_minute.v2026-06-14 for effort fields. Do not read alpha.analytics_school_day_minutes for future days; those are consumed-day rollups.
  • XP unit policy: 1 XP = 1 expected minute; 60 XP = 1 expected hour. expected_xp, daily_xp_goal, and xp_remaining values are therefore expected-minute units for GOALS effort projection, while XP awarding rules remain owned by Results/Policy.
  • Response object: analytics.school_days_remaining with stable field anchors in the data dictionary. first_instructional_date and last_instructional_date are null when the window has zero instructional days; effort.* fields are null unless the caller supplies the needed effort inputs.
  • The customer website and skill pack link to the data dictionary object and policy anchors; if the endpoint is missing, that is an implementation bug, not permission for clients to re-implement the calendar.

Trace

  • GET /alpha/analytics/v1/school-days-remaining
  • analytics.school_days_remaining
  • alpha.school_calendar
  • alpha.policy.school_day
  • alpha.policy.analytics.xp_expected_minute.v2026-06-14
  • GOALS target-date read
  • Brainlift GOALS goal-to-effort conversion
  • aitd-006-school-day-minutes
  • aitd-007-xp-rollups
  • aitd-016-scale-translation-apis
#aitd-019-age-grade-status

aitd 019 age grade status

AcceptedOwner: TimeBack Platform Architecture

Decision

Analytics owns GET /alpha/analytics/v1/grade-level-status as the composed age-grade comparison read. It reads People & Orgs age_grade_history and Results working_grade / highest_mastered_grade at the same asOfDate, then returns exactly two named comparisons: working_age_grade_status = working_grade - age_grade and mastered_age_grade_status = highest_mastered_grade - age_grade. The canonical labels are working_behind_age_grade, working_at_age_grade, working_above_age_grade, mastered_behind_age_grade, mastered_at_age_grade, and mastered_above_age_grade. There is no preferred generic ahead/behind/at field.

Alternatives and tradeoffs

  • Generic ahead/behind/at status
  • Results-owned roster comparison
  • Consumer-computed status from separate People & Orgs and Results reads

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Endpoint: GET /alpha/analytics/v1/grade-level-status?studentId=...&subjectId=...&asOfDate=YYYY-MM-DD. Response object: analytics.grade_level_status with age_grade, working_grade, highest_mastered_grade, working_age_grade_delta, working_age_grade_status, mastered_age_grade_delta, mastered_age_grade_status, within_grade_pacing, source refs to People & Orgs age_grade and Results working_grade/HMG, and deprecated_aliases. Deprecated aliases may exist only when they delegate to the named field: instructional_level_status -> working_age_grade_status and strict_mastery_status -> mastered_age_grade_status. Unsupported genericAheadBehind requests return a validation error. MAP/norm status and within-grade pacing remain separately named reads and must not overwrite roster age grade, working grade, or mastered status. Trace: GET /alpha/analytics/v1/grade-level-status | analytics.grade_level_status | loop/context/alpha-age-grade-and-grade-status-decision.md | People & Orgs age_grade_history | Results working_grade | Results highest_mastered_grade.

Downstream rules

  • Read People & Orgs age_grade_history and Results working_grade / highest_mastered_grade at the same asOfDate.
  • working_age_grade_status = working_grade - age_grade.
  • mastered_age_grade_status = highest_mastered_grade - age_grade.
  • Canonical labels are working_behind_age_grade, working_at_age_grade, working_above_age_grade, mastered_behind_age_grade, mastered_at_age_grade, and mastered_above_age_grade.
  • Deprecated aliases may exist only when they delegate to the named field: instructional_level_status -> working_age_grade_status and strict_mastery_status -> mastered_age_grade_status.
  • Unsupported genericAheadBehind requests return a validation error.

Trace

  • GET /alpha/analytics/v1/grade-level-status
  • analytics.grade_level_status
  • loop/context/alpha-age-grade-and-grade-status-decision.md
  • People & Orgs age_grade_history
  • Results working_grade
  • Results highest_mastered_grade
#aitd-101-axis-write-granularity

aitd 101 axis write granularity

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: public writes are bulk-only source imports (one endpoint per named adapter); live derived writes are internal close hooks from Events and Results; no public per-metric writes ship.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Data dictionary documents POST /source-imports/{adapter} only for writes. Implementation rejects client-supplied normalized metric rows. Close hooks use deterministic source keys. Trace: POST /alpha/analytics/v1/source-imports/timeback-xp-time-accuracy | POST /alpha/analytics/v1/source-imports/timeback-map | event/result close materializers.

Downstream rules

  • Data dictionary documents POST /source-imports/{adapter} only for writes.
  • Implementation rejects client-supplied normalized metric rows.
  • Close hooks use deterministic source keys.

Trace

  • POST /alpha/analytics/v1/source-imports/timeback-xp-time-accuracy
  • POST /alpha/analytics/v1/source-imports/timeback-map
  • event/result close materializers
#aitd-102-axis-read-shape

aitd 102 axis read shape

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: page-bounded list plus detail for each rollup collection, with source-ref subcollections for audit/provenance users.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Every collection has GET list and GET detail. List reads are page-bounded by limit/cursor and must not require an unbounded total-count scan before returning the page. GET /{id}/source-refs returns source_import provenance, authorized and paginated. Ordinary list rows include summary provenance counts and null_reason. Trace: GET /xp-rollups | GET /xp-rollups/{id}/source-refs | GET /map-growth-rollups/{id}/source-refs | GET /completion-rollups.

Downstream rules

  • Every collection has GET list and GET detail.
  • List reads are page-bounded by limit/cursor and must not require an unbounded total-count scan before returning the page.
  • GET /{id}/source-refs returns source_import provenance, authorized and paginated.
  • Ordinary list rows include summary provenance counts and null_reason.

Trace

  • GET /xp-rollups
  • GET /xp-rollups/{id}/source-refs
  • GET /map-growth-rollups/{id}/source-refs
  • GET /completion-rollups
#aitd-103-axis-query-model

aitd 103 axis query model

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: filter + sort + limit + cursor + modifiedSince for all list endpoints, with typed filters only.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Filters are typed: studentId, subjectId, startDate, endDate, windowKind, termId, normsSet, asOfDate, policyRef, modifiedSince. Vercel routing params (e.g. path) are stripped before filter validation. Unsupported filters return analytics:unsupported_parameter, including the per-student studentId filter the migration probe requires. Trace: all GET list endpoints.

Downstream rules

  • Filters are typed: studentId, subjectId, startDate, endDate, windowKind, termId, normsSet, asOfDate, policyRef, modifiedSince.
  • Vercel routing params (e.g. path) are stripped before filter validation.
  • Unsupported filters return analytics:unsupported_parameter, including the per-student studentId filter the migration probe requires.

Trace

  • all GET list endpoints
#aitd-104-axis-concurrency

aitd 104 axis concurrency

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: no public If-Match concurrency for derived facts; recompute uses materializer-owned calculation_version and supersedes/correction rows.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Derived rows expose calculation_version, computed_at, superseded_at, correction_of_ref. Imports use idempotency, not If-Match. Manual correction, if ever requested, reopens architecture. Trace: all alpha.analytics_* rows.

Downstream rules

  • Derived rows expose calculation_version, computed_at, superseded_at, correction_of_ref.
  • Imports use idempotency, not If-Match.
  • Manual correction, if ever requested, reopens architecture.

Trace

  • all alpha.analytics_* rows
#aitd-105-axis-idempotency

aitd 105 axis idempotency

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: Idempotency-Key required on source imports; deterministic source_fact_key required for materializer writes.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: POST imports require Idempotency-Key. source_fact_key includes tenant, source ref, fact kind, policy ref, and calculation version. Replay with same key and body is a no-op; conflict returns analytics:idempotency_conflict. Trace: alpha.analytics_source_import | all materializer writes.

Downstream rules

  • POST imports require Idempotency-Key.
  • source_fact_key includes tenant, source ref, fact kind, policy ref, and calculation version.
  • Replay with same key and body is a no-op; conflict returns analytics:idempotency_conflict.

Trace

  • alpha.analytics_source_import
  • all materializer writes
#aitd-106-axis-auth-shape

aitd 106 axis auth shape

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: role plus scopes plus scoped claims such as studentIds, schoolSourcedIds, and agentOf.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Every read scopes by tenant_id and authorized student/cohort. Student/parent reads see only their own rows. Migration/source-import scope is separate from ordinary report read scope. Trace: all endpoints | JWT claims.

Downstream rules

  • Every read scopes by tenant_id and authorized student/cohort.
  • Student/parent reads see only their own rows.
  • Migration/source-import scope is separate from ordinary report read scope.

Trace

  • all endpoints
  • JWT claims
#aitd-107-axis-eventing

aitd 107 axis eventing

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: poll plus modifiedSince; DEFER webhooks until an integration demonstrates a persona-blocking need for push.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: All list endpoints accept modifiedSince. Rows expose modified_at. Webhook request reopens architecture when polling cannot meet a live app's freshness target. Trace: all GET list endpoints.

Downstream rules

  • All list endpoints accept modifiedSince.
  • Rows expose modified_at.
  • Webhook request reopens architecture when polling cannot meet a live app's freshness target.

Trace

  • all GET list endpoints
#aitd-108-axis-error-envelope

aitd 108 axis error envelope

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: typed RFC 7807 Problem responses with stable analytics:* codes and stable type URIs.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: 400 analytics:validation_failed for envelope/filter/body errors. 422 analytics:adapter_rejected for valid source rows that cannot normalize, including a missing expected report source. 409 analytics:idempotency_conflict for replay mismatch. Never return HTTP 200/202 with an error code in the body. Trace: all endpoints | source import adapters.

Downstream rules

  • 400 analytics:validation_failed for envelope/filter/body errors.
  • 422 analytics:adapter_rejected for valid source rows that cannot normalize, including a missing expected report source.
  • 409 analytics:idempotency_conflict for replay mismatch.
  • Never return HTTP 200/202 with an error code in the body.

Trace

  • all endpoints
  • source import adapters
#aitd-109-axis-tenant-routing

aitd 109 axis tenant routing

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: tenant_id comes from the JWT claim only, not URL path or request body.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Requests with tenantId body/query are ignored or rejected. Raw DB rules always include tenant_id from caller context. Idempotency keys are tenant-scoped. Trace: all endpoints | all tables.

Downstream rules

  • Requests with tenantId body/query are ignored or rejected.
  • Raw DB rules always include tenant_id from caller context.
  • Idempotency keys are tenant-scoped.

Trace

  • all endpoints
  • all tables
#aitd-110-axis-conformance-evidence

aitd 110 axis conformance evidence

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: Analytics conformance is the migration metric_check runner plus the reporting probe plus three-way convergence, not a 1EdTech spec runner.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Surface QC runs metric_checks for minutes, XP, mastery, MAP, completion. loop/scripts/reporting-probe.mjs must be green from rows ingested from REAL production reporting data (seeded via the MCP/token-file path), never from literals or inputs reverse-engineered from the ground-truth tile (aitd-014). A tile whose prerequisite (aitd-014) has not landed reads a typed null_reason; an honest null is a pass, a fabricated green is a conformance failure. Data dictionary publishes raw-DB twin rules per metric; skill pack contains no math/norms/dedup/window logic. Trace: loop/migration/artifacts/alpha/reconcile | loop/scripts/reporting-probe.mjs | surface_qc | skill_pack.

Downstream rules

  • Surface QC runs metric_checks for minutes, XP, mastery, MAP, completion.
  • loop/scripts/reporting-probe.mjs must be green from rows ingested from REAL production reporting data (seeded via the MCP/token-file path), never from literals or inputs reverse-engineered from the ground-truth tile (aitd-014).
  • A tile whose prerequisite (aitd-014) has not landed reads a typed null_reason; an honest null is a pass, a fabricated green is a conformance failure.
  • Data dictionary publishes raw-DB twin rules per metric; skill pack contains no math/norms/dedup/window logic.

Trace

  • loop/migration/artifacts/alpha/reconcile
  • loop/scripts/reporting-probe.mjs
  • surface_qc
  • skill_pack
#aitd-111-axis-privacy-retention

aitd 111 axis privacy retention

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: derived/ingested facts inherit source retention and authorization, with tombstone/correction rows when a source is deleted or superseded; operational probe tenants retain only current materializations plus import audit rows.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Ordinary reads exclude superseded/tombstoned rows unless includeSuperseded=true and authorized. Rows carry source_deleted_at or superseded_at when source changes. PII raw source payloads are not stored in public rollup rows; only natural keys/hashes for replay. Reviewer/eval tenants (`reviewer-analytics` and `analytics-*debug*`) are operational probe tenants: after migration/import materialization, superseded or tombstoned Analytics materializations may be swept immediately while current rows and alpha.analytics_source_import audit rows remain. Trace: all alpha.analytics_* tables | alpha.analytics_source_import | reviewer/eval probe tenants.

Downstream rules

  • Ordinary reads exclude superseded/tombstoned rows unless includeSuperseded=true and authorized.
  • Rows carry source_deleted_at or superseded_at when source changes.
  • PII raw source payloads are not stored in public rollup rows; only natural keys/hashes for replay.
  • Reviewer/eval tenants (`reviewer-analytics` and `analytics-*debug*`) are operational probe tenants: after migration/import materialization, superseded or tombstoned Analytics materializations may be swept immediately while current rows and alpha.analytics_source_import audit rows remain.

Trace

  • all alpha.analytics_* tables
  • alpha.analytics_source_import
  • reviewer/eval probe tenants
#aitd-112-axis-list-endpoints

aitd 112 axis list endpoints

AcceptedOwner: TimeBack Platform Architecture

Decision

SHIP: one named list endpoint per Analytics collection, plus the readable MAP reference tables (/norms, /norms/table, /r90/table), the norms/R90 translations (/norms/rit, /norms/percentile, /r90), and the GOALS forward calendar read (/school-days-remaining); DEFER a generic metric endpoint until at least two external consumers need dynamic metric discovery.

Rationale and consequences

Owner: TimeBack Platform Architecture. Downstream rules: Publish /time-windows, /school-day-minutes, /xp-rollups, /mastery-deltas, /map-growth-rollups, /completion-rollups, /source-imports, the readable reference tables /norms, /norms/table and /r90/table (aitd-015), the translations /norms/rit, /norms/percentile, /r90 (aitd-016), and /school-days-remaining for GOALS target-date effort reads (aitd-018). No generic /metrics?metric=... endpoint ships in attempt 1. Reopen if skill-pack examples duplicate endpoint selection logic. Trace: API catalog | aitd-015-norms-r90-readable-resources | aitd-016-scale-translation-apis | aitd-018-goals-school-days-remaining.

Downstream rules

  • Publish /time-windows, /school-day-minutes, /xp-rollups, /mastery-deltas, /map-growth-rollups, /completion-rollups, /source-imports, the readable reference tables /norms, /norms/table and /r90/table (aitd-015), the translations /norms/rit, /norms/percentile, /r90 (aitd-016), and /school-days-remaining for GOALS target-date effort reads (aitd-018).
  • No generic /metrics?metric=... endpoint ships in attempt 1.
  • Reopen if skill-pack examples duplicate endpoint selection logic.

Trace

  • API catalog
  • aitd-015-norms-r90-readable-resources
  • aitd-016-scale-translation-apis
  • aitd-018-goals-school-days-remaining