Skip to content

feat: retarget CCSS tags to state standards via Interstandard; browse by state (Unit 10) - #42

Merged
jcasimir merged 7 commits into
mainfrom
feat/state-retargeting
Sep 13, 2026
Merged

feat: retarget CCSS tags to state standards via Interstandard; browse by state (Unit 10)#42
jcasimir merged 7 commits into
mainfrom
feat/state-retargeting

Conversation

@jcasimir

Copy link
Copy Markdown
Member

What

CommonMath's Grade 5+ lessons/topics now carry state standard codes (Colorado, Texas, and any other framework Interstandard maps) alongside their existing CCSS StandardTagging, and the app is browsable by state.

  • StateStandardTagging (migration + model): one row per (taggable, target_framework, state_code, standard), with relationship/confidence/review_state/disputed/provenance carried over from Interstandard's edge, plus retired and stale_at for lifecycle without deletion.
  • lib/interstandard/client.rb: thin Net::HTTP client — submit, fetch_report, poll (backoff + timeout) — raising Interstandard::Unauthorized (401), Interstandard::RateLimited (429, with retry_after), or Interstandard::RequestFailed (anything else) with the API's own error code/message.
  • lib/standards/retargeter.rb: submits every CCSS StandardTagging, stores confirmed exact/grade_shifted results, and marks a previously-confirmed row stale_at (never deletes) when a re-run no longer confirms it.
  • lib/standards/coverage.rb + standards:coverage: the success-criterion query — fraction of a grade's lessons with a confirmed state code.
  • standards:retarget[framework], standards:retarget_all rake tasks, and RetargetStandardsJob scheduled weekly via Solid Queue's config/recurring.yml.
  • Browse UI: /states (frameworks + counts), /states/:framework (codes + statements), /states/:framework/:code (tagged lessons/topics, linking into the existing grade/module/topic/lesson hierarchy). Lesson and Topic show pages now display CCSS + confirmed state codes.
  • Fixed the app's Rails 8.1 boot failure (Active Storage loads the vips transformer unconditionally now) by adding ruby-vips, unrelated to this feature but blocking any local test run.
  • CI: added a test job (this repo had none) against a postgres:16 service, gating PRs on the new suite.

Env vars needed

  • INTERSTANDARD_URL — defaults to the production map (https://interstandard.jumpstartlab.com).
  • INTERSTANDARD_API_KEY — required to actually call out; get one from Interstandard's /api_keys page.
  • INTERSTANDARD_TARGETS — comma-separated target framework slugs (default co-math-2020,tx-teks-math).

How to run

bin/rails standards:retarget[co-math-2020]   # one framework
bin/rails standards:retarget_all             # every framework in INTERSTANDARD_TARGETS
bin/rails standards:coverage                 # grade 5 (the success criterion)
bin/rails standards:coverage[6]              # a specific grade
bin/rails standards:coverage[all]            # every grade

Post-Deploy Monitoring & Validation

Run standards:retarget (or retarget_all) once INTERSTANDARD_API_KEY is set in production — nothing runs automatically until then beyond the weekly scheduled job. Watch the task's own stdout for 401/429 errors surfacing as Interstandard::Unauthorized/Interstandard::RateLimited — a 401 means the key is missing/revoked, a 429 means the request-rate or submission-frequency cap was hit and the task should be re-run later, not retried immediately. Validate with standards:coverage — it should report a non-zero fraction of Grade 5 lessons with a confirmed code for each configured framework.

🤖 Generated with Claude Code

https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu

jcasimir and others added 7 commits September 13, 2026 15:41
Active Storage's engine loads the vips variant transformer unconditionally
at boot on Rails 8.1, so the app can't even boot without ruby-vips despite
not using image variants yet. webmock (test group) is needed to stub the
Interstandard HTTP calls added in the next commits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu
Lessons and Topics keep their CCSS StandardTagging as-is; a
StateStandardTagging records a retargeted state code alongside it (target
framework, state code/statement, relationship, confidence, review state,
disputed, provenance, edge_provenance_ref). Re-running the retarget task
(next commit) upserts by the unique key and marks a code no longer
confirmed `stale_at` instead of deleting it, so review history and
retirement flags survive a transient hiccup.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu
Thin Net::HTTP client for the Interstandard translator API: submit() posts
a bulk retargeting submission, fetch_report() reads its report, poll()
backs off between fetches until the submission reaches a terminal status
(or times out). Raises Interstandard::Unauthorized on 401,
Interstandard::RateLimited (with Retry-After) on 429, and
Interstandard::RequestFailed (carrying the API's error code) on anything
else non-2xx.

Base URL and API key come from config/initializers/interstandard.rb
(INTERSTANDARD_URL / INTERSTANDARD_API_KEY env vars).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu
standards:retarget[target-framework] submits every CCSS StandardTagging to
Interstandard, polls the report, and stores confirmed exact/grade_shifted
results via StateStandardTagging; no_confirmed_match and invalid rows are
listed in the task's summary and stored nowhere. standards:retarget_all
runs it for every framework in INTERSTANDARD_TARGETS (default
co-math-2020,tx-teks-math). standards:coverage prints, per framework, the
fraction of a grade's lessons with a confirmed state code (grade 5 by
default, matching Unit 10's success criterion; a number or "all" for
others).

RetargetStandardsJob wraps retarget_all for a weekly Solid Queue recurring
run (config/recurring.yml), so demotions/retirements on the Interstandard
side propagate automatically.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu
/states lists target frameworks with confirmed-tagging counts; /states/:framework
lists that framework's codes with statements; /states/:framework/:code lists
the lessons/topics tagged to it (non-stale, non-retired only), linking back
into the existing grade/module/topic/lesson browse hierarchy. Lesson and
Topic show pages now display their CCSS codes plus any confirmed state
codes, linking to the per-code page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu
…kflow

This repo's CI had no test job at all — scan_ruby/scan_js/lint only, so a
red test suite never blocked a merge. Add one against a postgres:16
service (DATABASE_URL, mirroring the fleet's ci.yml pattern), installing
libvips since Active Storage now requires it at boot.

CLAUDE.md documents the new env vars, rake tasks, and the weekly Solid
Queue recurring job.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu
bin/importmap audit boots the whole app (config/application.rb), which now
requires ruby-vips/libvips just to load — the test job already installed
it, scan_js didn't and failed on this PR's CI run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EqZeVdG5q6KDS1Q8sbcxu
@jcasimir
jcasimir merged commit 0f5e6a1 into main Sep 13, 2026
4 checks passed
@jcasimir
jcasimir deleted the feat/state-retargeting branch September 13, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant