California School Dashboard#
The Dashboard is a different publication from the research files, and the difference matters. The research files say what students scored; the Dashboard says how the state judged a school. Five of its seven indicators have no assessment source at all.
Layer |
Source |
Grain |
Carries |
|---|---|---|---|
Assessment detail |
|
entity × year × test × student group × grade |
domains, subscores, scale scores |
Accountability |
CDE Dashboard data files |
entity × year × indicator × student group |
status, change, colour, n-size |
Underlying counts |
entity × year × student group |
absenteeism, discipline, graduates |
The two layers complement each other rather than substituting: the Dashboard files carry a performance colour but no grade or domain breakdown, and the research files carry grade and domain detail but no accountability result. This application ingests the first two; the third is not yet loaded.
Where the files live#
The state publishes one tab-delimited file per indicator per year at a predictable address:
https://www3.cde.ca.gov/researchfiles/cadashboard/{indicator}download{year}.txt
with {indicator} one of ela, math, chronic, susp, grad,
elpi, cci or science, and {year} the spring year of the school
year covered — 2025 means 2024–25. A matching .xlsx exists alongside
each one; this application reads the text version.
Three things differ from the research files and catch people out:
The encoding is UTF-8, not the Windows code page 1252 the research files use.
The files are revised in place after release. The 2024–25 academic files were reissued four months after the Dashboard came out, at the same URL. The importer fingerprints each file by entity tag and size, so a revision is picked up and replaces what it supersedes.
There are no county rows. Only
S(school),D(district) andX(state) appear inrtype.
Release timing#
The Dashboard lags the school year it describes by roughly six months. The
2025 Dashboard was released on 15 November 2025, while the chronic absenteeism
and discipline data behind it had to be certified in CALPADS by 31 July. That
gap — data certified in the summer, judgement published in the autumn — is what
app.service.dashboard_projection exists to fill.
No Dashboard was published for 2020 or 2021. Graduation and college/career files exist for those years, but with no colours; every other indicator has no file at all. Reports must show that as a break rather than as missing data.
One envelope, seven indicators#
Every indicator file shares the same record envelope:
cds, rtype, schoolname, districtname, countyname, charter_flag, coe_flag,
dass_flag, studentgroup, curr*/prior*, change, statuslevel, changelevel,
color, box, currnsizemet, priornsizemet, accountabilitymet, indicator,
reportingyear
so one table, dashboard_indicator_results, serves all of them. Only the
measure columns differ, and the ones that do not fit — the two dozen
curr_prep_* pathway columns in the college/career file, the
currprogressed* columns in the English learner progress file — are kept
verbatim in a source_extras JSON column rather than being flattened into
columns that are null six times out of seven.
Two quirks are worth knowing:
The chronic absenteeism file spells
changeLevelwith a capital L; every other file useschangelevel. Columns are matched case-insensitively.The
indicatorcolumn was only added in 2023, and the English learner progress files had nostudentgroupcolumn before 2024 because every row is English learners. Both are recovered from the file name.
The rest of the family#
Four datasets published beside the seven indicators, none of which is an accountability result:
Growth (growthmodeldownload) says how far students moved rather than
where they landed, for ELA and mathematics. A high-poverty school can sit Red
on the academic indicator and Exceptional on growth, and both are true. It has
its own table, because colour, change and every prior figure are meaningless
for it. Science has no growth score – the science test is not taken in
consecutive grades – and the files carry district and school rows only, so
there is no statewide growth figure.
Census Day enrolment (censusenrollratesdownload) gives each entity’s
total enrolment and the size of every student group in it, counted on the
first Wednesday in October. The groups overlap – a student can be Hispanic,
an English learner and socioeconomically disadvantaged at once – so the rates
do not sum to 100 and must never be stacked.
ELPAC participation (elpacpart) is the 95% testing rule behind the
participation penalties on the academic indicators. Its columns are named
after the year (enrolled25, prate25, and enrolled24 for the prior
year), so they change annually and are resolved from the reporting year. The
first file, for 2019, named them outright and carried no prior year.
The alternative-school graduation rate (dass1yeargraduationrate) is a
one-year rate for schools with Dashboard Alternative School Status. It is
stored as GRAD under the variant DASS1YR rather than overwriting the
four-year rate, and it carries no colour. The file is published with a byte
order mark on its first column.
Participation and growth are flagged is_informational so an interface can
keep them out of the accountability grid. The State Board adopted growth for
information only in July 2025; it is not used for Local Control Funding
Formula eligibility.
How a colour is decided#
An entity’s colour is a lookup on two things: where it stands (status) and how far it moved (change). Each is cut into five bands, and a five-by-five grid maps the pair onto one of five colours.
The bands and grids are published as fifteen HTML tables and are
transcribed into dashboard_cutpoints and dashboard_color_cells by
app.ingest.dashboard_reference.
Direction. Chronic absenteeism and suspension are judged in reverse: a low rate is the good outcome. Level 5 is always the best outcome and level 1 the worst, whichever way the underlying number runs.
Variants. A cut point is only meaningful together with the table it came
from. Suspension is published as six tables keyed by the file’s type
column — ED, HD and UD for elementary, high school and unified
districts; ES, MS and HS for schools — and the academic indicators
as two, split by the hscutpoints flag at grade 11. The same 4.0% suspension
rate is High for an elementary school and Medium for a high school.
Small denominators. An entity with fewer than 150 students is judged on a reduced grid. For chronic absenteeism, suspension and college/career the five change bands collapse to three — “increased significantly” folds into “increased”, “declined significantly” into “declined” — while the cut points themselves are unchanged. Graduation keeps five change bands but uses its own colours. The state does not publish these reduced grids as tables anywhere; they are derived from the published files and cross-checked against the tables wherever the two overlap.
Overrides. A row flagged dataerrorflag carries a colour the state
assigned by hand because the local agency submitted data known to be wrong.
Fifty-seven rows in the 2025 files are flagged. These are stored as published
and never reproduced by the rules.
Together these reproduce every statuslevel, changelevel and color
in the 2024–25 files exactly — 1,381,722 classifications across all eight
indicators, with no disagreements. tests/service/test_dashboard_projection
holds that to account.
Groups the state does not rate#
Six student groups are reported for information and never receive a colour, no
matter how large they are: ELO (English learners only), RFP
(reclassified fluent English proficient), EO (English only), and the
assessment-type groups SBA, CAA and CAST. Statewide these cover
hundreds of thousands of students, so “no colour” here means not rated, not
too few students, and reports must say which.
Importing#
$ uv run app/scripts/ingest_dashboard_files.py --year 2025
With no --source the importer reads directly from the state’s web server;
pass a directory or an s3:// prefix to load from a local copy. --only
narrows to one indicator, --year is repeatable, and --force reloads
files whose fingerprint has not changed.
The load is idempotent: everything stored for the (reporting_year,
indicator_code) pairs a file covers is deleted and replaced inside one
transaction, so re-running converges rather than duplicating.