-
Notifications
You must be signed in to change notification settings - Fork 0
Non-ODS years selectable if crossyear matching enabled OR roster file exists #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
0ec20ac
005e815
07f4393
d01a37b
04a2368
0c1071f
5230571
dccb7d3
0cce5f7
3c3b93d
e2bad1e
defa9df
301bb5e
b6bcdd8
8494598
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,7 +30,6 @@ import { | |
| EventEmitterService, | ||
| EVENT_EMITTER_SERVICE, | ||
| } from 'api/src/event-emitter/event-emitter.service'; | ||
| import { EduSnowflakePoolService } from './edu-snowflake-pool.service'; | ||
|
|
||
| @Injectable() | ||
| export class EarthbeamApiService { | ||
|
|
@@ -41,8 +40,7 @@ export class EarthbeamApiService { | |
| private readonly encryptionService: EncryptionService, | ||
| private readonly fileService: FileService, | ||
| private readonly configService: AppConfigService, | ||
| @Inject(EVENT_EMITTER_SERVICE) private readonly eventEmitter: EventEmitterService, | ||
| private readonly eduPool: EduSnowflakePoolService | ||
| @Inject(EVENT_EMITTER_SERVICE) private readonly eventEmitter: EventEmitterService | ||
| ) {} | ||
|
|
||
| async earthbeamInputForRun(runId: Run['id']) { | ||
|
|
@@ -143,9 +141,12 @@ export class EarthbeamApiService { | |
|
|
||
| const executorBaseUrl = this.configService.executorCallbackBaseUrl(); | ||
|
|
||
| const partnerId = job.tenant.partnerId; | ||
| const crossYearEnabled = job.tenant.partner.crossYearMatchingEnabled; | ||
| const crossYearMatchAvailable = crossYearEnabled && (await this.eduPool.canConnect(partnerId)); | ||
| // The partner toggle alone — no creds/connection check. Once the toggle is | ||
| // on (the admin enable endpoint requires working creds to turn it on), the | ||
| // EDU connection is an assumed dependency like postgres or S3: if EDU is | ||
| // unavailable mid-run, the run fails loudly rather than silently degrading | ||
| // to weaker matching. | ||
| const crossYearMatchAvailable = job.tenant.partner.crossYearMatchingEnabled; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Super helpful framing for reasoning about the behavior throughout the app and executor. |
||
|
|
||
| const payload: EarthbeamApiJobResponseDto = { | ||
| appDataBasePath: `${job.fileProtocol}://${job.fileBucketOrHost}/${job.fileBasePath}`, | ||
|
|
@@ -171,9 +172,14 @@ export class EarthbeamApiService { | |
| }, | ||
| crossYearMatchAvailable, | ||
| sendToOds: job.sendToOds, | ||
| rosterFilePath: job.sendToOds | ||
| ? undefined | ||
| : `s3://${this.configService.rosterBucket()}/${rosterFileKey(job, job.schoolYear)}`, | ||
| // When cross-year matching is available, the executor pulls the roster | ||
| // from EDU via appUrls.roster, so the S3 file path would be a dangling | ||
| // (often nonexistent) pointer — omit it. The executor only reads | ||
| // rosterFilePath in its non-cross-year branch. | ||
| rosterFilePath: | ||
| job.sendToOds || crossYearMatchAvailable | ||
| ? undefined | ||
| : `s3://${this.configService.rosterBucket()}/${rosterFileKey(job, job.schoolYear)}`, | ||
| // odsConnection check narrows the type — the early guard ensures it's present when sendToOds | ||
| assessmentDatastore: | ||
| odsConnection && job.sendToOds | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,7 +59,7 @@ export class JobsService { | |
| | 'school_year_config_missing' | ||
| | 'school_year_disabled' | ||
| | 'ods_not_found' | ||
| | 'roster_file_missing'; | ||
| | 'roster_unavailable'; | ||
| } | ||
| > { | ||
| const config = await this.prisma.schoolYearConfig.findUnique({ | ||
|
|
@@ -95,10 +95,31 @@ export class JobsService { | |
| } | ||
|
|
||
| if (!config.sendToOds) { | ||
| const rosterKey = rosterFileKey({ partnerId: input.tenant.partnerId, tenantCode: input.tenant.code }, config.schoolYear); | ||
| const rosterExists = await this.fileService.doesFileExist(rosterKey, this.appConfig.rosterBucket()); | ||
| if (!rosterExists) { | ||
| return { status: 'error', code: 'roster_file_missing' }; | ||
| // A no-ODS year is valid if a roster file exists OR the partner has | ||
| // cross-year matching enabled (EDU can supply the roster). | ||
| // Short-circuit the S3 check when the toggle is on; we don't need the file. | ||
| const partner = await this.prisma.partner.findUnique({ | ||
| where: { id: input.tenant.partnerId }, | ||
| select: { crossYearMatchingEnabled: true }, | ||
| }); | ||
| if (!partner) { | ||
| // Every tenant has a partner (FK) — a missing one is an invariant | ||
| // violation, not a "cross-year disabled" case. Don't proceed. | ||
| throw new Error(`Partner not found: ${input.tenant.partnerId}`); | ||
| } | ||
|
Comment on lines
+105
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about the philosophy of this kind of check. Is it that the partner could have been deleted after the payload was validated initially in human/airflow I know I once commented on a PR in this repo that I was trying to move away from the |
||
|
|
||
| if (!partner.crossYearMatchingEnabled) { | ||
| const rosterKey = rosterFileKey( | ||
| { partnerId: input.tenant.partnerId, tenantCode: input.tenant.code }, | ||
| config.schoolYear | ||
| ); | ||
| const rosterExists = await this.fileService.doesFileExist( | ||
| rosterKey, | ||
| this.appConfig.rosterBucket() | ||
| ); | ||
| if (!rosterExists) { | ||
| return { status: 'error', code: 'roster_unavailable' }; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
|
|
@@ -412,5 +433,4 @@ export class JobsService { | |
|
|
||
| return { result: 'JOB_STARTED', job, run }; | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The diff here is funky. Test on the left is removed, test on the right is added -- they're different tests, not an update to the same test.