FRESH Dashboard Data Model
What is FRESH?
FRESH is the network's enrollment recruitment dashboard. It tracks progress against recruitment targets (seats, new students, and the inquiry, application, offer and enrollment funnel) by region, school and grade. The Student Recruitment and Enrollment team (SRE) and school operations teams use it to see where each school stands against its goals and which student records need cleanup.
- Owner: the data team, with Anthony Walters as owner.
- Stakeholder: Maria-Cristina Ventresca, Managing Director, Marketing, Comms, and Enrollment.
- Targets: owned by SRE. SRE's own workbook is hand-maintained; the Finalsite goals Google Sheet is the copy dbt reads.
The Tableau workbook has four tabs: Landing Page (the default view),
Progress to Goals, School Ops Team and SRE Team. It reads three
reporting models, all in the fresh_dashboard exposure:
rpt_tableau__fresh_dashboard_progress_to_goals: enrolled students against enrollment targets.rpt_tableau__fresh_dashboard_aggregated: funnel counts against funnel goals.rpt_tableau__fresh_dashboard_qc: a worklist of students whose Finalsite record disagrees with the SIS.
The exposure also reads int_tableau__finalsite_student_scaffold directly (see
Known issues, need to fix). Which tab reads which data source, and who opens
each tab, is an open question (see Open questions).
Data model overview
1. THE SPINE (one row per school x grade)
stg_powerschool__schools ─────────────┐
stg_powerschool__students ────────────┤
int_focus__schools ───────────────────┤
int_focus__student_enrollment_roster ─┼─▶ int_tableau__fresh_enrollment_scaffold
stg_google_sheets__people__locations ─┤
int_finalsite__status_report_unpivot ─┘ (net-new schools/grades only)
2. THE GOALS (numeric targets)
stg_google_sheets__finalsite__goals ─┬─▶ int_tableau__fresh_goals_scaffold
│ (funnel goals, inner-joined to the spine)
└─▶ int_google_sheets__finalsite__goals_pivot
(the five Enrollment targets as columns;
consumers filter goal_type)
3. THE ACTUALS (where students are in the funnel)
stg_finalsite__status_report ─▶ int_finalsite__status_report_unpivot ────────┐
stg_google_sheets__finalsite__status_crosswalk │
└─▶ int_google_sheets__finalsite__status_crosswalk_unpivot ───────────────┤
int_extracts__student_enrollments (NJ regions; SIS side) ───────────────────┼─▶ int_tableau__finalsite_student_scaffold
int_focus__student_enrollment_roster (Miami; SIS side) ─────────────────────┤
int_finalsite__contact_id_attributes (Focus <-> Finalsite id bridge) ───────┘
4. THE CONSUMERS (the fresh_dashboard exposure)
int_tableau__fresh_enrollment_scaffold ────┐
int_google_sheets__finalsite__goals_pivot ─┤
int_people__location_crosswalk ────────────┼─▶ rpt_tableau__fresh_dashboard_progress_to_goals
int_tableau__finalsite_student_scaffold ───┘
int_tableau__fresh_goals_scaffold ─────────┐
int_tableau__finalsite_student_scaffold ───┴─▶ rpt_tableau__fresh_dashboard_aggregated
int_tableau__finalsite_student_scaffold ───┐
int_extracts__student_enrollments ─────────┤
int_finalsite__contact_id_attributes ──────┼─▶ rpt_tableau__fresh_dashboard_qc
stg_finalsite__status_report ──────────────┘
int_tableau__finalsite_student_scaffold ─────▶ fresh_dashboard (read directly)
The spine and the goals are two independent inputs joined together. The actuals come from a separate Finalsite pipeline and join in at the reporting layer.
Terms
| term | meaning |
|---|---|
| SRE | Student Recruitment and Enrollment, the team that owns recruitment targets and Finalsite data entry. |
| Spine / scaffold | int_tableau__fresh_enrollment_scaffold: one row per school and grade being reported on, so the dashboard has a row even where no student or goal exists. |
| Recruitment year | The finalsite_recruitment_year dbt var: the Finalsite cycle FRESH reports on. Start-year form (AY2026-2027 = 2026). |
grade_level = -9 |
A whole-school total row. A reporting convention, not a SIS grade. |
grade_level = -1 |
Pre-K (K is 0, grades 1-12 are 1-12). |
schoolid = 0 |
A region rollup row (spine and goals) or a Finalsite record with no assigned school yet (actuals, school = 'No School Assigned'). |
goal_granularity |
School (grade_level = -9), School/Grade Level, or Region/Grade Level (schoolid = 0). |
goal_type / goal_name |
The goal family and the specific goal. Funnel goal names come from the status crosswalk; Enrollment goal names are typed into the goals sheet. |
grouped_status |
The funnel stage a Finalsite status maps to, from the crosswalk's status_group_value. |
grouped_status_timeframe |
Ever counts a student who ever reached the stage; Current counts only a student whose latest status is in the stage. |
latest_status |
A student's most recent Finalsite status: latest status date, ties broken by status_order (highest wins). |
status_order |
A hardcoded rank per Finalsite status field in int_finalsite__status_report_unpivot, mirroring the crosswalk's detailed_status_ranking. |
enrollment_type |
New or Returning, from Finalsite. aligned_enrollment_type is the constant All, used to add New and Returning together. |
enroll_status |
The SIS enrollment code: 0 enrolled, 2 withdrawn, 3 graduated, -1 pre-registered. 1 is treated as invalid in this repo. |
finalsite_expected_enroll_status |
What the SIS should show if Finalsite is right: 0, 2 or NULL. See How Finalsite's latest_status becomes an expected enrollment status. |
| Persistence | A current student returning next year (Re-Enroll Projection). "Retention" means grade repetition in this network, a different thing. |
| Reset Protocol | SRE's fix for a same-day status tie: move the student to another status, wait a day, then set the status you want. |
Where the data comes from
| source | reaches FRESH through | owner |
|---|---|---|
| Finalsite status report (SFTP file drop, all four regions) | stg_finalsite__status_report → int_finalsite__status_report_unpivot |
SRE enters the data; data team owns ingestion |
| Finalsite contact ids | int_finalsite__contact_id_attributes (bridges Focus ids to Finalsite ids) |
data team |
| PowerSchool (Newark, Camden, Paterson) | stg_powerschool__schools, stg_powerschool__students, int_extracts__student_enrollments |
school operations enter; data team ingests |
| Focus (Miami) | int_focus__schools, int_focus__student_enrollment_roster |
school operations enter; data team ingests |
| Finalsite goals Google Sheet | stg_google_sheets__finalsite__goals |
SRE supplies values; data team pastes them |
| Finalsite status crosswalk Google Sheet | stg_google_sheets__finalsite__status_crosswalk |
data team, with SRE confirming the mapping |
| Finalsite exclude-ids Google Sheet | stg_google_sheets__finalsite__exclude_ids, applied inside stg_finalsite__status_report |
data team |
| Locations Google Sheet | stg_google_sheets__people__locations, int_people__location_crosswalk |
data team |
stg_finalsite__status_report's cleaning (grade decode, enrollment_type
default, name casing, active_school_year_display) lives in the finalsite
source-system package; the kipptaf model of the same name is a thin
union_relations wrapper over the four district sources that adds region,
_dbt_source_project and the exclude_ids filter.
int_focus__student_enrollment_roster is likewise a thin kipptaf wrapper over a
focus package model.
The dashboard views
Progress to Goals: rpt_tableau__fresh_dashboard_progress_to_goals
What it shows: Enrolled and in-progress students counted against the five
Enrollment targets (Seat Target, FDOS Target, Budget Target,
New Student Target, Re-Enroll Projection), per school and per school and
grade.
Grain: One row per scaffold row (academic_year, region, schoolid,
grade_level, enrollment_type in All/New/Returning) per matched student
or goal record. row_type is Student (with student_count = 1) or Goal.
Reads:
int_tableau__fresh_enrollment_scaffold: the rows.Schoolrows are thegrade_level = -9rows;School/Grade Levelrows are every other row except region rollups (schoolid != 0). There are no region rows in this view.int_people__location_crosswalk:school_levelfor the-9rows, fromlocation_grade_band. Grade rows takeschool_levelfrom the scaffold.int_tableau__finalsite_student_scaffold: students whoselatest_statusisEnrolledorEnrollment In Progress.int_google_sheets__finalsite__goals_pivot: Enrollment targets atSchoolandSchool/Grade Level, for the recruitment year.
Worth knowing:
- Each student is unioned in twice, once on their real
enrollment_typeand once onaligned_enrollment_type = 'All', so theAllrows add New and Returning together. Goals follow the same split:New Student Targetlands onNew,Re-Enroll ProjectiononReturning, everything else onAll. - A student with no assigned school (
schoolid = 0) has no scaffold row to land on and does not appear. - A student Finalsite has no record for never appears here. Those students are
what
is_missing_finalsite_recordon the QC worklist catches.
Aggregated: rpt_tableau__fresh_dashboard_aggregated
What it shows: Funnel counts (inquiries, applications, offers, pending offers by age, waitlisted, deferred, enrollment in progress, conversion rates) against funnel goals. Which goals and granularities reach it depends on the branch that selects them (table below).
Grain: One row per goals-scaffold row per matched student. A goal with no matching student keeps one row with NULL student columns.
Reads:
int_tableau__fresh_goals_scaffold: the goal rows (every non-Enrollment goal whose key exists in the spine).int_tableau__finalsite_student_scaffold: the students, left-joined in five branches:
| branch | goals | student join |
|---|---|---|
| Deferred / Waitlisted | Current, goal_name in ('Deferred', 'Waitlisted') |
school, grade, goal_type, and goal_name = latest_status |
| Enrollment In Progress | Current, School/Grade Level only |
school, grade, goal_type, and goal_name = latest_status |
| Pending Offers / Offers | all granularities | school, grade, goal_type, goal_name |
| Inquiries / Applications | Region/Grade Level only |
region and grade only (these students have no school yet) |
| Conversion | Ever |
school, grade, goal_type, goal_name, plus the matching Num row for the numerator |
Worth knowing:
school_levelhere comes from the goals sheet, not the scaffold, so it can differ from Progress to Goals for the same school and grade (seeschool_levelis banded per grade, and may disagree with the goals sheet).goal_name_valueis a Finalsite id column for counting. In the first four branches it is the matched student'sfinalsite_id. In the Conversion branch it is filled only when the same student also has the matchingCurrent... Numrow, restricted toenrollment_type = 'New'. A... Numstatus marks a student who reached the later stage of a conversion (forOffers to Enrolled, the enrolled ones), so countinggoal_name_valuegives the rate's numerator and countingfinalsite_idits denominator.- Region rollup goals carry
schoolid = 0, and so does a Finalsite student with no assigned school. A branch that joins onschoolidtherefore matches aRegion/Grade Levelgoal only to unassigned students, never to the region's students as a whole. Only the Inquiries/Applications branch skipsschoolid, so it is the only one where a region goal counts every student in the region and grade (see Known issues, need to fix). - The
School-granularity goals that reach this view are onlyOffersandPending Offers, and they never match a student (see Known issues, need to fix). - Only what the five branches select reaches this view:
Acceptednever does, andInquiriesandApp Targetonly atRegion/Grade Level(see Known issues, need to fix).
The QC worklist: rpt_tableau__fresh_dashboard_qc
What it shows: A worklist, not a report: one row per student per problem, where Finalsite and the SIS disagree. An empty result is the good outcome.
Grain: One row per student per fired flag (flag_name, with flag_value always
true).
Reads:
int_tableau__finalsite_student_scaffoldatgrouped_status_timeframe = 'Current': four flags, unpivoted into(flag_name, flag_value), keeping only the rows where a flag fired.int_extracts__student_enrollments,int_finalsite__contact_id_attributesandstg_finalsite__status_report: the fifth flag,is_missing_finalsite_record, unioned on.
Worth knowing: see The five flags, in plain language and Implementation notes below.
The scaffold: int_tableau__fresh_enrollment_scaffold
One row per (enrollment_academic_year, region, schoolid, grade_level), the
spine everything else joins against. The rpt_tableau__fresh_dashboard_* views
alias the year to academic_year. The scaffold is derived entirely from the SIS
and Finalsite; no hand-maintained sheet feeds it.
How the spine is built
school_directory: one row per reporting school, from two SIS sources. NJ comes fromstg_powerschool__schoolsatstate_excludefromreporting = 0, which drops administrative rows such as the graduated-students school. Miami comes fromint_focus__schoolsatmax_syear is null(open schools only), inner-joined tostg_google_sheets__people__locationsonfocus_school_idto get the abbreviation and the PowerSchool-spaceschoolid. Focus's own school number is a Focus code, not a PowerSchool id, so this join is what puts Miami in the same id space. The join also requiresnot is_pathways: Pathways locations are not schools FRESH recruits into.current_grade_levels: which grades each school serves, from current enrollment. NJ:stg_powerschool__studentsatenroll_status = 0(that table is current-state only). Miami:int_focus__student_enrollment_rosteratenroll_status = 0,academic_year = current_academic_yearandrn_year = 1(Focus carries several years, so the year filter scopes it). There is no explicit Miami exclusion on the PowerSchool branches; the kipptaf PowerSchool unions carry no Miami rows.sis_scaffold: the directory joined to grade membership on(schoolid, _dbt_source_project). Each PowerSchool instance assignsschoolidindependently, so the source project is part of the key.
The three row types the SIS can't produce directly
- Whole-school totals (
grade_level = -9): one row per school in the spine, withschool_levelNULL because a whole-school row spans bands.scaffold_sourceissisif any of the school's grade rows came from the SIS, elsefinalsite. - Region rollups (
schoolid = 0): aselect distinctover(region, grade_level, school_level), withschoolset to the region name. This is safe only becauseschool_levelis a function of grade alone. - Net-new schools/grades:
finalsite_new. School/grade pairs Finalsite has records for in the recruitment year, with an assigned school (schoolid != 0), that are not already in the SIS spine. The anti-join key is(region, schoolid, grade_level).
The CTE is gated by finalsite_recruitment_year != current_academic_year.
While the two vars are equal the gate is closed and finalsite_new returns no
rows. So a grade that is being recruited for, with nobody enrolled in the SIS
yet, gets no scaffold row: its goals drop out of
int_tableau__fresh_goals_scaffold's inner join, and its Finalsite students
have no row to land on in either reporting view. The gate opens only when the
recruitment year runs ahead of current_academic_year. Both vars are in
src/dbt/kipptaf/dbt_project.yml.
school_level is banded per grade, and may disagree with the goals sheet
school_level is computed from grade (>= 9 HS, >= 5 MS, else ES), not read
from either SIS's per-school field. A per-grade value keeps the region rollup at
one row per grade, and some schools report different levels for different
grades.
These are NJ bands. Miami's real ES/MS boundary is 5/6, so Miami ES schools
serving grade 5 report their grade-5 rows as MS here, while the goals sheet
reports them as ES. This is accepted: the goals sheet stays hand-entered
because some goals are standard by grade across the network.
Consequence: Progress to Goals takes grade-row school_level from this
scaffold, and Aggregated takes it from the goals sheet (through
int_tableau__fresh_goals_scaffold), so Miami grade 5 can show different
school_level values on the two views.
Miami is Focus-sourced
Miami's schools and grade membership come from int_focus__schools and
int_focus__student_enrollment_roster. The scaffold labels schoolid 30200805
Miami Tech, the same label int_finalsite__status_report_unpivot resolves to
through int_people__location_crosswalk. No join in the chain keys on the
school name.
The current academic year: a dedicated var, not current_academic_year
"The current Finalsite recruitment cycle" is the finalsite_recruitment_year
dbt var, read at every FRESH site that needs it. It is separate from
current_academic_year because Finalsite can carry two academic years of live
student data at once during a transition, and students and regions roll over on
their own timeline. There is no signal in the ingested data for "which year is
current now", and SRE's cycle has no fixed date. Always confirm the new year
with SRE before changing it; the fresh-dashboard skill has the file list.
The status crosswalk holds config for exactly one academic year at a time,
guarded by test_stg_google_sheets__finalsite__status_crosswalk_single_year
(count(distinct file_year) = 1).
Goal definitions
The Enrollment goal_type group is plain numeric targets typed into the goals
sheet. It reaches the dashboard through
int_google_sheets__finalsite__goals_pivot, never through the status crosswalk:
goal_name |
Definition |
|---|---|
Seat Target |
Total seats the school is targeting for the year. |
FDOS Target |
Enrollment target as of the first day of school. |
New Student Target |
Target count of new (not returning) students. |
Budget Target |
The enrollment number the school's budget was built against. |
Re-Enroll Projection |
Projected count of current students expected to persist (return) next year. |
Every other goal is a roll-up of the Finalsite funnel through the status
crosswalk. Ever goal types are Inquiries, Applications, Offers,
Assigned School, Accepted and the three Conversion rates; everything else
is Current.
goal_name (goal_type) |
Timeframe | Definition |
|---|---|---|
Inquiries |
Ever | Family ever submitted an inquiry. |
App Target (Applications) |
Ever | Family ever completed an application. |
Offers Target (Offers) |
Ever | Student was ever offered a seat. |
Accepted |
Ever | Family ever accepted an offered seat. |
Waitlisted |
Current | Current status is waitlisted. |
Deferred |
Current | Current status is deferred. |
Enrollment In Progress |
Current | Student is mid-enrollment. |
Pending Offers (+ <= 4 Days / >= 5 & <= 10 Days / > 10 Days) |
Current | Outstanding offer awaiting a family response, bucketed by days pending. |
Conversion: Accepted to Enrolled / Offers to Accepted / Offers to Enrolled |
Ever | Conversion rate between two funnel stages. |
Which goals exist at which granularity
Not every goal exists at every level. Expecting a value at a level a goal does
not live at produces a phantom gap, so check this before treating a missing row
as a problem. "scaffold" means the sheet carries rows for the combination with
no goal_value: they give the dashboard grid a row per school, grade and
status, and are never expected to receive a value. The table reflects the sheet
for AY2026; re-check it each cycle with the query below.
goal_name |
School |
School/Grade Level |
Region/Grade Level |
|---|---|---|---|
Budget Target |
yes | -- | -- |
FDOS Target |
yes | yes | -- |
Seat Target |
yes | yes | -- |
Conversion (3 names) |
-- | yes | -- |
Enrollment In Progress |
-- | scaffold only | -- |
New Student Target |
yes | yes | yes |
Re-Enroll Projection |
yes | yes | yes |
App Target |
yes | yes | yes |
Offers Target |
yes | yes | yes |
Accepted |
scaffold | scaffold | scaffold |
Pending Offers (4 names) |
scaffold | scaffold | scaffold |
Inquiries |
-- | -- | scaffold only |
Deferred |
-- | -- | scaffold only |
Waitlisted |
-- | -- | scaffold only |
select
goal_granularity,
goal_type,
goal_name,
count(*) as sheet_rows,
countif(goal_value is not null) as populated_rows,
from `teamster-332318`.kipptaf_google_sheets.stg_google_sheets__finalsite__goals
where enrollment_academic_year = <year>
group by goal_granularity, goal_type, goal_name
A NULL inside an otherwise populated combination is ambiguous: it may mean "no goal here" or "nobody filled this in". Ask SRE rather than infer.
Conversion goals are a flat per-grade lookup
SRE supplies the three Conversion rates by grade, identical across schools.
For AY2026 they collapse to two tiers: Kindergarten, and grades 1-12. A rate
change is a small uniform edit, and a reconciliation should check the shape (one
value per grade) rather than diff every row:
select grade_level, goal_name, count(distinct goal_value) as distinct_values,
from `teamster-332318`.kipptaf_google_sheets.stg_google_sheets__finalsite__goals
where enrollment_academic_year = <year> and goal_type = 'Conversion'
group by grade_level, goal_name
Anything other than 1 means a school has drifted off the common rate. The
Conversion Rate column on each region tab of SRE's workbook is a different
thing: an input used to derive "apps needed" from "new students needed".
Sourced vs derived goals
Where SRE's workbook states a goal, the goals sheet matches it. Where the
workbook states nothing, the data team derives the value. At
Region/Grade Level, App Target is sourced from the cover sheet's region
grid, while New Student Target and Re-Enroll Projection are derived as the
rounded sum of the school rows.
So a region row need not equal the sum of its school rows. A gap of 1 is
rounding. App Target can differ by more, because the cover sheet can leave out
a grade that is new mid-expansion; do not "fix" it by summing. The
fresh-dashboard skill has the reconciliation procedure.
A new school is not necessarily recruiting: Miami Tech
Re-Enroll Projection measures persistence in the network, not at one school,
so a school that did not exist last year can have returners. Miami Tech opened
to take KIPP's own grade-8 students into grade 9, with no external recruitment.
Its goals look broken and are correct:
| goal | expected | why |
|---|---|---|
Re-Enroll Projection |
a real figure | the incoming cohort persists internally |
New Student Target |
NULL | not recruiting externally |
App Target |
NULL | no application funnel |
Offers Target |
NULL | no lottery, so no offers |
This is also why Miami Tech lacks the lottery categories (Accepted, Offers,
Pending Offers) at School granularity. Do not move the returners figure into
New Student Target.
Full grouped_status → goal_type / goal_name crosswalk
int_tableau__finalsite_student_scaffold's roster CTE renames
grouped_status into goal_type and goal_name. Only these change:
Ever:Applications→goal_nameApp Target;Offers→goal_nameOffers Target;Accepted to Enrolled,Offers to AcceptedandOffers to Enrolled→goal_typeConversion.Current: the three... Numstatuses →goal_typeConversion. A... Numstatus is the numerator of a conversion rate: the students who reached the rate's later stage.goal_nameis never renamed forCurrentrows.CurrentPending Offersalso expands into the three day buckets.
Every other grouped_status passes through unchanged as both goal_type and
goal_name. To list the current grouped_status values:
select distinct grouped_status_timeframe, status_group_value,
from `teamster-332318`.kipptaf_google_sheets.int_google_sheets__finalsite__status_crosswalk_unpivot
int_finalsite__status_report_unpivot resolves assigned_school to a
PowerSchool schoolid and abbreviation through
int_people__location_crosswalk. Before Finalsite assigns a school (inquiries,
applications), the row falls back to schoolid = 0 and
school = 'No School Assigned', which is how those rows meet
Region/Grade Level goals. int_tableau__finalsite_student_scaffold also sets
school to the region for Inquiries and Applications rows.
How Finalsite's latest_status becomes an expected enrollment status
int_tableau__finalsite_student_scaffold carries two enrollment-status columns:
enroll_status from the SIS, and finalsite_expected_enroll_status, derived
from latest_status alone. rpt_tableau__fresh_dashboard_qc exposes
enroll_status as sis_enroll_status.
The SIS side is enrollment_lookup, a union all of
int_extracts__student_enrollments (rows with an infosnap_id, which excludes
Miami's rows there) and int_focus__student_enrollment_roster bridged to
Finalsite through int_finalsite__contact_id_attributes on the Focus student
id. Both branches are scoped to the recruitment year, deduplicated per
(academic_year, infosnap_id) preferring an active record.
latest_status |
expected | meaning |
|---|---|---|
Enrolled |
0 |
should be active in the SIS |
Mid Year Withdrawal, Never Attended, Summer Withdraw ("left") |
2 |
should not be active |
Accepted, Assigned School, Did Not Enroll, Campus Transfer Requested, Parent Declined, Enrollment In Progress ("pending") |
2 |
should not be active |
| anything else | NULL |
no expectation |
The values mirror the SIS's own enroll_status codes on purpose, because the
two columns are compared. The NULL case covers most of the funnel: a waitlisted
or deferred applicant has no business having an SIS record yet.
is_enroll_status_mismatch fires in two directions:
- Finalsite says enrolled (
0) but the SIS says withdrawn or graduated (enroll_status in (2, 3)). - Finalsite says the student should not be active (
2) but the SIS says enrolled (enroll_status = 0). This covers both the "left" and the "pending" statuses;latest_statustells SRE which situation a row is.
SIS 1 and -1 never trigger a mismatch. Beware the word "inactive": a
withdrawn student (2) often displays as inactive in the SIS and is caught,
while enroll_status = 1 is also called inactive and is excluded on purpose.
Name the code, or say "withdrawn" and "graduated".
The pending list is SRE-owned. Did Not Enroll and Parent Declined read as
exits rather than pending states, and a bare Accepted may match no rows.
Confirm with SRE before changing the list.
First day of school is hardcoded per region
is_enrolled_fdos is computed in int_tableau__finalsite_student_scaffold from
a first-day date hardcoded in that model:
| region | first day |
|---|---|
| Newark, Paterson | August 28 |
| Camden | August 24 |
| Miami | August 14 |
The year comes from var("finalsite_recruitment_year"); month and day are
hardcoded and exposed as custom_fdos_date. The dates came from SRE and move
from year to year, so re-confirming them is a required rollover step.
The flag is sis_entry_date <= custom_fdos_date, where sis_entry_date is
entrydate (PowerSchool) or startdate (Focus). It checks entry only: a
student who enrolled before the first day and left before it still reads true.
It is a bare comparison, so a student with no SIS record reads NULL rather than
false. Do not wrap it in if() to match its siblings.
Expect no visible effect until school starts: at rollover both SISs give every
student the same bulk entry date, before any first day, so the flag reads true
for everyone with a record.
int_extracts__student_enrollments and int_focus__student_enrollment_roster
still compute their own is_enrolled_fdos for other consumers; this model reads
their entry dates instead. Their is_enrolled_oct01 / oct15 / mar15 flags
pass through unchanged.
The QC worklist flags
The five flags, in plain language
Each row is one student with one problem; a student with several problems appears once per problem. Listed in SRE's triage order, most urgent first:
| # | flag | what it means | how it gets fixed |
|---|---|---|---|
| 1 | is_missing_sis_record |
Finalsite says enrolled and the SIS has no enrollment record to compare against. | Create or link the SIS record. |
| 2 | is_school_mismatch |
Finalsite's assigned school is not the SIS school, so the student counts against the wrong school's targets. | Confirm the true school, then fix whichever system is wrong. |
| 3 | is_enroll_status_mismatch |
Finalsite and the SIS disagree about whether the student is enrolled (the two directions above). | Decide which system is right, then correct the other. |
| 4 | is_grade_level_mismatch |
Finalsite's grade is not the SIS grade. | Confirm the true grade, then fix whichever system is wrong. |
| 5 | is_missing_finalsite_record |
Enrolled in the SIS for the recruitment year, but Finalsite has no record at all. These make the dashboard count low. | Create or restore the Finalsite record. Until then the student is invisible on Progress to Goals. |
Implementation notes
- Three flags come from
int_tableau__finalsite_student_scaffold.is_missing_sis_recordis computed in the QC model asfinalsite_expected_enroll_status = 0 and enroll_status is null, so it only fires forEnrolled: a student who should have an SIS record but was never advanced toEnrolledin Finalsite is invisible to it. is_missing_finalsite_recordis unioned on rather than unpivoted because it describes a student Finalsite has never heard of, who cannot be in a Finalsite-sourced roster. It starts fromint_extracts__student_enrollments(enroll_status = 0, recruitment year), takes the Finalsite id frominfosnap_idor, for Miami, fromint_finalsite__contact_id_attributes, and anti-joins against everystg_finalsite__status_reportrecord, unscoped by year: a record under any cycle means Finalsite knows the student.- The two absence flags are mirror images: for one Finalsite id, only one can
fire. A child whose Finalsite record and SIS record are not linked by id (no
or a wrong
infosnap_id, or no Focus bridge row) fires both, on two rows: the Finalsite side looks for an SIS record and finds none, and the SIS side looks for a Finalsite record and finds none. See the "absent from the SIS" and "present but unlinked" question under Open questions. is_grade_level_mismatchandis_school_mismatchuseif(<cmp>, true, false), so they readfalse, not NULL, when the SIS side is missing. Do not useis nullon them as a missing-SIS proxy. A student with no SIS record surfaces once, underis_missing_sis_record.- During the window after the recruitment year moves ahead of the SIS (see
Known data model caveats), the comparison flags fall silent network-wide and
is_missing_sis_recordcarries the volume.
Supporting models
In the family:
| model | role |
|---|---|
int_tableau__fresh_enrollment_scaffold |
The school x grade spine (above). |
int_tableau__fresh_goals_scaffold |
Non-Enrollment goals inner-joined to the spine on (enrollment_academic_year, region, schoolid, grade_level); adds grouped_status_timeframe. |
int_tableau__finalsite_student_scaffold |
One row per student per goal_type/goal_name, with latest_status, days in status, SIS comparison columns and the QC flags. Materialized as a table. |
int_google_sheets__finalsite__goals_pivot |
Every goals-sheet row pivoted to one column per Enrollment target, with enrollment_type derived from the goal name. No goal_type filter: Progress to Goals filters to Enrollment. The pivot takes avg(goal_value), so a duplicate goal row is averaged, not doubled. Table. |
int_google_sheets__finalsite__status_crosswalk_unpivot |
The crosswalk unpivoted to one row per status per goal group; adds grouped_status_order (1-8 funnel sequence, 0 otherwise) and grouped_status_timeframe. Table. |
stg_google_sheets__finalsite__goals |
select * over the goals sheet. Table. |
stg_google_sheets__finalsite__status_crosswalk |
select * over the crosswalk sheet, plus file_year from the partition key. Table. |
int_finalsite__status_report_unpivot |
The 24 status-date columns of the status report as one row per enrollment, status and load partition, with schoolid, school and status_order. Also read by rpt_gsheets__finalsite__log, a retirement candidate pending a check with its user. |
Shared upstreams (outside the family):
stg_finalsite__status_report: read byint_finalsite__status_report_unpivotfor every status date, and by the QC model for Finalsite record existence, joined onfinalsite_enrollment_id.stg_google_sheets__finalsite__exclude_ids: read bystg_finalsite__status_reportto drop test records (finalsite_enrollment_id not inthe sheet'sfinalsite_student_id).int_finalsite__contact_id_attributes: read by the student scaffold and the QC model for the Focus-to-Finalsite id bridge, joined on the Focus student id and_dbt_source_project.int_extracts__student_enrollments: read by the student scaffold and the QC model for NJ SIS enrollment, joined oninfosnap_idandacademic_year.int_focus__schools: read by the scaffold for Miami's school list, joined to the locations sheet onfocus_school_id.int_focus__student_enrollment_roster: read by the scaffold for Miami grade membership (onps_schoolid,_dbt_source_project) and by the student scaffold for Miami SIS enrollment (on the Focus student id).stg_powerschool__schools/stg_powerschool__students: read by the scaffold for the NJ school list and grade membership, joined on(schoolid, _dbt_source_project).stg_google_sheets__people__locations: read by the scaffold for Miami school abbreviations and PowerSchool-space ids, joined onfocus_school_id.int_people__location_crosswalk: read byint_finalsite__status_report_unpivotforschoolid(onassigned_school = location_name) and by Progress to Goals for-9rowschool_level(onlocation_powerschool_school_id).
Inputs
All three sheets are Google Sheets external tables; ask the data team for the links.
- Finalsite goals sheet (
src_google_sheets__finalsite__goals): one row per year, region, school, grade, granularity, goal type and goal name, with the value. SRE supplies values in a new workbook each cycle; the data team pastes them in. The external table reads the sheet live, butstg_google_sheets__finalsite__goalsand the pivot are tables frozen at their last build, so an edit is invisible until they rebuild. A dashboard number that doesn't match the sheet usually means the sheet changed after the last build: compare the sheet's Drive modified time against the table'slast_modified_timeinkipptaf_google_sheets.__TABLES__. - Finalsite status crosswalk sheet: maps each Finalsite status and
enrollment_typeto funnel goal groups, for one year at a time. Column reference under Rolling the dashboard over to a new cycle. - Finalsite exclude-ids sheet: Finalsite test and fake records to drop. A test record created today counts until its id is added.
- SRE's goals workbook: not read by dbt. It is the source the goals sheet is reconciled against (see Sourced vs derived goals).
Decisions
- Grade membership comes from current enrollment, not the declared grade
span. PowerSchool's
low_gradeis below what some schools serve, sogenerate_array(low_grade, high_grade)would add phantom grades, and nobody maintains it when a school's band shifts. Enrollment is self-maintaining. The cost: a newly opening grade with no enrolled student has no row untilfinalsite_newsupplies it. - The net-new gate compares the two year vars. Equal vars mean Finalsite and
the SIS are on the same cycle, so a Finalsite school/grade missing from the
SIS is treated as a data-entry error. Diverging vars mean Finalsite is
recruiting ahead, which is when not-yet-enrolled grades should be trusted. The
predicate is plain SQL, not a Jinja
if, so the model compiles the same way every cycle. - First day of school is regional and hardcoded. Focus computes its own flag against one network-wide first day, which marked most Miami students late; PowerSchool's is per school. The enrollment team reports against a regional date, so the date lives in this one model. One date per region is coarser than PowerSchool's per-school date for NJ; that imprecision is accepted.
finalsite_expected_enroll_statususes the SIS's own codes so the two compared columns never give one number two meanings.2slightly overstates the pending statuses (truer: "no active record"), but the check only tests against SIS0, so no outcome changes.- Same-day status ties are not a QC flag. The pending statuses in direction
2 of
is_enroll_status_mismatchcover the cases SRE needs to act on; the tie itself is handled with the Reset Protocol.
Known data model caveats
These are properties of how Finalsite works, not defects. They explain recurring gaps between raw Finalsite numbers and the dashboard.
- Concurrent academic years, non-standardized rollover. Two years of live student data can coexist; students and regions roll over on their own timeline.
- Status dates are mutable and student-scoped, not year-scoped. Editing a status in the Finalsite UI can overwrite its date. It is not an audit trail.
grouped_status_order(the 8-stage funnel sequence) is a best-assumption ordering. Students can skip steps or move backward.detailed_status_ranking(crosswalk sheet) is hand-duplicated into thestatus_orderCASEinint_finalsite__status_report_unpivot.sql, per the repo's rule against staging-layer joins to Google Sheets.test_int_finalsite__status_order_matches_crosswalk_rankingcompares the sheet against a static list mirroring theCASE. Edit theCASE, the test's list and the sheet together.- Same-day status ties can pick the wrong latest status. The pipeline
compares dates, not timestamps, and breaks ties with
status_order desc, which picks wrong for an exit status (Parent Declined, rank 15) against an in-progress one (Enrollment In Progress, rank 16) set the same day. The fix is the Reset Protocol. To find them, use the Progress to Goals tab's OPEN ROSTER button to see every student's current status, or the Finalsite Log sheet (rpt_gsheets__finalsite__log), which lists students with two or more statuses on their latest date. To prevent them, avoid two status changes for one student on the same day. - Ingestion lag.
stg_finalsite__status_reportloads on a file-drop sensor, not a fixed schedule, so a cleanup done late in someone's day may not show until the next day's file. Whether this applies to Miami is unconfirmed. - SIS comparison columns go NULL for a while after the recruitment year moves
forward. Both branches of
enrollment_lookupare scoped to the recruitment year, and neither SIS has rows for a year it has not rolled into. Until each SIS catches up,enroll_status,sis_entry_dateand theis_enrolled_*flags are NULL for every student in that region. Expected; no action needed. - Fake or test Finalsite records inflate counts until someone adds their ids to the exclude-ids sheet.
Open questions
- What is each dashboard tab for, who opens it, and which data source feeds
it? Landing Page, Progress to Goals, School Ops Team and SRE Team. This is
an open request with the stakeholder. The answer decides which reporting
models each audience depends on and whether the direct read of
int_tableau__finalsite_student_scaffoldcan move to arpt_model. - What is KIPP Purpose's new student target? SRE's workbook states two different values for it on different tabs. Asked of the stakeholder; tracked on #5436.
- A stray seat-target value on SRE's Miami tab inflates SRE's own Legacy MS total. Only SRE can fix their workbook; tracked on #5436.
- How should the QC checks handle retained students? Retention can put
Finalsite and the SIS legitimately out of step: Finalsite may carry the
student at the next grade while the SIS has them repeating
(
is_grade_level_mismatch), and a repeated grade can keep a student at a school they would otherwise have left (is_school_mismatch, most likely at the grade 5/6 boundary). Options: suppress them, label them, or leave them firing. Choosing needs agreement on who records retention and when, and whether Finalsite'sRetained Dateis populated anywhere. Pending SRE. - QC questions from the AY2026 definitions review, pending SRE: should the
Enrolled-only gate onis_missing_sis_recordwiden? Should "absent from the SIS" and "present but unlinked" be separate flags (they need different fixes)? Is the triage order in The five flags, in plain language still right? - Could
stg_finalsite__status_report.active_school_yeargive a per-record rollover signal? It is the school year a record is active under, and it is mixed at any moment. Comparing it to the recruitment year could replace the single network-wide anchor. An idea, not a design. - Historical or multi-year scaffold reporting is not supported. Both SIS sources are scoped to the current cycle. Needs its own design if it becomes a requirement.
Known issues, need to fix
- School-granularity goals never match students in Aggregated. Goals at
goal_granularity = 'School'carrygrade_level = -9, and every branch ofrpt_tableau__fresh_dashboard_aggregatedexcept Inquiries/Applications joins students ongrade_level(lines 58, 108, 161, 261), which no student has as-9. The Inquiries/Applications branch is limited toRegion/Grade Level. SoSchoolgoal rows always show zero students:
select
goal_granularity,
count(*) as goal_rows,
countif(finalsite_id is not null) as rows_with_student,
from `teamster-332318`.kipptaf_tableau.rpt_tableau__fresh_dashboard_aggregated
group by goal_granularity
rows_with_student is 0 for School.
- Accepted goals, and App Target goals below region level, never reach
Aggregated.
int_tableau__fresh_goals_scaffoldcarries them, but none of Aggregated's five branches selectsgoal_type = 'Accepted', and the Inquiries/Applications branch keeps onlygoal_granularity = 'Region/Grade Level', soApp Targetgoals atSchoolandSchool/Grade Levelare dropped too. Compare the two models:
select
'goals_scaffold' as model, goal_type, goal_granularity, count(*) as goal_rows,
from `teamster-332318`.kipptaf_tableau.int_tableau__fresh_goals_scaffold
group by goal_type, goal_granularity
union all
select
'aggregated' as model, goal_type, goal_granularity, count(*) as goal_rows,
from `teamster-332318`.kipptaf_tableau.rpt_tableau__fresh_dashboard_aggregated
group by goal_type, goal_granularity
Accepted at every granularity, and Applications at School and
School/Grade Level, appear for the goals scaffold only.
Confirm with the stakeholder whether any tab expects them before adding a branch.
- Region goals in the Offers, Pending Offers, Deferred and Waitlisted branches
count only unassigned students. These branches join students on
schoolid(rpt_tableau__fresh_dashboard_aggregated.sqllines 57 and 160). ARegion/Grade Levelgoal row hasschoolid = 0, which matches only students with no school assigned, so the region-level count for these goals reads far too low. Most students at these stages have a school:
select
goal_type,
countif(schoolid = 0) as unassigned_students,
countif(schoolid != 0) as assigned_students,
from `teamster-332318`.kipptaf_tableau.int_tableau__finalsite_student_scaffold
where goal_type in ('Offers', 'Pending Offers', 'Deferred', 'Waitlisted')
group by goal_type
Only unassigned_students can reach a region row. The fix is to join region
rows on region and grade only, as the Inquiries/Applications branch does.
-
The
fresh_dashboardexposure readsint_tableau__finalsite_student_scaffolddirectly, with norpt_model buffering it (src/dbt/kipptaf/models/exposures/tableau.yml). The repo rule is that an external tool never reads an intermediate model directly. -
int_tableau__fresh_goals_scaffold's uniqueness test includesgoal_value. Two goals-sheet rows with the same key and different values pass the test and double the goal. The staging test onstg_google_sheets__finalsite__goals(key withoutgoal_value) is what currently prevents it. Check the scaffold on its own key:
select count(*) as duplicate_keys,
from (
select enrollment_academic_year,
from `teamster-332318`.kipptaf_tableau.int_tableau__fresh_goals_scaffold
group by enrollment_academic_year, region, schoolid, grade_level,
goal_granularity, goal_type, goal_name, grouped_status_timeframe
having count(*) > 1
)
The fix is to drop goal_value from the test's columns.
int_finalsite__status_report_unpivot.latest_status_dateignores the load partition. The model's grain includes_dagster_partition_key, but the window partitions byfinalsite_enrollment_id, enrollment_academic_yearonly, so a record loaded in several partitions gets one date across all of them._dagster_partition_keynames the Finalsite export file a row came from. There is one file per school year, and the key is that year in2025_26form, taken from the file name. Finalsite carries an enrollment into more than one school year's export, so the same record repeats across loads. Nothing reads the column today (rpt_gsheets__finalsite__logcomputes its own). Records loaded in several partitions:
select count(*) as ids_in_several_partitions,
from (
select finalsite_enrollment_id, enrollment_academic_year,
from `teamster-332318`.kipptaf_finalsite.int_finalsite__status_report_unpivot
group by finalsite_enrollment_id, enrollment_academic_year
having count(distinct _dagster_partition_key) > 1
)
Fix or drop the column.
-
detailed_status_branched_rankingis read by nothing. It is declared instg_google_sheets__finalsite__status_crosswalkandint_google_sheets__finalsite__status_crosswalk_unpivotand passed through, but no model reads it. Either wire up its intended use or remove it from both models and the sheet. -
Pre-K: is it in scope? The goals staging
accepted_valuestest ongrade_levelrejects-1, so no Pre-K goal can be entered. The enrollment scaffold has no filter excluding-1, so a school with enrolled Pre-K students would get Pre-K spine rows with no possible goals. To check for Pre-K spine rows:
select count(*) as prek_rows,
from `teamster-332318`.kipptaf_tableau.int_tableau__fresh_enrollment_scaffold
where grade_level = -1
Decide with the stakeholder whether FRESH reports Pre-K, then either allow
-1 in the goals test or filter it out of the scaffold.
Rolling the dashboard over to a new cycle
There is no fixed date. The rollover starts when SRE says its cycle has
advanced, not when current_academic_year bumps on July 1.
Order matters. finalsite_recruitment_year repoints the whole pipeline, and
several models inner-join sheets scoped to that year. Flipping the var before
the sheets carry the new year's rows does not error; it silently returns zero
rows.
Steps, in order
| # | Step | Owner |
|---|---|---|
| 1 | Enter any new schools/grades in Finalsite under the new FS year | SRE |
| 2 | Agree which Finalsite enrollment year is now active | SRE + data team |
| 3 | Update status_crosswalk's partition key and confirm its columns |
Analyst + SRE |
| 4 | Supply the new goals workbook | SRE |
| 5 | Reconcile the goals sheet against SRE's workbook | Data team + SRE |
| 6 | Review exclude_ids for the new cycle's test records |
Analyst |
| 7 | Re-confirm the four first-day-of-school dates | Data team + SRE |
| 8 | Bump finalsite_recruitment_year in dbt_project.yml |
Data team |
| 9 | Build and verify the FRESH models | Data team |
1-2. New schools and grades come from Finalsite
A school or grade being recruited for with nobody enrolled yet is entered in
Finalsite by SRE under the new Finalsite year. Once the recruitment year is
bumped ahead of current_academic_year, finalsite_new brings those rows in
(see The three row types the SIS can't produce directly). Agreeing on the
active year is the real gate on the rollover.
3. status_crosswalk
- Replace the
_dagster_partition_keyvalue (column A) with the new year. The sheet holds one year at a time. - Confirm with SRE that columns D, H, and I-P still make sense for the new cycle. They encode judgment about the funnel; there is no way to derive them.
This is the loudest failure mode: latest_status_calc inner-joins the crosswalk
on _dagster_partition_key, and the Current roster branch joins it on
file_year, so a key that doesn't match the Finalsite data drops every status
and the dashboard goes empty.
4-5. Goals
SRE supplies a new workbook each cycle, so ask for it rather than assume last cycle's. Then:
- Confirm goal names are unchanged: the join is on
goal_name, so a renamed goal silently stops matching. - Reconcile the workbook against
stg_google_sheets__finalsite__goalsat all three granularities; grade-level goals change independently of the cover sheet's school totals. - Rebuild
stg_google_sheets__finalsite__goalsbetween rounds; it is a table.
Run this reconciliation whenever goals change, not only at rollover. SRE does not always flag mid-year changes. The fresh-dashboard skill has the procedure.
8. The var bump
One line in one file. Every model and test reads
var("finalsite_recruitment_year").
After the bump, expect the SIS comparison columns to be NULL until each SIS rolls over (see Known data model caveats).
status_crosswalk column reference
The staging model is select *, so sheet columns map straight to model columns.
Bold rows are the ones SRE re-confirms each cycle.
| col | column | what it drives |
|---|---|---|
| A | _dagster_partition_key |
The cycle year. Replaced at rollover; file_year is derived from it. |
| B | enrollment_type |
New vs Returning. |
| C | detailed_status |
The Finalsite status name being mapped. |
| D | detailed_status_ranking |
Orders statuses. Hand-mirrored by the status_order CASE; change both. |
| E | detailed_status_branched_ranking |
Read by nothing (see Known issues, need to fix). |
| F | valid_detailed_status |
false silently drops the row. "Is this status legitimate for this enrollment_type." |
| G | fs_status_field |
The Finalsite date column the status came from. |
| H | qa_flag |
true silently drops the row. |
| I-P | status_enrollment, status_group_numerator, status_group_denominator, conversion_metric_numerator_1..3, conversion_metric_denominator_1..2 |
The goal-group mapping. Unpivoted into status_group_name / status_group_value, which is how a raw status becomes a goal_type / goal_name. |
| Q | file_year |
Derived in the staging model from column A; not in the sheet. |