Skip to content

Non-ODS years selectable if crossyear matching enabled OR roster file exists#67

Draft
edandylytics wants to merge 11 commits into
developmentfrom
app/crossyear-3
Draft

Non-ODS years selectable if crossyear matching enabled OR roster file exists#67
edandylytics wants to merge 11 commits into
developmentfrom
app/crossyear-3

Conversation

@edandylytics
Copy link
Copy Markdown
Collaborator

This PR updates the app to allow users and API clients to submit jobs for non-ODS years when either:

  • (a) a roster file for that year + tenant exists on S3 (the current condition), OR
  • (b) cross year ID matching is enabled for the partner

This is a matter of:

  • Updating resolveJobDestination to take partner.crossYearMatchingEnabled into account when determining whether a job can be sent to a given no-ODS year.
  • Making non-ODS years selectable based on partner.crossYearMatchingEnabled when submitting jobs
  • Updating the ✅ vs ❗ logic on the ODS config page for non-ODS years.

A couple design decisions:

  • We're just checking partner.crossYearMatchingEnabled and not that we have EDU creds on file or an active connection. Having EDU creds is a condition for toggling partner.crossYearMatchingEnabled to true in the first place. It's possible someone could delete the AWS secret and not update the partner setting... but that's not really a case I think we need to handle.
    • I'm considering adding a canConnect check (or similar) for resolveJobDestination. On submitting a job, it could be good to confirm we really do have creds so we can present an error if for some reason they're not there or not available....
  • The /school-year-config/tenant has a single combined flag (hasNonOdsRoster) that indicates a year has a roster, either from a file or EDU. Exposing how the roster is sourced doesn't seem helpful for this endpoint. It's just meant to answer the question "can non-ods jobs be submitted to this year?"

edandylytics and others added 11 commits June 3, 2026 13:58
A no-ODS year with no roster file should be selectable at job creation
when the partner has cross-year matching enabled (EDU can supply the
roster), and still rejected when the toggle is off. Tests fail until
resolveJobDestination reads the partner toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolveJobDestination now reads the partner's crossYearMatchingEnabled
flag. A no-ODS year is valid if a roster file exists OR the toggle is on;
the S3 existence check is short-circuited when the toggle is on. Partner
setting only — no creds/connection check, per spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For a no-ODS year, hasRoster should be true when the partner toggle is on
even with no S3 file (and the S3 check should be skipped). ODS years stay
null. Fails until the toggle is folded into hasRoster.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hasRoster for a no-ODS year is now true when crossYearMatchingEnabled is
on, short-circuiting the S3 doesFileExist call. ODS years stay null. No
DTO change — hasRoster's shape is unchanged, only its computation, so the
crossYearMatchingEnabled flag never reaches the FE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On a no-ODS job with cross-year matching available, the executor payload
should carry appUrls.roster and omit rosterFilePath (a dangling S3
pointer). Fails until rosterFilePath is gated on crossYearMatchAvailable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When crossYearMatchAvailable is true the executor pulls the roster from
EDU via appUrls.roster, so the S3 rosterFilePath would be a dangling
pointer. Omit it. The executor only reads rosterFilePath in its
non-cross-year branch, so the absent key is safe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hasRoster now folds in the cross-year toggle, so all FE gating (already
keyed off hasRoster === true) is correct with no logic change. Drop
"file" from user-facing strings — a roster may come from EDU or an S3
file, and the FE shouldn't reason about the source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a roster-source precedence note (ODS -> EDU -> S3 file) and the
partner-setting-only selectability/green gate to AGENTS.md. The executor
already prefers EDU over the S3 file (merged in #60); no executor change
in this work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR 3 makes no-ODS roster availability source-agnostic (S3 file or EDU),
but two reachable FE surfaces still implied an S3 roster file was
required. Make the copy source-neutral so it stays accurate when the
roster comes from EDU:
- SetupRequiredPage: "Roster File Required" -> "Roster Required" and
  drop "file" from the body + contact-support message.
- UnmatchedStudents: no-ODS branch "the roster file" -> "the roster".

Also add a positive smoke test for the external API v1 job-create path.
Both job-create paths (internal POST /jobs and external v1) flow through
the shared resolveJobDestination, but only the internal path had a
toggle-on/no-file coverage test. Add the analogous test for the v1
endpoint: no-ODS year, no roster file, partner cross-year matching
enabled -> 201 with odsId=null/sendToOds=false. Passes against the
behavior already on this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The EDU-precedence bullet said "no app change in PR 3", but PR 3 does
change the app (selectability + rosterFilePath omission). The point being
made is that the executor is unchanged — it already prefers EDU over the
S3 file (merged in #60). Reword to match the kickoff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-review polish:

- Rename the GetTenantSchoolYearConfigDto field hasRoster ->
  hasNonOdsRoster. ODS years already have an ODS-fetched roster, so
  "hasRoster" was ambiguous; the field specifically reports whether a
  no-ODS year has a roster available (S3 file or EDU). Stays null for ODS
  years. Updated the controller, FE consumers (JobCreatePage,
  OdsConfigsPage, routes/index), and the integration tests.
- Status copy "roster loaded" / "roster not loaded" -> "roster available"
  / "roster not available", and the JobCreate suffix "(no roster loaded)"
  -> "(no roster available)". "Available" reads better for a status that
  may be satisfied by EDU rather than a loaded file.
- Drop transient cross-references from code comments and AGENTS.md (the
  "(see project brief)" notes and the "PR 3" mention) so they don't rot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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