OK State Reporting forms plugin for PowerSchool
This plugin currently implements one form — the Oklahoma ED 506 (Indian Student
Eligibility Certification) — as a read-only, server-rendered PowerSchool page. It has
no data-entry or save path: every value is pulled from fields the district already
populates elsewhere (U_Tps_Indian_Education, U_StudentsUserFields, the Tribal Names codeset and its U_TRIBAL_HQ_ADDRESS extension). The page must render correctly
both standalone and inside PowerSchool's "mass print student screens" feature, which
does not execute page JavaScript — so the whole form has to resolve server-side through
PowerSchool tag syntax alone.
src/WEB_ROOT/admin/studentlist/counselor/aet_massprint.ok_state_forms.content.footer.txt
Content-footer hook for the base AET Mass Print Student Screen plugin. Injects an
<option> into mass print's page-selection dropdown so "Ed 506 Form" is one of the
screens a counselor can add to a batch, gated behind
~[if.~(studentscreenaccess;quicklookup.html)=1] so it only appears for users with
access to the underlying page.
src/WEB_ROOT/admin/students/ok_state_forms/ed_506_form.html
The form itself — a single HTML file, no separate elements/partial file and no
JavaScript, since mass print only ever executes the final rendered markup. All dynamic
content resolves through three PowerSchool access patterns, used according to what
actually works on this server:
- plain page-level DATs (
~([Table]field)) forStudentsand native one-to-one extensions likeU_StudentsUserFields; - one
tlist_sqlblock for everything that touches a codeset or codeset-extension table (CODESET,U_TRIBAL_HQ_ADDRESS) and forU_Tps_Indian_Education, none of which resolve viaDirectTable.Select/DAT on this server; - a second, minimal
tlist_sqlfor the one remainingU_Tps_Indian_Educationvalue (ED506_Date) needed outside that block.
Two hard constraints on tlist_sql shaped this file and are worth knowing before
touching it: its ~( ) body references bind to the SELECT list by position, not by
name, and ~[if] and DATs (including *contact_info) do not evaluate inside a
tlist_sql body — only bare ~(column_alias) values from that block's own query do.
See [[tlist-sql-positional-binding]] and [[tlist-sql-reserved-characters]] in
project memory for the full detail, including the reserved-character (CHR())
requirement for brackets, carets and colons embedded in the SQL.
The print stylesheet in this file was also tuned to fix blank/spillover pages under
mass print: .box-round's forced page-break-inside: avoid was removed (it was
pushing a whole tall form to a fresh page rather than letting it split, producing blank
pages before it), and the guardian Address/City/State/Zip fields were resized — Address
widened and given a smaller font so a full street address reliably renders on one line
instead of wrapping to 2–3 lines and pushing the form's total height past a full page.
src/WEB_ROOT/images
Static assets (oksde_logo.png, checkbox.png, checkedbox.png) carried over from an
earlier iteration of the form. checkbox.png/checkedbox.png are not referenced by
the current ed_506_form.html — the form now draws its radio buttons with plain CSS
(input[type="radio"] styling) rather than image swaps — and are candidates for
removal if no other page in this plugin ends up using them.
src/WEB_ROOT/pagecataloging/state_forms_nav.json
PowerSchool page-cataloging manifest. Adds an "OK State Forms" entry under the Student
Compliance nav section with one child, "Ed 506 Form", linking to
/admin/students/ok_state_forms/ed_506_form.html?frn=~(studentfrn) — this is the
standalone (non-mass-print) entry point into the form from a student's record.
make plugin (via Makefile) zips src/WEB_ROOT and src/plugin.xml into
dist/<name>-v<version>.zip, reading the plugin name from plugin.xml's name
attribute and its version from the version attribute — bump version in
src/plugin.xml before packaging a release.
tribal_memberonU_Tps_Indian_Educationcarries values (Mother,Father,Student,Grandmother,Grandfather,Sister,Self Support,Other, …) that don't map to the form's three radio options (Child/Child's Parent/Child's Grandparent) — roughly 600 records. No mapping decision has been made for these yet.- The PPTX slide-8 rule (deriving the proof-of-membership bubble from roll-number presence) has not been implemented.
Technical reference for the codeset/extension pair that drives the tribe name, address, and federal/state recognition status on the ED 506 form.
Standard PowerSchool codeset table (PS.CODESET). One row per federally/state
recognized tribe.
- 649 rows,
CODESETIDrange 97132–101174 (not contiguous — PowerSchool assigns ids; a new code cannot be created with a chosen id). DESCRIPTION— the tribe's full legal name, e.g.The Muscogee (Creek) Nation,Cherokee Nation. This is the value shown to the district and matched against.CODE,DISPLAYVALUE— standard codeset fields, not used by the form.- One entry,
Unknown(codesetid101174), was added as a placeholder for stored values that name no real tribe (garbage entries, "N/A", etc.).
Custom table, one row per codeset entry, joined 1:1 via CODESETCODESETID.
| Column | Type | Notes |
|---|---|---|
| CODESETCODESETID | NUMBER (19,0) | PK / FK to CODESET.CODESETID, not null |
| STREET | VARCHAR2(800) | |
| LINE_TWO | VARCHAR2(800) | present, unused by the form |
| CITY | VARCHAR2(800) | |
| STATE | VARCHAR2(56) | full state name, not abbreviation |
| POSTAL_CODE | VARCHAR2(56) | |
| TRIBE_STATUS | VARCHAR2(160) | Federal, State, or blank |
| WHOCREATED/WHENCREATED/WHOMODIFIED/WHENMODIFIED | — | standard extension audit columns, auto-populated |
Access pattern: ~[DirectTable.Select] and page-level DATs (~([Table]field)) do not
resolve against codeset or codeset-extension tables on this server. The form reads this
table exclusively through a tlist_sql block that joins it in from Oracle directly — see
[[tlist-sql-reserved-characters]] and [[tlist-sql-positional-binding]] in the project
memory for the syntax rules that made that block work.
VARCHAR2(4000), one-to-one Students extension. Holds the codesetid(s) resolved for a
student, " : "-delimited when a student has more than one tribe (616 students).
A colon was chosen as the delimiter because it needs no escaping in a regex, in
JavaScript, or in PowerSchool tag syntax. The ED 506 form takes only the first id via
substr/instr in SQL (see the tlist_sql block in ed_506_form.html) — it does not
attempt to display multiple tribes.
Both loads went through PowerSchool's Quick Import, which requires headers to be
the fully-qualified TABLE.FIELD name — a bare field name shows as "auto-matched" in
the UI but the server maps nothing ("You must map at least one column before you are
able to proceed"). Quick Import also required LF line endings on this server; CRLF
files silently failed to import.
Source: the existing tps_tribal_info_json wildcard data (.claude/tps_tribal_info_json.txt),
which already carried codesetid, address, and status per tribe.
- Parse the wildcard's JS array literal as JSON, dropping its metadata record.
- Fix two known source misspellings (
Louisana→Louisiana,New Jesrsey→New Jersey). - Emit tab-delimited rows keyed by
CodesetCodesetID, LF line endings, header row using fully-qualified column names. - Loaded via Quick Import → 649 tribes.
Source: each student's stored tribe text in U_Tps_Indian_Education
(tps_natamer_tribes, plus up to three "extra" fields tribes1/2/3 used when a
student has more than one tribe on file).
- Build a resolver (
resolve()) that tries, in order: an exact normalized-string match againstCODESET.DESCRIPTION; a substring/containment match; then an all-tokens-present match (stopwords like "tribe", "nation", "band", "of" stripped). Two hardcoded district rulings are applied first (CHEROKEE→ Cherokee Nation97397,CREEK→ The Muscogee (Creek) Nation97410) since those bare words would otherwise be ambiguous. - Anything the resolver can't pin to exactly one codesetid is deferred to a human decision (§3) rather than guessed.
- For a student with multiple tribe fields, resolved ids are de-duplicated (the extra
tribes1/2/3fields very often just repeat the main tribe under a different spelling) and joined with" : ". - Output: tab-delimited,
Students.Student_Number+ the codesetid field, LF line endings. - Loaded via Quick Import → 15,384 students.
The stored tribe text predates the codeset and was free-typed/select-list text, so it
does not line up with CODESET.DESCRIPTION cleanly. Two supporting artifacts were
built to make every non-obvious mapping decision explicit and reviewable before import,
rather than resolved by fuzzy-match guesswork:
Maps the old select-list options (tps_indian_education_tribe_options, the values a
user could have picked from the on-page dropdown before this rewrite) onto
CODESET.DESCRIPTION, for reference/spot-checking, not for driving the import directly.
Each row carries a confidence score (100 = exact normalized match, down through
"truncated at '('" / "state qualifier dropped" variants, to fuzzy difflib matches
flagged REVIEW) and the match method, so low-confidence rows can be reviewed by a
human before anything downstream trusts them. Fuzzy matching alone was explicitly
rejected as unsafe here — e.g. Fort Sill Apache Tribe of Oklahoma is 0.84 similar to
Apache Tribe of Oklahoma, a different tribe.
Normalization folds: & → and, two-letter state abbreviations → full state names,
punctuation stripped, a leading The optionally dropped, and everything after the
first ( or last , optionally dropped (to match a description that added a
parenthetical or a state qualifier the option text lacked).
One row per student whose stored tribe text did not resolve to exactly one codeset
entry automatically. For each, every plausible candidate tribe is listed
(candidate_1, candidate_2, …, each codesetid | description), ranked so Oklahoma
tribes (the district is in Tulsa) and codeset entries starting with The sort first.
Column G (chosen_codesetid) is where the district records its decision — nothing in
this file is auto-applied to the import.
Key cleanup steps handled here:
- PascalCase un-collapsing: the original data-entry page stripped spaces from the
select value before storing it (e.g.
NavajoNationArizonaNewMexicoUtah), so adecamel()step reinserts word boundaries before any matching is attempted. - Token-based matching with stopwords:
of/the/and/tribe/tribes/nation/band/ indians/indianare excluded from the comparison so e.g.Cheyenne-Arapahostill matchesCheyenne and Arapaho Tribes, Oklahoma. Unknownplaceholder: since PowerSchool assigns codesetids and none could be pre-chosen for a newUnknowncode, decisions in column G that should map to "no real tribe" were written as999999and swapped for the real codesetid (101174) once that code existed in the codeset, bybuild_codesetid_import.py(UNKNOWN_CODESETID).- Decision scope: a ruling is keyed by
(student_number, source_field)so a decision made for a student'stribes1value doesn't leak onto their main tribe field, but a ruling on a stored text value is also generalized to every other student who has that exact same stored text (DECIDED_BY_VALUE) — except where two different students' rows ruled the same text two different ways, in which case that value is left ungeneralized and reported.
The form's single tlist_sql block (in ed_506_form.html) does, in one query:
- Pull the student's
TPS_TRIBAL_NAMES_CODESET_ID, take the first:-delimited id (substr/instr, with the colon passed aschr(58)— literal reserved characters silently break the tag). - Join
CODESETon that id to get the tribe'sDESCRIPTION. - Left join
U_TRIBAL_HQ_ADDRESSonCODESETCODESETIDfor street/city/state/zip andTRIBE_STATUS(rendered as pre-checkedFederal/Staterecognition radios viaDECODE, since~[if]does not evaluate inside atlist_sqlbody). - Left join
U_Tps_Indian_Educationfor the remaining ED 506 fields (proof of membership, ED506_Date, etc.) — DirectTable.Select/DAT access to that table does not resolve on this server, so everything from it is pulled the same way.
Everything referenced in the page body after the ~[tlist_sql;...] tag must be a bare
~(column_alias) bound by position to the SELECT list, not by name — see
[[tlist-sql-positional-binding]].