Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ docs/superpowers/plans/
docs/superpowers/specs/
docs/copilot/plans/
docs/copilot/specs/
bruno/docs/diagrams/*.png
12 changes: 12 additions & 0 deletions bruno/SIS/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,17 @@ const logSpecStudentEdOrgAssociation = {
raceDescriptors: r => (r?.races || []).map(rc => extractDescriptor(rc.raceDescriptor)),
};

// StudentEdOrgResponsibilityAssoc spec map (StudentEnrollment > StudentEdOrgResponsibilityAssocs)
// Primary keys: beginDate, educationOrganizationId, responsibilityDescriptor, studentUniqueId
// Include identifiers and mutated field (responsibilityDescriptor) plus endDate.
const logSpecStudentEdOrgResponsibilityAssoc = {
beginDate: r => r?.beginDate,
responsibilityDescriptor: r => extractDescriptor(r?.responsibilityDescriptor),
educationOrganizationId: r => r?.educationOrganizationReference?.educationOrganizationId,
studentUniqueId: r => r?.studentReference?.studentUniqueId,
endDate: r => r?.endDate,
};

// StudentSchoolAssociation spec map (StudentEnrollment > StudentSchoolAssociations)
// Primary keys per config: schoolId, studentUniqueId, entryDate (naturalIdField null)
// Include required descriptors & mutated-capable fields (entryDate, exitWithdrawDate, exitWithdrawTypeDescriptor, graduationPlanReference constituents, calendarReference.calendarCode)
Expand Down Expand Up @@ -938,6 +949,7 @@ module.exports = {
,logSpecStudent
,logSpecGraduationPlan
,logSpecStudentEdOrgAssociation
,logSpecStudentEdOrgResponsibilityAssoc
,logSpecStudentSchoolAssociation
,logSpecStudentSectionAssociation
,logSpecStudentCTEProgramAssociation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
meta {
name: 01 - 1st StudentEdOrgResponsibilityAssoc is valid
type: http
seq: 1
}

get {
url: {{resourceBaseUrl}}/ed-fi/studentEducationOrganizationResponsibilityAssociations?beginDate=[ENTER FIRST BEGIN DATE YYYY-MM-DD]&educationOrganizationId=[ENTER FIRST EDUCATION ORGANIZATION ID]&studentUniqueId=[ENTER FIRST STUDENT UNIQUE ID]&responsibilityDescriptor={{firstResponsibilityDescriptorEncoded}}&responsibilityDescriptor_KEEP_IT_AT_THE_END=[ENTER FIRST RESPONSIBILITY DESCRIPTOR]
body: none
auth: inherit
}

params:query {
beginDate: [ENTER FIRST BEGIN DATE YYYY-MM-DD]
educationOrganizationId: [ENTER FIRST EDUCATION ORGANIZATION ID]
studentUniqueId: [ENTER FIRST STUDENT UNIQUE ID]
responsibilityDescriptor: {{firstResponsibilityDescriptorEncoded}}
responsibilityDescriptor_KEEP_IT_AT_THE_END: [ENTER FIRST RESPONSIBILITY DESCRIPTOR]
}

assert {
res.status: eq 200
res.body: isArray
res.body: isNotEmpty
res.body[0].id: isString
res.body[0].id: isNotEmpty
res.body[0].beginDate: isString
res.body[0].beginDate: isNotEmpty
res.body[0].responsibilityDescriptor: isString
res.body[0].responsibilityDescriptor: isNotEmpty
res.body[0].educationOrganizationReference: isDefined
res.body[0].educationOrganizationReference.educationOrganizationId: isNumber
res.body[0].educationOrganizationReference.educationOrganizationId: neq 0
res.body[0].studentReference: isDefined
res.body[0].studentReference.studentUniqueId: isString
res.body[0].studentReference.studentUniqueId: isNotEmpty
res.body[0].endDate: isString
res.body[0].endDate: isNotEmpty
}

script:pre-request {
const { encodeDescriptorParameter, setVar } = require('./utils');
const responsibilityDescriptorEncoded = encodeDescriptorParameter(req.url, 'responsibilityDescriptor_KEEP_IT_AT_THE_END');

setVar(bru, 'firstResponsibilityDescriptorEncoded', responsibilityDescriptorEncoded);
}

script:post-response {
const { pickSingle, setVars, wipeVars, extractDescriptor } = require('./utils');
const { logSpecStudentEdOrgResponsibilityAssoc, logScenario } = require('./logging');
const entityName = 'StudentEdOrgResponsibilityAssoc';
const scenarioName = this.req.name;
const single = pickSingle(res.getBody());

if (!single) {
wipeVars(bru, [
'firstStudentEdOrgResponsibilityAssocUniqueId',
'firstStudentEdOrgResponsibilityAssocResponsibilityDescriptor'
], entityName, true);
}

setVars(bru, {
firstStudentEdOrgResponsibilityAssocUniqueId: single.id,
firstStudentEdOrgResponsibilityAssocResponsibilityDescriptor: extractDescriptor(single.responsibilityDescriptor)
}, entityName);

logScenario(entityName, scenarioName, single, logSpecStudentEdOrgResponsibilityAssoc);
}

settings {
encodeUrl: false
timeout: 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
meta {
name: 02 - 2nd StudentEdOrgResponsibilityAssoc is valid
type: http
seq: 2
}

get {
url: {{resourceBaseUrl}}/ed-fi/studentEducationOrganizationResponsibilityAssociations?beginDate=[ENTER SECOND BEGIN DATE YYYY-MM-DD]&educationOrganizationId=[ENTER SECOND EDUCATION ORGANIZATION ID]&responsibilityDescriptor={{secondResponsibilityDescriptorEncoded}}&studentUniqueId=[ENTER SECOND STUDENT UNIQUE ID]&responsibilityDescriptor_KEEP_IT_AT_THE_END=[ENTER SECOND RESPONSIBILITY DESCRIPTOR]
body: none
auth: inherit
}

params:query {
beginDate: [ENTER SECOND BEGIN DATE YYYY-MM-DD]
educationOrganizationId: [ENTER SECOND EDUCATION ORGANIZATION ID]
responsibilityDescriptor: {{secondResponsibilityDescriptorEncoded}}
studentUniqueId: [ENTER SECOND STUDENT UNIQUE ID]
responsibilityDescriptor_KEEP_IT_AT_THE_END: [ENTER SECOND RESPONSIBILITY DESCRIPTOR]
}

assert {
res.status: eq 200
res.body: isArray
res.body: isNotEmpty
res.body[0].id: isString
res.body[0].id: isNotEmpty
res.body[0].beginDate: isString
res.body[0].beginDate: isNotEmpty
res.body[0].responsibilityDescriptor: isString
res.body[0].responsibilityDescriptor: isNotEmpty
res.body[0].educationOrganizationReference: isDefined
res.body[0].educationOrganizationReference.educationOrganizationId: isNumber
res.body[0].educationOrganizationReference.educationOrganizationId: neq 0
res.body[0].studentReference: isDefined
res.body[0].studentReference.studentUniqueId: isString
res.body[0].studentReference.studentUniqueId: isNotEmpty
res.body[0].endDate: isString
res.body[0].endDate: isNotEmpty
}

script:pre-request {
const { encodeDescriptorParameter, setVar } = require('./utils');
const responsibilityDescriptorEncoded = encodeDescriptorParameter(req.url, 'responsibilityDescriptor_KEEP_IT_AT_THE_END');

setVar(bru, 'secondResponsibilityDescriptorEncoded', responsibilityDescriptorEncoded);
}

script:post-response {
const { pickSingle, setVars, wipeVars } = require('./utils');
const { logSpecStudentEdOrgResponsibilityAssoc, logScenario } = require('./logging');
const entityName = 'StudentEdOrgResponsibilityAssoc';
const scenarioName = this.req.name;
const single = pickSingle(res.getBody());

if (!single) {
wipeVars(bru, [
'secondStudentEdOrgResponsibilityAssocUniqueId',
'secondStudentEdOrgResponsibilityAssocEndDate'
], entityName, true);
}

setVars(bru, {
secondStudentEdOrgResponsibilityAssocUniqueId: single.id,
secondStudentEdOrgResponsibilityAssocEndDate: single.endDate
}, entityName);

logScenario(entityName, scenarioName, single, logSpecStudentEdOrgResponsibilityAssoc);
}

settings {
encodeUrl: false
timeout: 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
meta {
name: 03 - 2nd StudentEdOrgResponsibilityAssoc - endDate was Updated
type: http
seq: 3
}

get {
url: {{resourceBaseUrl}}/ed-fi/studentEducationOrganizationResponsibilityAssociations/{{secondStudentEdOrgResponsibilityAssocUniqueId}}
body: none
auth: inherit
}

assert {
res.status: eq 200
res.body: isDefined
res.body.id: isString
res.body.id: isNotEmpty
res.body.beginDate: isString
res.body.beginDate: isNotEmpty
res.body.responsibilityDescriptor: isString
res.body.responsibilityDescriptor: isNotEmpty
res.body.educationOrganizationReference: isDefined
res.body.educationOrganizationReference.educationOrganizationId: isNumber
res.body.educationOrganizationReference.educationOrganizationId: neq 0
res.body.studentReference: isDefined
res.body.studentReference.studentUniqueId: isString
res.body.studentReference.studentUniqueId: isNotEmpty
res.body.endDate: isString
res.body.endDate: isNotEmpty
}

script:pre-request {
const { validateDependency } = require('./utils');

validateDependency(bru, 'secondStudentEdOrgResponsibilityAssocUniqueId', '02 - 2nd StudentEdOrgResponsibilityAssoc is valid', { actionHint: 'Ensure you ran the second certification scenario successfully before continuing.' });

validateDependency(bru, 'secondStudentEdOrgResponsibilityAssocEndDate', '02 - 2nd StudentEdOrgResponsibilityAssoc is valid', { actionHint: 'Ensure you ran the second certification scenario successfully before continuing.' });
}

script:post-response {
const { getVar, expectChanged, throwNotFoundOrSpecificError } = require('./utils');
const { logSpecStudentEdOrgResponsibilityAssoc, logScenario } = require('./logging');
const entityName = 'StudentEdOrgResponsibilityAssoc';
const scenarioName = this.req.name;

if (res.status !== 200 || !res.body) {
throwNotFoundOrSpecificError(entityName);
}

const current = res.getBody();
const previousEndDate = getVar(bru, 'secondStudentEdOrgResponsibilityAssocEndDate');

expectChanged(previousEndDate, current.endDate, 'endDate');

logScenario(entityName, scenarioName, current, logSpecStudentEdOrgResponsibilityAssoc, [
'endDate'
]);
}

settings {
encodeUrl: false
timeout: 0
followRedirects: true
maxRedirects: 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "../../../../schemas/entity-config.schema.json",
"version": 1,
"identity": {
"primaryKeyFields": ["beginDate", "educationOrganizationId", "responsibilityDescriptor", "studentUniqueId"],
"naturalIdField": null
},
"overrides": {
"entityName": "StudentEdOrgResponsibilityAssoc",
"endpointSegment": "studentEducationOrganizationResponsibilityAssociations",
"loggingSpecName": "StudentEdOrgResponsibilityAssoc",
"fileNameAlias": "StudentEdOrgResponsibilityAssoc"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
meta {
name: Student Education Organization Responsibility Associations
}

auth {
mode: inherit
}

docs {
# v5 Student Enrollment > StudentEdOrgResponsibilityAssociation Scenarios

The StudentEducationOrganizationResponsibilityAssociation (SEORA) indicates a relationship between a student and an education organization other than an enrollment relationship, and generally indicates some kind of accountability or responsibility of the education organization for the student. The kind of responsibility is specified in the Responsibility descriptor value according to policy.

This association represents student information that is specific to a student's relationship with an EducationOrganization. Enrollment relationship semantics are covered by [StudentSchoolAssociation. Domain: Enrollment](https://docs.ed-fi.org/reference/data-exchange/data-standard/model-reference/enrollment-domain/best-practices/#best-practices-related-to-the-studenteducationorganizationresponsibilityassociation-seora)

## Prerequisites

* Student
* School
* StudentSchoolAssociation (enrollment)

## Scenarios tasks

1. __CREATE__ a `first` `Student EdOrg Responsibility Association` for an `elementary Student` at Grand Bend Elementary School District.
1. __CREATE__ a `second` `Student EdOrg Responsibility Association` for the same `elementary Student` at Grand Bend High School District.
2. __UPDATE__ the _endDate_ on the second `Student EdOrg Responsibility Association`.

### Additional Notes

* A SEORA is written when the responsibility for a student is a different education organization than the enrollment school in the SSA.
* The _BeginDate_ and _EndDate_ of the SEORA should be informed by the _EntryDate_ and _ExitWithdrawDate_ of a student’s SSA(s). There may be circumstances when they may be legitimately different.

## Scenarios example data

| Resource | Property Name | Is Collection | Data Type | Required | Scenario 1: POST | Scenario 2: POST | Scenario 3: PUT |
| ------------------------------------------------------------------ | --------------------------------------- | ------------- | ------------------------------------- | ----------- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- |
| StudentEducationOrganizationResponsibilityAssociations | educationOrganizationReference | FALSE | educationOrganizationReference | REQUIRED | | | |
| educationOrganizationReference | educationOrganizationId | FALSE | integer | REQUIRED | 255901107 | 255901001 | 255901001 |
| StudentEducationOrganizationResponsibilityAssociations | studentReference | FALSE | studentReference | REQUIRED | | | |
| studentReference | studentUniqueId | FALSE | string | REQUIRED | 111111 | 111111 | 111111 |
| StudentEducationOrganizationResponsibilityAssociations | beginDate | FALSE | string | REQUIRED | [current school year]-08-23 | [current school year]-08-23 | [current school year]-08-23 |
| StudentEducationOrganizationResponsibilityAssociations | responsibilityDescriptor | FALSE | string | REQUIRED | Residency | Individualized Education Program | Individualized Education Program |
| StudentEducationOrganizationResponsibilityAssociations | endDate | FALSE | string | REQUIRED | [current school year]-06-01 | [current school year]-06-01 | [current school year]-10-01 |

## API response format

```json
{
"id": "string",
"beginDate": "2026-06-23",
"responsibilityDescriptor": "string",
"educationOrganizationReference": {
"educationOrganizationId": 0
},
"responsibleEducationOrganizationReference": {
"educationOrganizationId": 0
},
"studentReference": {
"studentUniqueId": "string"
},
"endDate": "2026-06-23"
}
```

}
25 changes: 25 additions & 0 deletions bruno/Sample Data/Descriptors/ResponsibilityDescriptors.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
meta {
name: ResponsibilityDescriptors
type: http
seq: 27
}

get {
url: {{resourceBaseUrl}}/ed-fi/responsibilityDescriptors
body: none
auth: bearer
}

params:query {
~description: Individualized Education Program
~codeValue: Residency
}

auth:bearer {
token: {{edFiCertToken}}
}

settings {
encodeUrl: true
timeout: 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
meta {
name: Aux 1 - StaffEdOrgAssociations
type: http
seq: 1
}

get {
url: {{resourceBaseUrl}}/ed-fi/staffEducationOrganizationAssignmentAssociations?staffUniqueId=[ENTER_STAFF_UNIQUE_ID]&educationOrganizationId=[ENTER_EDORG_ID]&beginDate=[ENTER_BEGIN_DATE]
body: none
auth: bearer
}

params:query {
staffUniqueId: [ENTER_STAFF_UNIQUE_ID]
educationOrganizationId: [ENTER_EDORG_ID]
beginDate: [ENTER_BEGIN_DATE]
}

auth:bearer {
token: {{edFiCertToken}}
}

script:post-response {
const { setVar, setVarsMessage, wipeVar, wipeVarsWarning, pickSingle } = require('./utils');
const item = pickSingle(res.getBody());

if (item) {
setVar(bru, 'tempStaffEdOrgAssociationsUUID', item.id);
setVarsMessage('StaffEdOrgAssociations');
} else {
wipeVar(bru, 'tempStaffEdOrgAssociationsUUID');
wipeVarsWarning('StaffEdOrgAssociations');
}
}

settings {
encodeUrl: true
timeout: 0
}
Loading
Loading