Academic Health Data Model
How the GPA and course-grade side of the Academic & Gradebook Health Suite works: where its data comes from, how the dbt models turn PowerSchool grades and the GPA goals sheet into the dashboard, and what to know before changing any of it.
What it is
The Academic & Gradebook Health Suite is a Tableau workbook that school and network leaders use to watch high school and middle school grades during the year: GPA distributions, course failures, students near the 3.0 line, and progress against the network's GPA goals. Anthony Walters built it and owns it.
The workbook has two halves:
- Academic health (this page). Landing Page, Academic Health Home, Academic Health Schools, and Cumulative GPA Monitor. They read GPA and course grades.
- Gradebook health. Gradebook School Rollup and Gradebook Teacher View. They
read
rpt_tableau__gradebook_auditand are covered on the Gradebook Audit Data Model page.
It is declared in dbt as the exposure academic_gradebook_health_suite, and
Dagster refreshes its extracts daily at 4 AM Eastern. The four rpt_ models and
the goal models are views, so a refresh shows whatever the PowerSchool tables
beneath them held at their last build, not a live read of PowerSchool.
This page explains the models. How to read each view, tab by tab, is in the end-user guides Walters drafted on open PR #5264; full view documentation is his to add once that lands.
How it fits together
PowerSchool: Newark, Camden, Paterson
(stored grades, live gradebook, terms, grade scales, course enrollments)
│
├─► base_powerschool__final_grades (current-year term and Y1 grades)
│
├─► int_powerschool__gpa_term ─────► daily snapshot ─► ..._gpa_term_lookback
├─► int_powerschool__gpa_cumulative (current state, no year)
│ └─► int_powerschool__gpa_cumulative_year (one row per year)
└─► int_powerschool__student_course_grades_spine
│
+ int_extracts__student_enrollments (roster, demographics)
│
┌─────────────┴──────────────┐
▼ ▼
rpt_tableau__student_ rpt_tableau__gpa_cumulative_year
course_grades │
│ │ GPA goals tab (Google Sheet)
│ │ └─► stg_ ─► int_google_sheets_
│ │ __gpa_goals
│ │ │
│ │ ┌────────┴──────────┐
│ │ ▼ ▼
│ │ int_gpa__student_ int_gpa__goal_
│ │ goal_definitions student_metrics
│ │ │ ▼
│ ▼ ▼ int_gpa__goal_
│ rpt_tableau__gpa_goal_progress aggregations
│ │ ▼
│ │ rpt_tableau__gpa_goals
▼ ▼ ▼
Home and Schools tabs Cumulative GPA Monitor goal panels and tile
Three things carry the whole family:
- The course-grain extract,
rpt_tableau__student_course_grades, holds one row per student, term, course, and gradebook category for this year and last year. The Home and Schools tabs read it. - The year-grain extract,
rpt_tableau__gpa_cumulative_year, holds each student's cumulative GPA at the end of every year. The Cumulative GPA Monitor reads it throughrpt_tableau__gpa_goal_progress, which adds each student's goal. - The goal rollup,
rpt_tableau__gpa_goals, compares school, region, and network rates against the GPA goals sheet.
The workbook's embedded data sources are rpt_tableau__gpa_goals,
rpt_tableau__gpa_goal_progress, rpt_tableau__gradebook_audit,
rpt_tableau__student_course_grades (a custom-SQL source, shown in Tableau as
rpt_tableau__student_course_grades+), and one named GPA Goals - Y1 whose
source is not yet confirmed. #5169 also records
rpt_tableau__gpa_cumulative_year embedded with nothing reading it (see Known
issues, need to fix).
How the SQL treats Miami
Miami is not in the Health Suite. Miami's student information system moved to Focus, and every GPA model here is built from PowerSchool, so the models exclude Miami by name rather than show it empty:
rpt_tableau__student_course_gradeskeeps Newark, Camden, and Paterson.rpt_tableau__gpa_cumulative_year, and sorpt_tableau__gpa_goal_progress, keep Newark and Camden only. Their year-grain source unions only the three NJ regions, and Paterson has no high school.int_gpa__goal_student_metricskeeps Newark, Camden, and Paterson, and so does every goal rate. Putting Miami back is tracked in #5171.
A Miami goal row on the goals sheet would match no students and never reach the dashboard.
Terms
Kinds of GPA
"GPA" means several different numbers here, and they are not interchangeable.
Every one is credit-weighted: each course's grade points times its credit hours,
summed, divided by the credit hours. Courses PowerSchool marks
excludefromgpa = 1 (lunch, homeroom, study hall) never count.
| Term | Column | What it is |
|---|---|---|
| Term GPA | gpa_term, gpa_for_quarter |
One marking period (Q1 to Q4); on the Y1 row, gpa_for_quarter holds the Y1 GPA |
| Semester GPA | gpa_semester |
S1 (Q1, Q2) or S2 (Q3, Q4), from the term rows |
| Y1 GPA | gpa_y1 |
The year so far, from each course's running Y1 grade; for past years, the stored end-of-year Y1 grades |
| Cumulative GPA | cumulative_y1_gpa |
Every stored Y1 grade the student has at that school |
| Projected | cumulative_y1_gpa_projected |
Cumulative plus this year's in-progress Y1 grades, as if the year ended today |
| S1 projected | cumulative_y1_gpa_projected_s1 |
Cumulative plus this year's semester 1 (as of Q2) grades |
| Core cumulative | core_cumulative_y1_gpa |
Cumulative over math, science, English, and social studies credit types, stored grades only |
| Year-end cumulative | cumulative_y1_gpa on the year extract |
Cumulative as of the end of each past year; on the current year, the projected value |
Cumulative GPA is kept per student and school. A student's high school cumulative starts fresh in grade 9 and does not include middle school grades, and a student who changes schools starts a new series.
is_projected marks the current-year row of the year extract, where the value
is the projection rather than a stored result. "As of today" columns
(cumulative_y1_gpa_unweighted_as_of_today) carry the running value, counting
only grades already stored; the projection is the basis for goals.
Weighted and unweighted
Weighted grade points come from the course's own PowerSchool grade scale.
Unweighted grade points come from looking the course's percent up on that
scale's unweighted twin. The SQL names the twins: KIPP NJ 2019 (5-12) Weighted
and KIPP NJ 2024 (5-12) Weighted - Honors map to
KIPP NJ 2019 (5-12) Unweighted, and NCA Honors maps to
KIPP NJ 2016 (5-12). Stored grades also map NCA Honors to NCA 2011 before
2016, and a blank scale to a default. Other scales map to themselves, so for a
course on one the two numbers are equal.
Nothing in the SQL checks school level. Weighting exists only where a course is on a weighted scale, and only high school courses are, so a middle school student's weighted and unweighted GPAs are the same by design (#4978). Weighted GPA can reach 5.33 and unweighted 4.33, so an unweighted GPA above 4.0 is not an error.
Every goal and band on the dashboard uses unweighted GPA except the
y1_gpa_weighted goal metric.
Bands and flags
| Term | Meaning |
|---|---|
gpa_band_label |
Cumulative unweighted band: 3.5+, 3.0-3.49, 2.5-2.99, 2.0-2.49, below 2.0 |
gpa_band_projected_... |
The same cut points as a number, 1 (below 2.0) to 5 (3.5 and up), the KIPP Foundation five-band scale |
is_on_cusp_3_0 |
Cumulative unweighted GPA at least 2.75 and below 3.00 |
gpa_needed_for_cumulative_3_0 |
The unweighted Y1 GPA a student must average this year to finish at exactly 3.00; negative means already safe |
is_gpa_band_slide |
Projected band at least one band below last year's band |
F* |
Not a PowerSchool grade: a live gradebook grade below 50% is floored to 50% and labelled F*. Failure counts match F% |
need_60 to need_90, need_next |
The percent needed in the current term for the year-to-date course grade to reach a target |
| Lookbacks | gpa_y1_1_week_prior and siblings: the Y1 GPA in effect at the end of the day 1, 2, or 4 weeks ago |
Populations
| Term | Meaning |
|---|---|
rn_year = 1 |
A student's primary enrollment in a year; every model filters on it for one school per student-year |
is_enrolled_recent |
The enrollment ran to the end of its school year or is active today, so mid-year leavers drop and graduates stay |
enroll_status |
Current PowerSchool status, not as of the year: 0 active, 2 transferred out, 3 graduated |
Goal terms
| Term | Meaning |
|---|---|
Rung (org_level) |
Where a goal applies: org (the network), region, or school |
metric |
y1_gpa_weighted, y1_gpa_unweighted, cumulative_gpa_unweighted, or on_pace |
threshold |
The GPA a student must reach, compared by direction (>= or <=) |
goal |
The target share of students reaching the threshold, entered as a percent (69 means 69%) |
| Grade band | grade_low to grade_high; one goal row covers each grade in the band |
aggregation_hash |
The key joining a rate to its goal: org_9-12, Newark_11-11, or <schoolid>_10-10 |
metric_rate |
Students meeting the threshold over students with a value, rounded to three places |
Where the data comes from
| Source | What it provides | Owner |
|---|---|---|
| PowerSchool, Newark, Camden, Paterson | Stored grades, live gradebook grades, terms, grade scales, courses, sections, enrollments | Schools enter grades; data team runs the load |
| GPA goals tab (Google Sheet) | Goal threshold and target per year, rung, grade band, and metric | Data team enters; the network sets the goals |
| Staff roster (ADP) | Teacher Tableau usernames and managers, for row-level security | People team |
| Daily GPA snapshot | Yesterday's and earlier Y1 GPAs, for the lookbacks | Built in the warehouse |
PowerSchool reaches every model through the kipptaf union views
(int_powerschool__gpa_term, int_powerschool__gpa_cumulative,
int_powerschool__gpa_cumulative_year,
int_powerschool__student_course_grades_spine), which read each region's
district project. Two of them, int_powerschool__gpa_term and
int_powerschool__gpa_cumulative, also union a Miami relation; the region
filters above keep it out.
The daily snapshot, snapshot_powerschool__gpa_term, records each student's Y1
GPA whenever it changes, captured once a day at 11 PM. The lookbacks read the
version in effect at the end of a past day. A lookback before the year's first
capture reads null.
Demographics, school, grade, advisory, ADA, and school leaders come from
int_extracts__student_enrollments, as of each year's primary enrollment.
Tutoring and tier flags come from int_extracts__student_enrollments_subjects.
Dashboard models
Which tab reads which model, from the exposure and the workbook's data sources:
| Tab | Model |
|---|---|
| Academic Health Home, Schools | rpt_tableau__student_course_grades, plus rpt_tableau__gpa_goals for the goal tile |
| Cumulative GPA Monitor | rpt_tableau__gpa_goal_progress |
| Landing Page | Navigation; its build is on open PR #5246 |
| Gradebook School Rollup, Teacher View | rpt_tableau__gradebook_audit (see the gradebook audit page) |
rpt_tableau__student_course_grades: grades and GPA by course
What it shows: the Home and Schools tabs: GPA by term and year to date, course grades and failures, students near a grade boundary, lowest gradebook category per course, week-over-week GPA change, and the office hours roster.
Grain: one row per student, term (Q1 to Q4, plus a Y1 year row),
course, and gradebook category, for the current and prior academic year. Terms
that have not started yet are left out. Reads
int_extracts__student_enrollments (the student roster),
int_powerschool__student_course_grades_spine (course grades),
int_powerschool__gpa_term (term, Y1, and prior-quarter GPA),
int_powerschool__gpa_cumulative (cumulative and projected GPA),
int_powerschool__gpa_cumulative_year (last year's final cumulative),
int_powerschool__gpa_term_lookback,
int_extracts__student_enrollments_subjects, and int_people__staff_roster.
Worth knowing:
- Student-level columns repeat on every course and category row. Count students with a distinct count, never by summing rows.
- As-of-today measures (cumulative GPA, needed GPA, lookbacks, prior-quarter and prior-year comparisons) fill current-year rows only. Prior-year rows read null for them by design.
gpa_y1on prior-year rows is the stored end-of-year value on every term, because PowerSchool stores only the final Y1 grade.gpa_y1_prior_yearis last year's final weighted Y1 GPA, credit-weighted across schools for a student who attended two.cumulative_y1_gpa_unweighted_change_from_prior_yearcompares this year's projection with last year's final cumulative. Both are per school, so for a grade 9 student it compares a high school projection with a middle school result.is_quarter_course_failingmatchesF%, so it countsF*. It is null, not false, on an ungraded course: divide failures by graded rows, not all rows.office_hours_priority_rankranks a student's courses in a term from lowest percent up; filter to rank 3 or less for a three-teacher list.need_nextis the percent needed in the current term for the year-to-date grade to reach the next letter on the course's own scale, not the letter for the quarter alone.section_or_periodis the section number below grade 9 and the period expression from grade 9.- Its uniqueness test currently warns because of duplicate stored grades in prior years (see Known issues, need to fix).
- Do not relate it to the year extract on
student_numberalone; the match needsacademic_yeartoo, or a student fans out across every year.
rpt_tableau__gpa_goal_progress: the Cumulative GPA Monitor
What it shows: the Cumulative GPA Monitor: cumulative unweighted GPA bands by grade and school, students on the cusp of 3.0, whether 3.0 is still reachable, and each grade and school against its goal.
Grain: one row per student and academic year, guarded by an error-level
uniqueness test. Reads rpt_tableau__gpa_cumulative_year and left joins
int_gpa__student_goal_definitions for the cumulative_gpa_unweighted goal.
Worth knowing:
- Every column of the year extract passes through, plus four goal columns:
gpa_goal_thresholdand the target share at the network, region, and school rung. - The columns are listed by hand. A column added to the year extract does not appear here until it is added to this model's select list and contract too.
- Test
gpa_goal_proportion_orgto find rows with a goal. A null region or school target only means that rung has no goal; the network target still applies. - The threshold is the network rung's. A region or school row on the sheet with
a different threshold changes only that rung's target share here, while
rpt_tableau__gpa_goalsuses each rung's own threshold (see Decisions). - The goal comparison on the dashboard is
>=; the model does not carry the goal'sdirection. - A singular test fails if any grade 9 to 12 row in a year the sheet covers has no network goal. A year counts as covered if it has a goal row for any metric, so a year with only Y1 goals still fails for missing cumulative goals.
rpt_tableau__gpa_cumulative_year: cumulative GPA by year
What it shows: no tab reads it directly. It is the base of
rpt_tableau__gpa_goal_progress (see Known issues, need to fix for its place
in the workbook).
Grain: one row per student and academic year, Newark and Camden only. Reads
int_powerschool__gpa_cumulative_year for the year-end values, inner joined to
the year's primary enrollment in int_extracts__student_enrollments on student,
year, school, and district, and left joins int_powerschool__gpa_cumulative for
the as-of-today values on the current year.
Worth knowing:
- Past years are recomputed from stored Y1 grades as running totals per student
and school; the current year is the projection. Demographics are as of that
year, but
enroll_statusis today's. - The population is
is_enrolled_recent,enroll_status0, 2, or 3, and not out of district. A student-year whose grades were stored at a school other than that year's primary enrollment school drops at the join (#4343). is_latest_graded_yearmarks the most recent year with a non-nullgpa_y1in Newark or Camden. The current year'sgpa_y1comes from the live gradebook, so the flag moves to the current year as soon as any in-progress grade exists, not when Y1 grades are stored. Before then the monitor opens on the prior year.gpa_band_labelis filled on every year;gpa_band_as_of_today_labelonly on the current year.gpa_needed_for_cumulative_3_0andis_cumulative_3_0_attainableare current-year only. They compute for any grade but mean something only in high school.
rpt_tableau__gpa_goals: school, region, and network against goal
What it shows: the GPA goal panels: for each goal on the sheet, the share of students meeting the threshold and whether that meets the goal.
Grain: one row per academic year, metric, and aggregation_hash, that is,
one per goal row on the sheet that matched at least one student. A thin select
over int_gpa__goal_aggregations.
Worth knowing:
metric_ratedivides students meeting the threshold by students with a value, not by every student in the grain.n_students_in_grain,n_students_measured, andn_students_metshow all three counts. A rate is null, not zero, before a year's grades post.progress_to_goalis the rate over the goal, capped at 1.- A grade with no goal row produces no row at all, indistinguishable from a grade with no students. Two singular tests on the sheet guard against a missing or unmatched goal (see Process).
- The population is high school students only, whatever grades the sheet names.
on_pacegoals always read null rates: the on-pace flags are placeholders that nothing populates yet.- The cumulative metric reads the projected cumulative GPA, including for completed years (see Known issues, need to fix).
Process: the GPA goals sheet
The goals behind every goal line live on one tab of a Google Sheet, staged as
stg_google_sheets__gpa_goals. The tab sits in the same workbook as the
gradebook audit tabs; ask the data team for access.
What triggers it
The network sets GPA goals for a new school year, or changes one mid-year. Goals exist per academic year, so a year with no rows has no goal lines.
Inputs
One row per goal, with these columns in this order:
| Column | Holds |
|---|---|
academic_year |
Start year: 2026 means SY26-27 |
org_level |
org, region, or school |
region |
Newark, Camden, or Paterson; required on region and school rows |
schoolid |
PowerSchool school number; school rows only |
grade_low, grade_high |
The grade band; equal for a single grade |
metric |
One of the four metrics under Goal terms |
threshold |
The GPA to reach, for example 3.0 |
direction |
>= or <= |
goal |
Target percent of students, 0 to 100 |
Steps
- Edit the tab. The sheet is read through a named range and by column position, so a column inserted mid-tab shifts every value after it, and a row past the range's end is ignored.
- A Dagster sensor polls the sheet every few minutes and rebuilds the staging
table after an edit. Its tests are error-level: one row per year, rung,
region, school, grade band, and metric;
grade_lowno higher thangrade_high; known values fororg_level,metric, anddirection; andgoalbetween 0 and 100. int_google_sheets__gpa_goalsturns the percent into a proportion, labels the grade band, and buildsaggregation_hash. Four singular tests then check the sheet against the students:- every region and school goal sits inside a network goal's grade band for the same year and metric, or its students would lose the goal silently;
- every goal matches at least one student in years the student data covers, which catches a mistyped school number or region;
- within a rung, every grain covers the same grades, which catches one school missing a grade its peers have;
- every high school student in a goal year gets a network goal on the Cumulative GPA Monitor.
- The goal views read the staging table directly, so the next Tableau refresh at 4 AM shows the change.
Outputs
rpt_tableau__gpa_goals: rates against each goal row, for the goal panels.rpt_tableau__gpa_goal_progress: each student's cumulative goal at the three rungs, for the Cumulative GPA Monitor's goal lines.
Who runs it and when
The data team enters goals when the network sets them, usually before the school year starts, and checks the tests after the rebuild. Anthony Walters owns the goal models. School rows are optional: a school with no goal still shows the network target.
Supporting models
The goal chain
int_google_sheets__gpa_goals: the sheet withgoal_proportion,grade_band,aggregation_hash, andhigher_is_betteradded. One row per year, metric, and hash.int_gpa__goal_student_metrics: one row per high school student, school, and year, carrying Y1 GPA weighted and unweighted (from the current-term row ofint_powerschool__gpa_term) and cumulative unweighted GPA (the projection, fromint_extracts__student_enrollments). A student with no GPA yet stays in with null measures, which counts inn_students_in_grainbut not in the rate.int_gpa__goal_aggregations: joins those students to each goal by year and grade band, three times (school on school and region, region on region, network on nothing more), and computes the counts, rate,is_goal_met, andprogress_to_goal.int_gpa__student_goal_definitions: one row per student, year, and metric with a network goal, carrying the network threshold and the target share at each rung. Its only filter isrn_year = 1; its only reader,rpt_tableau__gpa_goal_progress, supplies the population.
The PowerSchool GPA models
These build in each NJ district project from the shared powerschool package
and are unioned in kipptaf.
int_powerschool__gpa_term: one row per student, school, year, and term with term, semester, and Y1 GPA and the failing-course count. The current year comes from the live gradebook, past years from stored grades.is_currentmarks the term whose dates cover today, or Q4 for a past year.int_powerschool__gpa_cumulative: one row per student and school, current state only (no year). Stored Y1 grades give the cumulative; adding this year's unstored Y1 grades for courses whose term covers today gives the projection. It also computes the GPA needed for a 3.0 and whether it is attainable. Throughint_students__gpa_cumulative, it also reaches every reader ofint_extracts__student_enrollments.int_powerschool__gpa_cumulative_year: one row per student, school, and year. Completed years are running totals of stored Y1 grades per student and school; the current year is the projection fromint_powerschool__gpa_cumulative, copied as is.int_powerschool__gpa_term_lookback: current year only, one row per student and school, with the Y1 GPA and failing count in effect at the end of the day 1, 2, and 4 weeks ago, from the daily snapshot. The snapshot never closes a row for a student who leaves, so readers must scope by enrollment, which the extract does.int_powerschool__student_course_grades_spine: one row per student, course, term, and category. Current-year grades come from the live gradebook, with an in-progressY1row from the current term; past years from stored grades. It builds theneed_*columns, the lowest-category drivers, andoffice_hours_priority_rank, and drops lunch, advisory, study hall, and early dismissal courses by course number.
Shared upstreams
base_powerschool__final_grades: read by the GPA term, cumulative, and course spine models for current-year term and running Y1 grades. The term model reads it directly; the cumulative model joins enrollments to it on student and year. Quarters weigh 25 each in a course with no exam; with exams, 22 per quarter and 5 per exam term.stg_powerschool__storedgrades: read for every stored grade, joined on student, course, year, and store code; it also names each grade's unweighted scale.int_powerschool__gpa_term: also read by the goal metrics for Y1 GPA, joined on student, school, year, and district; the daily snapshot captures its current-term rows.int_powerschool__gpa_cumulative: also read by the course extract and the year extract for as-of-today values, joined on student, school, and district.int_students__gpa_cumulative: read byint_extracts__student_enrollmentsfor cumulative and projected GPA, joined on student number and school only, with no year. It adds Focus GPAs for Miami, which this family filters out.int_extracts__student_enrollments: read by every model for the roster and demographics, joined on student, year, school, and district, filtered torn_year = 1.
Inputs
The only hand-maintained input is the GPA goals tab described under Process. Everything else is PowerSchool as teachers and schools enter it: grade scales and course credit hours set up in PowerSchool decide grade points, weighting, and GPA eligibility.
Decisions
Cumulative GPA is kept per school
Cumulative GPA accumulates per student and school, matching how PowerSchool computes it. High school GPA therefore starts in grade 9 without middle school grades, which is what the high school goals measure. The cost is that a student who transfers mid-career starts a new series, and a grade 9 year-over-year comparison sets a high school number against a middle school one.
An unmeasured student is not a non-achiever
Goal rates divide by students who have a GPA, not by every enrolled student. A student without grades yet is unknown, and counting them as missing the goal would make every rate read low in the first weeks of the year and zero before grades post. The in-grain count is kept beside the rate so the gap stays visible.
Populations follow enrollment dates, not status
enroll_status is current and student-level, so filtering on it drops every
graduate from every completed year and reported past senior classes at almost
zero. The goal and year models use is_enrolled_recent instead, which keeps a
year the student finished and drops a year they left partway through.
The network rung defines who has a goal
A student has a goal when a network goal covers their grade; region and school targets attach to that row when they exist. So a school without its own goal still shows the network line, and the threshold on the Cumulative GPA Monitor is always the network's. School-level goals are not being set this year (#5169).
The goal wrapper is a separate model
rpt_tableau__gpa_goal_progress adds goal columns to the year extract instead
of changing the extract itself. Two published dashboards read the extract
through a Tableau relationship that declares it unique, and the extract is a
view, so a join that fanned it out would ship without an error. Folding the
wrapper into the extract is planned; until then its columns are maintained by
hand.
Prior-year Y1 GPA is the final value
PowerSchool stores only the end-of-year Y1 grade, so there is no "last year as of this quarter" to compare against. Comparisons with last year use its final Y1 and final cumulative GPA.
The Cumulative GPA Monitor opens on the latest graded year
The current year has schedules but no grades for its first weeks, so the
monitor's default year is the most recent year with posted Y1 grades
(is_latest_graded_year) rather than the current one.
Known issues, need to fix
Completed-year cumulative goal rates use today's projection
Tracked in #5562.
int_gpa__goal_student_metrics takes cumulative_gpa_unweighted from
int_extracts__student_enrollments, which joins cumulative GPA on student and
school with no year. Every past year of a student at their current school
therefore carries today's projection, not that year's final value, so the
cumulative_gpa_unweighted rows of rpt_tableau__gpa_goals for completed years
do not report what those years ended at. Most past-year students differ:
select
m.academic_year,
count(*) as n_students,
countif(
abs(m.cumulative_gpa_unweighted - cy.cumulative_y1_gpa_unweighted)
> 0.005
) as n_differ_from_year_end,
from `teamster-332318.kipptaf_gpa.int_gpa__goal_student_metrics` as m
left join
`teamster-332318.kipptaf_tableau.rpt_tableau__gpa_cumulative_year` as cy
on m.student_number = cy.student_number
and m.academic_year = cy.academic_year
group by m.academic_year
The fix is to read the year-end value from
int_powerschool__gpa_cumulative_year for completed years. The Cumulative GPA
Monitor is not affected: it reads the year extract.
Honors courses read weighted points as unweighted this year
Tracked in #5563.
Stored grades map the KIPP NJ 2024 (5-12) Weighted - Honors scale to its
unweighted twin by name, but the current-year path maps unweighted scales by id
in base_powerschool__sections, and only covers the 2016 and 2019 weighted
scales. Honors courses on the 2024 scale therefore carry weighted points in
every current-year unweighted GPA: in-progress Y1, projected cumulative, and the
needed-GPA columns. Scale 1075 tops out at 4.83, so this year's unweighted
honors GPA can pass 4.33. Stored past years are correct.
select
_dbt_source_project,
academic_year,
count(distinct course_number) as n_courses,
countif(y1_grade_points != y1_grade_points_unweighted) as n_rows_differing,
from `teamster-332318.kipptaf_powerschool.base_powerschool__final_grades`
where courses_gradescaleid_unweighted = 1075
group by _dbt_source_project, academic_year
n_rows_differing reads 0 for every honors course. The mapping change belongs
with the grade-scale work in #5092, which rewrites the same case.
Some student-years drop from the year extract
The year extract's inner join to the year's primary enrollment includes the school, so a year whose grades were stored at another school, and a year with no primary enrollment row, drop out. The student's GPA history shows a gap for those years. Tracked in #4343.
Duplicate stored grades
PowerSchool holds more than one stored grade for some student, course, year, and
store code, each a real record (#5221). In the course extract they surface as
duplicate prior-year rows, so its uniqueness test is at warn until the cleanup
lands (TODO(#3915) in the model's YAML). Count them with:
select
count(*) - count(
distinct format(
'%T|%T|%T|%T|%T|%T',
_dbt_source_relation,
studentid,
academic_year,
`quarter`,
course_number,
category_name_code
)
) as n_duplicate_rows,
from `teamster-332318.kipptaf_tableau.rpt_tableau__student_course_grades`
Failing count reads 0 with no graded work
n_failing_y1 sums a flag, so a student with a GPA row but no graded Y1 course
reads 0 failures instead of unknown. It reaches gpa_n_failing_y1 and
n_failing_y1_prior_quarter on the course extract. The lookbacks already guard
it. Tracked, with its query, in #5173.
Y1 F* label is on the wrong scale
Tracked in #5564.
base_powerschool__final_grades labels a Y1 grade F* when
y1_percent_grade < 0.500, but that column is on a 0 to 100 scale, so only a Y1
of 0% gets the label. Other failing Y1 grades read F. Failure counts match
F%, so they are unaffected; only the label differs from the term grades, where
F* means below 50%.
Workbook data sources to confirm with Walters
- The exposure lists
rpt_tableau__gpa_cumulative_year, but no tab reads it directly. #5169 records it embedded as its own data source and related intorpt_tableau__student_course_grades+, with no worksheet reading a column from either, and proposes removing both. Once decided, drop it from the exposure or keep it. - The data source
GPA Goals - Y1has no known model behind it. Confirm what it reads and add that model to the exposure. - Two more exposures read this family:
gpa_goals_dashboard(rpt_tableau__gpa_goals) andcumulative_gpa_monitor(rpt_tableau__gpa_goal_progress). Both carry placeholder URLs (TODO(#4581),TODO(#4619)). Confirm whether they are separate published workbooks or the Health Suite tabs, and retire or fill them.
Smaller items
on_pacehas no data:is_on_paceand its denominator are nulls pending follow-on work (TODO(#4581)), so anon_pacegoal row reads a null rate.- Miami is out of the goal population until its GPA data is available (#5171).
base_powerschool__sectionstestscase cou.gradescaleid when null, which never matches, so the blank-scale default that stored grades apply never applies to current-year courses.- Past-year
gpa_termmultiplies by storedpotentialcrhrsin the numerator but divides by coursecredit_hours(int_powerschool__gpa_term.sql). Impact not measured; a likely bug where the two differ. - The singular test
int_google_sheets__gpa_goals__every_goal_aggregatesand its YAML description say Paterson is excluded from the student metrics. It is not any more; the reasoning (Paterson has no high school, so a Paterson goal matches nobody) still holds. Correct the wording.
Open work that touches this family
-
5183 names the school on
rpt_tableau__gpa_goalsand labels every rung. -
5140 adds the projected four-year college enrollment goal beside the
cumulative GPA goal. -
5092 supports pass/fail courses without dropping them from grade reporting.
-
4655 adds a missing-assignment count to the course extract.
-
4978 proposes a glossary of GPA terms and the high-school-only weighting
rule. -
5169, #5174, #5190 (with PR #5198), and PR #5246 are dashboard changes: open
decisions on the Academic Health tabs, gaps against the HS GPA monitoring protocol, cards and captions on the Cumulative GPA Monitor, and the landing page.
The Gradebook and GPA Dashboard
The older Tableau workbook, exposure gradebook_and_gpa_dashboard, reads
rpt_tableau__gradebook_gpa, rpt_tableau__gradebook_gpa_cumulative, and
rpt_tableau__gradebook_es_comments. The Health Suite replaced it. Dagster no
longer refreshes its extracts: the exposure carries no refresh schedule. Anthony
Walters decides when to retire it; retiring a model here means disabling it,
never deleting it.
Yearly upkeep
| Who | Does what |
|---|---|
| The network | Sets GPA goals and grading policy for the year |
| T&L | Usually sends a grading-policy Google Doc for the next year |
| Data team | Enters goals on the sheet, reads the policy for code changes, checks the tests and dashboard |
| Automatic | current_academic_year rolls over in July; sheet edits rebuild staging; Tableau refreshes daily |
Each school year:
- Grading policy. T&L usually send a grading-policy document for the next year, and it decides whether code changes: term weights, grade scales, failing rules, excluded courses. None exists for this year yet; Walters adds it here when it arrives.
- Goals. Enter the new year's rows on the goals tab, network rung first, and confirm the four singular tests pass.
- Rollover. In July
current_academic_yearmoves forward, and the course extract's two-year window, the projection, and the lookbacks move with it. The dashboard keeps opening on the last graded year until new grades post. - Grade scales. A new weighted scale in PowerSchool needs its unweighted
twin mapped in both
stg_powerschool__storedgrades(by name) andbase_powerschool__sections(by id), or its unweighted GPA equals the weighted one. - Regions. A Paterson high school appears in the course extract and goal metrics on its own, but the year extract lists Newark and Camden by name. Miami returns when #5171 lands.
Owner
The Data Team. Anthony Walters owns the Academic & Gradebook Health Suite and this model family; questions about the dashboard or the goals go to him.