diff --git a/.github/ISSUE_TEMPLATE/epic-template.md b/.github/ISSUE_TEMPLATE/epic-template.md new file mode 100644 index 0000000000..64b08d6c82 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/epic-template.md @@ -0,0 +1,13 @@ +## Description + +[//]: # ( A plain language description of the epic. Who, what, why. ) + +**Who**: +**What**: +**Why**: + + +```[tasklist] +### Stories +- [ ] +``` diff --git a/.github/ISSUE_TEMPLATE/helpdesk-issue.yml b/.github/ISSUE_TEMPLATE/helpdesk-issue.yml new file mode 100644 index 0000000000..3449e6f084 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/helpdesk-issue.yml @@ -0,0 +1,71 @@ +name: Helpdesk Issue +description: Used as output from helpdesk issue triage. +title: "[HD]: " +labels: ["helpdesk"] +assignees: + - jadudm + - carley-sullivan +body: + - type: markdown + attributes: + value: | + **Remember to keep all PII out of tickets.** + + Do not use names, and do not attach files to this ticket. + - type: input + id: zendesk-link + attributes: + label: Zendesk link + description: Link to the issue in Zendesk + placeholder: ex. https://fac-something.zendesk.com/something/... + validations: + required: true + - type: checkboxes + id: fac-components + attributes: + label: FAC components involved + description: Select all that apply + options: + - label: submission system (app.fac.gov) + - label: workbooks + - label: static site (fac.gov) + - label: The helpdesk (zendesk) + - label: API (api.fac.gov) + - label: other + validations: + required: true + - type: dropdown + id: browser + attributes: + label: What browser did the user report as using? + multiple: false + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + - Other (Opera, Brave, etc.) + - type: markdown + attributes: + value: | + Audits are not yet public at this point. + + Place all files and screenshots in the [Google Drive Helpdesk folder](https://drive.google.com/drive/folders/1jgb2YRxaFOjKS6CwZsBTqUsbzUCzktic) and link to that folder here. + + Delete the files from Zendesk when you are done. + - type: input + id: gdrive-link + attributes: + label: Gdrive link + description: Link to supporting files in GDrive + placeholder: ex. https://google.com/drive/something/... + validations: + required: false + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Summarize the issue the user is experiencing. + placeholder: + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/snip-acceptance-criteria.md b/.github/ISSUE_TEMPLATE/snip-acceptance-criteria.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/ISSUE_TEMPLATE/snip-at-a-glance.md b/.github/ISSUE_TEMPLATE/snip-at-a-glance.md new file mode 100644 index 0000000000..fe1c1414f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/snip-at-a-glance.md @@ -0,0 +1,11 @@ +# At a glance + +[comment]: # "Begin with a short summary so intent can be understood at a glance." +[comment]: # "In order to: some objective or value to be achieved" +[comment]: # "as a: stakeholder" +[comment]: # "I want: some new feature" + +**In order to** +**as a** +**I want** + diff --git a/.github/ISSUE_TEMPLATE/snip-background.md b/.github/ISSUE_TEMPLATE/snip-background.md new file mode 100644 index 0000000000..7414709b4d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/snip-background.md @@ -0,0 +1,4 @@ + +# Background + +[comment]: # "Any helpful contextual notes or links to artifacts/evidence, if needed" diff --git a/.github/ISSUE_TEMPLATE/snip-content-signoff.md b/.github/ISSUE_TEMPLATE/snip-content-signoff.md new file mode 100644 index 0000000000..a3e0ae104b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/snip-content-signoff.md @@ -0,0 +1,12 @@ +### Content signoff + +[comment]: # "As each step is completed, assign the next team member to this ticket. At-mention (@-mention) them in a comment for visibility." + +```[tasklist] +### Signed off by... +- [ ] Author +- [ ] Content review +- [ ] Content lead +- [ ] Optional: Product +``` + diff --git a/.github/ISSUE_TEMPLATE/snip-security-considerations.md b/.github/ISSUE_TEMPLATE/snip-security-considerations.md new file mode 100644 index 0000000000..04b0d13ea4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/snip-security-considerations.md @@ -0,0 +1,8 @@ + +# Security Considerations + +Required per [CM-4](https://nvd.nist.gov/800-53/Rev4/control/CM-4). + +[comment]: # "Our SSP says 'The team ensures security implications are considered as part of the agile requirements refinement process by including a section in the issue template used as a basis for new work.'" +[comment]: # "Please do not remove this section without care." +[comment]: # "Note any security concerns that might be implicated in the change. 'None' is OK, but we must be explicit here." diff --git a/.github/ISSUE_TEMPLATE/snip-shepherd.md b/.github/ISSUE_TEMPLATE/snip-shepherd.md new file mode 100644 index 0000000000..fd04a2a4fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/snip-shepherd.md @@ -0,0 +1,9 @@ +### Shepherds + +[comment]: # "@ mention shepherds as we move across the board." +[comment]: # "Add/remove as needed" + +* Content shepherd: +* Design shepherd: +* Engineering shepherd: + diff --git a/.github/ISSUE_TEMPLATE/snip-story-process.md b/.github/ISSUE_TEMPLATE/snip-story-process.md new file mode 100644 index 0000000000..12dd7a0ca3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/snip-story-process.md @@ -0,0 +1,55 @@ +

Process Checklist

+

How it moves across the board...

+ +
+ Process checklist + +# Sketch + +[comment]: # "Notes or a checklist reflecting our understanding of the selected approach" + +Team members who will likely need to be involved in doing all the things: + +- [ ] Content +- [ ] Data +- [ ] Design +- [ ] Engineering +- [ ] Infrastructure +- [ ] Product + +# Definition of Done + +## Triage + +### If not likely to be important in the next quarter... +- [ ] Archived from the board + +### Otherwise... + +- [ ] Has a clear story statement +- [ ] Product team moves it to the appropriate backlog + +## Backlog + +- [ ] Has clearly stated/testable acceptance criteria +- [ ] One or more shepherds have been identified + +## In Progress + +- [ ] Meets the acceptance criteria +- [ ] (As appropriate) Is relabeled and triaged for movement from design to engineering, etc. + +## Review Needed + +- [ ] Necessary outside review/sign-off was provided + +## Done + +- [ ] Includes screenshots or references to artifacts + +### If there's UI... +- [ ] Screen reader - Listen to the experience with a screen reader extension, ensure the information presented in order +- [ ] Keyboard navigation - Run through acceptance criteria with keyboard tabs, ensure it works. +- [ ] Text scaling - Adjust viewport to 1280 pixels wide and zoom to 200%, ensure everything renders as expected. Document 400% zoom issues with USWDS if appropriate. + +
diff --git a/.github/workflows/deploy-application.yml b/.github/workflows/deploy-application.yml index 9de031d7ae..de28ed20a8 100644 --- a/.github/workflows/deploy-application.yml +++ b/.github/workflows/deploy-application.yml @@ -69,6 +69,26 @@ jobs: cf_space: ${{ env.space }} cf_command: update-user-provided-service fac-key-service -p '"{\"SAM_API_KEY\":\"${{ secrets.SAM_API_KEY }}\", \"DJANGO_SECRET_LOGIN_KEY\":\"${{ secrets.DJANGO_SECRET_LOGIN_KEY }}\", \"LOGIN_CLIENT_ID\":\"${{ secrets.LOGIN_CLIENT_ID }}\", \"SECRET_KEY\":\"${{ secrets.SECRET_KEY}}\"}"' + - name: Bind backup s3 bucket to prod app + if: startsWith(github.ref, 'refs/tags/v1.') + uses: cloud-gov/cg-cli-tools@main + with: + cf_username: ${{ secrets.CF_USERNAME }} + cf_password: ${{ secrets.CF_PASSWORD }} + cf_org: gsa-tts-oros-fac + cf_space: ${{ env.space }} + command: cf bind-service gsa-fac backups -w + + - name: Backup the database (Prod Only) + if: startsWith(github.ref, 'refs/tags/v1.') + uses: cloud-gov/cg-cli-tools@main + with: + cf_username: ${{ secrets.CF_USERNAME }} + cf_password: ${{ secrets.CF_PASSWORD }} + cf_org: gsa-tts-oros-fac + cf_space: ${{ env.space }} + command: cf run-task gsa-fac -k 2G -m 2G --name pg_backup --command "./backup_database.sh ${{ env.space }}" + - name: Deploy fac to cloud.gov uses: cloud-gov/cg-cli-tools@main with: @@ -80,6 +100,16 @@ jobs: cf_vars_file: backend/manifests/vars/vars-${{ env.space }}.yml command: bin/ops/deploy.sh + - name: Unbind backup s3 bucket from prod app + if: startsWith(github.ref, 'refs/tags/v1.') + uses: cloud-gov/cg-cli-tools@main + with: + cf_username: ${{ secrets.CF_USERNAME }} + cf_password: ${{ secrets.CF_PASSWORD }} + cf_org: gsa-tts-oros-fac + cf_space: ${{ env.space }} + command: cf unbind-service gsa-fac backups + - name: Load historical data uses: cloud-gov/cg-cli-tools@main with: diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 98846f622d..80fadd1f56 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -47,6 +47,11 @@ jobs: python -m pip install --upgrade pip pip install -r dev-requirements.txt + - name: Install type stubs + working-directory: ./backend + run: | + pip install types-pytz + - name: Lint with flake8 working-directory: ./backend run: flake8 . --count --show-source --statistics diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index ab1f19d057..2e75c9f492 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -27,6 +27,8 @@ jobs: runs-on: ubuntu-latest environment: ${{ inputs.environment }} env: + CYPRESS_API_GOV_KEY: ${{ secrets.CYPRESS_API_GOV_KEY }} + CYPRESS_API_GOV_URL: ${{ secrets.CYPRESS_API_GOV_URL }} CYPRESS_BASE_URL: ${{ inputs.url }} CYPRESS_LOGIN_TEST_EMAIL: ${{ secrets.CYPRESS_LOGIN_TEST_EMAIL }} CYPRESS_LOGIN_TEST_PASSWORD: ${{ secrets.CYPRESS_LOGIN_TEST_PASSWORD }} diff --git a/.github/workflows/terraform-apply-env.yml b/.github/workflows/terraform-apply-env.yml index 0cf743c292..fd833bb103 100644 --- a/.github/workflows/terraform-apply-env.yml +++ b/.github/workflows/terraform-apply-env.yml @@ -51,3 +51,24 @@ jobs: bucket=${{ secrets.terraform_BUCKET }}, region=${{ secrets.terraform_REGION }}, key=${{ env.KEY }}, + + + - name: Unshare backups s3 bucket to staging space + if: ${{ inputs.environment == 'meta' }} + uses: cloud-gov/cg-cli-tools@main + with: + cf_username: ${{ secrets.CF_USERNAME }} + cf_password: ${{ secrets.CF_PASSWORD }} + cf_org: gsa-tts-oros-fac + cf_space: production + command: cf unshare-service backups -s staging -f + + - name: Share backups s3 bucket to staging space + if: ${{ inputs.environment == 'meta' }} + uses: cloud-gov/cg-cli-tools@main + with: + cf_username: ${{ secrets.CF_USERNAME }} + cf_password: ${{ secrets.CF_PASSWORD }} + cf_org: gsa-tts-oros-fac + cf_space: production + command: cf share-service backups -s staging diff --git a/.github/workflows/testing-from-build.yml b/.github/workflows/testing-from-build.yml index 6421b79efe..790fa2164f 100644 --- a/.github/workflows/testing-from-build.yml +++ b/.github/workflows/testing-from-build.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Create .env working-directory: ./backend @@ -73,7 +73,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Start services working-directory: ./backend run: | diff --git a/.github/workflows/testing-from-ghcr.yml b/.github/workflows/testing-from-ghcr.yml index 23cdd7b57d..b74202d525 100644 --- a/.github/workflows/testing-from-ghcr.yml +++ b/.github/workflows/testing-from-ghcr.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Create .env file working-directory: ./backend diff --git a/backend/Dockerfile b/backend/Dockerfile index 86094fe543..0cdfe9842a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,14 +19,17 @@ RUN \ apt-get update && \ apt-get install -yqq apt-transport-https wget gnupg2 +# Updated nodesource install via: https://github.com/nodesource/distributions#ubuntu-versions RUN \ apt-get update -yq && \ - apt install curl -y && \ - apt-get install -y gcc && \ - curl -fsSL https://deb.nodesource.com/setup_14.x | bash - && \ - apt-get install -y nodejs && \ - apt-get install -y npm && \ - npm i -g npm@^8 + apt install build-essential curl -y && \ + apt-get install -y gcc ca-certificates gnupg && \ + mkdir -p /etc/apt/keyrings && \ + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ + NODE_MAJOR=18 && \ + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \ + apt-get update && \ + apt-get install nodejs -y COPY requirements.txt /tmp/requirements.txt COPY dev-requirements.txt /tmp/dev-requirements.txt diff --git a/backend/Makefile b/backend/Makefile index 5bd3091be5..3f86a93ecb 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -34,6 +34,8 @@ lint: @black --check . @echo "bandit:" @bandit -c pyproject.toml -r . + @echo "Installing type stubs:" + @python -m pip install types-pytz @echo "mypy:" @mypy . @echo "djlint:" @@ -81,7 +83,7 @@ docker-nctest: docker-lint: docker compose build - docker compose run web bash -c 'flake8 && black --check . && bandit -c pyproject.toml -r . && mypy . && djlint .' + docker compose run web bash -c 'flake8 && black --check . && bandit -c pyproject.toml -r . && python -m pip install types-pytz && mypy . && djlint .' ghcr-first-run: docker compose -f docker-compose-web.yml run web python manage.py makemigrations @@ -103,7 +105,7 @@ ghcr-nctest: docker compose -f docker-compose-web.yml run web python manage.py test --parallel ${fac.test.scope} ghcr-lint: - docker compose -f docker-compose-web.yml run web bash -c 'flake8 && black --check . && bandit -c pyproject.toml -r . && mypy . && djlint .' + docker compose -f docker-compose-web.yml run web bash -c 'flake8 && black --check . && bandit -c pyproject.toml -r . && python -m pip install types-pytz && mypy . && djlint .' docker-clean: docker compose down diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 08ec198968..fc37e3b43f 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -38,6 +38,10 @@ "Auditor Contacts needs to be a list of full names and emails" ) +CERTIFIERS_HAVE_DIFFERENT_EMAILS = _( + "The certifying auditee and certifying auditor must have different email addresses." +) + class EligibilitySerializer(serializers.Serializer): user_provided_organization_type = serializers.CharField() @@ -166,6 +170,18 @@ class AccessAndSubmissionSerializer(serializers.Serializer): min_length=0, ) + def validate(self, data): + certifying_auditee_contact_email = data["certifying_auditee_contact_email"] + certifying_auditor_contact_email = data["certifying_auditor_contact_email"] + + if ( + certifying_auditee_contact_email.lower() + == certifying_auditor_contact_email.lower() + ): + raise ValidationError(CERTIFIERS_HAVE_DIFFERENT_EMAILS) + + return data + class SingleAuditChecklistSerializer(serializers.ModelSerializer): class Meta: diff --git a/backend/api/test_serializers.py b/backend/api/test_serializers.py index 57defe4d49..318e784933 100644 --- a/backend/api/test_serializers.py +++ b/backend/api/test_serializers.py @@ -13,6 +13,7 @@ AccessSerializer, AccessListSerializer, AccessAndSubmissionSerializer, + CERTIFIERS_HAVE_DIFFERENT_EMAILS, ) from audit.models import User, Access @@ -226,13 +227,19 @@ def test_empty_payload(self): def test_missing_certifying_auditee(self): missing_cert_auditee = { - "certifying_auditor_contact_email": "b@b.com", + "certifying_auditor_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@a.com", "auditor_contacts_email": ["c@c.com", "d@d.com"], "auditee_contacts_email": ["e@e.com", "f@f.com"], } - self.assertFalse( - AccessAndSubmissionSerializer(data=missing_cert_auditee).is_valid() + + serializer = AccessAndSubmissionSerializer(data=missing_cert_auditee) + self.assertFalse(serializer.is_valid()) + + cert_auditee_errors = serializer.errors.get( + "certifying_auditee_contact_email", None ) + self.assertIsNotNone(cert_auditee_errors) def test_missing_certifying_auditor(self): missing_cert_auditor = { @@ -241,95 +248,234 @@ def test_missing_certifying_auditor(self): "auditor_contacts_email": ["c@c.com", "d@d.com"], "auditee_contacts_email": ["e@e.com", "f@f.com"], } - self.assertFalse( - AccessAndSubmissionSerializer(data=missing_cert_auditor).is_valid() + + serializer = AccessAndSubmissionSerializer(data=missing_cert_auditor) + self.assertFalse(serializer.is_valid()) + + cert_auditor_errors = serializer.errors.get( + "certifying_auditor_contact_email", None ) + self.assertIsNotNone(cert_auditor_errors) def test_missing_auditor_contacts(self): missing_auditor_contacts = { "certifying_auditee_contact_email": "a@a.com", - "certifying_auditor_contact_email": "b@b.com", - "auditee_contacts_email": ["e@e.com", "f@f.com"], + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@b.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditee_contacts_email": ["e@e.com"], + "auditee_contacts_fullname": [ + "Fuller C. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=missing_auditor_contacts).is_valid() + + serializer = AccessAndSubmissionSerializer(data=missing_auditor_contacts) + self.assertFalse(serializer.is_valid()) + + auditor_contacts_email_errors = serializer.errors.get( + "auditor_contacts_email", None ) + self.assertIsNotNone(auditor_contacts_email_errors) def test_missing_auditee_contacts(self): missing_auditee_contacts = { "certifying_auditee_contact_email": "a@a.com", - "certifying_auditor_contact_email": "b@b.com", - "auditor_contacts_email": ["c@c.com", "d@d.com"], + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@b.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditor_contacts_email": ["e@e.com"], + "auditor_contacts_fullname": [ + "Fuller C. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=missing_auditee_contacts).is_valid() + + serializer = AccessAndSubmissionSerializer(data=missing_auditee_contacts) + self.assertFalse(serializer.is_valid()) + + auditee_contacts_email_errors = serializer.errors.get( + "auditee_contacts_email", None ) + self.assertIsNotNone(auditee_contacts_email_errors) def test_auditee_contacts_not_in_a_list(self): - auditee_contacts_not_list = { + auditee_contacts_email_not_list = { "certifying_auditee_contact_email": "a@a.com", - "certifying_auditor_contact_email": "b@b.com", - "auditor_contacts_email": "c@c.com", - "auditee_contacts_email": ["e@e.com", "f@f.com"], + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@b.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditor_contacts_email": ["e@e.com"], + "auditor_contacts_fullname": [ + "Fuller C. Namesmith", + ], + "auditee_contacts_email": "a@d.com", + "auditee_contacts_fullname": [ + "Fuller D. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=auditee_contacts_not_list).is_valid() + + serializer = AccessAndSubmissionSerializer(data=auditee_contacts_email_not_list) + self.assertFalse(serializer.is_valid()) + + auditee_contacts_email_errors = serializer.errors.get( + "auditee_contacts_email", None ) + self.assertIsNotNone(auditee_contacts_email_errors) def test_auditor_contacts_not_in_a_list(self): - auditor_contacts_not_list = { + auditor_contacts_email_not_list = { "certifying_auditee_contact_email": "a@a.com", - "certifying_auditor_contact_email": "b@b.com", - "auditor_contacts_email": ["c@c.com", "d@d.com"], - "auditee_contacts_email": "e@e.com", + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@b.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditor_contacts_email": "e@e.com", + "auditor_contacts_fullname": [ + "Fuller C. Namesmith", + ], + "auditee_contacts_email": ["a@d.com", "a@e.com"], + "auditee_contacts_fullname": [ + "Fuller D. Namesmith", + "Fuller DD. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=auditor_contacts_not_list).is_valid() + + serializer = AccessAndSubmissionSerializer(data=auditor_contacts_email_not_list) + self.assertFalse(serializer.is_valid()) + + auditor_contacts_email_errors = serializer.errors.get( + "auditor_contacts_email", None ) + self.assertIsNotNone(auditor_contacts_email_errors) def test_certifying_auditee_not_valid_email(self): cert_auditee_not_valid_email = { "certifying_auditee_contact_email": "this is not an email", - "certifying_auditor_contact_email": "b@b.com", - "auditor_contacts_email": ["c@c.com", "d@d.com"], - "auditee_contacts_email": ["e@e.com", "f@f.com"], + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@b.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditor_contacts_email": ["e@e.com", "f@f.com"], + "auditor_contacts_fullname": [ + "Fuller C. Namesmith", + "Fuller CC. Namesmith", + ], + "auditee_contacts_email": ["a@d.com", "a@e.com"], + "auditee_contacts_fullname": [ + "Fuller D. Namesmith", + "Fuller DD. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=cert_auditee_not_valid_email).is_valid() + + serializer = AccessAndSubmissionSerializer(data=cert_auditee_not_valid_email) + self.assertFalse(serializer.is_valid()) + + cert_auditee_email_errors = serializer.errors.get( + "certifying_auditee_contact_email", None ) + self.assertIsNotNone(cert_auditee_email_errors) def test_certifying_auditor_not_valid_email(self): cert_auditor_not_valid_email = { "certifying_auditee_contact_email": "a@a.com", + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", "certifying_auditor_contact_email": "this is not an email", - "auditor_contacts_email": ["c@c.com", "d@d.com"], - "auditee_contacts_email": ["e@e.com", "f@f.com"], + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditor_contacts_email": ["e@e.com", "f@f.com"], + "auditor_contacts_fullname": [ + "Fuller C. Namesmith", + "Fuller CC. Namesmith", + ], + "auditee_contacts_email": ["a@d.com", "a@e.com"], + "auditee_contacts_fullname": [ + "Fuller D. Namesmith", + "Fuller DD. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=cert_auditor_not_valid_email).is_valid() + + serializer = AccessAndSubmissionSerializer(data=cert_auditor_not_valid_email) + self.assertFalse(serializer.is_valid()) + + cert_auditor_email_errors = serializer.errors.get( + "certifying_auditor_contact_email", None ) + self.assertIsNotNone(cert_auditor_email_errors) def test_auditor_list_not_all_valid_emails(self): auditor_not_all_valid_emails = { "certifying_auditee_contact_email": "a@a.com", - "certifying_auditor_contact_email": "b@b.com", - "auditor_contacts_email": ["c@c.com", "this is not an email", "d@d.com"], - "auditee_contacts_email": ["e@e.com", "f@f.com"], + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@b.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditor_contacts_email": ["e@e.com", "this is not an email", "f@f.com"], + "auditor_contacts_fullname": [ + "Fuller C. Namesmith", + "Fuller CC. Namesmith", + "Fuller CCC. Namesmith", + ], + "auditee_contacts_email": ["a@d.com", "a@e.com"], + "auditee_contacts_fullname": [ + "Fuller D. Namesmith", + "Fuller DD. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=auditor_not_all_valid_emails).is_valid() + + serializer = AccessAndSubmissionSerializer(data=auditor_not_all_valid_emails) + self.assertFalse(serializer.is_valid()) + + auditor_contacts_email_errors = serializer.errors.get( + "auditor_contacts_email", None ) + self.assertIsNotNone(auditor_contacts_email_errors) def test_auditee_list_not_all_valid_emails(self): auditee_not_all_valid_emails = { "certifying_auditee_contact_email": "a@a.com", - "certifying_auditor_contact_email": "b@b.com", - "auditor_contacts_email": ["c@c.com", "d@d.com"], + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@b.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", "auditee_contacts_email": ["e@e.com", "this is not an email", "f@f.com"], + "auditee_contacts_fullname": [ + "Fuller C. Namesmith", + "Fuller CC. Namesmith", + "Fuller CCC. Namesmith", + ], + "auditor_contacts_email": ["a@d.com", "a@e.com"], + "auditor_contacts_fullname": [ + "Fuller D. Namesmith", + "Fuller DD. Namesmith", + ], } - self.assertFalse( - AccessAndSubmissionSerializer(data=auditee_not_all_valid_emails).is_valid() + + serializer = AccessAndSubmissionSerializer(data=auditee_not_all_valid_emails) + self.assertFalse(serializer.is_valid()) + + auditee_contacts_email_errors = serializer.errors.get( + "auditee_contacts_email", None ) + self.assertIsNotNone(auditee_contacts_email_errors) + + def test_certifiers_same_email(self): + certifiers_same_email = { + "certifying_auditee_contact_email": "a@a.com", + "certifying_auditee_contact_fullname": "Fuller A. Namesmith", + "certifying_auditor_contact_email": "a@a.com", + "certifying_auditor_contact_fullname": "Fuller B. Namesmith", + "auditee_contacts_email": ["a@c.com"], + "auditee_contacts_fullname": [ + "Fuller C. Namesmith", + "Fuller CC. Namesmith", + ], + "auditor_contacts_email": ["a@d.com"], + "auditor_contacts_fullname": [ + "Fuller D. Namesmith", + "Fuller DD. Namesmith", + ], + } + + serializer = AccessAndSubmissionSerializer(data=certifiers_same_email) + self.assertFalse(serializer.is_valid()) + + non_field_errors = serializer.errors.get("non_field_errors", None) + self.assertIsNotNone(non_field_errors) + self.assertIn(CERTIFIERS_HAVE_DIFFERENT_EMAILS, non_field_errors) class AccessListSerializerTests(TestCase): diff --git a/backend/api/test_views.py b/backend/api/test_views.py index 387ee5463a..231c8a0f79 100644 --- a/backend/api/test_views.py +++ b/backend/api/test_views.py @@ -64,11 +64,11 @@ "auditee_fiscal_period_start": "2021-10-01", "auditee_fiscal_period_end": "2022-10-01", "audit_period_covered": "annual", - "ein": None, - "ein_not_an_ssn_attestation": None, - "multiple_eins_covered": None, + "ein": "123456789", + "ein_not_an_ssn_attestation": True, + "multiple_eins_covered": False, "auditee_uei": "ZQGGHJH74DW7", - "multiple_ueis_covered": None, + "multiple_ueis_covered": False, "auditee_name": "Auditee McAudited", "auditee_address_line_1": "200 feet into left field", "auditee_city": "New York", @@ -82,8 +82,8 @@ "met_spending_threshold": True, "is_usa_based": True, "auditor_firm_name": "Dollar Audit Store", - "auditor_ein": None, - "auditor_ein_not_an_ssn_attestation": None, + "auditor_ein": "987654321", + "auditor_ein_not_an_ssn_attestation": True, "auditor_country": "USA", "auditor_address_line_1": "100 Percent Respectable St.", "auditor_city": "Podunk", diff --git a/backend/apt.yml b/backend/apt.yml index 27c7d72c62..7f4a7d9c1b 100644 --- a/backend/apt.yml +++ b/backend/apt.yml @@ -1,3 +1,14 @@ --- +# Because cf is using jammy jellyfish, and this https://packages.ubuntu.com/jammy/amd64/database/ +# does not actually have postgresql-client-15 natively (only goes to 14), +# we are having to source the version from apt.postgresql.org. This however, +# eliminates the need to run v14 for load_data.sh, and can be run on v15. +# With v15, we are also able to run python manage.py dbbackup to backup the database +# to a defined s3 bucket. Running v15 also matches the client and server database versions. +cleancache: true +keys: +- https://www.postgresql.org/media/keys/ACCC4CF8.asc +repos: +- deb https://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main packages: -- postgresql-client +- postgresql-client-15 diff --git a/backend/audit/cog_agency.py b/backend/audit/cog_agency.py deleted file mode 100644 index 501ca03db7..0000000000 --- a/backend/audit/cog_agency.py +++ /dev/null @@ -1,169 +0,0 @@ -# TODO - This file should be removed eventually after more testing is done. - Please check with Sudha K. -############################################################# -# cog_over_assignment -# Input - federal_awards_data - federal awards json object -# Returns - cog_agency_prefix, over_agency_prefix - Agency prefix integer -# -# Algorithm: -# Use all "federal_awards" to do calculations required to -# determine cognizant / oversight agency assignment - -# Calculate total amount_expended (tot_amount_expended) -# Calculate total amount_expended by agency -# tot_amount_agency = {agency, tot_amt} -# Calculate total direct award amount_expended by agency -# tot_da_amount_agency = {agency, tot_da_amt} -# Calculate total direct award amount_expended (tot_da_amount_expended) -# -# If tot_amount_expended > $ 50,000,000, find cog agency -# Pull 2019 data from DB -# If 2019 data exists, use this for all calculations -# data_to_use = 2019 data -# Do all the above calculations with data_to_use -# else use current data -# data_to_use = current data -# if tot_da_amount_expended >= 25% tot_amount_expended -# cog_agency_prefix = agency with max tot_da_amt in tot_da_amount_agency -# else -# cog_agency_prefix = agency with max tot_amt in tot_amount_agency -# else, find over agency -# if tot_da_amount_expended >= 25% tot_amount_expended -# over_agency_prefix = agency with max tot_da_amt in tot_da_amount_agency -# else -# over_agency_prefix = agency with max tot_amt in tot_amount_agency -############################################################# - -from collections import defaultdict - - -def calc_amount_expended_limit(): - MILLION = 1_000_000 - amount_expended_limit = 50 * MILLION - return amount_expended_limit - - -def load_2019_federal_award_data(auditee_ein): - # Load 2019 ["FederalAwards"] where - # 2019 Cfda.auditee_ein = auditee_ein - # return Cfda19.objects.filter(ein=auditee_ein) - pass - - -def auditee_2019_submission_exists(auditee_ein, general_2019_data): - # Check if auditee_ein exists in 2019 data - # return Gen19.objects.filter(ein=auditee_ein) - return auditee_ein == general_2019_data["General"]["ein"] - - -def calc_total_amounts_agency(fed_award_data, year=2023): - tot_amount_agency = defaultdict(lambda: 0) - tot_da_amount_agency = defaultdict(lambda: 0) - tot_da_amount_expended = 0 - for award in fed_award_data["FederalAwards"]["federal_awards"]: - if year == 2019: - agency_prefix = int(str(award["cfda"])[:2]) - tot_amount_agency[agency_prefix] += award["amount"] - if award["direct"] == "Y": - tot_da_amount_expended += award["amount"] - tot_da_amount_agency[agency_prefix] += award["amount"] - else: # 2023 - agency_prefix = award["program"]["federal_agency_prefix"] - tot_amount_agency[agency_prefix] += award["program"]["amount_expended"] - if award["direct_or_indirect_award"]["is_direct"] == "Y": - tot_da_amount_expended += award["program"]["amount_expended"] - tot_da_amount_agency[agency_prefix] += award["program"][ - "amount_expended" - ] - tot_amount_agency = list( - sorted(tot_amount_agency.items(), reverse=True, key=lambda item: item[1]) - ) - tot_da_amount_agency = list( - sorted(tot_da_amount_agency.items(), reverse=True, key=lambda item: item[1]) - ) - # print("tot_amount_agency = ", tot_amount_agency) - # print("tot_da_amount_agency = ", tot_da_amount_agency) - # print("tot_da_amount_expended = ", tot_da_amount_expended) - - return tot_da_amount_expended, tot_da_amount_agency[0], tot_amount_agency[0] - - -def select_agency( - tot_da_amount_expended, tot_da_amount_agency, tot_amount_agency, tot_amount_expended -): - if tot_da_amount_expended >= 0.25 * tot_amount_expended: - selected_agency_prefix, val = tot_da_amount_agency - else: - selected_agency_prefix, val = tot_amount_agency - # print("selected agency prefix = ", selected_agency_prefix) - return selected_agency_prefix - - -def calc_tot_amt_expended(federal_awards_data, auditee_ein, general_2019_data): - if auditee_2019_submission_exists(auditee_ein, general_2019_data): - # Calculate from 2019 data - # gen = Gen19.objects.filter(EIN=auditee_ein) - return general_2019_data["General"]["totfedexpend"] - else: - return federal_awards_data["FederalAwards"]["total_amount_expended"] - - -def cog_over_assignment( - federal_awards_data, auditee_ein, federal_awards_data_2019_data, general_2019_data -): - cog_agency_prefix = 0 - over_agency_prefix = 0 - - # #print(CensusCfda19.objects.filter(index__lt = 10)) - # #print("Cfda first row = ", Cfda.objects.first()) - # print("Cfda first row = ", Cfda19.objects.filter(index__lt=10)) - # #cfda = Cfda.objects.filter(EIN = 731084819) - # print("Gen first row = ", Gen19.objects.filter(index__lt=10)) - - if ( - calc_tot_amt_expended(federal_awards_data, auditee_ein, general_2019_data) - > calc_amount_expended_limit() - ): - # Cognizant agency - if auditee_2019_submission_exists(auditee_ein, general_2019_data): - # federal_awards_data_2019 = load_2019_federal_award_data(auditee_ein) - ( - tot_da_amount_expended, - tot_da_amount_agency, - tot_amount_agency, - ) = calc_total_amounts_agency(federal_awards_data_2019_data, 2019) - cog_agency_prefix = select_agency( - tot_da_amount_expended, - tot_da_amount_agency, - tot_amount_agency, - general_2019_data["General"]["totfedexpend"], - ) - else: - print("No 2019 submission") - ( - tot_da_amount_expended, - tot_da_amount_agency, - tot_amount_agency, - ) = calc_total_amounts_agency(federal_awards_data) - cog_agency_prefix = select_agency( - tot_da_amount_expended, - tot_da_amount_agency, - tot_amount_agency, - federal_awards_data["FederalAwards"]["total_amount_expended"], - ) - else: - # Oversight agency - ( - tot_da_amount_expended, - tot_da_amount_agency, - tot_amount_agency, - ) = calc_total_amounts_agency(federal_awards_data) - over_agency_prefix = select_agency( - tot_da_amount_expended, - tot_da_amount_agency, - tot_amount_agency, - federal_awards_data["FederalAwards"]["total_amount_expended"], - ) - - print("cog_agency_prefix = ", cog_agency_prefix) - print("over_agency_prefix = ", over_agency_prefix) - return cog_agency_prefix, over_agency_prefix diff --git a/backend/audit/cog_over.py b/backend/audit/cog_over.py deleted file mode 100644 index 97d2b8777c..0000000000 --- a/backend/audit/cog_over.py +++ /dev/null @@ -1,145 +0,0 @@ -from collections import defaultdict -import os -from .models import SingleAuditChecklist, CognizantBaseline -import sqlalchemy - - -COG_LIMIT = 50_000_000 -DA_THRESHOLD_FACTOR = 0.25 -REF_YEAR = "2019" - - -def cog_over(sac: SingleAuditChecklist): - awards = sac.federal_awards["FederalAwards"] - total_amount_expended = awards.get("total_amount_expended") - cognizant_agency = oversight_agency = None - (total_da_amount_expended, max_total_agency, max_da_agency) = calc_award_amounts( - awards - ) - - agency = determine_agency( - total_amount_expended, - total_da_amount_expended, - max_total_agency, - max_da_agency, - ) - - if total_amount_expended <= COG_LIMIT: - oversight_agency = agency - return (cognizant_agency, oversight_agency) - cognizant_agency = determine_2019_agency(sac.ein) - if cognizant_agency: - return (cognizant_agency, oversight_agency) - cognizant_agency = agency - return (cognizant_agency, oversight_agency) - - -def calc_award_amounts(awards): - total_amount_agency = defaultdict(lambda: 0) - total_da_amount_agency = defaultdict(lambda: 0) - total_da_amount_expended = 0 - for award in awards["federal_awards"]: - agency = award["program"]["federal_agency_prefix"] - total_amount_agency[agency] += award["program"]["amount_expended"] - if award["direct_or_indirect_award"]["is_direct"] == "Y": - total_da_amount_expended += award["program"]["amount_expended"] - total_da_amount_agency[agency] += award["program"]["amount_expended"] - max_total_agency, max_da_agency = _extract_max_agency( - total_amount_agency, total_da_amount_agency - ) - return total_da_amount_expended, max_total_agency, max_da_agency - - -def determine_agency( - total_amount_expended, total_da_amount_expended, max_total_agency, max_da_agency -): - if total_da_amount_expended >= DA_THRESHOLD_FACTOR * total_amount_expended: - return max_da_agency[0] - return max_total_agency[0] - - -def determine_2019_agency(ein): - try: - cognizant_agency = CognizantBaseline.objects.get( - audit_year=2019, - ein=ein, - ).cognizant_agency - return cognizant_agency - except CognizantBaseline.DoesNotExist: - return None - - -def set_2019_baseline(): - engine = sqlalchemy.create_engine( - os.getenv("DATABASE_URL").replace("postgres", "postgresql", 1) - ) - AUDIT_QUERY = """ - SELECT gen."DBKEY", gen."EIN", cast(gen."TOTFEDEXPEND" as BIGINT), - cfda."CFDA", cast(cfda."AMOUNT" as BIGINT), cfda."DIRECT", cast(cfda."PROGRAMTOTAL" as BIGINT) - FROM census_gen19 gen, census_cfda19 cfda - WHERE gen."AUDITYEAR" = :ref_year - AND cast(gen."TOTFEDEXPEND" as BIGINT) >= :threshold - AND gen."DBKEY" = cfda."DBKEY" - ORDER BY gen."DBKEY" - """ - with engine.connect() as conn: - result = conn.execute( - sqlalchemy.text(AUDIT_QUERY), {"ref_year": REF_YEAR, "threshold": COG_LIMIT} - ) - gens = [] - cfdas = [] - - for row in result: - (DBKEY, EIN, TOTFEDEXPEND, CFDA, AMOUNT, DIRECT, PROGRAMTOTAL) = row - if (DBKEY, EIN, TOTFEDEXPEND) not in gens: - gens.append((DBKEY, EIN, TOTFEDEXPEND)) - cfdas.append((DBKEY, CFDA, AMOUNT, DIRECT, PROGRAMTOTAL)) - - CognizantBaseline.objects.all().delete() - for gen in gens: - dbkey = gen[0] - ein = gen[1] - total_amount_expended = gen[2] - (total_da_amount_expended, max_total_agency, max_da_agency) = calc_cfda_amounts( - cfdas=[cfda for cfda in cfdas if cfda[0] == dbkey] - ) - cognizant_agency = determine_agency( - total_amount_expended, - total_da_amount_expended, - max_total_agency, - max_da_agency, - ) - if cognizant_agency: - CognizantBaseline( - dbkey=dbkey, audit_year=2019, ein=ein, cognizant_agency=cognizant_agency - ).save() - return CognizantBaseline.objects.count() - - -def calc_cfda_amounts(cfdas): - total_amount_agency = defaultdict(lambda: 0) - total_da_amount_agency = defaultdict(lambda: 0) - total_da_amount_expended = 0 - for cfda in cfdas: - agency = cfda[1][:2] - amount = cfda[2] or 0 - direct = cfda[3] - # TODO use amount rather than programamount? - # programtotal = cfda[4] or 0 - total_amount_agency[agency] += amount - if direct == "Y": - total_da_amount_expended += amount - total_da_amount_agency[agency] += amount - max_total_agency, max_da_agency = _extract_max_agency( - total_amount_agency, total_da_amount_agency - ) - return total_da_amount_expended, max_total_agency, max_da_agency - - -def _extract_max_agency(total_amount_agency, total_da_amount_agency): - max_total_agency = max(total_amount_agency.items(), key=lambda x: x[1]) - if len(total_da_amount_agency) > 0: - max_da_agency = max(total_da_amount_agency.items(), key=lambda x: x[1]) - else: - max_da_agency = total_da_amount_agency - return max_total_agency, max_da_agency diff --git a/backend/audit/cross_validation/__init__.py b/backend/audit/cross_validation/__init__.py index f4dca39b71..ac986df3cb 100644 --- a/backend/audit/cross_validation/__init__.py +++ b/backend/audit/cross_validation/__init__.py @@ -55,18 +55,20 @@ ] """ +from .additional_ueis import additional_ueis +from .auditee_ueis_match import auditee_ueis_match +from .check_certifying_contacts import check_certifying_contacts from .check_award_reference_uniqueness import check_award_reference_uniqueness from .check_award_ref_declaration import check_award_ref_declaration from .check_findings_count_consistency import check_findings_count_consistency from .check_award_ref_existence import check_award_ref_existence from .check_ref_number_in_cap import check_ref_number_in_cap from .check_ref_number_in_findings_text import check_ref_number_in_findings_text -from .additional_ueis import additional_ueis -from .auditee_ueis_match import auditee_ueis_match from .check_finding_reference_uniqueness import check_finding_reference_uniqueness from .sac_validation_shape import sac_validation_shape # noqa: F401 from .submission_progress_check import submission_progress_check from .tribal_data_sharing_consent import tribal_data_sharing_consent +from .validate_general_information import validate_general_information functions = [ auditee_ueis_match, @@ -74,10 +76,12 @@ check_award_reference_uniqueness, check_award_ref_existence, check_award_ref_declaration, + check_certifying_contacts, check_finding_reference_uniqueness, check_findings_count_consistency, check_ref_number_in_cap, check_ref_number_in_findings_text, submission_progress_check, tribal_data_sharing_consent, + validate_general_information, ] diff --git a/backend/audit/cross_validation/check_certifying_contacts.py b/backend/audit/cross_validation/check_certifying_contacts.py new file mode 100644 index 0000000000..370eaa66f4 --- /dev/null +++ b/backend/audit/cross_validation/check_certifying_contacts.py @@ -0,0 +1,20 @@ +from .errors import ( + err_certifying_contacts_should_not_match, +) + + +def check_certifying_contacts(sac_dict, *_args, **_kwargs): + """ + Check that the certifying auditor and auditee do not have the same email address. + """ + + sf_sac_meta = sac_dict.get("sf_sac_meta", {}) + certifying_auditee_contact = sf_sac_meta.get("certifying_auditee_contact") + certifying_auditor_contact = sf_sac_meta.get("certifying_auditor_contact") + errors = [] + + if certifying_auditee_contact and certifying_auditor_contact: + if certifying_auditee_contact == certifying_auditor_contact: + errors.append({"error": err_certifying_contacts_should_not_match()}) + + return errors diff --git a/backend/audit/cross_validation/errors.py b/backend/audit/cross_validation/errors.py index c75ffab8fb..ba631fe44f 100644 --- a/backend/audit/cross_validation/errors.py +++ b/backend/audit/cross_validation/errors.py @@ -37,6 +37,10 @@ def err_missing_tribal_data_sharing_consent(): ) +def err_certifying_contacts_should_not_match(): + return "The certifying auditor and auditee should not have the same email address." + + def err_duplicate_finding_reference(award_ref, ref_number): return f"Award {award_ref} repeats reference {ref_number}. The reference {ref_number} should only appear once for award {award_ref}." diff --git a/backend/audit/cross_validation/sac_validation_shape.py b/backend/audit/cross_validation/sac_validation_shape.py index 0edbc19aff..eeb188ad56 100644 --- a/backend/audit/cross_validation/sac_validation_shape.py +++ b/backend/audit/cross_validation/sac_validation_shape.py @@ -65,6 +65,7 @@ def sac_validation_shape(sac): } """ + from audit.models import Access shape = { "sf_sac_sections": {k: get_shaped_section(sac, k) for k in SECTION_NAMES}, @@ -78,4 +79,23 @@ def sac_validation_shape(sac): "transition_date": sac.transition_date, }, } + + certifying_auditee = Access.objects.filter( + sac=sac, role="certifying_auditee_contact" + ).first() + + certifying_auditor = Access.objects.filter( + sac=sac, role="certifying_auditor_contact" + ).first() + + if certifying_auditee: + shape["sf_sac_meta"].update( + {"certifying_auditee_contact": certifying_auditee.email} + ) + + if certifying_auditor: + shape["sf_sac_meta"].update( + {"certifying_auditor_contact": certifying_auditor.email} + ) + return shape diff --git a/backend/audit/cross_validation/submission_progress_check.py b/backend/audit/cross_validation/submission_progress_check.py index 07165d6f65..d108c93a68 100644 --- a/backend/audit/cross_validation/submission_progress_check.py +++ b/backend/audit/cross_validation/submission_progress_check.py @@ -1,4 +1,6 @@ from audit.cross_validation.naming import NC, find_section_by_name +from audit.validators import validate_general_information_complete_json +from django.core.exceptions import ValidationError def submission_progress_check(sac, sar=None, crossval=True): @@ -90,6 +92,7 @@ def get_num_findings(award): if sections[NC.FEDERAL_AWARDS]: awards = sections.get(NC.FEDERAL_AWARDS, {}).get(NC.FEDERAL_AWARDS, []) general_info = sections.get(NC.GENERAL_INFORMATION, {}) or {} + num_findings = sum(get_num_findings(award) for award in awards) conditions = { NC.GENERAL_INFORMATION: True, @@ -105,6 +108,10 @@ def get_num_findings(award): NC.SINGLE_AUDIT_REPORT: True, } + # The General Information has its own condition, as it can be partially completed. + if key == "general_information": + return general_information_progress_check(progress, general_info) + # If it's not required, it's inactive: if not conditions[key]: return {key: progress | {"display": "inactive"}} @@ -114,3 +121,25 @@ def get_num_findings(award): return {key: progress | {"display": "complete", "completed": True}} return {key: progress | {"display": "incomplete", "completed": False}} + + +def general_information_progress_check(progress, general_info): + """ + Given a base "progress" dictionary and the general_info object from a submission, + run validations to determine its completeness. Then, return a dictionary with + "general_information" as the key and the progress as the value. + """ + try: + is_general_info_complete = bool( + validate_general_information_complete_json(general_info) + ) + except ValidationError: + is_general_info_complete = False + + if is_general_info_complete: + return { + "general_information": progress | {"display": "complete", "completed": True} + } + return { + "general_information": progress | {"display": "incomplete", "completed": False} + } diff --git a/backend/audit/cross_validation/test_check_certifying_contacts.py b/backend/audit/cross_validation/test_check_certifying_contacts.py new file mode 100644 index 0000000000..7dbe045d31 --- /dev/null +++ b/backend/audit/cross_validation/test_check_certifying_contacts.py @@ -0,0 +1,43 @@ +from django.test import TestCase +from audit.models import Access, SingleAuditChecklist +from .sac_validation_shape import sac_validation_shape +from .check_certifying_contacts import check_certifying_contacts +from .errors import err_certifying_contacts_should_not_match + +from model_bakery import baker + + +class CheckCertifyingContactsTest(TestCase): + def setUp(self): + """Set up the common variables for the test cases.""" + self.email1 = "auditee@example.com" + self.email2 = "auditor@example.com" + self.email3 = "same@example.com" + + def _make_sac(self, auditee_email, auditor_email) -> SingleAuditChecklist: + sac = baker.make(SingleAuditChecklist) + baker.make( + Access, sac=sac, role="certifying_auditee_contact", email=auditee_email + ) + baker.make( + Access, sac=sac, role="certifying_auditor_contact", email=auditor_email + ) + + return sac + + def test_diff_certifying_contacts(self): + """When auditor and auditee emails are different, no errors should be raised.""" + sac = self._make_sac(self.email1, self.email2) + errors = check_certifying_contacts(sac_validation_shape(sac)) + self.assertEqual(errors, []) + + def test_same_certifying_contacts_raise_errors(self): + """When auditor and auditee emails are the same, the appropriate error should be raised.""" + sac = self._make_sac(self.email3, self.email3) + errors = check_certifying_contacts(sac_validation_shape(sac)) + + self.assertEqual(len(errors), 1) + + expected_error = err_certifying_contacts_should_not_match() + + self.assertIn({"error": expected_error}, errors) diff --git a/backend/audit/cross_validation/validate_general_information.py b/backend/audit/cross_validation/validate_general_information.py new file mode 100644 index 0000000000..e3ce1cdb9f --- /dev/null +++ b/backend/audit/cross_validation/validate_general_information.py @@ -0,0 +1,30 @@ +import json +from django.conf import settings +from jsonschema import FormatChecker, validate +from jsonschema.exceptions import ValidationError as JSONSchemaValidationError + +from audit.cross_validation.naming import NC + + +def validate_general_information(sac_dict, *_args, **_kwargs): + """ + Runs the general information data through JSON Schema validation. + + This largely repeats logic in + audit.validators.validate_general_information_json, + but that function doesn't pass along the full error context and we need that here. + + The errors this presents to the user are arcane, but should make clear that they + must return to the General Information page, and once there they will get + friendlier errors when they try to proceed. + """ + all_sections = sac_dict["sf_sac_sections"] + general_information = all_sections[NC.GENERAL_INFORMATION] + schema_path = settings.SECTION_SCHEMA_DIR / "GeneralInformationComplete.schema.json" + schema = json.loads(schema_path.read_text(encoding="utf-8")) + + try: + validate(general_information, schema, format_checker=FormatChecker()) + except JSONSchemaValidationError as err: + return [{"error": f"General Information: {str(err)}"}] + return [] diff --git a/backend/audit/excel.py b/backend/audit/excel.py index 211ac26550..fc99f3edc5 100644 --- a/backend/audit/excel.py +++ b/backend/audit/excel.py @@ -377,6 +377,11 @@ def _set_pass_through_entity_id(obj, target, value): "note_content", _set_by_path, ), + "contains_chart_or_table": ( + "NotesToSefa.notes_to_sefa_entries", + "contains_chart_or_table", + _set_by_path, + ), "seq_number": ( "NotesToSefa.notes_to_sefa_entries", "seq_number", diff --git a/backend/audit/fixtures/json/federal-awards--test0001test--simple-pass.json b/backend/audit/fixtures/json/federal-awards--test0001test--simple-pass.json index 0969498bea..27e92099d5 100644 --- a/backend/audit/fixtures/json/federal-awards--test0001test--simple-pass.json +++ b/backend/audit/fixtures/json/federal-awards--test0001test--simple-pass.json @@ -12,8 +12,8 @@ "is_major": "N", "audit_report_type": "", "number_of_audit_findings": 0, - "amount_expended": 500, - "federal_program_total": 500 + "amount_expended": 5000000, + "federal_program_total": 5000000 }, "loan_or_loan_guarantee": { "is_guaranteed": "N", diff --git a/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json b/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json index 8be3fc1308..25edfbf69b 100644 --- a/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json +++ b/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json @@ -15,7 +15,7 @@ "auditor_email": "fictional.fac.tester+test@gsa.gov", "auditor_phone": "5558675309", "auditor_state": "CA", - "auditor_country": "United States", + "auditor_country": "USA", "auditor_firm_name": "Fellowship", "audit_period_covered": "annual", "auditee_contact_name": "Frodo Baggins", @@ -27,9 +27,12 @@ "auditee_address_line_1": "HAL", "auditor_address_line_1": "Main Hall", "met_spending_threshold": true, + "secondary_auditors_exist": false, + "audit_period_other_months": "null", "auditee_fiscal_period_end": "2023-01-01", "ein_not_an_ssn_attestation": true, "auditee_fiscal_period_start": "2022-01-01", + "auditor_international_address": "", "user_provided_organization_type": "non-profit", - "auditor_ein_not_an_ssn_attestation": true + "auditor_ein_not_an_ssn_attestation": true } diff --git a/backend/audit/intake_to_dissemination.py b/backend/audit/intake_to_dissemination.py index 26ce4f6a6d..64911c636d 100644 --- a/backend/audit/intake_to_dissemination.py +++ b/backend/audit/intake_to_dissemination.py @@ -1,6 +1,6 @@ import logging from datetime import datetime - +import pytz from django.db import IntegrityError from dissemination.models import ( @@ -15,14 +15,18 @@ AdditionalUei, AdditionalEin, ) -from audit.models import SingleAuditChecklist from audit.utils import Util logger = logging.getLogger(__name__) +def omit(remove, d) -> dict: + """omit(["a"], {"a":1, "b": 2}) => {"b": 2}""" + return {k: d[k] for k in d if k not in remove} + + class IntakeToDissemination(object): - def __init__(self, sac: SingleAuditChecklist) -> None: + def __init__(self, sac) -> None: self.single_audit_checklist = sac self.report_id = sac.report_id audit_date = sac.general_information.get( @@ -122,12 +126,6 @@ def load_findings(self): self.loaded_objects["Findings"] = findings_objects return findings_objects - def conditional_lookup(self, dict, key, default): - if key in dict: - return dict[key] - else: - return default - def load_federal_award(self): federal_awards = self.single_audit_checklist.federal_awards federal_awards_objects = [] @@ -190,7 +188,7 @@ def load_captext(self): return cap_text_objects def load_notes(self): - sefa = self.single_audit_checklist.notes_to_sefa + sefa = self.single_audit_checklist.notes_to_sefa or {} n2sefa = sefa.get("NotesToSefa", {}) sefa_objects = [] if n2sefa: @@ -215,6 +213,7 @@ def load_notes(self): rate_explained=rate_explained, content=entry["note_content"], note_title=entry["note_title"], + contains_chart_or_table=entry["contains_chart_or_table"], ) sefa_objects.append(note) self.loaded_objects["Notes"] = sefa_objects @@ -250,117 +249,127 @@ def _get_dates_from_sac(self): return_dict[status] = None return return_dict + def _convert_utc_to_american_samoa_zone(self, date): + us_samoa_zone = pytz.timezone("US/Samoa") + # Ensure the datetime object is time zone aware + if date.tzinfo is None or date.tzinfo.utcoffset(date) is None: + date = pytz.utc.localize(date) + # Convert to American Samoa timezone (UTC-11) + american_samoa_time = date.astimezone(us_samoa_zone) + # Extract the date and format it as YYYY-MM-DD + formatted_date = american_samoa_time.strftime("%Y-%m-%d") + + return formatted_date + def load_general(self): general_information = self.single_audit_checklist.general_information - auditee_certification = self.single_audit_checklist.auditee_certification - audit_information = self.single_audit_checklist.audit_information - # auditor_certification = self.single_audit_checklist.auditor_certification + audit_information = self.single_audit_checklist.audit_information or {} + auditee_certification = self.single_audit_checklist.auditee_certification or {} + # auditor_certification = self.single_audit_checklist.auditor_certification or {} + cognizant_agency = self.single_audit_checklist.cognizant_agency or "" + oversight_agency = self.single_audit_checklist.oversight_agency or "" dates_by_status = self._get_dates_from_sac() + status = self.single_audit_checklist.STATUS + ready_for_certification_date = dates_by_status[status.READY_FOR_CERTIFICATION] + auditor_certified_date = dates_by_status[status.AUDITOR_CERTIFIED] + auditee_certified_date = dates_by_status[status.AUDITEE_CERTIFIED] + submitted_date = self._convert_utc_to_american_samoa_zone( + dates_by_status[status.SUBMITTED] + ) + auditee_certify_name = auditee_certification.get("auditee_signature", {}).get( + "auditee_name", "" + ) + auditee_certify_title = auditee_certification.get("auditee_signature", {}).get( + "auditee_title", "" + ) - general = General( - report_id=self.report_id, - auditee_certify_name=auditee_certification["auditee_signature"][ - "auditee_name" - ], - auditee_certify_title=auditee_certification["auditee_signature"][ - "auditee_title" - ], - auditee_contact_name=general_information["auditee_contact_name"], - auditee_email=general_information["auditee_email"], - auditee_name=general_information["auditee_name"], - auditee_phone=general_information["auditee_phone"], - auditee_contact_title=general_information["auditee_contact_title"], - auditee_address_line_1=general_information["auditee_address_line_1"], - auditee_city=general_information["auditee_city"], - auditee_state=general_information["auditee_state"], - auditee_ein=general_information["ein"], - auditee_uei=general_information["auditee_uei"], - is_additional_ueis=Util.bool_to_yes_no( - general_information["multiple_ueis_covered"] - ), - auditee_zip=general_information["auditee_zip"], - auditor_phone=general_information["auditor_phone"], - auditor_state=general_information["auditor_state"], - auditor_city=general_information["auditor_city"], - auditor_contact_title=general_information["auditor_contact_title"], - auditor_address_line_1=general_information["auditor_address_line_1"], - auditor_zip=general_information["auditor_zip"], - auditor_country=general_information["auditor_country"], - auditor_contact_name=general_information["auditor_contact_name"], - auditor_email=general_information["auditor_email"], - auditor_firm_name=general_information["auditor_firm_name"], - auditor_foreign_address=general_information.get( + total_amount_expended = self.single_audit_checklist.federal_awards[ + "FederalAwards" + ]["total_amount_expended"] + + # Some keys in sac.general_information are different or absent in General + gen_key_exceptions = ( + # Handled below: + "audit_period_other_months", + "auditee_fiscal_period_end", + "auditee_fiscal_period_start", + "auditor_international_address", + "ein", + "multiple_ueis_covered", + "user_provided_organization_type", + # Omitted: + "auditor_ein_not_an_ssn_attestation", + "ein_not_an_ssn_attestation", + "is_usa_based", + "met_spending_threshold", + "multiple_eins_covered", + "secondary_auditors_exist", + ) + general_data = omit(gen_key_exceptions, general_information) + general_data = general_data | { + "number_months": general_information.get("audit_period_other_months", ""), + "fy_end_date": general_information["auditee_fiscal_period_end"], + "fy_start_date": general_information["auditee_fiscal_period_start"], + "auditor_foreign_address": general_information.get( "auditor_international_address", "" ), - auditor_ein=general_information["auditor_ein"], - cognizant_agency=self.single_audit_checklist.cognizant_agency - if self.single_audit_checklist.cognizant_agency - else "", - oversight_agency=self.single_audit_checklist.oversight_agency - if self.single_audit_checklist.oversight_agency - else "", + "auditee_ein": general_information["ein"], + "entity_type": general_information["user_provided_organization_type"], + } + if "multiple_ueis_covered" in general_information: + addl = Util.bool_to_yes_no(general_information["multiple_ueis_covered"]) + general_data["is_additional_ueis"] = addl + + # Various values in audit_information need special handling + audit_data = { + "agencies_with_prior_findings": Util.json_array_to_str( + audit_information["agencies"] + ), + "dollar_threshold": audit_information["dollar_threshold"], + "gaap_results": Util.json_array_to_str(audit_information["gaap_results"]), + } + audit_keys_arrtostr_opt = ( + "sp_framework_basis", + "sp_framework_opinions", + ) + audit_keys_yn = ( + "is_going_concern_included", + "is_internal_control_deficiency_disclosed", + "is_internal_control_material_weakness_disclosed", + "is_material_noncompliance_disclosed", + "is_aicpa_audit_guide_included", + "is_low_risk_auditee", + ) + audit_keys_opt_bool = ("is_sp_framework_required",) + for key in audit_keys_arrtostr_opt: + audit_data[key] = Util.json_array_to_str(audit_information.get(key)) + for key in audit_keys_yn: + audit_data[key] = Util.bool_to_yes_no(audit_information[key]) + for key in audit_keys_opt_bool: + audit_data[key] = Util.optional_bool(audit_information.get(key, None)) + + general = General( + report_id=self.report_id, + auditee_certify_name=auditee_certify_name, + auditee_certify_title=auditee_certify_title, + cognizant_agency=cognizant_agency, + oversight_agency=oversight_agency, date_created=self.single_audit_checklist.date_created, - ready_for_certification_date=dates_by_status[ - self.single_audit_checklist.STATUS.READY_FOR_CERTIFICATION - ], - auditor_certified_date=dates_by_status[ - self.single_audit_checklist.STATUS.AUDITOR_CERTIFIED - ], - auditee_certified_date=dates_by_status[ - self.single_audit_checklist.STATUS.AUDITEE_CERTIFIED - ], - submitted_date=dates_by_status[ - self.single_audit_checklist.STATUS.SUBMITTED - ], + ready_for_certification_date=ready_for_certification_date, + auditor_certified_date=auditor_certified_date, + auditee_certified_date=auditee_certified_date, + submitted_date=submitted_date, # auditor_signature_date=auditor_certification["auditor_signature"]["auditor_certification_date_signed"], # auditee_signature_date=auditee_certification["auditee_signature"]["auditee_certification_date_signed"], - fy_end_date=general_information["auditee_fiscal_period_end"], - fy_start_date=general_information["auditee_fiscal_period_start"], audit_year=str(self.audit_year), - audit_type=general_information["audit_type"], - gaap_results=Util.json_array_to_str(audit_information["gaap_results"]), - sp_framework_basis=Util.json_array_to_str( - audit_information.get("sp_framework_basis") - ), - is_sp_framework_required=Util.optional_bool( - audit_information.get("is_sp_framework_required", None) - ), - sp_framework_opinions=Util.json_array_to_str( - audit_information.get("sp_framework_opinions") - ), - is_going_concern_included=Util.bool_to_yes_no( - audit_information["is_going_concern_included"] - ), - is_internal_control_deficiency_disclosed=Util.bool_to_yes_no( - audit_information["is_internal_control_deficiency_disclosed"] - ), - is_internal_control_material_weakness_disclosed=Util.bool_to_yes_no( - audit_information["is_internal_control_material_weakness_disclosed"] - ), - is_material_noncompliance_disclosed=Util.bool_to_yes_no( - audit_information["is_material_noncompliance_disclosed"] - ), # is_duplicate_reports = Util.bool_to_yes_no(audit_information["is_aicpa_audit_guide_included"]), #FIXME This mapping does not seem correct - is_aicpa_audit_guide_included=Util.bool_to_yes_no( - audit_information["is_aicpa_audit_guide_included"] - ), - dollar_threshold=audit_information["dollar_threshold"], - is_low_risk_auditee=Util.bool_to_yes_no( - audit_information["is_low_risk_auditee"] - ), - agencies_with_prior_findings=Util.json_array_to_str( - audit_information["agencies"] - ), - entity_type=general_information["user_provided_organization_type"], - number_months=general_information.get("audit_period_other_months", ""), - audit_period_covered=general_information["audit_period_covered"], - total_amount_expended=self.single_audit_checklist.federal_awards[ - "FederalAwards" - ]["total_amount_expended"], + total_amount_expended=total_amount_expended, type_audit_code="UG", is_public=self.single_audit_checklist.is_public, data_source=self.single_audit_checklist.data_source, + **general_data, + **audit_data, ) self.loaded_objects["Generals"] = [general] diff --git a/backend/audit/migrations/0001_initial.py b/backend/audit/migrations/0001_initial.py new file mode 100644 index 0000000000..3118977b69 --- /dev/null +++ b/backend/audit/migrations/0001_initial.py @@ -0,0 +1,472 @@ +# Generated by Django 4.2.3 on 2023-09-20 13:57 + +import audit.models +import audit.validators +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion +import django_fsm + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="SingleAuditChecklist", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date_created", models.DateTimeField(auto_now_add=True)), + ( + "submission_status", + django_fsm.FSMField( + choices=[ + ("in_progress", "In Progress"), + ("ready_for_certification", "Ready for Certification"), + ("auditor_certified", "Auditor Certified"), + ("auditee_certified", "Auditee Certified"), + ("certified", "Certified"), + ("submitted", "Submitted"), + ("disseminated", "Disseminated"), + ], + default="in_progress", + max_length=50, + ), + ), + ("data_source", models.CharField(default="GSA")), + ( + "transition_name", + django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + choices=[ + ("in_progress", "In Progress"), + ("ready_for_certification", "Ready for Certification"), + ("auditor_certified", "Auditor Certified"), + ("auditee_certified", "Auditee Certified"), + ("certified", "Certified"), + ("submitted", "Submitted"), + ("disseminated", "Disseminated"), + ], + max_length=40, + ), + blank=True, + default=list, + null=True, + size=None, + ), + ), + ( + "transition_date", + django.contrib.postgres.fields.ArrayField( + base_field=models.DateTimeField(), + blank=True, + default=list, + null=True, + size=None, + ), + ), + ("report_id", models.CharField(max_length=17, unique=True)), + ( + "audit_type", + models.CharField( + blank=True, + choices=[ + ("single-audit", "Single Audit"), + ("program-specific", "Program-Specific Audit"), + ], + max_length=20, + null=True, + ), + ), + ( + "general_information", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_general_information_json], + ), + ), + ( + "audit_information", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_audit_information_json], + ), + ), + ( + "federal_awards", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_federal_award_json], + ), + ), + ( + "corrective_action_plan", + models.JSONField( + blank=True, + null=True, + validators=[ + audit.validators.validate_corrective_action_plan_json + ], + ), + ), + ( + "findings_text", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_findings_text_json], + ), + ), + ( + "findings_uniform_guidance", + models.JSONField( + blank=True, + null=True, + validators=[ + audit.validators.validate_findings_uniform_guidance_json + ], + ), + ), + ( + "additional_ueis", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_additional_ueis_json], + ), + ), + ( + "additional_eins", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_additional_eins_json], + ), + ), + ( + "secondary_auditors", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_secondary_auditors_json], + ), + ), + ( + "notes_to_sefa", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_notes_to_sefa_json], + ), + ), + ( + "auditor_certification", + models.JSONField( + blank=True, + null=True, + validators=[ + audit.validators.validate_auditor_certification_json + ], + ), + ), + ( + "auditee_certification", + models.JSONField( + blank=True, + null=True, + validators=[ + audit.validators.validate_auditee_certification_json + ], + ), + ), + ( + "tribal_data_consent", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_tribal_data_consent_json], + ), + ), + ( + "cognizant_agency", + models.CharField( + blank=True, + max_length=2, + null=True, + verbose_name="Agency assigned to this large submission. Computed when the submisson is finalized, but may be overridden", + ), + ), + ( + "oversight_agency", + models.CharField( + blank=True, + max_length=2, + null=True, + verbose_name="Agency assigned to this not so large submission. Computed when the submisson is finalized", + ), + ), + ( + "submitted_by", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "SF-SAC", + "verbose_name_plural": "SF-SACs", + }, + bases=(models.Model, audit.models.GeneralInformationMixin), + ), + migrations.CreateModel( + name="SubmissionEvent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "event", + models.CharField( + choices=[ + ("access-granted", "Access granted"), + ("additional-eins-updated", "Additional EINs updated"), + ("additional-ueis-updated", "Additional UEIs updated"), + ("audit-information-updated", "Audit information updated"), + ("audit-report-pdf-updated", "Audit report PDF updated"), + ( + "auditee-certification-completed", + "Auditee certification completed", + ), + ( + "auditor-certification-completed", + "Auditor certification completed", + ), + ( + "corrective-action-plan-updated", + "Corrective action plan updated", + ), + ("created", "Created"), + ("federal-awards-updated", "Federal awards updated"), + ( + "federal-awards-audit-findings-updated", + "Federal awards audit findings updated", + ), + ( + "federal-awards-audit-findings-text-updated", + "Federal awards audit findings text updated", + ), + ( + "findings-uniform-guidance-updated", + "Findings uniform guidance updated", + ), + ( + "general-information-updated", + "General information updated", + ), + ("locked-for-certification", "Locked for certification"), + ("notes-to-sefa-updated", "Notes to SEFA updated"), + ( + "secondary-auditors-updated", + "Secondary auditors updated", + ), + ("submitted", "Submitted to the FAC for processing"), + ] + ), + ), + ( + "sac", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="audit.singleauditchecklist", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="SingleAuditReportFile", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "file", + models.FileField( + upload_to=audit.models.single_audit_report_path, + validators=[audit.validators.validate_single_audit_report_file], + ), + ), + ("filename", models.CharField(max_length=255)), + ("date_created", models.DateTimeField(auto_now_add=True)), + ( + "component_page_numbers", + models.JSONField( + blank=True, + null=True, + validators=[audit.validators.validate_component_page_numbers], + ), + ), + ( + "sac", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="audit.singleauditchecklist", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="ExcelFile", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "file", + models.FileField( + upload_to=audit.models.excel_file_path, + validators=[audit.validators.validate_excel_file], + ), + ), + ("filename", models.CharField(max_length=255)), + ("form_section", models.CharField(max_length=255)), + ("date_created", models.DateTimeField(auto_now_add=True)), + ( + "sac", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="audit.singleauditchecklist", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="Access", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "role", + models.CharField( + choices=[ + ( + "certifying_auditee_contact", + "Auditee Certifying Official", + ), + ( + "certifying_auditor_contact", + "Auditor Certifying Official", + ), + ("editor", "Audit Editor"), + ], + help_text="Access type granted to this user", + max_length=50, + ), + ), + ("fullname", models.CharField(blank=True)), + ("email", models.EmailField(max_length=254)), + ( + "sac", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="audit.singleauditchecklist", + ), + ), + ( + "user", + models.ForeignKey( + help_text="User ID associated with this email address, empty if no FAC account exists", + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name_plural": "accesses", + }, + ), + migrations.AddConstraint( + model_name="access", + constraint=models.UniqueConstraint( + condition=models.Q(("role", "certifying_auditee_contact")), + fields=("sac",), + name="audit_$(class)s_single_certifying_auditee", + ), + ), + migrations.AddConstraint( + model_name="access", + constraint=models.UniqueConstraint( + condition=models.Q(("role", "certifying_auditor_contact")), + fields=("sac",), + name="audit_access_single_certifying_auditor", + ), + ), + ] diff --git a/backend/audit/migrations/0001_initial_sac.py b/backend/audit/migrations/0001_initial_sac.py deleted file mode 100644 index ecea3b1e5b..0000000000 --- a/backend/audit/migrations/0001_initial_sac.py +++ /dev/null @@ -1,184 +0,0 @@ -# Generated by Django 4.0.4 on 2022-04-18 18:51 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="SingleAuditChecklist", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("date_created", models.DateTimeField(auto_now_add=True)), - ( - "user_provided_organization_type", - models.CharField( - choices=[ - ("state", "State"), - ("local", "Local Government"), - ("tribal", "Indian Tribe or Tribal Organization"), - ("non-profit", "Non-profit"), - ("unknown", "Unknown"), - ("none", "None of these (for example, for-profit"), - ], - max_length=12, - ), - ), - ("met_spending_threshold", models.BooleanField()), - ( - "is_usa_based", - models.BooleanField(verbose_name="Is USA Based"), - ), - ( - "uei", - models.CharField( - help_text="Unique Entity Identifier", - max_length=12, - verbose_name="UEI", - ), - ), - ("auditee_fiscal_period_start", models.DateField()), - ("auditee_fiscal_period_end", models.DateField()), - ( - "audit_type", - models.CharField( - choices=[ - ("single-audit", "Single Audit"), - ("program-specific", "Program-Specific Audit"), - ], - max_length=20, - null=True, - ), - ), - ( - "audit_period_covered", - models.CharField( - choices=[ - ("annual", "Annual"), - ("biennial", "Biennial"), - ("other", "Other"), - ], - max_length=20, - null=True, - ), - ), - ( - "ein", - models.CharField( - help_text="Auditee Employer Identification Number", - max_length=12, - null=True, - verbose_name="EIN", - ), - ), - ( - "ein_not_an_ssn_attestation", - models.BooleanField( - null=True, verbose_name="Attestation: EIN Not an SSN" - ), - ), - ( - "multiple_eins_covered", - models.BooleanField( - null=True, verbose_name="Multiple EINs covered" - ), - ), - ( - "multiple_ueis_covered", - models.BooleanField( - null=True, verbose_name="Multiple UEIs covered" - ), - ), - ("auditee_name", models.CharField(max_length=100, null=True)), - ( - "auditee_address_line_1", - models.CharField(max_length=100, null=True), - ), - ( - "auditee_address_line_2", - models.CharField(max_length=100, null=True), - ), - ("auditee_city", models.CharField(max_length=100, null=True)), - ("auditee_zip", models.CharField(max_length=100, null=True)), - ("auditee_state", models.CharField(max_length=2, null=True)), - ( - "auditee_contact_title", - models.CharField(max_length=100, null=True), - ), - ( - "auditee_contact_name", - models.CharField(max_length=100, null=True), - ), - ( - "auditee_email", - models.EmailField(max_length=100, null=True), - ), - ("auditee_phone", models.CharField(max_length=100, null=True)), - ("auditor_name", models.CharField(max_length=100, null=True)), - ( - "auditor_ein", - models.CharField( - max_length=12, null=True, verbose_name="Auditor EIN" - ), - ), - ( - "auditor_ein_not_an_ssn_attestation", - models.BooleanField( - null=True, - verbose_name="Attestation: Auditor EIN Not an SSN", - ), - ), - ( - "auditor_address_line_1", - models.CharField(max_length=100, null=True), - ), - ( - "auditor_address_line_2", - models.CharField(max_length=100, null=True), - ), - ("auditor_city", models.CharField(max_length=100, null=True)), - ("auditor_zip", models.CharField(max_length=100, null=True)), - ("auditor_state", models.CharField(max_length=100, null=True)), - ( - "auditor_contact_title", - models.CharField(max_length=100, null=True), - ), - ( - "auditor_contact_name", - models.CharField(max_length=100, null=True), - ), - ( - "auditor_email", - models.EmailField(max_length=100, null=True), - ), - ("auditor_phone", models.CharField(max_length=100, null=True)), - ( - "submitted_by", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "verbose_name": "SF-SAC", - "verbose_name_plural": "SF-SACs", - }, - ), - ] diff --git a/backend/audit/migrations/0002_alter_singleauditchecklist_user_provided_organization_type_and_more.py b/backend/audit/migrations/0002_alter_singleauditchecklist_user_provided_organization_type_and_more.py deleted file mode 100644 index d2cd27ad50..0000000000 --- a/backend/audit/migrations/0002_alter_singleauditchecklist_user_provided_organization_type_and_more.py +++ /dev/null @@ -1,76 +0,0 @@ -# Generated by Django 4.0.4 on 2022-04-22 19:09 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("audit", "0001_initial_sac"), - ] - - operations = [ - migrations.AlterField( - model_name="singleauditchecklist", - name="user_provided_organization_type", - field=models.CharField( - choices=[ - ("state", "State"), - ("local", "Local Government"), - ("tribal", "Indian Tribe or Tribal Organization"), - ("higher-ed", "Institution of higher education (IHE)"), - ("non-profit", "Non-profit"), - ("unknown", "Unknown"), - ("none", "None of these (for example, for-profit"), - ], - max_length=12, - ), - ), - migrations.CreateModel( - name="Access", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "role", - models.CharField( - choices=[ - ("auditee_contact", "Auditee Contact"), - ("auditee_cert", "Auditee Certifying Official"), - ("auditor_contact", "Auditor Contact"), - ("auditor_cert", "Auditor Certifying Official"), - ], - help_text="Access type granted to this user", - max_length=15, - ), - ), - ("email", models.EmailField(max_length=254)), - ( - "sac", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="users", - to="audit.singleauditchecklist", - ), - ), - ( - "user_id", - models.ForeignKey( - help_text="User ID associated with this email address, empty if no FAC account exists", - null=True, - on_delete=django.db.models.deletion.PROTECT, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/audit/migrations/0003_rename_auditor_name_singleauditchecklist_auditor_firm_name_and_more.py b/backend/audit/migrations/0003_rename_auditor_name_singleauditchecklist_auditor_firm_name_and_more.py deleted file mode 100644 index 7d314a6794..0000000000 --- a/backend/audit/migrations/0003_rename_auditor_name_singleauditchecklist_auditor_firm_name_and_more.py +++ /dev/null @@ -1,56 +0,0 @@ -# Generated by Django 4.0.4 on 2022-05-20 22:33 - -import audit.validators -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ( - "audit", - "0002_alter_singleauditchecklist_user_provided_organization_type_and_more", - ), - ] - - operations = [ - migrations.RenameField( - model_name="singleauditchecklist", - old_name="auditor_name", - new_name="auditor_firm_name", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_address_line_2", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_address_line_2", - ), - migrations.AddField( - model_name="singleauditchecklist", - name="auditor_country", - field=models.CharField(max_length=100, null=True), - ), - migrations.RenameField( - model_name="singleauditchecklist", - old_name="uei", - new_name="auditee_uei", - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_uei", - field=models.CharField( - help_text="Auditee Unique Entity Identifier", - max_length=12, - validators=[ - django.core.validators.MinLengthValidator(12), - audit.validators.validate_uei_alphanumeric, - audit.validators.validate_uei_valid_chars, - audit.validators.validate_uei_leading_char, - audit.validators.validate_uei_nine_digit_sequences, - ], - verbose_name="Auditee UEI", - ), - ), - ] diff --git a/backend/audit/migrations/0004_singleauditchecklist_submission_status.py b/backend/audit/migrations/0004_singleauditchecklist_submission_status.py deleted file mode 100644 index 1e8205c22d..0000000000 --- a/backend/audit/migrations/0004_singleauditchecklist_submission_status.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 4.0.4 on 2022-06-08 19:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ( - "audit", - "0003_rename_auditor_name_singleauditchecklist_auditor_firm_name_and_more", - ), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="submission_status", - field=models.CharField( - choices=[ - ("in_progress", "In progress"), - ("submitted", "Submitted"), - ("received", "Received"), - ("available", "Available"), - ], - default="in_progress", - max_length=16, - ), - ), - ] diff --git a/backend/audit/migrations/0005_singleauditchecklist_report_id.py b/backend/audit/migrations/0005_singleauditchecklist_report_id.py deleted file mode 100644 index 008934d668..0000000000 --- a/backend/audit/migrations/0005_singleauditchecklist_report_id.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.0.4 on 2022-06-29 06:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0004_singleauditchecklist_submission_status"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="report_id", - field=models.CharField(default="foo", max_length=17, unique=True), - ), - ] diff --git a/backend/audit/migrations/0006_alter_singleauditchecklist_report_id.py b/backend/audit/migrations/0006_alter_singleauditchecklist_report_id.py deleted file mode 100644 index 863a729fca..0000000000 --- a/backend/audit/migrations/0006_alter_singleauditchecklist_report_id.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.0.4 on 2022-06-29 07:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0005_singleauditchecklist_report_id"), - ] - - operations = [ - migrations.AlterField( - model_name="singleauditchecklist", - name="report_id", - field=models.CharField(max_length=17, unique=True), - ), - ] diff --git a/backend/audit/migrations/0007_alter_singleauditchecklist_auditee_uei.py b/backend/audit/migrations/0007_alter_singleauditchecklist_auditee_uei.py deleted file mode 100644 index 17dce83720..0000000000 --- a/backend/audit/migrations/0007_alter_singleauditchecklist_auditee_uei.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.0.4 on 2022-07-06 19:50 - -import audit.validators -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0006_alter_singleauditchecklist_report_id"), - ] - - operations = [ - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_uei", - field=models.CharField( - blank=True, - help_text="Auditee Unique Entity Identifier", - max_length=12, - null=True, - validators=[ - django.core.validators.MinLengthValidator(12), - audit.validators.validate_uei_alphanumeric, - audit.validators.validate_uei_valid_chars, - audit.validators.validate_uei_leading_char, - audit.validators.validate_uei_nine_digit_sequences, - ], - verbose_name="Auditee UEI", - ), - ), - ] diff --git a/backend/audit/migrations/0008_alter_access_options_rename_user_id_access_user_and_more.py b/backend/audit/migrations/0008_alter_access_options_rename_user_id_access_user_and_more.py deleted file mode 100644 index 6dfc12c6bd..0000000000 --- a/backend/audit/migrations/0008_alter_access_options_rename_user_id_access_user_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.0.4 on 2022-08-05 14:29 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0007_alter_singleauditchecklist_auditee_uei"), - ] - - operations = [ - migrations.AlterModelOptions( - name="access", - options={"verbose_name_plural": "accesses"}, - ), - migrations.RenameField( - model_name="access", - old_name="user_id", - new_name="user", - ), - migrations.AlterField( - model_name="access", - name="sac", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="audit.singleauditchecklist", - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_name", - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/backend/audit/migrations/0009_alter_access_role_and_more.py b/backend/audit/migrations/0009_alter_access_role_and_more.py deleted file mode 100644 index 44393991f8..0000000000 --- a/backend/audit/migrations/0009_alter_access_role_and_more.py +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by Django 4.0.4 on 2022-08-05 17:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0008_alter_access_options_rename_user_id_access_user_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="access", - name="role", - field=models.CharField( - choices=[ - ("auditee_contact", "Auditee Contact"), - ("auditee_cert", "Auditee Certifying Official"), - ("auditor_contact", "Auditor Contact"), - ("auditor_cert", "Auditor Certifying Official"), - ("creator", "Audit Creator"), - ], - help_text="Access type granted to this user", - max_length=15, - ), - ), - migrations.AddConstraint( - model_name="access", - constraint=models.UniqueConstraint( - condition=models.Q(("role", "creator")), - fields=("sac",), - name="audit_$(class)s_single_creator", - ), - ), - migrations.AddConstraint( - model_name="access", - constraint=models.UniqueConstraint( - condition=models.Q(("role", "auditee_cert")), - fields=("sac",), - name="audit_$(class)s_single_certifying_auditee", - ), - ), - migrations.AddConstraint( - model_name="access", - constraint=models.UniqueConstraint( - condition=models.Q(("role", "auditor_cert")), - fields=("sac",), - name="audit_access_single_certifying_auditor", - ), - ), - ] diff --git a/backend/audit/migrations/0010_alter_singleauditchecklist_audit_period_covered_and_more.py b/backend/audit/migrations/0010_alter_singleauditchecklist_audit_period_covered_and_more.py deleted file mode 100644 index f4edcd7276..0000000000 --- a/backend/audit/migrations/0010_alter_singleauditchecklist_audit_period_covered_and_more.py +++ /dev/null @@ -1,177 +0,0 @@ -# Generated by Django 4.0.4 on 2022-08-12 18:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0009_alter_access_role_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="singleauditchecklist", - name="audit_period_covered", - field=models.CharField( - blank=True, - choices=[ - ("annual", "Annual"), - ("biennial", "Biennial"), - ("other", "Other"), - ], - max_length=20, - null=True, - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="audit_type", - field=models.CharField( - blank=True, - choices=[ - ("single-audit", "Single Audit"), - ("program-specific", "Program-Specific Audit"), - ], - max_length=20, - null=True, - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_address_line_1", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_city", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_contact_name", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_contact_title", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_email", - field=models.EmailField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_phone", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_state", - field=models.CharField(blank=True, max_length=2, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditee_zip", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_address_line_1", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_city", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_contact_name", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_contact_title", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_country", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_ein", - field=models.CharField( - blank=True, max_length=12, null=True, verbose_name="Auditor EIN" - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_ein_not_an_ssn_attestation", - field=models.BooleanField( - blank=True, - null=True, - verbose_name="Attestation: Auditor EIN Not an SSN", - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_email", - field=models.EmailField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_firm_name", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_phone", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_state", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="auditor_zip", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="ein", - field=models.CharField( - blank=True, - help_text="Auditee Employer Identification Number", - max_length=12, - null=True, - verbose_name="EIN", - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="ein_not_an_ssn_attestation", - field=models.BooleanField( - blank=True, null=True, verbose_name="Attestation: EIN Not an SSN" - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="multiple_eins_covered", - field=models.BooleanField( - blank=True, null=True, verbose_name="Multiple EINs covered" - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="multiple_ueis_covered", - field=models.BooleanField( - blank=True, null=True, verbose_name="Multiple UEIs covered" - ), - ), - ] diff --git a/backend/audit/migrations/0011_alter_access_role.py b/backend/audit/migrations/0011_alter_access_role.py deleted file mode 100644 index 1d2926aba5..0000000000 --- a/backend/audit/migrations/0011_alter_access_role.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.0.4 on 2022-09-12 20:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0010_alter_singleauditchecklist_audit_period_covered_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="access", - name="role", - field=models.CharField( - choices=[ - ("auditee_contact", "Auditee Contact"), - ("auditor_contact", "Auditor Contact"), - ("certifying_auditee_contact ", "Auditee Certifying Official"), - ("certifying_auditor_contact ", "Auditor Certifying Official"), - ("creator", "Audit Creator"), - ], - help_text="Access type granted to this user", - max_length=50, - ), - ), - ] diff --git a/backend/audit/migrations/0012_remove_access_audit_$(class)s_single_certifying_auditee_and_more.py b/backend/audit/migrations/0012_remove_access_audit_$(class)s_single_certifying_auditee_and_more.py deleted file mode 100644 index 2cd9c5e62a..0000000000 --- a/backend/audit/migrations/0012_remove_access_audit_$(class)s_single_certifying_auditee_and_more.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 4.0.4 on 2022-09-12 20:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0011_alter_access_role"), - ] - - operations = [ - migrations.RemoveConstraint( - model_name="access", - name="audit_$(class)s_single_certifying_auditee", - ), - migrations.RemoveConstraint( - model_name="access", - name="audit_access_single_certifying_auditor", - ), - migrations.AddConstraint( - model_name="access", - constraint=models.UniqueConstraint( - condition=models.Q(("role", "certifying_auditee_contact")), - fields=("sac",), - name="audit_$(class)s_single_certifying_auditee", - ), - ), - migrations.AddConstraint( - model_name="access", - constraint=models.UniqueConstraint( - condition=models.Q(("role", "certifying_auditor_contact")), - fields=("sac",), - name="audit_access_single_certifying_auditor", - ), - ), - ] diff --git a/backend/audit/migrations/0013_singleauditchecklist_federal_awards.py b/backend/audit/migrations/0013_singleauditchecklist_federal_awards.py deleted file mode 100644 index 54d1b854d8..0000000000 --- a/backend/audit/migrations/0013_singleauditchecklist_federal_awards.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.1 on 2022-10-26 19:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ( - "audit", - "0012_remove_access_audit_$(class)s_single_certifying_auditee_and_more", - ), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="federal_awards", - field=models.JSONField(blank=True, null=True), - ), - ] diff --git a/backend/audit/migrations/0014_alter_singleauditchecklist_federal_awards.py b/backend/audit/migrations/0014_alter_singleauditchecklist_federal_awards.py deleted file mode 100644 index 7fedecada5..0000000000 --- a/backend/audit/migrations/0014_alter_singleauditchecklist_federal_awards.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.1.3 on 2022-11-23 16:27 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0013_singleauditchecklist_federal_awards"), - ] - - operations = [ - migrations.AlterField( - model_name="singleauditchecklist", - name="federal_awards", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_federal_award_json], - ), - ), - ] diff --git a/backend/audit/migrations/0015_singleauditchecklist_general_information.py b/backend/audit/migrations/0015_singleauditchecklist_general_information.py deleted file mode 100644 index 3886b51ab7..0000000000 --- a/backend/audit/migrations/0015_singleauditchecklist_general_information.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.1.4 on 2022-12-16 14:36 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0014_alter_singleauditchecklist_federal_awards"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="general_information", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_general_information_json], - ), - ), - ] diff --git a/backend/audit/migrations/0016_remove_singleauditchecklist_audit_period_covered_and_more.py b/backend/audit/migrations/0016_remove_singleauditchecklist_audit_period_covered_and_more.py deleted file mode 100644 index 931c28e420..0000000000 --- a/backend/audit/migrations/0016_remove_singleauditchecklist_audit_period_covered_and_more.py +++ /dev/null @@ -1,140 +0,0 @@ -# Generated by Django 4.1.4 on 2022-12-16 18:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0015_singleauditchecklist_general_information"), - ] - - operations = [ - migrations.RemoveField( - model_name="singleauditchecklist", - name="audit_period_covered", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_address_line_1", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_city", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_contact_name", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_contact_title", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_email", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_fiscal_period_end", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_fiscal_period_start", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_name", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_phone", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_state", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_uei", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditee_zip", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_address_line_1", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_city", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_contact_name", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_contact_title", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_country", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_ein", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_ein_not_an_ssn_attestation", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_email", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_firm_name", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_phone", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_state", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="auditor_zip", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="ein", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="ein_not_an_ssn_attestation", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="is_usa_based", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="met_spending_threshold", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="multiple_eins_covered", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="multiple_ueis_covered", - ), - migrations.RemoveField( - model_name="singleauditchecklist", - name="user_provided_organization_type", - ), - ] diff --git a/backend/audit/migrations/0017_excelfile.py b/backend/audit/migrations/0017_excelfile.py deleted file mode 100644 index 9525af6564..0000000000 --- a/backend/audit/migrations/0017_excelfile.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 4.1.4 on 2023-02-09 16:09 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("audit", "0016_remove_singleauditchecklist_audit_period_covered_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="ExcelFile", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("file", models.FileField(upload_to="excel")), - ("filename", models.CharField(max_length=255)), - ("date_created", models.DateTimeField(auto_now_add=True)), - ( - "sac", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="audit.singleauditchecklist", - ), - ), - ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/audit/migrations/0018_alter_excelfile_file.py b/backend/audit/migrations/0018_alter_excelfile_file.py deleted file mode 100644 index 84772d6269..0000000000 --- a/backend/audit/migrations/0018_alter_excelfile_file.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.1.4 on 2023-02-22 13:24 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0017_excelfile"), - ] - - operations = [ - migrations.AlterField( - model_name="excelfile", - name="file", - field=models.FileField( - upload_to="excel", validators=[audit.validators.validate_excel_file] - ), - ), - ] diff --git a/backend/audit/migrations/0019_singleauditchecklist_corrective_action_plan_and_more.py b/backend/audit/migrations/0019_singleauditchecklist_corrective_action_plan_and_more.py deleted file mode 100644 index b1f662bc93..0000000000 --- a/backend/audit/migrations/0019_singleauditchecklist_corrective_action_plan_and_more.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.1.7 on 2023-04-20 21:27 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0018_alter_excelfile_file"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="corrective_action_plan", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_corrective_action_plan_json], - ), - ), - migrations.AddField( - model_name="singleauditchecklist", - name="findings_uniform_guidance", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_findings_uniform_guidance_json], - ), - ), - ] diff --git a/backend/audit/migrations/0020_singleauditchecklist_findings_text.py b/backend/audit/migrations/0020_singleauditchecklist_findings_text.py deleted file mode 100644 index daabdc07b0..0000000000 --- a/backend/audit/migrations/0020_singleauditchecklist_findings_text.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.1.7 on 2023-04-26 14:14 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0019_singleauditchecklist_corrective_action_plan_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="findings_text", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_findings_text_json], - ), - ), - ] diff --git a/backend/audit/migrations/0021_alter_singleauditchecklist_submission_status.py b/backend/audit/migrations/0021_alter_singleauditchecklist_submission_status.py deleted file mode 100644 index 6ec19e43f7..0000000000 --- a/backend/audit/migrations/0021_alter_singleauditchecklist_submission_status.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 4.1.7 on 2023-04-12 20:32 - -from django.db import migrations -import django_fsm - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0020_singleauditchecklist_findings_text"), - ] - - operations = [ - migrations.AlterField( - model_name="singleauditchecklist", - name="submission_status", - field=django_fsm.FSMField( - choices=[ - ("in_progress", "In Progress"), - ("ready_for_certification", "Ready for Certification"), - ("auditor_certified", "Auditor Certified"), - ("auditee_certified", "Auditee Certified"), - ("certified", "Certified"), - ("submitted", "Submitted"), - ], - default="in_progress", - max_length=50, - ), - ), - ] diff --git a/backend/audit/migrations/0022_remove_access_audit_single_creator_and_more.py b/backend/audit/migrations/0022_remove_access_audit_single_creator_and_more.py deleted file mode 100644 index bd1604661e..0000000000 --- a/backend/audit/migrations/0022_remove_access_audit_single_creator_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 4.1.7 on 2023-05-02 17:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0021_alter_singleauditchecklist_submission_status"), - ] - - operations = [ - migrations.RemoveConstraint( - model_name="access", - name="audit_$(class)s_single_creator", - ), - migrations.AlterField( - model_name="access", - name="role", - field=models.CharField( - choices=[ - ("certifying_auditee_contact ", "Auditee Certifying Official"), - ("certifying_auditor_contact ", "Auditor Certifying Official"), - ("editor", "Audit Editor"), - ], - help_text="Access type granted to this user", - max_length=50, - ), - ), - ] diff --git a/backend/audit/migrations/0023_alter_access_role.py b/backend/audit/migrations/0023_alter_access_role.py deleted file mode 100644 index 123684c5d6..0000000000 --- a/backend/audit/migrations/0023_alter_access_role.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.1.7 on 2023-05-11 16:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0022_remove_access_audit_single_creator_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="access", - name="role", - field=models.CharField( - choices=[ - ("certifying_auditee_contact", "Auditee Certifying Official"), - ("certifying_auditor_contact", "Auditor Certifying Official"), - ("editor", "Audit Editor"), - ], - help_text="Access type granted to this user", - max_length=50, - ), - ), - ] diff --git a/backend/audit/migrations/0024_excelfile_form_section.py b/backend/audit/migrations/0024_excelfile_form_section.py deleted file mode 100644 index eda9354584..0000000000 --- a/backend/audit/migrations/0024_excelfile_form_section.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.1 on 2023-06-17 00:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0023_alter_access_role"), - ] - - operations = [ - migrations.AddField( - model_name="excelfile", - name="form_section", - field=models.CharField(default="not-actually-default", max_length=255), - ), - ] diff --git a/backend/audit/migrations/0025_alter_excelfile_form_section.py b/backend/audit/migrations/0025_alter_excelfile_form_section.py deleted file mode 100644 index 0c7cc10f1a..0000000000 --- a/backend/audit/migrations/0025_alter_excelfile_form_section.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.1 on 2023-06-17 06:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0024_excelfile_form_section"), - ] - - operations = [ - migrations.AlterField( - model_name="excelfile", - name="form_section", - field=models.CharField(max_length=255), - ), - ] diff --git a/backend/audit/migrations/0026_singleauditchecklist_transition_date_and_more.py b/backend/audit/migrations/0026_singleauditchecklist_transition_date_and_more.py deleted file mode 100644 index 6a34da9a59..0000000000 --- a/backend/audit/migrations/0026_singleauditchecklist_transition_date_and_more.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 4.2.1 on 2023-06-22 00:05 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0025_alter_excelfile_form_section"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="transition_date", - field=django.contrib.postgres.fields.ArrayField( - base_field=models.DateTimeField(), null=True, size=None - ), - ), - migrations.AddField( - model_name="singleauditchecklist", - name="transition_name", - field=django.contrib.postgres.fields.ArrayField( - base_field=models.CharField( - choices=[ - ("in_progress", "In Progress"), - ("ready_for_certification", "Ready for Certification"), - ("auditor_certified", "Auditor Certified"), - ("auditee_certified", "Auditee Certified"), - ("certified", "Certified"), - ("submitted", "Submitted"), - ], - max_length=40, - ), - null=True, - size=None, - ), - ), - ] diff --git a/backend/audit/migrations/0027_singleauditchecklist_additional_ueis_and_more.py b/backend/audit/migrations/0027_singleauditchecklist_additional_ueis_and_more.py deleted file mode 100644 index 68abe5100e..0000000000 --- a/backend/audit/migrations/0027_singleauditchecklist_additional_ueis_and_more.py +++ /dev/null @@ -1,106 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-07 20:17 - -import audit.models -import audit.validators -from django.conf import settings -import django.contrib.postgres.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("audit", "0026_singleauditchecklist_transition_date_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="additional_ueis", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_additional_ueis_json], - ), - ), - migrations.AlterField( - model_name="excelfile", - name="file", - field=models.FileField( - upload_to=audit.models.excel_file_path, - validators=[audit.validators.validate_excel_file], - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="transition_date", - field=django.contrib.postgres.fields.ArrayField( - base_field=models.DateTimeField(), - blank=True, - default=list, - null=True, - size=None, - ), - ), - migrations.AlterField( - model_name="singleauditchecklist", - name="transition_name", - field=django.contrib.postgres.fields.ArrayField( - base_field=models.CharField( - choices=[ - ("in_progress", "In Progress"), - ("ready_for_certification", "Ready for Certification"), - ("auditor_certified", "Auditor Certified"), - ("auditee_certified", "Auditee Certified"), - ("certified", "Certified"), - ("submitted", "Submitted"), - ], - max_length=40, - ), - blank=True, - default=list, - null=True, - size=None, - ), - ), - migrations.CreateModel( - name="SingleAuditReportFile", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "file", - models.FileField( - upload_to=audit.models.single_audit_report_path, - validators=[audit.validators.validate_single_audit_report_file], - ), - ), - ("filename", models.CharField(max_length=255)), - ("date_created", models.DateTimeField(auto_now_add=True)), - ( - "sac", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="audit.singleauditchecklist", - ), - ), - ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/audit/migrations/0028_singleauditchecklist_notes_to_sefa.py b/backend/audit/migrations/0028_singleauditchecklist_notes_to_sefa.py deleted file mode 100644 index b16f363211..0000000000 --- a/backend/audit/migrations/0028_singleauditchecklist_notes_to_sefa.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.1 on 2023-07-13 16:33 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0027_singleauditchecklist_additional_ueis_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="notes_to_sefa", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_notes_to_sefa_json], - ), - ), - ] diff --git a/backend/audit/migrations/0029_singleauditchecklist_secondary_auditors.py b/backend/audit/migrations/0029_singleauditchecklist_secondary_auditors.py deleted file mode 100644 index 64d83d367c..0000000000 --- a/backend/audit/migrations/0029_singleauditchecklist_secondary_auditors.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.1 on 2023-07-19 07:00 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0028_singleauditchecklist_notes_to_sefa"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="secondary_auditors", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_secondary_auditors_json], - ), - ), - ] diff --git a/backend/audit/migrations/0030_singleauditchecklist_audit_information.py b/backend/audit/migrations/0030_singleauditchecklist_audit_information.py deleted file mode 100644 index dbf4fb2d2f..0000000000 --- a/backend/audit/migrations/0030_singleauditchecklist_audit_information.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.3 on 2023-07-31 17:38 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0029_singleauditchecklist_secondary_auditors"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="audit_information", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_audit_information_json], - ), - ), - ] diff --git a/backend/audit/migrations/0031_singleauditreportfile_component_page_numbers.py b/backend/audit/migrations/0031_singleauditreportfile_component_page_numbers.py deleted file mode 100644 index d6f72bd465..0000000000 --- a/backend/audit/migrations/0031_singleauditreportfile_component_page_numbers.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-04 16:00 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0030_singleauditchecklist_audit_information"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditreportfile", - name="component_page_numbers", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_component_page_numbers], - ), - ), - ] diff --git a/backend/audit/migrations/0032_singleauditchecklist_auditee_certification_and_more.py b/backend/audit/migrations/0032_singleauditchecklist_auditee_certification_and_more.py deleted file mode 100644 index 634e0c5884..0000000000 --- a/backend/audit/migrations/0032_singleauditchecklist_auditee_certification_and_more.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-07 18:21 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0031_singleauditreportfile_component_page_numbers"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="auditee_certification", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_auditee_certification_json], - ), - ), - migrations.AddField( - model_name="singleauditchecklist", - name="auditor_certification", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_auditor_certification_json], - ), - ), - ] diff --git a/backend/audit/migrations/0033_singleauditchecklist_tribal_data_consent.py b/backend/audit/migrations/0033_singleauditchecklist_tribal_data_consent.py deleted file mode 100644 index f8ab5dd211..0000000000 --- a/backend/audit/migrations/0033_singleauditchecklist_tribal_data_consent.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-10 19:49 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0032_singleauditchecklist_auditee_certification_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="tribal_data_consent", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_tribal_data_consent_json], - ), - ), - ] diff --git a/backend/audit/migrations/0034_singleauditchecklist_additional_eins.py b/backend/audit/migrations/0034_singleauditchecklist_additional_eins.py deleted file mode 100644 index 014ee2b411..0000000000 --- a/backend/audit/migrations/0034_singleauditchecklist_additional_eins.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-15 19:58 - -import audit.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0033_singleauditchecklist_tribal_data_consent"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="additional_eins", - field=models.JSONField( - blank=True, - null=True, - validators=[audit.validators.validate_additional_eins_json], - ), - ), - ] diff --git a/backend/audit/migrations/0035_singleauditchecklist_data_source.py b/backend/audit/migrations/0035_singleauditchecklist_data_source.py deleted file mode 100644 index 61d42dab83..0000000000 --- a/backend/audit/migrations/0035_singleauditchecklist_data_source.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-16 21:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0034_singleauditchecklist_additional_eins"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="data_source", - field=models.CharField(default="GSA"), - ), - ] diff --git a/backend/audit/migrations/0036_cognizantbaseline.py b/backend/audit/migrations/0036_cognizantbaseline.py deleted file mode 100644 index eaba7d9a82..0000000000 --- a/backend/audit/migrations/0036_cognizantbaseline.py +++ /dev/null @@ -1,58 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-18 15:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0035_singleauditchecklist_data_source"), - ] - - operations = [ - migrations.CreateModel( - name="CognizantBaseline", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "dbkey", - models.IntegerField( - null=True, - verbose_name="Identifier for a submission along with audit_year in C-FAC", - ), - ), - ( - "audit_year", - models.IntegerField( - null=True, verbose_name="Audit year from fy_start_date" - ), - ), - ( - "ein", - models.CharField( - max_length=30, - null=True, - verbose_name="Primary Employer Identification Number", - ), - ), - ( - "cognizant_agency", - models.CharField( - max_length=2, - null=True, - verbose_name="Two digit Federal agency prefix of the cognizant agency", - ), - ), - ], - options={ - "unique_together": {("dbkey", "audit_year")}, - }, - ), - ] diff --git a/backend/audit/migrations/0037_access_fullname.py b/backend/audit/migrations/0037_access_fullname.py deleted file mode 100644 index 34f12e98f8..0000000000 --- a/backend/audit/migrations/0037_access_fullname.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-18 19:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0036_cognizantbaseline"), - ] - - operations = [ - migrations.AddField( - model_name="access", - name="fullname", - field=models.CharField(blank=True), - ), - ] diff --git a/backend/audit/migrations/0038_submissionevent.py b/backend/audit/migrations/0038_submissionevent.py deleted file mode 100644 index d076a0defa..0000000000 --- a/backend/audit/migrations/0038_submissionevent.py +++ /dev/null @@ -1,93 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-30 20:04 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("audit", "0037_access_fullname"), - ] - - operations = [ - migrations.CreateModel( - name="SubmissionEvent", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("timestamp", models.DateTimeField(auto_now_add=True)), - ( - "event", - models.CharField( - choices=[ - ("access-granted", "Access granted"), - ("additional-eins-updated", "Additional EINs updated"), - ("additional-ueis-updated", "Additional UEIs updated"), - ("audit-information-updated", "Audit information updated"), - ("audit-report-pdf-updated", "Audit report PDF updated"), - ( - "auditee-certification-completed", - "Auditee certification completed", - ), - ( - "auditor-certification-completed", - "Auditor certification completed", - ), - ( - "corrective-action-plan-updated", - "Corrective action plan updated", - ), - ("created", "Created"), - ("federal-awards-updated", "Federal awards updated"), - ( - "federal-awards-audit-findings-updated", - "Federal awards audit findings updated", - ), - ( - "federal-awards-audit-findings-text-updated", - "Federal awards audit findings text updated", - ), - ( - "findings-uniform-guidance-updated", - "Findings uniform guidance updated", - ), - ( - "general-information-updated", - "General information updated", - ), - ("locked-for-certification", "Locked for certification"), - ("notes-to-sefa-updated", "Notes to SEFA updated"), - ( - "secondary-auditors-updated", - "Secondary auditors updated", - ), - ("submitted", "Submitted to the FAC for processing"), - ] - ), - ), - ( - "sac", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="audit.singleauditchecklist", - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/audit/migrations/0039_singleauditchecklist_cognizant_agency_and_more.py b/backend/audit/migrations/0039_singleauditchecklist_cognizant_agency_and_more.py deleted file mode 100644 index 0e88b630cb..0000000000 --- a/backend/audit/migrations/0039_singleauditchecklist_cognizant_agency_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.3 on 2023-09-05 19:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("audit", "0038_submissionevent"), - ] - - operations = [ - migrations.AddField( - model_name="singleauditchecklist", - name="cognizant_agency", - field=models.TextField(null=True), - ), - migrations.AddField( - model_name="singleauditchecklist", - name="oversight_agency", - field=models.TextField(null=True), - ), - ] diff --git a/backend/audit/models.py b/backend/audit/models.py index fbae76c1c7..9b86e1e655 100644 --- a/backend/audit/models.py +++ b/backend/audit/models.py @@ -15,7 +15,8 @@ from django_fsm import FSMField, RETURN_VALUE, transition import audit.cross_validation -from .validators import ( +from audit.intake_to_dissemination import IntakeToDissemination +from audit.validators import ( validate_additional_ueis_json, validate_additional_eins_json, validate_corrective_action_plan_json, @@ -33,6 +34,7 @@ validate_audit_information_json, validate_component_page_numbers, ) +from support.cog_over import compute_cog_over, record_cog_assignment User = get_user_model() @@ -160,6 +162,43 @@ def save(self, *args, **kwargs): return super().save() + def disseminate(self): + """ + Cognizant/Oversight agency assignment followed by dissemination + ETL. + """ + # try: + if not self.cognizant_agency and not self.oversight_agency: + self.assign_cog_over() + intake_to_dissem = IntakeToDissemination(self) + intake_to_dissem.load_all() + intake_to_dissem.save_dissemination_objects() + # TODO: figure out what exceptions to catch here + # except Exception as err: + # return {"error": err} + + def assign_cog_over(self): + """ + Function that the FAC app uses when a submission is completed and cog_over needs to be assigned. + """ + if not self.federal_awards: + logger.warning( + "Trying to determine cog_over for a self with zero awards with status = %s", + self.submission_status, + ) + + cognizant_agency, oversight_agency = compute_cog_over( + self.federal_awards, self.submission_status, self.ein, self.auditee_uei + ) + if oversight_agency: + self.oversight_agency = oversight_agency + self.save() + return + if cognizant_agency: + self.cognizant_agency = cognizant_agency + self.save() + record_cog_assignment(self.report_id, self.submitted_by, cognizant_agency) + def _reject_late_changes(self): """ This should only be called if status isn't STATUS.IN_PROGRESS. @@ -195,6 +234,7 @@ class STATUS: AUDITEE_CERTIFIED = "auditee_certified" CERTIFIED = "certified" SUBMITTED = "submitted" + DISSEMINATED = "disseminated" STATUS_CHOICES = ( (STATUS.IN_PROGRESS, "In Progress"), @@ -203,6 +243,7 @@ class STATUS: (STATUS.AUDITEE_CERTIFIED, "Auditee Certified"), (STATUS.CERTIFIED, "Certified"), (STATUS.SUBMITTED, "Submitted"), + (STATUS.DISSEMINATED, "Disseminated"), ) USER_PROVIDED_ORGANIZATION_TYPE_CODE = ( @@ -319,9 +360,16 @@ class STATUS: blank=True, null=True, validators=[validate_tribal_data_consent_json] ) - cognizant_agency = models.TextField(null=True) - - oversight_agency = models.TextField( + cognizant_agency = models.CharField( + "Agency assigned to this large submission. Computed when the submisson is finalized, but may be overridden", + max_length=2, + blank=True, + null=True, + ) + oversight_agency = models.CharField( + "Agency assigned to this not so large submission. Computed when the submisson is finalized", + max_length=2, + blank=True, null=True, ) @@ -426,18 +474,18 @@ def transition_to_submitted(self): the appropriate privileges will done at the view level. """ - from audit.intake_to_dissemination import IntakeToDissemination - from audit.cog_over import cog_over - self.transition_name.append(SingleAuditChecklist.STATUS.SUBMITTED) self.transition_date.append(datetime.now(timezone.utc)) - if self.general_information: - # cog / over assignment - self.cognizant_agency, self.oversight_agency = cog_over(self) - intake_to_dissem = IntakeToDissemination(self) - intake_to_dissem.load_all() - # FIXME MSHD: Handle exceptions raised by the save methods - intake_to_dissem.save_dissemination_objects() + + @transition( + field="submission_status", + source=STATUS.SUBMITTED, + target=STATUS.DISSEMINATED, + ) + def transition_to_disseminated(self): + logger.info("Transitioning to DISSEMINATED") + self.transition_name.append(SingleAuditChecklist.STATUS.DISSEMINATED) + self.transition_date.append(datetime.now(timezone.utc)) @transition( field="submission_status", @@ -672,30 +720,6 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) -class CognizantBaseline(models.Model): - dbkey = models.IntegerField( - "Identifier for a submission along with audit_year in C-FAC", - null=True, - ) - audit_year = models.IntegerField( - "Audit year from fy_start_date", - null=True, - ) - ein = models.CharField( - "Primary Employer Identification Number", - null=True, - max_length=30, - ) - cognizant_agency = models.CharField( - "Two digit Federal agency prefix of the cognizant agency", - max_length=2, - null=True, - ) - - class Meta: - unique_together = (("dbkey", "audit_year"),) - - class SubmissionEvent(models.Model): class EventType: ACCESS_GRANTED = "access-granted" diff --git a/backend/audit/test_cog_agency.py b/backend/audit/test_cog_agency.py deleted file mode 100644 index c0bcafe1ec..0000000000 --- a/backend/audit/test_cog_agency.py +++ /dev/null @@ -1,217 +0,0 @@ -# TODO - This file should be removed eventually after more testing is done. - Please check with Sudha K. -from django.test import TestCase -from model_bakery import baker -from .models import User -from audit.cog_agency import cog_over_assignment - -# AUDIT_JSON_FIXTURES = Path(__file__).parent / "fixtures" / "json" - -# Note: Fake data is generated for Federal Awards and General. -# Using only the data fields that apply to cog / over assignment. - - -class CogOverAssignmentTests(TestCase): - def __init__(self, methodName: str = "runTest") -> None: - super().__init__(methodName) - - def setUp(self): - self.user = baker.make(User) - self.federal_awards_for_test = self._fake_federal_awards() - self.federal_awards_2019_for_test = self._fake_federal_awards_2019() - self.general_2019_for_test = self._fake_general_2019() - self.federal_awards_for_test2 = self._fake_federal_awards2() - self.federal_awards_2019_for_test3 = self._fake_federal_awards_2019_3() - self.federal_awards_for_test4 = self._fake_federal_awards4() - - @staticmethod - def _fake_federal_awards(): - return { - "FederalAwards": { - "auditee_uei": "ABC123DEF456", - "federal_awards": [ - { - "award_reference": "ABC123", - "program": { - "program_name": "RETIRED AND SENIOR VOLUNTEER PROGRAM", - "amount_expended": 40_000_000, - "federal_agency_prefix": "10", - "federal_program_total": 45_000_000, - "three_digit_extension": "600", - }, - "direct_or_indirect_award": { - "is_direct": "N", - "entities": [ - { - "passthrough_name": "Bob's Granting House", - "passthrough_identifying_number": "12345", - } - ], - }, - }, - { - "award_reference": "ABC124", - "program": { - "program_name": "SENIOR VOLUNTEER PROGRAM", - "amount_expended": 11_000_000, - "federal_agency_prefix": "10", - "federal_program_total": 12_000_000, - "three_digit_extension": "600", - }, - "direct_or_indirect_award": {"is_direct": "Y"}, - }, - ], - "total_amount_expended": 51_000_000, - } - } - - @staticmethod - def _fake_federal_awards_2019(): - return { - "FederalAwards": { - "ein": "731084819", - "federal_awards": [ - { - "amount": 20_000_000, - "direct": "Y", - "cfda": 93123, # Federal agency prefix + 3 digit extension - }, - { - "amount": 31_000_000, - "direct": "Y", - "cfda": 93123, # Federal agency prefix + 3 digit extension - }, - ], - } - } - - @staticmethod - def _fake_federal_awards_2019_3(): - return { - "FederalAwards": { - "ein": "731084819", - "federal_awards": [ - { - "amount": 10_000_000, - "direct": "Y", - "cfda": 15123, # Federal agency prefix + 3 digit extension - }, - { - "amount": 1_000_000, - "direct": "Y", - "cfda": 15123, # Federal agency prefix + 3 digit extension - }, - ], - } - } - - @staticmethod - def _fake_general_2019(): - return { - "General": { - "ein": "731084819", - "totfedexpend": 51_000_000, - } - } - - @staticmethod - def _fake_federal_awards2(): - return { - "FederalAwards": { - "auditee_uei": "ABC123DEF456", - "federal_awards": [ - { - "award_reference": "ABC125", - "program": { - "program_name": "SENIOR VOLUNTEER PROGRAM", - "amount_expended": 11_000_000, - "federal_agency_prefix": "10", - "federal_program_total": 12_000_000, - "three_digit_extension": "600", - }, - "direct_or_indirect_award": {"is_direct": "Y"}, - }, - ], - "total_amount_expended": 11_000_000, - } - } - - @staticmethod - def _fake_federal_awards4(): - return { - "FederalAwards": { - "auditee_uei": "ABC123DEF456", - "federal_awards": [ - { - "award_reference": "ABC125", - "program": { - "program_name": "SENIOR VOLUNTEER PROGRAM", - "amount_expended": 15_000_000, - "federal_agency_prefix": "20", - "federal_program_total": 12_000_000, - "three_digit_extension": "600", - }, - "direct_or_indirect_award": {"is_direct": "Y"}, - }, - ], - "total_amount_expended": 51_000_000, - } - } - - def test_cog_over_for_gt_threshold_cog_2019(self): - # info = json.loads(Path(AUDIT_JSON_FIXTURES / filename).read_text(encoding="utf-8")) - # # info = json.loads(AUDIT_JSON_FIXTURES / filename) - # cfda19 = baker.make(Cfda19, info) - - # fixtures = [AUDIT_JSON_FIXTURES / filename] - # print(fixtures) - - # Test Case #1 - Cog agency from 2019 with Direct Award > 0.25 * total expended - # print( - # "\n\nTest Case 1 - Cog agency from 2019 with Direct Award > 0.25 * total expended" - # ) - cog_agency, over_agency = cog_over_assignment( - self.federal_awards_for_test, - "731084819", - self.federal_awards_2019_for_test, - self.general_2019_for_test, - ) - self.assertEqual(over_agency, 0) - - def test_cog_over_for_gt_threshold_oversight(self): - # Test Case #2 - Oversight agency 2023 with Direct Award > 0.25 * total expended - # print( - # "\n\n Test Case 2 - Oversight agency 2023 with Direct Award > 0.25 * total expended" - # ) - cog_agency, over_agency = cog_over_assignment( - self.federal_awards_for_test2, - "731084818", - self.federal_awards_2019_for_test, - self.general_2019_for_test, - ) - self.assertEqual(cog_agency, 0) - - def test_cog_over_for_lt_threshold_cog_2019(self): - # Test Case #3 - Cog agency from 2019 with Direct Award < 0.25 * total expended - # print( - # "\n\nTest Case 3 - Cog agency from 2019 with Direct Award < 0.25 * total expended" - # ) - cog_agency, over_agency = cog_over_assignment( - self.federal_awards_for_test, - "731084819", - self.federal_awards_2019_for_test3, - self.general_2019_for_test, - ) - self.assertEqual(over_agency, 0) - - def test_cog_over_lt_threshold_oversight(self): - # Test Case #4 - Oversight agency 2023 with Direct Award < 0.25 * total expended - # print( - # "\n\n Test Case 4 - Oversight agency 2023 with Direct Award < 0.25 * total expended" - # ) - cog_agency, over_agency = cog_over_assignment( - self.federal_awards_for_test4, - "731084818", - self.federal_awards_2019_for_test, - self.general_2019_for_test, - ) - self.assertEqual(over_agency, 0) diff --git a/backend/audit/test_intake_to_dissemination.py b/backend/audit/test_intake_to_dissemination.py index 54b2d3bbdb..2be4c9573d 100644 --- a/backend/audit/test_intake_to_dissemination.py +++ b/backend/audit/test_intake_to_dissemination.py @@ -1,11 +1,13 @@ -from datetime import datetime, timezone +from datetime import datetime, time, timezone, timedelta import json from django.test import TestCase - from model_bakery import baker from faker import Faker -from .models import SingleAuditChecklist, User +from audit.models import SingleAuditChecklist, User +from audit.intake_to_dissemination import IntakeToDissemination +from audit.test_views import AUDIT_JSON_FIXTURES, _load_json +from audit.utils import Util from dissemination.models import ( General, FederalAward, @@ -18,8 +20,35 @@ AdditionalEin, AdditionalUei, ) -from audit.intake_to_dissemination import IntakeToDissemination -from audit.utils import Util + + +def _set_transitions_hour(sac, hour): + statuses = [ + SingleAuditChecklist.STATUS.READY_FOR_CERTIFICATION, + SingleAuditChecklist.STATUS.AUDITOR_CERTIFIED, + SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED, + SingleAuditChecklist.STATUS.SUBMITTED, + ] + # Get the current time in UTC + current = datetime.now(timezone.utc).date() + transition_date = datetime.combine( + current, + time( + hour=hour, + minute=0, + second=0, + microsecond=0, + tzinfo=timezone.utc, + ), + ) + + for status in statuses: + sac.transition_date.append(transition_date) + sac.transition_name.append(status) + # Increment the minute by 2 + transition_date += timedelta(minutes=2) + + return sac class IntakeToDisseminationTests(TestCase): @@ -33,11 +62,16 @@ def _run_state_transition(self, sac): SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED, SingleAuditChecklist.STATUS.SUBMITTED, ] + # Get the current time in UTC + transition_date = datetime.now(timezone.utc) for status in statuses: - sac.transition_date.append(datetime.now(timezone.utc)) + sac.transition_date.append(transition_date) sac.transition_name.append(status) - sac.save() + # Increment the minute by 2 + transition_date += timedelta(minutes=2) + + return sac def setUp(self): self.user = baker.make(User) @@ -58,7 +92,7 @@ def setUp(self): cognizant_agency="42", oversight_agency="42", ) - self._run_state_transition(sac) + sac = self._run_state_transition(sac) self.sac = sac self.intake_to_dissemination = IntakeToDissemination(self.sac) self.report_id = sac.report_id @@ -66,7 +100,10 @@ def setUp(self): @staticmethod def _fake_general(): fake = Faker() - return { + geninfofile = "general-information--test0001test--simple-pass.json" + geninfo = _load_json(AUDIT_JSON_FIXTURES / geninfofile) + + return geninfo | { "ein": fake.ssn().replace("-", ""), "audit_type": "single-audit", "auditee_uei": "ZQGGHJH74DW7", @@ -210,6 +247,7 @@ def _fake_notes_to_sefa(): { "note_title": "First Note", "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nVestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus nec tortor ut ligula sollicitudin euismod.", + "contains_chart_or_table": "Y", "seq_number": 1, }, ], @@ -332,6 +370,33 @@ def test_load_general(self): general.gaap_results, ) + def test_submitted_date(self): + """ + The date of submission should be disseminated using the time in American Samoa. + """ + hours = range(0, 24) + + for hour in hours: + with self.subTest(): + self.setUp() + self.sac.transition_date = [] + self.sac.transition_name = [] + sac = _set_transitions_hour(self.sac, hour) + sac.save() + self.intake_to_dissemination.load_general() + self.intake_to_dissemination.save_dissemination_objects() + generals = General.objects.all() + self.assertEqual(len(generals), 1) + general = generals.first() + + # Get the sac submitted date + subdate = self.sac.get_transition_date(self.sac.STATUS.SUBMITTED) + # Calculate the date at UTC-11 (the American Samoa timezone does not do DST) + date_in_american_samoa = (subdate - timedelta(hours=11)).date() + + self.assertEqual(general.submitted_date, date_in_american_samoa) + general.delete() + def test_load_federal_award(self): self.intake_to_dissemination.load_federal_award() self.intake_to_dissemination.save_dissemination_objects() @@ -438,7 +503,7 @@ def test_load_all(self): audit_information=self._fake_audit_information(), auditee_certification=self._fake_auditee_certification(), ) - self._run_state_transition(sac) + sac = self._run_state_transition(sac) self.sac = sac self.intake_to_dissemination = IntakeToDissemination(self.sac) self.intake_to_dissemination.load_all() @@ -464,7 +529,7 @@ def test_load_and_return_objects(self): audit_information=self._fake_audit_information(), auditee_certification=self._fake_auditee_certification(), ) - self._run_state_transition(sac) + sac = self._run_state_transition(sac) self.sac = sac self.intake_to_dissemination = IntakeToDissemination(self.sac) self.report_id = sac.report_id diff --git a/backend/audit/test_schemas.py b/backend/audit/test_schemas.py index 293c768d3b..29c8dd2617 100644 --- a/backend/audit/test_schemas.py +++ b/backend/audit/test_schemas.py @@ -72,7 +72,7 @@ def test_invalid_auditee_fiscal_period_start(self): self.assertRaisesRegex( exceptions.ValidationError, - f"'{bad_date}' is not a 'date'", + "'' was expected", # Value also accepts a blank string, so this error comes back. validate, simple_case, schema, @@ -91,7 +91,7 @@ def test_invalid_auditee_fiscal_period_end(self): self.assertRaisesRegex( exceptions.ValidationError, - f"'{bad_date}' is not a 'date'", + "'' was expected", # Value also accepts a blank string, so this error comes back. validate, simple_case, schema, @@ -129,7 +129,7 @@ def test_invalid_ein(self): with self.assertRaisesRegex( exceptions.ValidationError, - "does not match", + "is not valid", msg=f"ValidationError not raised with EIN = {bad_ein}", ): validate(instance, schema) @@ -261,7 +261,7 @@ def test_invalid_zip(self): with self.assertRaisesRegex( exceptions.ValidationError, - "does not match", + "is not valid", msg=f"ValidationError not raised with zip = {bad_zip}", ): validate(instance, schema) @@ -294,7 +294,7 @@ def test_invalid_zip_plus_4(self): with self.assertRaisesRegex( exceptions.ValidationError, - "does not match", + "is not valid", msg=f"ValidationError not raised with zip = {bad_zip}", ): validate(instance, schema) @@ -357,7 +357,7 @@ def test_invalid_phone(self): with self.assertRaisesRegex( exceptions.ValidationError, - "does not match", + "is not valid", msg=f"ValidationError not raised with phone = {bad_phone}", ): validate(instance, schema) diff --git a/backend/audit/test_views.py b/backend/audit/test_views.py index a5e4060fa7..c3945e498f 100644 --- a/backend/audit/test_views.py +++ b/backend/audit/test_views.py @@ -13,7 +13,7 @@ from openpyxl import load_workbook from openpyxl.cell import Cell -from .fixtures.excel import ( +from audit.fixtures.excel import ( ADDITIONAL_UEIS_TEMPLATE, ADDITIONAL_EINS_TEMPLATE, FEDERAL_AWARDS_TEMPLATE, @@ -32,12 +32,18 @@ NOTES_TO_SEFA_ENTRY_FIXTURES, FORM_SECTIONS, ) -from .fixtures.single_audit_checklist import ( +from audit.fixtures.single_audit_checklist import ( fake_auditor_certification, fake_auditee_certification, ) -from .models import Access, SingleAuditChecklist, SingleAuditReportFile, SubmissionEvent -from .views import MySubmissions +from audit.models import ( + Access, + SingleAuditChecklist, + SingleAuditReportFile, + SubmissionEvent, +) +from audit.cross_validation.naming import NC, SECTION_NAMES as SN +from audit.views import MySubmissions User = get_user_model() @@ -176,12 +182,42 @@ def test_post_redirect(self): The status should be "submitted" after the post. The user should be redirected to the submissions table. """ + filename = "general-information--test0001test--simple-pass.json" + info = _load_json(AUDIT_JSON_FIXTURES / filename) + awardsfile = "federal-awards--test0001test--simple-pass.json" + awards = _load_json(AUDIT_JSON_FIXTURES / awardsfile) + auditor_certification, auditor_signature = fake_auditor_certification() + auditee_certification, auditee_signature = fake_auditee_certification() + user = baker.make(User) sac = baker.make( SingleAuditChecklist, submission_status=SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED, + general_information=info, + audit_information={"stuff": "whatever"}, + federal_awards=awards, + auditor_certification=auditor_certification | auditor_signature, + auditee_certification=auditee_certification | auditee_signature, + corrective_action_plan={ + SN[NC.CORRECTIVE_ACTION_PLAN].camel_case: { + "auditee_uei": "TEST0001TEST" + } + }, + findings_text={ + SN[NC.FINDINGS_TEXT].camel_case: {"auditee_uei": "TEST0001TEST"} + }, + findings_uniform_guidance={ + SN[NC.FINDINGS_UNIFORM_GUIDANCE].camel_case: { + "auditee_uei": "TEST0001TEST" + } + }, + notes_to_sefa={ + SN[NC.NOTES_TO_SEFA].camel_case: {"auditee_uei": "TEST0001TEST"} + }, ) + baker.make(Access, user=user, sac=sac, role="certifying_auditee_contact") + response = _authed_post( Client(), user, @@ -228,19 +264,29 @@ def test_ready_for_certification(self): filename = "general-information--test0001test--simple-pass.json" info = _load_json(AUDIT_JSON_FIXTURES / filename) + auditor_certification, auditor_signature = fake_auditor_certification() + auditee_certification, auditee_signature = fake_auditee_certification() + # Update the SAC so that it will pass overall validation: sac = SingleAuditChecklist.objects.get(report_id=report_id) sac.general_information = info sac.audit_information = {"stuff": "whatever"} - sac.federal_awards = {"FederalAwards": {"federal_awards": []}} + awards = {SN[NC.FEDERAL_AWARDS].camel_case: {"federal_awards": []}} + sac.federal_awards = awards + sac.auditor_certification = auditor_certification | auditor_signature + sac.auditee_certification = auditee_certification | auditee_signature sac.corrective_action_plan = { - "CorrectiveActionPlan": {"auditee_uei": "TEST0001TEST"} + SN[NC.CORRECTIVE_ACTION_PLAN].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.findings_text = { + SN[NC.FINDINGS_TEXT].camel_case: {"auditee_uei": "TEST0001TEST"} } - sac.findings_text = {"FindingsText": {"auditee_uei": "TEST0001TEST"}} sac.findings_uniform_guidance = { - "FindingsUniformGuidance": {"auditee_uei": "TEST0001TEST"} + SN[NC.FINDINGS_UNIFORM_GUIDANCE].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.notes_to_sefa = { + SN[NC.NOTES_TO_SEFA].camel_case: {"auditee_uei": "TEST0001TEST"} } - sac.notes_to_sefa = {"NotesToSefa": {"auditee_uei": "TEST0001TEST"}} baker.make(SingleAuditReportFile, sac=sac) sac.save() @@ -267,6 +313,18 @@ def test_auditor_certification(self): user, sac = _make_user_and_sac(submission_status="ready_for_certification") baker.make(Access, sac=sac, user=user, role="certifying_auditor_contact") + sac.corrective_action_plan = { + SN[NC.CORRECTIVE_ACTION_PLAN].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.findings_text = { + SN[NC.FINDINGS_TEXT].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.findings_uniform_guidance = { + SN[NC.FINDINGS_UNIFORM_GUIDANCE].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.notes_to_sefa = { + SN[NC.NOTES_TO_SEFA].camel_case: {"auditee_uei": "TEST0001TEST"} + } kwargs = {"report_id": sac.report_id} _authed_post( @@ -306,6 +364,18 @@ def test_auditee_certification(self): user, sac = _make_user_and_sac(submission_status="auditor_certified") baker.make(Access, sac=sac, user=user, role="certifying_auditee_contact") + sac.corrective_action_plan = { + SN[NC.CORRECTIVE_ACTION_PLAN].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.findings_text = { + SN[NC.FINDINGS_TEXT].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.findings_uniform_guidance = { + SN[NC.FINDINGS_UNIFORM_GUIDANCE].camel_case: {"auditee_uei": "TEST0001TEST"} + } + sac.notes_to_sefa = { + SN[NC.NOTES_TO_SEFA].camel_case: {"auditee_uei": "TEST0001TEST"} + } kwargs = {"report_id": sac.report_id} _authed_post( @@ -341,7 +411,37 @@ def test_submission(self): """ Test that certifying auditee contacts can perform submission """ - user, sac = _make_user_and_sac(submission_status="auditee_certified") + + geninfofile = "general-information--test0001test--simple-pass.json" + geninfo = _load_json(AUDIT_JSON_FIXTURES / geninfofile) + awardsfile = "federal-awards--test0001test--simple-pass.json" + awards = _load_json(AUDIT_JSON_FIXTURES / awardsfile) + auditor_certification, auditor_signature = fake_auditor_certification() + auditee_certification, auditee_signature = fake_auditee_certification() + user, sac = _make_user_and_sac( + auditee_certification=auditee_certification | auditee_signature, + auditor_certification=auditor_certification | auditor_signature, + corrective_action_plan={ + SN[NC.CORRECTIVE_ACTION_PLAN].camel_case: { + "auditee_uei": "TEST0001TEST" + } + }, + federal_awards=awards, + findings_text={ + SN[NC.FINDINGS_TEXT].camel_case: {"auditee_uei": "TEST0001TEST"} + }, + findings_uniform_guidance={ + SN[NC.FINDINGS_UNIFORM_GUIDANCE].camel_case: { + "auditee_uei": "TEST0001TEST" + } + }, + general_information=geninfo, + notes_to_sefa={ + SN[NC.NOTES_TO_SEFA].camel_case: {"auditee_uei": "TEST0001TEST"} + }, + submission_status="auditee_certified", + ) + baker.make(Access, sac=sac, user=user, role="certifying_auditee_contact") kwargs = {"report_id": sac.report_id} diff --git a/backend/audit/validators.py b/backend/audit/validators.py index b70d77132a..d68d26adc4 100644 --- a/backend/audit/validators.py +++ b/backend/audit/validators.py @@ -241,6 +241,22 @@ def validate_general_information_json(value): return value +def validate_general_information_complete_json(value): + """ + Apply JSON Schema for general information completeness and report errors. + """ + schema_path = settings.SECTION_SCHEMA_DIR / "GeneralInformationComplete.schema.json" + schema = json.loads(schema_path.read_text(encoding="utf-8")) + + try: + validate(value, schema, format_checker=FormatChecker()) + except JSONSchemaValidationError as err: + raise ValidationError( + _(err.message), + ) from err + return value + + def validate_audit_information_json(value): """ Apply JSON Schema for audit information and report errors. diff --git a/backend/audit/views.py b/backend/audit/views.py index 04c6761f79..0d35d1af90 100644 --- a/backend/audit/views.py +++ b/backend/audit/views.py @@ -718,6 +718,14 @@ def post(self, request, *args, **kwargs): sac.save( event_user=request.user, event_type=SubmissionEvent.EventType.SUBMITTED ) + disseminated = sac.disseminate() + # FIXME: We should now provide a reasonable error to the user. + if disseminated is None: + sac.transition_to_disseminated() + + logger.info( + "Dissemination errors: %s, report_id: %s", disseminated, report_id + ) return redirect(reverse("audit:MySubmissions")) diff --git a/backend/backup_database.sh b/backend/backup_database.sh new file mode 100755 index 0000000000..507050a3d1 --- /dev/null +++ b/backend/backup_database.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +echo "Environment set as: $1" +export PATH=/home/vcap/deps/0/apt/usr/lib/postgresql/15/bin:$PATH +date=$(date '+%Y-%m-%d-%H%M') +python manage.py dbbackup -o "$1-db-backup-$date.psql.bin" +python manage.py mediabackup -o "$1-media-backup-$date.tar" diff --git a/backend/config/context_processors.py b/backend/config/context_processors.py new file mode 100644 index 0000000000..f593fc7970 --- /dev/null +++ b/backend/config/context_processors.py @@ -0,0 +1,17 @@ +from config import settings + + +def static_site_url(request): + """ + Returns the STATIC_SITE_URL. This is added to the context of every template request + made on the site. Used most frequently for links in the primary nav. + """ + return {"STATIC_SITE_URL": settings.STATIC_SITE_URL} + + +def omb_num_exp_date(request): + """ + Returns the OMB_NUMBER (str) and OMB_EXP_DATE (str) in template context form. + Displayed as a legal requirement on the header of every page. + """ + return {"OMB_NUMBER": settings.OMB_NUMBER, "OMB_EXP_DATE": settings.OMB_EXP_DATE} diff --git a/backend/config/settings.py b/backend/config/settings.py index 34e0b28a57..a521515365 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -109,6 +109,7 @@ "corsheaders", "storages", "djangooidc", + "dbbackup", ] # Our apps @@ -120,6 +121,7 @@ "cms", # "data_distro", "dissemination", + "support", ] MIDDLEWARE = [ @@ -146,6 +148,9 @@ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", + "config.context_processors.static_site_url", + "config.context_processors.omb_num_exp_date", + "report_submission.context_processors.certifiers_emails_must_not_match", ], "builtins": [ "report_submission.templatetags.get_attr", @@ -249,6 +254,10 @@ DISABLE_AUTH = env.bool("DISABLE_AUTH", default=False) + # Used for backing up the database https://django-dbbackup.readthedocs.io/en/master/installation.html + DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage" + DBBACKUP_STORAGE_OPTIONS = {"location": BASE_DIR / "backup"} + else: # One of the Cloud.gov environments STATICFILES_STORAGE = "storages.backends.s3boto3.S3ManifestStaticStorage" @@ -302,6 +311,17 @@ f"https://{AWS_S3_PRIVATE_CUSTOM_DOMAIN}/{AWS_PRIVATE_LOCATION}/" ) + elif service["instance_name"] == "backups": + s3_creds = service["credentials"] + # Used for backing up the database https://django-dbbackup.readthedocs.io/en/master/storage.html#id2 + DBBACKUP_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" + DBBACKUP_STORAGE_OPTIONS = { + "access_key": s3_creds["access_key_id"], + "secret_key": s3_creds["secret_access_key"], + "bucket_name": s3_creds["bucket"], + "default_acl": "private", # type: ignore + } + # secure headers MIDDLEWARE.append("csp.middleware.CSPMiddleware") # see settings options https://django-csp.readthedocs.io/en/latest/configuration.html#configuration-chapter @@ -484,5 +504,9 @@ ] + MIDDLEWARE DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _: True} -# Links to the most applicable static site URL. Becomes more permanent post-beta. -STATIC_SITE_URL = "https://federalist-35af9df5-a894-4ae9-aa3d-f6d95427c7bc.sites.pages.cloud.gov/preview/gsa-tts/fac-transition-site/lh/ia-updates/" +# Link to the most applicable static site URL. Passed in context to all templates. +STATIC_SITE_URL = "https://fac.gov/" + +# OMB-assigned values. Number doesn't change, date does. +OMB_NUMBER = "3090-0330" +OMB_EXP_DATE = "09/30/2026" diff --git a/backend/cypress/e2e/additional-eins.cy.js b/backend/cypress/e2e/additional-eins.cy.js new file mode 100644 index 0000000000..2794a398a2 --- /dev/null +++ b/backend/cypress/e2e/additional-eins.cy.js @@ -0,0 +1,65 @@ +import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; + +import { + testWorkbookAdditionalEINs, + testWorkbookFederalAwards, +} from '../support/workbook-uploads.js'; + +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Additional EINs page', () => { + before(() => { + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); + }); + + it('Additional EINs uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Additional EINs").click(); + testWorkbookAdditionalEINs(false); + }); + + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Additional EINs'); + }); + +}); \ No newline at end of file diff --git a/backend/cypress/e2e/additional-ueis.cy.js b/backend/cypress/e2e/additional-ueis.cy.js new file mode 100644 index 0000000000..9af561c301 --- /dev/null +++ b/backend/cypress/e2e/additional-ueis.cy.js @@ -0,0 +1,65 @@ +import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; + +import { + testWorkbookAdditionalUEIs, + testWorkbookFederalAwards, +} from '../support/workbook-uploads.js'; + +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Additional UEIs page', () => { + before(() => { + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); + }); + + it('Additional UEIs uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Additional UEIs").click(); + testWorkbookAdditionalUEIs(false); + }); + + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Additional UEIs'); + }); + +}); \ No newline at end of file diff --git a/backend/cypress/e2e/audit-findings-text.cy.js b/backend/cypress/e2e/audit-findings-text.cy.js index 0cf07c56d2..37ac483b79 100644 --- a/backend/cypress/e2e/audit-findings-text.cy.js +++ b/backend/cypress/e2e/audit-findings-text.cy.js @@ -1,40 +1,65 @@ import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; -describe('Audit findings text page', () => { - const reportTestId = '2023MAY0001000001' +import { + testWorkbookFederalAwards, + testWorkbookFindingsText, +} from '../support/workbook-uploads.js'; +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Audit Findings Text page', () => { before(() => { - cy.visit(`/report_submission/audit-findings-text/${reportTestId}`); + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); }); - it('Page loads successfully', () => { - cy.url().should('include', `/report_submission/audit-findings-text/${reportTestId}`); + + it('Audit Findings Text uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Federal Awards Audit Findings Text").click(); + testWorkbookFindingsText(false); }); - describe('findings text workbook upload successful', () => { - it('Successfully uploads audit findings text', () => { - cy.intercept('/audit/excel/findings-text/*', { - fixture: 'success-res.json', - }).as('uploadSuccess') - cy.visit(`/report_submission/audit-findings-text/${reportTestId}`); - cy.get('#file-input-audit-findings-text-xlsx').attachFile('findings-text-UPDATE.xlsx'); - cy.wait('@uploadSuccess').its('response.statusCode').should('eq', 200) - cy.wait(2000).get('#info_box').should('have.text', 'File successfully validated! Your work has been saved.'); - cy.get('#continue').click(); - cy.url().should('contain', `/audit/submission-progress/${reportTestId}`); - }) + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Federal Awards Audit Findings Text'); }); - describe('File upload fail', () => { - it('unsuccessful upload audit findings text', () => { - cy.intercept('POST', '/audit/excel/findings-text/*', { - statusCode: 400, - fixture: 'fail-res.json', - }).as('uploadFail') - cy.visit(`/report_submission/audit-findings-text/${reportTestId}`); - cy.get('#file-input-audit-findings-text-xlsx').attachFile('federal-awards-Test.xlsx'); - cy.wait('@uploadFail').its('response.statusCode').should('eq', 400) - cy.wait(2000).get('#info_box').should('contain', 'A field is missing'); - }) - }) - -}); \ No newline at end of file +}); diff --git a/backend/cypress/e2e/audit-findings.cy.js b/backend/cypress/e2e/audit-findings.cy.js index 83ae298a06..f4a3445f21 100644 --- a/backend/cypress/e2e/audit-findings.cy.js +++ b/backend/cypress/e2e/audit-findings.cy.js @@ -1,39 +1,69 @@ import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; -describe('Audit findings page', () => { - const reportTestId = '2023MAY0001000001' +import { + testWorkbookFederalAwards, + testWorkbookFindingsUniformGuidance, +} from '../support/workbook-uploads.js'; +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Audit Findings page', () => { before(() => { - cy.visit(`/report_submission/audit-findings/${reportTestId}`); + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); }); - it('Page loads successfully', () => { - cy.url().should('include', `/report_submission/audit-findings/${reportTestId}`); + + it('Audit Findings uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + + testValidGeneralInfo(); + + cy.get('.usa-link').contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Federal Awards Audit Findings").click(); + testWorkbookFindingsUniformGuidance(false); + }); - describe('File upload successful', () => { - it('Successfully uploads audit findings', () => { - cy.intercept('/audit/excel/findings-uniform-guidance/*', { - fixture: 'success-res.json', - }).as('uploadSuccess') - cy.visit(`/report_submission/audit-findings/${reportTestId}`); - cy.get('#file-input-audit-findings-xlsx').attachFile('findings-uniform-guidance-UPDATE.xlsx'); - cy.wait('@uploadSuccess').its('response.statusCode').should('eq', 200) - cy.wait(2000).get('#info_box').should('have.text', 'File successfully validated! Your work has been saved.'); - cy.get('#continue').click(); - }) + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Federal Awards Audit Findings'); }); - describe('File upload fail', () => { - it('unsuccessful upload audit findings', () => { - cy.intercept('POST', '/audit/excel/findings-uniform-guidance/*', { - statusCode: 400, - fixture: 'fail-res.json', - }).as('uploadFail') - cy.visit(`/report_submission/audit-findings/${reportTestId}`); - cy.get('#file-input-audit-findings-xlsx').attachFile('federal-awards-Test.xlsx'); - cy.wait('@uploadFail').its('response.statusCode').should('eq', 400) - cy.wait(2000).get('#info_box').should('contain', 'A field is missing'); - }) - }) - -}); \ No newline at end of file +}); + + + diff --git a/backend/cypress/e2e/audit-report-pdf.cy.js b/backend/cypress/e2e/audit-report-pdf.cy.js new file mode 100644 index 0000000000..4e00a002a7 --- /dev/null +++ b/backend/cypress/e2e/audit-report-pdf.cy.js @@ -0,0 +1,66 @@ +import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testPdfAuditReport } from '../support/report-pdf.js'; + +import { + testWorkbookFederalAwards, +} from '../support/workbook-uploads.js'; + +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Audit report PDF page', () => { + before(() => { + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); + }); + + it('Audit report PDF uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Audit report PDF").click(); + testPdfAuditReport(false); + }); + + it('Displays message if file has already been uploaded', () => { + cy.visit(`/audit/`); + cy.url().should('match', /\/audit\//); + cy.get(':nth-child(4) > .usa-table > tbody > tr').last().find('td:nth-child(1)>.usa-link').click(); + cy.get('.usa-link').contains('Edit the Audit report PDF').click(); + cy.get('#already-submitted') + .invoke('text') + .then((text) => { + const expectedText = 'A file has already been uploaded for this section. A successful reupload will overwrite your previous submission.'; + expect(text.trim()).to.equal(expectedText); + }); + }); + + }); \ No newline at end of file diff --git a/backend/cypress/e2e/check-ueid.cy.js b/backend/cypress/e2e/check-ueid.cy.js index a5cd1ad962..7af4422b29 100644 --- a/backend/cypress/e2e/check-ueid.cy.js +++ b/backend/cypress/e2e/check-ueid.cy.js @@ -122,6 +122,17 @@ describe('Create New Audit', () => { cy.get('#auditee_fiscal_period_end').type('01/31/2022').blur(); cy.get('#auditee_fiscal_period_end-not-null').should('not.be.visible'); }); + + it('should ensure that the end date is later than the start date', () => { + cy.get('#auditee_fiscal_period_start').type('12/31/2022').blur(); + cy.get('#auditee_fiscal_period_end').type('11/31/2022').blur(); + cy.get('#auditee_fiscal_period_end-date-order').should('be.visible'); + }); + + it('should remove the error message when valid end date is supplied', () => { + cy.get('#auditee_fiscal_period_end').type('01/31/2023').blur(); + cy.get('#auditee_fiscal_period_end-date-order').should('not.be.visible'); + }); }); describe('UEI Validation via API', () => { diff --git a/backend/cypress/e2e/corrective-action-plan.cy.js b/backend/cypress/e2e/corrective-action-plan.cy.js index 36068033b6..06a8086ec1 100644 --- a/backend/cypress/e2e/corrective-action-plan.cy.js +++ b/backend/cypress/e2e/corrective-action-plan.cy.js @@ -1,39 +1,66 @@ import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; -describe('Audit findings page', () => { - const reportTestId = '2023MAY0001000001' +import { + testWorkbookFederalAwards, + testWorkbookCorrectiveActionPlan, +} from '../support/workbook-uploads.js'; +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Corrective Action Plan page', () => { before(() => { - cy.visit(`/report_submission/CAP/${reportTestId}`); + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); }); - it('Page loads successfully', () => { - cy.url().should('include', `/report_submission/CAP/${reportTestId}`); + + it('Corrective Action Plan uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Corrective Action Plan").click(); + testWorkbookCorrectiveActionPlan(false); }); - describe('File upload successful', () => { - it('Successfully uploads CAP', () => { - cy.intercept('/audit/excel/corrective-action-plan/*', { - fixture: 'success-res.json', - }).as('uploadSuccess') - cy.visit(`/report_submission/CAP/${reportTestId}`); - cy.get('#file-input-CAP-xlsx').attachFile('corrective-action-plan-UPDATE.xlsx'); - cy.wait('@uploadSuccess').its('response.statusCode').should('eq', 200) - cy.wait(2000).get('#info_box').should('have.text', 'File successfully validated! Your work has been saved.'); - cy.get('#continue').click(); - cy.url().should('contain', `/audit/submission-progress/${reportTestId}`); - }) - - describe('File upload fail', () => { - it('unsuccessful upload CAP', () => { - cy.intercept('POST', '/audit/excel/corrective-action-plan/*', { - statusCode: 400, - fixture: 'fail-res.json', - }).as('uploadFail') - cy.visit(`/report_submission/CAP/${reportTestId}`); - cy.get('#file-input-CAP-xlsx').attachFile('cap-invalid.xlsx'); - cy.wait('@uploadFail').its('response.statusCode').should('eq', 400) - cy.wait(2000).get('#info_box').should('contain', 'A field is missing'); - }) - }) + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Corrective Action Plan'); }); -}); \ No newline at end of file + +}); + diff --git a/backend/cypress/e2e/federal-awards.cy.js b/backend/cypress/e2e/federal-awards.cy.js index fc6c1b28e9..daaef88359 100644 --- a/backend/cypress/e2e/federal-awards.cy.js +++ b/backend/cypress/e2e/federal-awards.cy.js @@ -1,51 +1,62 @@ import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; -describe('Federal awards page', () => { - const reportTestId = '2023MAY0001000001' +import { + testWorkbookFederalAwards, +} from '../support/workbook-uploads.js'; - before(() => { - cy.visit(`/report_submission/federal-awards/${reportTestId}`); - }); - it('Page loads successfully', () => { - cy.url().should('include', `/report_submission/federal-awards/${reportTestId}`); - }); +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); - describe('File upload successful', () => { - it('Successfully uploads Federal Awards', () => { - cy.intercept('/audit/excel/federal-awards-expended/*', { - fixture: 'success-res.json', - }).as('uploadSuccess') - cy.visit(`report_submission/federal-awards/${reportTestId}`); - cy.get('#file-input-federal-awards-xlsx').attachFile('federal-awards-expended-PASS.xlsx'); - cy.wait('@uploadSuccess').its('response.statusCode').should('eq', 200) - cy.wait(2000).get('#info_box').should('have.text', 'File successfully validated! Your work has been saved.'); - cy.get('#continue').click(); - cy.url().should('contain', `/audit/submission-progress/${reportTestId}`); +describe('Federal awards page', () => { + before(() => { + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); }); }); - describe('File already uploaded', () => { - it('Displays message if file has already been uploaded', () => { - cy.visit(`/report_submission/federal-awards/${reportTestId}`); - cy.get('#already-submitted') - .invoke('text') - .then((text) => { - const expectedText = 'A file has already been uploaded for this section. A successful reupload will overwrite your previous submission.'; - expect(text.trim()).to.equal(expectedText); - }); + it('Federal Awards uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); }); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); }); - describe('File upload failure', () => { - it('unsuccessful upload Federal Award', () => { - cy.intercept('/audit/excel/federal-awards-expended/*', { - statusCode: 400, - fixture: 'fail-res.json', - }).as('uploadFail'); - cy.visit(`/report_submission/federal-awards/${reportTestId}`); - cy.get('#file-input-federal-awards-xlsx').attachFile('fed-awards-invalid.xlsx'); - cy.wait('@uploadFail').its('response.statusCode').should('eq', 400) - cy.wait(2000).get('#info_box').should('contain', 'A field is missing'); - }); + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Federal Awards'); }); + }); + diff --git a/backend/cypress/e2e/full-submission.cy.js b/backend/cypress/e2e/full-submission.cy.js index 107a3fd2e9..1c02d57c1f 100644 --- a/backend/cypress/e2e/full-submission.cy.js +++ b/backend/cypress/e2e/full-submission.cy.js @@ -9,6 +9,7 @@ import { testAuditInformationForm } from '../support/audit-info-form.js'; import { testPdfAuditReport } from '../support/report-pdf.js'; import { testAuditorCertification } from '../support/auditor-certification.js'; import { testAuditeeCertification } from '../support/auditee-certification.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; import { testWorkbookFederalAwards, testWorkbookNotesToSEFA, @@ -23,6 +24,7 @@ import { const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); +const API_GOV_JWT = Cypress.env('API_GOV_JWT'); describe('Full audit submission', () => { before(() => { @@ -53,6 +55,12 @@ describe('Full audit submission', () => { // Now the accessandsubmission screen testValidAccess(); + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + // Fill out the general info form testValidGeneralInfo(); @@ -80,11 +88,10 @@ describe('Full audit submission', () => { cy.get(".usa-link").contains("Secondary Auditors").click(); testWorkbookSecondaryAuditors(false); - - + cy.get(".usa-link").contains("Additional EINs").click(); testWorkbookAdditionalEINs(false); - + // Complete the audit information form cy.get(".usa-link").contains("Audit Information form").click(); testAuditInformationForm(); @@ -96,9 +103,8 @@ describe('Full audit submission', () => { cy.get(".usa-link").contains("Auditor Certification").click(); testAuditorCertification(); - // Auditee certification + // Grab the report ID from the URL cy.url().then(url => { - // Grab the report ID from the URL const reportId = url.split('/').pop(); testLogoutGov(); @@ -112,14 +118,24 @@ describe('Full audit submission', () => { cy.visit(`/audit/submission-progress/${reportId}`); + // Auditee certification cy.get(".usa-link").contains("Auditee Certification").click(); testAuditeeCertification(); - }) - cy.get(".usa-link").contains("Submit to the FAC for processing").click(); - cy.url().should('match', /\/audit\/submission\/[0-9A-Z]{17}/); - cy.get('#continue').click(); - cy.url().should('match', /\/audit\//); - + // Submit + cy.get(".usa-link").contains("Submit to the FAC for processing").click(); + cy.url().should('match', /\/audit\/submission\/[0-9A-Z]{17}/); + cy.get('#continue').click(); + cy.url().should('match', /\/audit\//); + + // The report ID should be found in the Completed Audits table + cy.get('.usa-table').contains( + 'caption', + 'The audits listed below have been submitted to the FAC for processing and may not be edited.', + ).siblings().contains('td', reportId); + + // Report should now be in the dissemination table + testReportIdFound(reportId); + }); }); }); diff --git a/backend/cypress/e2e/notes-to-sefa.cy.js b/backend/cypress/e2e/notes-to-sefa.cy.js new file mode 100644 index 0000000000..86eee54d3b --- /dev/null +++ b/backend/cypress/e2e/notes-to-sefa.cy.js @@ -0,0 +1,65 @@ +import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; + +import { + testWorkbookFederalAwards, + testWorkbookNotesToSEFA +} from '../support/workbook-uploads.js'; + +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Notes to SEFA page', () => { + before(() => { + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); + }); + + it('Notes to SEFA uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Notes to SEFA").click(); + testWorkbookNotesToSEFA(false); + }); + + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Notes to SEFA'); + }); + +}); diff --git a/backend/cypress/e2e/secondary-auditors.cy.js b/backend/cypress/e2e/secondary-auditors.cy.js new file mode 100644 index 0000000000..6bb7ec3719 --- /dev/null +++ b/backend/cypress/e2e/secondary-auditors.cy.js @@ -0,0 +1,65 @@ +import 'cypress-file-upload'; +import { testCrossValidation } from '../support/cross-validation.js'; +import { testLoginGovLogin } from '../support/login-gov.js'; +//import { testLogoutGov } from '../support/logout-gov.js'; +import { testValidAccess } from '../support/check-access.js'; +import { testValidEligibility } from '../support/check-eligibility.js'; +import { testValidAuditeeInfo } from '../support/auditee-info.js'; +import { testValidGeneralInfo } from '../support/general-info.js'; +import { testReportIdFound, testReportIdNotFound } from '../support/dissemination-table.js'; +import { testFileUploadMsg } from '../support/file-uploaded-msg.js'; + +import { + testWorkbookSecondaryAuditors, + testWorkbookFederalAwards, +} from '../support/workbook-uploads.js'; + +const LOGIN_TEST_EMAIL_AUDITEE = Cypress.env('LOGIN_TEST_EMAIL_AUDITEE'); +const LOGIN_TEST_PASSWORD_AUDITEE = Cypress.env('LOGIN_TEST_PASSWORD_AUDITEE'); +const LOGIN_TEST_OTP_SECRET_AUDITEE = Cypress.env('LOGIN_TEST_OTP_SECRET_AUDITEE'); + +describe('Secondary Auditors page', () => { + before(() => { + cy.session('login-session', () => { + cy.visit('/'); + cy.login(); + }); + }); + + it('Secondary auditors uploads successfully', () => { + cy.visit('/'); + + cy.url().should('include', '/'); + + cy.get('label[for=check-start-new-submission]').click(); + + cy.get('.usa-button').contains('Accept and start').click(); + + cy.url().should('match', /\/report_submission\/eligibility\/$/); + + testValidEligibility(); + + testValidAuditeeInfo(); + + testValidAccess(); + + // Report should not yet be in the dissemination table + cy.url().then(url => { + const reportId = url.split('/').pop(); + testReportIdNotFound(reportId); + }); + + testValidGeneralInfo(); + + cy.get(".usa-link").contains("Federal Awards").click(); + testWorkbookFederalAwards(false); + + cy.get(".usa-link").contains("Secondary Auditors").click(); + testWorkbookSecondaryAuditors(false); + }); + + it('Displays message if file has already been uploaded', () => { + testFileUploadMsg('Edit the Secondary Auditors'); + }); + +}); \ No newline at end of file diff --git a/backend/cypress/fixtures/test_workbooks/notes-to-sefa-workbook.xlsx b/backend/cypress/fixtures/test_workbooks/notes-to-sefa-workbook.xlsx index c3435d19af..2d380b5b15 100644 Binary files a/backend/cypress/fixtures/test_workbooks/notes-to-sefa-workbook.xlsx and b/backend/cypress/fixtures/test_workbooks/notes-to-sefa-workbook.xlsx differ diff --git a/backend/cypress/support/commands.js b/backend/cypress/support/commands.js index 556839458e..19aa4caff5 100644 --- a/backend/cypress/support/commands.js +++ b/backend/cypress/support/commands.js @@ -26,60 +26,6 @@ //import '@cypress-audit/lighthouse/commands'; //import '@cypress-audit/pa11y/commands'; import 'cypress-file-upload'; - -// Cypress.Commands.add('login', (email, password) => { -// cy.session([email, password], () => { -// cy.visit('/'); -// cy.get('a.usa-button.sign-in-button').click(); - -// cy.origin( -// 'https://idp.int.identitysandbox.gov/', -// { args: [email, password] }, -// ([email, password]) => { -// cy.get('#user_email').type(email); -// cy.get('input[id^="password-toggle-input-"]').type(password); -// cy.get('lg-submit-button > .usa-button').click(); -// } - -// ) -// cy.url().should('contain', 'https://idp.int.identitysandbox.gov/login/two_factor/authenticator'); -// }) -// }) - -// Cypress.Commands.add('login', () => { -// const email = 'edward.zapata@gsa.gov'; -// const password = 'Testsitechange1!'; -// cy.visit('/'); -// cy.get('a.usa-button.sign-in-button').click(); -// cy.get('button.usa-button.sign-in-button') -// .should('contain.text', 'Authenticate with Login.gov').click(); -// cy.origin('https://idp.int.identitysandbox.gov/', () => { -// cy.get('#user_email').type(email); -// cy.get('input[id^="password-toggle-input-"]').type(password); -// cy.get('lg-submit-button > .usa-button').click(); -// cy.url().should('contain', 'https://idp.int.identitysandbox.gov/login/two_factor/authenticator'); -// //cy.get('#code-776936') -// }) -//}) - -// Cypress.Commands.add('login', (email, password) => { -// cy.visit('/'); -// cy.get('a.usa-button.sign-in-button').click(); - -// cy.origin( -// 'https://idp.int.identitysandbox.gov/', -// { args: [email, password] }, -// ([email, password]) => { -// cy.get('#user_email').type(email); -// cy.get('input[id^="password-toggle-input-"]').type(password); -// cy.get('lg-submit-button > .usa-button').click(); -// } - -// ) -// cy.url().should('contain', 'https://idp.int.identitysandbox.gov/login/two_factor/authenticator'); -// }) -// - import { testLoginGovLogin } from './login-gov.js'; import { testLogoutGov } from './logout-gov.js'; diff --git a/backend/cypress/support/dissemination-table.js b/backend/cypress/support/dissemination-table.js new file mode 100644 index 0000000000..13c22e279c --- /dev/null +++ b/backend/cypress/support/dissemination-table.js @@ -0,0 +1,36 @@ +/* + Re-useable code for testing the dissemination table. +*/ + +const API_GOV_JWT = Cypress.env('API_GOV_JWT') || ''; +const API_GOV_KEY = Cypress.env('API_GOV_KEY') || ''; +const API_GOV_URL = Cypress.env('API_GOV_URL'); + +const requestOptions = { + method: 'GET', + url: `${API_GOV_URL}/general`, + headers: { + Authorization: `Bearer ${API_GOV_JWT}`, + 'X-Api-Key': API_GOV_KEY, + }, +} + +export function testReportIdNotFound(reportId) { + cy.request({ + ...requestOptions, + qs: {report_id: `eq.${reportId}`}, + }).should((response) => { + expect(response.body).to.have.length(0); + }); +} + +export function testReportIdFound(reportId) { + cy.request({ + ...requestOptions, + qs: {report_id: `eq.${reportId}`}, + }).should((response) => { + expect(response.body).to.have.length(1); + const hasAgency = !!(response.body[0]?.cognizant_agency || response.body[0]?.oversight_agency); + expect(hasAgency).to.be.true; + }); +} diff --git a/backend/cypress/support/file-uploaded-msg.js b/backend/cypress/support/file-uploaded-msg.js new file mode 100644 index 0000000000..3235abc977 --- /dev/null +++ b/backend/cypress/support/file-uploaded-msg.js @@ -0,0 +1,13 @@ + +export function testFileUploadMsg(fileSectionName) { + cy.visit(`/audit/`); + cy.url().should('match', /\/audit\//); + cy.get(':nth-child(4) > .usa-table > tbody > tr').last().find('td:nth-child(1)>.usa-link').click(); + cy.get('.usa-link').contains(fileSectionName).click(); + cy.get('#already-submitted') + .invoke('text') + .then((text) => { + const expectedText = 'A file has already been uploaded for this section. A successful reupload will overwrite your previous submission.'; + expect(text.trim()).to.equal(expectedText); + }); + } \ No newline at end of file diff --git a/backend/cypress/support/general-info.js b/backend/cypress/support/general-info.js index cb3d3c17f0..716fa58343 100644 --- a/backend/cypress/support/general-info.js +++ b/backend/cypress/support/general-info.js @@ -1,42 +1,50 @@ // reusable code for filling out a valid general info form export function testValidGeneralInfo() { - //auditee info + // Fiscal period, pre-filled using info from the previous screen. + //cy.get('#auditee_fiscal_period_start').type('05/08/2023'); + //cy.get('#auditee_fiscal_period_end').type('05/08/2024'); + + // Audit Type cy.get('label[for=single-audit]').click(); cy.get('label[for=audit-period-annual]').click(); - // these both have dates from a previous screen - //cy.get('#auditee_fiscal_period_start').type('05/08/2023'); - //cy.get('#auditee_fiscal_period_end').type('05/08/2024'); - // this has a name from the previous screen - //cy.get('#auditee_name').type('Commonwealth of Virginia'); + // Auditee information + cy.get('#ein').type('546000173'); + cy.get('label[for=ein_not_an_ssn_attestation]').click(); + cy.get('label[for=multiple-eins-yes]').click(); cy.get('#auditee_address_line_1').type('1111 E Broad ST'); cy.get('#auditee_city').type('Richmond'); cy.get('#auditee_state').type('VA{enter}'); cy.get('#auditee_zip').type('23219'); - // there should already be a UEI in this box + + // Auditee UEI is pre-filled and uneditable. // cy.get('#auditee_uei').type('CMBSGK6P7BE1'); cy.get('label[for=multiple-ueis-yes]').click(); - cy.get('#ein').type('546000173'); - cy.get('label[for=ein_not_an_ssn_attestation]').click(); - cy.get('label[for=multiple-eins-yes]').click(); + + // Auditee contact information cy.get('#auditee_contact_name').type('John Doe'); cy.get('#auditee_contact_title').type('Keymaster'); cy.get('#auditee_phone').type('5558675309'); cy.get('#auditee_email').type('va@test'); - //auditor info + // Auditor information + cy.get('#auditor_ein').type('987654321'); + cy.get('label[for=auditor_ein_not_an_ssn_attestation]').click(); cy.get('#auditor_firm_name').type('House of Audit'); - cy.get('#auditor_country').type('USA{enter}'); + // Pre-filled as USA + // cy.get('#auditor_country').type('USA{enter}'); cy.get('#auditor_address_line_1').type('123 Around the corner'); cy.get('#auditor_city').type('Centreville'); cy.get('#auditor_state').type('VA{enter}'); cy.get('#auditor_zip').type('20121'); - cy.get('#auditor_ein').type('987654321'); + + // Auditor contact information cy.get('#auditor_contact_name').type('Jane Doe'); cy.get('#auditor_contact_title').type('Auditor'); cy.get('#auditor_phone').type('5555555555'); cy.get('#auditor_email').type('qualified.human.accountant@auditor'); + cy.get('label[for=secondary_auditors-yes]').click(); cy.get('#continue').click(); diff --git a/backend/data_fixtures/audit/excel_schema_test_files/notes-to-sefa-pass-01.json b/backend/data_fixtures/audit/excel_schema_test_files/notes-to-sefa-pass-01.json index f2ea6343ed..b3e1123aa4 100644 --- a/backend/data_fixtures/audit/excel_schema_test_files/notes-to-sefa-pass-01.json +++ b/backend/data_fixtures/audit/excel_schema_test_files/notes-to-sefa-pass-01.json @@ -11,13 +11,15 @@ { "note_title": "First Note", "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nVestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus nec tortor ut ligula sollicitudin euismod.", + "contains_chart_or_table": "N", "seq_number": 1 }, { "note_title": "Second Note", "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nNulla facilisis urna et sem congue, ac pharetra ipso lorem sagittis. Maecenas sed nunc vel neque dignissim consequat.", + "contains_chart_or_table": "Y", "seq_number": 2 } ] } -} \ No newline at end of file +} diff --git a/backend/data_fixtures/audit/test_data_entries/notes-to-sefa-entries.json b/backend/data_fixtures/audit/test_data_entries/notes-to-sefa-entries.json index 85f7113ceb..1f84d65796 100644 --- a/backend/data_fixtures/audit/test_data_entries/notes-to-sefa-entries.json +++ b/backend/data_fixtures/audit/test_data_entries/notes-to-sefa-entries.json @@ -1,10 +1,12 @@ [ { "note_title": "First Note", - "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nVestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus nec tortor ut ligula sollicitudin euismod." + "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nVestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus nec tortor ut ligula sollicitudin euismod.", + "contains_chart_or_table": "N" }, { "note_title": "Second Note", - "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nNulla facilisis urna et sem congue, ac pharetra ipso lorem sagittis. Maecenas sed nunc vel neque dignissim consequat." + "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nNulla facilisis urna et sem congue, ac pharetra ipso lorem sagittis. Maecenas sed nunc vel neque dignissim consequat.", + "contains_chart_or_table": "Y" } -] \ No newline at end of file +] diff --git a/backend/data_fixtures/audit/test_data_entries/simple-cases.json b/backend/data_fixtures/audit/test_data_entries/simple-cases.json index 1224eb9f30..ca6dd20479 100644 --- a/backend/data_fixtures/audit/test_data_entries/simple-cases.json +++ b/backend/data_fixtures/audit/test_data_entries/simple-cases.json @@ -280,7 +280,8 @@ "notes_to_sefa_entries": [ { "note_title": "Note one", - "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nVestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus nec tortor ut ligula sollicitudin euismod." + "note_content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nVestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus nec tortor ut ligula sollicitudin euismod.", + "contains_chart_or_table": "Y" } ] } diff --git a/backend/dev-requirements.txt b/backend/dev-requirements.txt index bade20ff7f..c970923d3a 100644 --- a/backend/dev-requirements.txt +++ b/backend/dev-requirements.txt @@ -14,37 +14,37 @@ bandit==1.7.5 \ --hash=sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549 \ --hash=sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e # via -r ./requirements/dev-requirements.in -black==23.7.0 \ - --hash=sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3 \ - --hash=sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb \ - --hash=sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087 \ - --hash=sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320 \ - --hash=sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6 \ - --hash=sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3 \ - --hash=sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc \ - --hash=sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f \ - --hash=sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587 \ - --hash=sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91 \ - --hash=sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a \ - --hash=sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad \ - --hash=sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926 \ - --hash=sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9 \ - --hash=sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be \ - --hash=sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd \ - --hash=sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96 \ - --hash=sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491 \ - --hash=sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2 \ - --hash=sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a \ - --hash=sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f \ - --hash=sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995 +black==23.9.1 \ + --hash=sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f \ + --hash=sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7 \ + --hash=sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100 \ + --hash=sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573 \ + --hash=sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d \ + --hash=sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f \ + --hash=sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9 \ + --hash=sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300 \ + --hash=sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948 \ + --hash=sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325 \ + --hash=sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9 \ + --hash=sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71 \ + --hash=sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186 \ + --hash=sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f \ + --hash=sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe \ + --hash=sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855 \ + --hash=sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80 \ + --hash=sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393 \ + --hash=sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c \ + --hash=sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204 \ + --hash=sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377 \ + --hash=sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301 # via -r ./requirements/dev-requirements.in -build==0.10.0 \ - --hash=sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171 \ - --hash=sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269 +build==1.0.3 \ + --hash=sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b \ + --hash=sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f # via pip-tools -click==8.1.6 \ - --hash=sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd \ - --hash=sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5 +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de # via # black # djlint @@ -53,74 +53,66 @@ colorama==0.4.6 \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 # via djlint -coverage==7.2.7 \ - --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \ - --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \ - --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \ - --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \ - --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \ - --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \ - --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \ - --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \ - --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \ - --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \ - --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \ - --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \ - --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \ - --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \ - --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \ - --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \ - --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \ - --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \ - --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \ - --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \ - --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \ - --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \ - --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \ - --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \ - --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \ - --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \ - --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \ - --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \ - --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \ - --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \ - --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \ - --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \ - --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \ - --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \ - --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \ - --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \ - --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \ - --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \ - --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \ - --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \ - --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \ - --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \ - --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \ - --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \ - --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \ - --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \ - --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \ - --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \ - --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \ - --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \ - --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \ - --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \ - --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \ - --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \ - --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \ - --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \ - --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \ - --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \ - --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \ - --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3 +coverage==7.3.1 \ + --hash=sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375 \ + --hash=sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344 \ + --hash=sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e \ + --hash=sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745 \ + --hash=sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f \ + --hash=sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194 \ + --hash=sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a \ + --hash=sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f \ + --hash=sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760 \ + --hash=sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8 \ + --hash=sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392 \ + --hash=sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d \ + --hash=sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc \ + --hash=sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40 \ + --hash=sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981 \ + --hash=sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0 \ + --hash=sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92 \ + --hash=sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3 \ + --hash=sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0 \ + --hash=sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086 \ + --hash=sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7 \ + --hash=sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465 \ + --hash=sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140 \ + --hash=sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952 \ + --hash=sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3 \ + --hash=sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8 \ + --hash=sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f \ + --hash=sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593 \ + --hash=sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0 \ + --hash=sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204 \ + --hash=sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037 \ + --hash=sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276 \ + --hash=sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9 \ + --hash=sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26 \ + --hash=sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce \ + --hash=sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7 \ + --hash=sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136 \ + --hash=sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a \ + --hash=sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4 \ + --hash=sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c \ + --hash=sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f \ + --hash=sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832 \ + --hash=sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3 \ + --hash=sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969 \ + --hash=sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520 \ + --hash=sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887 \ + --hash=sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3 \ + --hash=sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6 \ + --hash=sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1 \ + --hash=sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff \ + --hash=sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981 \ + --hash=sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e # via -r ./requirements/dev-requirements.in cssbeautifier==1.14.9 \ --hash=sha256:2da432472f68170eb854aff97b16a24721f5090ee36af2e31199590a89e7f71f # via djlint -django==4.2.3 \ - --hash=sha256:45a747e1c5b3d6df1b141b1481e193b033fd1fdbda3ff52677dc81afdaacbaed \ - --hash=sha256:f7c7852a5ac5a3da5a8d5b35cc6168f31b605971441798dac845f17ca8028039 +django==4.2.5 \ + --hash=sha256:5e5c1c9548ffb7796b4a8a4782e9a2e5a3df3615259fc1bfd3ebc73b646146c1 \ + --hash=sha256:b6b2b5cae821077f137dc4dade696a1c2aa292f892eca28fa8d7bfdf2608ddd4 # via # -c ./requirements/../requirements.txt # django-debug-toolbar @@ -139,23 +131,23 @@ editorconfig==0.12.3 \ # via # cssbeautifier # jsbeautifier -faker==19.2.0 \ - --hash=sha256:78840b94843f3aa32a34a220b2b5e8b309e3ffff3a231b0c54e841bb68e0757d \ - --hash=sha256:c6c1218482faf79ae1d791bb7124067d12339e0b8f400de855e2c281bcf78c77 +faker==19.6.1 \ + --hash=sha256:5d6b7880b3bea708075ddf91938424453f07053a59f8fa0453c1870df6ff3292 \ + --hash=sha256:64c8513c53c3a809075ee527b323a0ba61517814123f3137e4912f5d43350139 # via # -c ./requirements/../requirements.txt # -r ./requirements/dev-requirements.in -flake8==6.0.0 \ - --hash=sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7 \ - --hash=sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181 +flake8==6.1.0 \ + --hash=sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23 \ + --hash=sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5 # via -r ./requirements/dev-requirements.in gitdb==4.0.10 \ --hash=sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a \ --hash=sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7 # via gitpython -gitpython==3.1.32 \ - --hash=sha256:8d9b8cb1e80b9735e8717c9362079d3ce4c6e5ddeebedd0361b228c3a67a62f6 \ - --hash=sha256:e3d59b1c2c6ebb9dfa7a184daf3b6dd4914237e7488a1730a6d8f6f5d0b4187f +gitpython==3.1.36 \ + --hash=sha256:4bb0c2a6995e85064140d31a33289aa5dce80133a23d36fcd372d716c54d3ebf \ + --hash=sha256:8d22b5cfefd17c79914226982bb7851d6ade47545b1735a9d010a2a4c26d8388 # via bandit html-tag-names==0.1.2 \ --hash=sha256:04924aca48770f36b5a41c27e4d917062507be05118acb0ba869c97389084297 \ @@ -186,37 +178,38 @@ mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py -model-bakery==1.12.0 \ - --hash=sha256:70e991dd503e3f1956632c621946bb8e71012172db740bab75cd9d2a447283a3 \ - --hash=sha256:bfa5d118f91f08ef4694d4aaab168c98efc91dab086aee587e71d66a7001701a +model-bakery==1.15.0 \ + --hash=sha256:16178e608e2f414814e3383a9855e39c08810c9dee7b1d8e1354f1fdb7c013bc \ + --hash=sha256:c76813d8836ce339df4abd8648d6ed195fd0363f395dd1cb11b8a1898224e4e7 # via -r ./requirements/dev-requirements.in -mypy==1.4.1 \ - --hash=sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042 \ - --hash=sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd \ - --hash=sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2 \ - --hash=sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01 \ - --hash=sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7 \ - --hash=sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3 \ - --hash=sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816 \ - --hash=sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3 \ - --hash=sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc \ - --hash=sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4 \ - --hash=sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b \ - --hash=sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8 \ - --hash=sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c \ - --hash=sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462 \ - --hash=sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7 \ - --hash=sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc \ - --hash=sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258 \ - --hash=sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b \ - --hash=sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9 \ - --hash=sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6 \ - --hash=sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f \ - --hash=sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1 \ - --hash=sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828 \ - --hash=sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878 \ - --hash=sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f \ - --hash=sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b +mypy==1.5.1 \ + --hash=sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315 \ + --hash=sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0 \ + --hash=sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373 \ + --hash=sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a \ + --hash=sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161 \ + --hash=sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275 \ + --hash=sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693 \ + --hash=sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb \ + --hash=sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65 \ + --hash=sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4 \ + --hash=sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb \ + --hash=sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243 \ + --hash=sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14 \ + --hash=sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4 \ + --hash=sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1 \ + --hash=sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a \ + --hash=sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160 \ + --hash=sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25 \ + --hash=sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12 \ + --hash=sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d \ + --hash=sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92 \ + --hash=sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770 \ + --hash=sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2 \ + --hash=sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70 \ + --hash=sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb \ + --hash=sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5 \ + --hash=sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f # via -r ./requirements/dev-requirements.in mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ @@ -224,32 +217,32 @@ mypy-extensions==1.0.0 \ # via # black # mypy -numpy==1.25.1 \ - --hash=sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe \ - --hash=sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004 \ - --hash=sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19 \ - --hash=sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f \ - --hash=sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631 \ - --hash=sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd \ - --hash=sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c \ - --hash=sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009 \ - --hash=sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d \ - --hash=sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105 \ - --hash=sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09 \ - --hash=sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf \ - --hash=sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e \ - --hash=sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588 \ - --hash=sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa \ - --hash=sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb \ - --hash=sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf \ - --hash=sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57 \ - --hash=sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171 \ - --hash=sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625 \ - --hash=sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6 \ - --hash=sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b \ - --hash=sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800 \ - --hash=sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7 \ - --hash=sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503 +numpy==1.25.2 \ + --hash=sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2 \ + --hash=sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55 \ + --hash=sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf \ + --hash=sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01 \ + --hash=sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca \ + --hash=sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901 \ + --hash=sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d \ + --hash=sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4 \ + --hash=sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf \ + --hash=sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380 \ + --hash=sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044 \ + --hash=sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545 \ + --hash=sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f \ + --hash=sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f \ + --hash=sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3 \ + --hash=sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364 \ + --hash=sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9 \ + --hash=sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418 \ + --hash=sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f \ + --hash=sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295 \ + --hash=sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3 \ + --hash=sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187 \ + --hash=sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926 \ + --hash=sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357 \ + --hash=sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760 # via # -c ./requirements/../requirements.txt # pandas @@ -260,38 +253,32 @@ packaging==23.1 \ # -c ./requirements/../requirements.txt # black # build -pandas==2.0.3 \ - --hash=sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682 \ - --hash=sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc \ - --hash=sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b \ - --hash=sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089 \ - --hash=sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5 \ - --hash=sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26 \ - --hash=sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210 \ - --hash=sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b \ - --hash=sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641 \ - --hash=sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd \ - --hash=sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78 \ - --hash=sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b \ - --hash=sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e \ - --hash=sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061 \ - --hash=sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0 \ - --hash=sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e \ - --hash=sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8 \ - --hash=sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d \ - --hash=sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0 \ - --hash=sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c \ - --hash=sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183 \ - --hash=sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df \ - --hash=sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8 \ - --hash=sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f \ - --hash=sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02 +pandas==2.1.0 \ + --hash=sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437 \ + --hash=sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957 \ + --hash=sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5 \ + --hash=sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242 \ + --hash=sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3 \ + --hash=sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918 \ + --hash=sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09 \ + --hash=sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c \ + --hash=sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb \ + --hash=sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc \ + --hash=sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644 \ + --hash=sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6 \ + --hash=sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd \ + --hash=sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f \ + --hash=sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694 \ + --hash=sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f \ + --hash=sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b \ + --hash=sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9 \ + --hash=sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421 # via # -c ./requirements/../requirements.txt # -r ./requirements/dev-requirements.in -pathspec==0.11.1 \ - --hash=sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687 \ - --hash=sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293 +pathspec==0.11.2 \ + --hash=sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20 \ + --hash=sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3 # via # black # djlint @@ -299,25 +286,25 @@ pbr==5.11.1 \ --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 # via stevedore -pip-tools==7.1.0 \ - --hash=sha256:4e60b7d05b046f49ad5bf3c2818df8e78dec5820e9b331cd9898cff5ec19ff2f \ - --hash=sha256:f6ead499e726c8cfee04b2dea6282a9faf29663c378d9a4aca2ea6b86c8ec715 +pip-tools==7.3.0 \ + --hash=sha256:8717693288720a8c6ebd07149c93ab0be1fced0b5191df9e9decd3263e20d85e \ + --hash=sha256:8e9c99127fe024c025b46a0b2d15c7bd47f18f33226cf7330d35493663fc1d1d # via -r ./requirements/dev-requirements.in -platformdirs==3.9.1 \ - --hash=sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421 \ - --hash=sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f +platformdirs==3.10.0 \ + --hash=sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d \ + --hash=sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d # via black -pycodestyle==2.10.0 \ - --hash=sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053 \ - --hash=sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610 +pycodestyle==2.11.0 \ + --hash=sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0 \ + --hash=sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8 # via flake8 -pyflakes==3.0.1 \ - --hash=sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf \ - --hash=sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd +pyflakes==3.1.0 \ + --hash=sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774 \ + --hash=sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc # via flake8 -pygments==2.15.1 \ - --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ - --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1 +pygments==2.16.1 \ + --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ + --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 # via rich pyproject-hooks==1.0.0 \ --hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \ @@ -330,14 +317,16 @@ python-dateutil==2.8.2 \ # -c ./requirements/../requirements.txt # faker # pandas -pytz==2023.3 \ - --hash=sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588 \ - --hash=sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb +pytz==2023.3.post1 \ + --hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \ + --hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7 # via # -c ./requirements/../requirements.txt # pandas pyyaml==6.0.1 \ + --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ @@ -345,7 +334,10 @@ pyyaml==6.0.1 \ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ + --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ @@ -353,9 +345,12 @@ pyyaml==6.0.1 \ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ + --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ @@ -370,7 +365,9 @@ pyyaml==6.0.1 \ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ @@ -381,99 +378,99 @@ pyyaml==6.0.1 \ # -c ./requirements/../requirements.txt # bandit # djlint -regex==2023.6.3 \ - --hash=sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938 \ - --hash=sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6 \ - --hash=sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef \ - --hash=sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525 \ - --hash=sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af \ - --hash=sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc \ - --hash=sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54 \ - --hash=sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8 \ - --hash=sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7 \ - --hash=sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568 \ - --hash=sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c \ - --hash=sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f \ - --hash=sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536 \ - --hash=sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2 \ - --hash=sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb \ - --hash=sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df \ - --hash=sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019 \ - --hash=sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18 \ - --hash=sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f \ - --hash=sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac \ - --hash=sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8 \ - --hash=sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9 \ - --hash=sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697 \ - --hash=sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06 \ - --hash=sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d \ - --hash=sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036 \ - --hash=sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1 \ - --hash=sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787 \ - --hash=sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9 \ - --hash=sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0 \ - --hash=sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7 \ - --hash=sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461 \ - --hash=sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee \ - --hash=sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3 \ - --hash=sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0 \ - --hash=sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f \ - --hash=sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14 \ - --hash=sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477 \ - --hash=sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd \ - --hash=sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16 \ - --hash=sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc \ - --hash=sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff \ - --hash=sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd \ - --hash=sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2 \ - --hash=sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e \ - --hash=sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef \ - --hash=sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07 \ - --hash=sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3 \ - --hash=sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3 \ - --hash=sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a \ - --hash=sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7 \ - --hash=sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa \ - --hash=sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82 \ - --hash=sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77 \ - --hash=sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222 \ - --hash=sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c \ - --hash=sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd \ - --hash=sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d \ - --hash=sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68 \ - --hash=sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9 \ - --hash=sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693 \ - --hash=sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487 \ - --hash=sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06 \ - --hash=sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591 \ - --hash=sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27 \ - --hash=sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a \ - --hash=sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969 \ - --hash=sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3 \ - --hash=sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0 \ - --hash=sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7 \ - --hash=sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751 \ - --hash=sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747 \ - --hash=sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289 \ - --hash=sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72 \ - --hash=sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777 \ - --hash=sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9 \ - --hash=sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1 \ - --hash=sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1 \ - --hash=sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e \ - --hash=sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf \ - --hash=sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f \ - --hash=sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd \ - --hash=sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2 \ - --hash=sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c \ - --hash=sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1 \ - --hash=sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88 \ - --hash=sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9 \ - --hash=sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23 +regex==2023.8.8 \ + --hash=sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf \ + --hash=sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46 \ + --hash=sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18 \ + --hash=sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7 \ + --hash=sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7 \ + --hash=sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9 \ + --hash=sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559 \ + --hash=sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71 \ + --hash=sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280 \ + --hash=sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898 \ + --hash=sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684 \ + --hash=sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3 \ + --hash=sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9 \ + --hash=sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8 \ + --hash=sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca \ + --hash=sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c \ + --hash=sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c \ + --hash=sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab \ + --hash=sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd \ + --hash=sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56 \ + --hash=sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586 \ + --hash=sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7 \ + --hash=sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103 \ + --hash=sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac \ + --hash=sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177 \ + --hash=sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109 \ + --hash=sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033 \ + --hash=sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb \ + --hash=sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61 \ + --hash=sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800 \ + --hash=sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb \ + --hash=sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8 \ + --hash=sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570 \ + --hash=sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34 \ + --hash=sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e \ + --hash=sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4 \ + --hash=sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb \ + --hash=sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7 \ + --hash=sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208 \ + --hash=sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc \ + --hash=sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb \ + --hash=sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3 \ + --hash=sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504 \ + --hash=sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb \ + --hash=sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57 \ + --hash=sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b \ + --hash=sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601 \ + --hash=sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116 \ + --hash=sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8 \ + --hash=sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6 \ + --hash=sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6 \ + --hash=sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93 \ + --hash=sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09 \ + --hash=sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a \ + --hash=sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921 \ + --hash=sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a \ + --hash=sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495 \ + --hash=sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6 \ + --hash=sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7 \ + --hash=sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236 \ + --hash=sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235 \ + --hash=sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470 \ + --hash=sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b \ + --hash=sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5 \ + --hash=sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61 \ + --hash=sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c \ + --hash=sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db \ + --hash=sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be \ + --hash=sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96 \ + --hash=sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a \ + --hash=sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2 \ + --hash=sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63 \ + --hash=sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef \ + --hash=sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739 \ + --hash=sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e \ + --hash=sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217 \ + --hash=sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90 \ + --hash=sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4 \ + --hash=sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8 \ + --hash=sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3 \ + --hash=sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357 \ + --hash=sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4 \ + --hash=sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b \ + --hash=sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882 \ + --hash=sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a \ + --hash=sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675 \ + --hash=sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf \ + --hash=sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e # via djlint -rich==13.4.2 \ - --hash=sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec \ - --hash=sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898 +rich==13.5.2 \ + --hash=sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808 \ + --hash=sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39 # via bandit six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ @@ -516,9 +513,9 @@ tomli==2.0.1 \ # mypy # pip-tools # pyproject-hooks -tqdm==4.65.0 \ - --hash=sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5 \ - --hash=sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671 +tqdm==4.66.1 \ + --hash=sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386 \ + --hash=sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7 # via djlint types-python-slugify==8.0.0.3 \ --hash=sha256:2353c161c79ab6cce955b50720c6cd03586ec297558122236d130e4a19f21209 \ @@ -538,6 +535,7 @@ typing-extensions==4.7.1 \ # via # -c ./requirements/../requirements.txt # asgiref + # black # mypy tzdata==2023.3 \ --hash=sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a \ @@ -545,21 +543,24 @@ tzdata==2023.3 \ # via # -c ./requirements/../requirements.txt # pandas -wheel==0.40.0 \ - --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \ - --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247 +wheel==0.41.2 \ + --hash=sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985 \ + --hash=sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8 # via pip-tools whitenoise==6.5.0 \ --hash=sha256:15fe60546ac975b58e357ccaeb165a4ca2d0ab697e48450b8f0307ca368195a8 \ --hash=sha256:16468e9ad2189f09f4a8c635a9031cc9bb2cdbc8e5e53365407acf99f7ade9ec # via -r ./requirements/dev-requirements.in +xkcdpass==1.19.4 \ + --hash=sha256:2935d54b482d19bcb54656bda01cbbec9ee41ffd42d235a52705fd95cab70fd7 + # via -r ./requirements/dev-requirements.in # The following packages are considered to be unsafe in a requirements file: -pip==23.2 \ - --hash=sha256:78e5353a9dda374b462f2054f83a7b63f3f065c98236a68361845c1b0ee7e35f \ - --hash=sha256:a160a170f3331d9ca1a0247eb1cd79c758879f1f81158f9cd05bbb5df80bea5c +pip==23.2.1 \ + --hash=sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be \ + --hash=sha256:fb0bd5435b3200c602b5bf61d2d43c2f13c02e29c1707567ae7fbc514eb9faf2 # via pip-tools -setuptools==68.0.0 \ - --hash=sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f \ - --hash=sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235 +setuptools==68.2.2 \ + --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ + --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a # via pip-tools diff --git a/backend/dissemination/admin.py b/backend/dissemination/admin.py index 4185d360e9..ebbd7b0b2f 100644 --- a/backend/dissemination/admin.py +++ b/backend/dissemination/admin.py @@ -1,3 +1,25 @@ -# from django.contrib import admin +from django.contrib import admin -# Register your models here. +from dissemination.models import ( + AdditionalEin, + AdditionalUei, + CapText, + FederalAward, + Finding, + FindingText, + General, + Note, + Passthrough, + SecondaryAuditor, +) + +admin.site.register(AdditionalEin) +admin.site.register(AdditionalUei) +admin.site.register(CapText) +admin.site.register(FederalAward) +admin.site.register(Finding) +admin.site.register(FindingText) +admin.site.register(General) +admin.site.register(Note) +admin.site.register(Passthrough) +admin.site.register(SecondaryAuditor) diff --git a/backend/dissemination/api/api_v1_0_0/create_views.sql b/backend/dissemination/api/api_v1_0_0/create_views.sql index f0948b376d..44f65bbe94 100644 --- a/backend/dissemination/api/api_v1_0_0/create_views.sql +++ b/backend/dissemination/api/api_v1_0_0/create_views.sql @@ -150,7 +150,8 @@ create view api_v1_0_0.notes_to_sefa as note.accounting_policies, note.is_minimis_rate_used, note.rate_explained, - note.content + note.content, + note.contains_chart_or_table from dissemination_general gen, dissemination_note note diff --git a/backend/dissemination/docs.py b/backend/dissemination/docs.py index 565d897e16..ebee925ad7 100644 --- a/backend/dissemination/docs.py +++ b/backend/dissemination/docs.py @@ -102,6 +102,7 @@ ein_list = "Data sources: SF-SAC 1997-2000: I/5/a; SF-SAC 2001-2003: I/5/a; SF-SAC 2004-2007: I/5/a; SF-SAC 2008-2009: I/4/a; SF-SAC 2010-2012: I/4/a; SF-SAC 2013-2015: I/4/a; SF-SAC 2016-2018: I/4/a; SF-SAC 2019-2021: I/4/a; SF-SAC 2022: I/4/a Census mapping: GENERAL, EIN (AND) Data sources: SF-SAC 2001-2003: I/5/c; SF-SAC 2004-2007: I/5/c; SF-SAC 2008-2009: I/4/c; SF-SAC 2010-2012: I/4/c; SF-SAC 2013-2015: I/4/c; SF-SAC 2016-2018: I/4/c; SF-SAC 2019-2021: I/4/c; SF-SAC 2022: I/4/c Census mapping: EIN INFO, EIN" charts_tables_captext = "Census mapping: CAPTEXT, CHARTSTABLES" charts_tables_findingstext = "Census mapping: FINDINGSTEXT, CHARTSTABLES" +charts_tables_note = "Census mapping: " # TODO: fill in census mapping here finding_ref_nums_captext = "Data sources: SF-SAC 2019-2021: IV/1; SF-SAC 2022: IV/1 Census mapping: CAPTEXT, FINDINGREFNUMS" finding_ref_nums_cfdainfo = "Data sources: SF-SAC 1997-2000: III/7/e; SF-SAC 2001-2003: III/11/b; SF-SAC 2004-2007: III/10/b; SF-SAC 2008-2009: III/10/b; SF-SAC 2010-2012: III/10/b; SF-SAC 2013-2015: III/7/d; SF-SAC 2016-2018: III/4/e; SF-SAC 2019-2021: III/4/e; SF-SAC 2022: III/4/e Census mapping: CFDA INFO, FINDINGREFNUMS" finding_ref_nums_findings = "Data sources: SF-SAC 2013-2015: III/7/d; SF-SAC 2016-2018: III/4/e; SF-SAC 2019-2021: III/4/e; SF-SAC 2022: III/4/e Census mapping: FINDINGS, FINDINGSREFNUMS" diff --git a/backend/dissemination/hist_models/__init__.py b/backend/dissemination/hist_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/dissemination/hist_models/census_2019.py b/backend/dissemination/hist_models/census_2019.py new file mode 100644 index 0000000000..1cec1c0e1f --- /dev/null +++ b/backend/dissemination/hist_models/census_2019.py @@ -0,0 +1,308 @@ +from django.db import models + + +class CensusCfda19(models.Model): + index = models.BigIntegerField(blank=True, primary_key=True) + audityear = models.TextField( + db_column="AUDITYEAR", blank=True, null=True + ) # Field name made lowercase. + dbkey = models.TextField( + db_column="DBKEY", blank=True, null=True + ) # Field name made lowercase. + ein = models.TextField( + db_column="EIN", blank=True, null=True + ) # Field name made lowercase. + cfda = models.TextField( + db_column="CFDA", blank=True, null=True + ) # Field name made lowercase. + awardidentification = models.TextField( + db_column="AWARDIDENTIFICATION", blank=True, null=True + ) # Field name made lowercase. + rd = models.TextField( + db_column="RD", blank=True, null=True + ) # Field name made lowercase. + federalprogramname = models.TextField( + db_column="FEDERALPROGRAMNAME", blank=True, null=True + ) # Field name made lowercase. + amount = models.TextField( + db_column="AMOUNT", blank=True, null=True + ) # Field name made lowercase. + clustername = models.TextField( + db_column="CLUSTERNAME", blank=True, null=True + ) # Field name made lowercase. + stateclustername = models.TextField( + db_column="STATECLUSTERNAME", blank=True, null=True + ) # Field name made lowercase. + programtotal = models.TextField( + db_column="PROGRAMTOTAL", blank=True, null=True + ) # Field name made lowercase. + clustertotal = models.TextField( + db_column="CLUSTERTOTAL", blank=True, null=True + ) # Field name made lowercase. + direct = models.TextField( + db_column="DIRECT", blank=True, null=True + ) # Field name made lowercase. + passthroughaward = models.TextField( + db_column="PASSTHROUGHAWARD", blank=True, null=True + ) # Field name made lowercase. + passthroughamount = models.TextField( + db_column="PASSTHROUGHAMOUNT", blank=True, null=True + ) # Field name made lowercase. + majorprogram = models.TextField( + db_column="MAJORPROGRAM", blank=True, null=True + ) # Field name made lowercase. + typereport_mp = models.TextField( + db_column="TYPEREPORT_MP", blank=True, null=True + ) # Field name made lowercase. + typerequirement = models.TextField( + db_column="TYPEREQUIREMENT", blank=True, null=True + ) # Field name made lowercase. + qcosts2 = models.TextField( + db_column="QCOSTS2", blank=True, null=True + ) # Field name made lowercase. + findings = models.TextField( + db_column="FINDINGS", blank=True, null=True + ) # Field name made lowercase. + findingrefnums = models.TextField( + db_column="FINDINGREFNUMS", blank=True, null=True + ) # Field name made lowercase. + arra = models.TextField( + db_column="ARRA", blank=True, null=True + ) # Field name made lowercase. + loans = models.TextField( + db_column="LOANS", blank=True, null=True + ) # Field name made lowercase. + loanbalance = models.TextField( + db_column="LOANBALANCE", blank=True, null=True + ) # Field name made lowercase. + findingscount = models.TextField( + db_column="FINDINGSCOUNT", blank=True, null=True + ) # Field name made lowercase. + elecauditsid = models.TextField( + db_column="ELECAUDITSID", blank=True, null=True + ) # Field name made lowercase. + otherclustername = models.TextField( + db_column="OTHERCLUSTERNAME", blank=True, null=True + ) # Field name made lowercase. + cfdaprogramname = models.TextField( + db_column="CFDAPROGRAMNAME", blank=True, null=True + ) # Field name made lowercase. + + class Meta: + managed = False + db_table = "census_cfda19" + + +class CensusGen19(models.Model): + index = models.BigIntegerField(blank=True, primary_key=True) + audityear = models.TextField( + db_column="AUDITYEAR", blank=True, null=True + ) # Field name made lowercase. + dbkey = models.TextField( + db_column="DBKEY", blank=True, null=True + ) # Field name made lowercase. + typeofentity = models.TextField( + db_column="TYPEOFENTITY", blank=True, null=True + ) # Field name made lowercase. + fyenddate = models.TextField( + db_column="FYENDDATE", blank=True, null=True + ) # Field name made lowercase. + audittype = models.TextField( + db_column="AUDITTYPE", blank=True, null=True + ) # Field name made lowercase. + periodcovered = models.TextField( + db_column="PERIODCOVERED", blank=True, null=True + ) # Field name made lowercase. + numbermonths = models.TextField( + db_column="NUMBERMONTHS", blank=True, null=True + ) # Field name made lowercase. + ein = models.TextField( + db_column="EIN", blank=True, null=True + ) # Field name made lowercase. + multipleeins = models.TextField( + db_column="MULTIPLEEINS", blank=True, null=True + ) # Field name made lowercase. + einsubcode = models.TextField( + db_column="EINSUBCODE", blank=True, null=True + ) # Field name made lowercase. + duns = models.TextField( + db_column="DUNS", blank=True, null=True + ) # Field name made lowercase. + multipleduns = models.TextField( + db_column="MULTIPLEDUNS", blank=True, null=True + ) # Field name made lowercase. + auditeename = models.TextField( + db_column="AUDITEENAME", blank=True, null=True + ) # Field name made lowercase. + street1 = models.TextField( + db_column="STREET1", blank=True, null=True + ) # Field name made lowercase. + street2 = models.TextField( + db_column="STREET2", blank=True, null=True + ) # Field name made lowercase. + city = models.TextField( + db_column="CITY", blank=True, null=True + ) # Field name made lowercase. + state = models.TextField( + db_column="STATE", blank=True, null=True + ) # Field name made lowercase. + zipcode = models.TextField( + db_column="ZIPCODE", blank=True, null=True + ) # Field name made lowercase. + auditeecontact = models.TextField( + db_column="AUDITEECONTACT", blank=True, null=True + ) # Field name made lowercase. + auditeetitle = models.TextField( + db_column="AUDITEETITLE", blank=True, null=True + ) # Field name made lowercase. + auditeephone = models.TextField( + db_column="AUDITEEPHONE", blank=True, null=True + ) # Field name made lowercase. + auditeefax = models.TextField( + db_column="AUDITEEFAX", blank=True, null=True + ) # Field name made lowercase. + auditeeemail = models.TextField( + db_column="AUDITEEEMAIL", blank=True, null=True + ) # Field name made lowercase. + auditeedatesigned = models.TextField( + db_column="AUDITEEDATESIGNED", blank=True, null=True + ) # Field name made lowercase. + auditeenametitle = models.TextField( + db_column="AUDITEENAMETITLE", blank=True, null=True + ) # Field name made lowercase. + cpafirmname = models.TextField( + db_column="CPAFIRMNAME", blank=True, null=True + ) # Field name made lowercase. + cpastreet1 = models.TextField( + db_column="CPASTREET1", blank=True, null=True + ) # Field name made lowercase. + cpastreet2 = models.TextField( + db_column="CPASTREET2", blank=True, null=True + ) # Field name made lowercase. + cpacity = models.TextField( + db_column="CPACITY", blank=True, null=True + ) # Field name made lowercase. + cpastate = models.TextField( + db_column="CPASTATE", blank=True, null=True + ) # Field name made lowercase. + cpazipcode = models.TextField( + db_column="CPAZIPCODE", blank=True, null=True + ) # Field name made lowercase. + cpacontact = models.TextField( + db_column="CPACONTACT", blank=True, null=True + ) # Field name made lowercase. + cpatitle = models.TextField( + db_column="CPATITLE", blank=True, null=True + ) # Field name made lowercase. + cpaphone = models.TextField( + db_column="CPAPHONE", blank=True, null=True + ) # Field name made lowercase. + cpafax = models.TextField( + db_column="CPAFAX", blank=True, null=True + ) # Field name made lowercase. + cpaemail = models.TextField( + db_column="CPAEMAIL", blank=True, null=True + ) # Field name made lowercase. + cpadatesigned = models.TextField( + db_column="CPADATESIGNED", blank=True, null=True + ) # Field name made lowercase. + cog_over = models.TextField( + db_column="COG_OVER", blank=True, null=True + ) # Field name made lowercase. + cogagency = models.TextField( + db_column="COGAGENCY", blank=True, null=True + ) # Field name made lowercase. + oversightagency = models.TextField( + db_column="OVERSIGHTAGENCY", blank=True, null=True + ) # Field name made lowercase. + typereport_fs = models.TextField( + db_column="TYPEREPORT_FS", blank=True, null=True + ) # Field name made lowercase. + sp_framework = models.TextField( + db_column="SP_FRAMEWORK", blank=True, null=True + ) # Field name made lowercase. + sp_framework_required = models.TextField( + db_column="SP_FRAMEWORK_REQUIRED", blank=True, null=True + ) # Field name made lowercase. + typereport_sp_framework = models.TextField( + db_column="TYPEREPORT_SP_FRAMEWORK", blank=True, null=True + ) # Field name made lowercase. + goingconcern = models.TextField( + db_column="GOINGCONCERN", blank=True, null=True + ) # Field name made lowercase. + reportablecondition = models.TextField( + db_column="REPORTABLECONDITION", blank=True, null=True + ) # Field name made lowercase. + materialweakness = models.TextField( + db_column="MATERIALWEAKNESS", blank=True, null=True + ) # Field name made lowercase. + materialnoncompliance = models.TextField( + db_column="MATERIALNONCOMPLIANCE", blank=True, null=True + ) # Field name made lowercase. + typereport_mp = models.TextField( + db_column="TYPEREPORT_MP", blank=True, null=True + ) # Field name made lowercase. + dup_reports = models.TextField( + db_column="DUP_REPORTS", blank=True, null=True + ) # Field name made lowercase. + dollarthreshold = models.TextField( + db_column="DOLLARTHRESHOLD", blank=True, null=True + ) # Field name made lowercase. + lowrisk = models.TextField( + db_column="LOWRISK", blank=True, null=True + ) # Field name made lowercase. + reportablecondition_mp = models.TextField( + db_column="REPORTABLECONDITION_MP", blank=True, null=True + ) # Field name made lowercase. + materialweakness_mp = models.TextField( + db_column="MATERIALWEAKNESS_MP", blank=True, null=True + ) # Field name made lowercase. + qcosts = models.TextField( + db_column="QCOSTS", blank=True, null=True + ) # Field name made lowercase. + cyfindings = models.TextField( + db_column="CYFINDINGS", blank=True, null=True + ) # Field name made lowercase. + pyschedule = models.TextField( + db_column="PYSCHEDULE", blank=True, null=True + ) # Field name made lowercase. + totfedexpend = models.TextField( + db_column="TOTFEDEXPEND", blank=True, null=True + ) # Field name made lowercase. + datefirewall = models.TextField( + db_column="DATEFIREWALL", blank=True, null=True + ) # Field name made lowercase. + previousdatefirewall = models.TextField( + db_column="PREVIOUSDATEFIREWALL", blank=True, null=True + ) # Field name made lowercase. + reportrequired = models.TextField( + db_column="REPORTREQUIRED", blank=True, null=True + ) # Field name made lowercase. + multiple_cpas = models.TextField( + db_column="MULTIPLE_CPAS", blank=True, null=True + ) # Field name made lowercase. + auditor_ein = models.TextField( + db_column="AUDITOR_EIN", blank=True, null=True + ) # Field name made lowercase. + facaccepteddate = models.TextField( + db_column="FACACCEPTEDDATE", blank=True, null=True + ) # Field name made lowercase. + cpaforeign = models.TextField( + db_column="CPAFOREIGN", blank=True, null=True + ) # Field name made lowercase. + cpacountry = models.TextField( + db_column="CPACOUNTRY", blank=True, null=True + ) # Field name made lowercase. + entity_type = models.TextField( + db_column="ENTITY_TYPE", blank=True, null=True + ) # Field name made lowercase. + uei = models.TextField( + db_column="UEI", blank=True, null=True + ) # Field name made lowercase. + multipleueis = models.TextField( + db_column="MULTIPLEUEIS", blank=True, null=True + ) # Field name made lowercase. + + class Meta: + managed = False + db_table = "census_gen19" diff --git a/backend/dissemination/hist_models/census_2022.py b/backend/dissemination/hist_models/census_2022.py new file mode 100644 index 0000000000..443af300be --- /dev/null +++ b/backend/dissemination/hist_models/census_2022.py @@ -0,0 +1,308 @@ +from django.db import models + + +class CensusCfda22(models.Model): + index = models.BigIntegerField(blank=True, primary_key=True) + audityear = models.TextField( + db_column="AUDITYEAR", blank=True, null=True + ) # Field name made lowercase. + dbkey = models.TextField( + db_column="DBKEY", blank=True, null=True + ) # Field name made lowercase. + ein = models.TextField( + db_column="EIN", blank=True, null=True + ) # Field name made lowercase. + cfda = models.TextField( + db_column="CFDA", blank=True, null=True + ) # Field name made lowercase. + awardidentification = models.TextField( + db_column="AWARDIDENTIFICATION", blank=True, null=True + ) # Field name made lowercase. + rd = models.TextField( + db_column="RD", blank=True, null=True + ) # Field name made lowercase. + federalprogramname = models.TextField( + db_column="FEDERALPROGRAMNAME", blank=True, null=True + ) # Field name made lowercase. + amount = models.TextField( + db_column="AMOUNT", blank=True, null=True + ) # Field name made lowercase. + clustername = models.TextField( + db_column="CLUSTERNAME", blank=True, null=True + ) # Field name made lowercase. + stateclustername = models.TextField( + db_column="STATECLUSTERNAME", blank=True, null=True + ) # Field name made lowercase. + programtotal = models.TextField( + db_column="PROGRAMTOTAL", blank=True, null=True + ) # Field name made lowercase. + clustertotal = models.TextField( + db_column="CLUSTERTOTAL", blank=True, null=True + ) # Field name made lowercase. + direct = models.TextField( + db_column="DIRECT", blank=True, null=True + ) # Field name made lowercase. + passthroughaward = models.TextField( + db_column="PASSTHROUGHAWARD", blank=True, null=True + ) # Field name made lowercase. + passthroughamount = models.TextField( + db_column="PASSTHROUGHAMOUNT", blank=True, null=True + ) # Field name made lowercase. + majorprogram = models.TextField( + db_column="MAJORPROGRAM", blank=True, null=True + ) # Field name made lowercase. + typereport_mp = models.TextField( + db_column="TYPEREPORT_MP", blank=True, null=True + ) # Field name made lowercase. + typerequirement = models.TextField( + db_column="TYPEREQUIREMENT", blank=True, null=True + ) # Field name made lowercase. + qcosts2 = models.TextField( + db_column="QCOSTS2", blank=True, null=True + ) # Field name made lowercase. + findings = models.TextField( + db_column="FINDINGS", blank=True, null=True + ) # Field name made lowercase. + findingrefnums = models.TextField( + db_column="FINDINGREFNUMS", blank=True, null=True + ) # Field name made lowercase. + arra = models.TextField( + db_column="ARRA", blank=True, null=True + ) # Field name made lowercase. + loans = models.TextField( + db_column="LOANS", blank=True, null=True + ) # Field name made lowercase. + loanbalance = models.TextField( + db_column="LOANBALANCE", blank=True, null=True + ) # Field name made lowercase. + findingscount = models.TextField( + db_column="FINDINGSCOUNT", blank=True, null=True + ) # Field name made lowercase. + elecauditsid = models.TextField( + db_column="ELECAUDITSID", blank=True, null=True + ) # Field name made lowercase. + otherclustername = models.TextField( + db_column="OTHERCLUSTERNAME", blank=True, null=True + ) # Field name made lowercase. + cfdaprogramname = models.TextField( + db_column="CFDAPROGRAMNAME", blank=True, null=True + ) # Field name made lowercase. + + class Meta: + managed = False + db_table = "census_cfda22" + + +class CensusGen22(models.Model): + index = models.BigIntegerField(blank=True, primary_key=True) + audityear = models.TextField( + db_column="AUDITYEAR", blank=True, null=True + ) # Field name made lowercase. + dbkey = models.TextField( + db_column="DBKEY", blank=True, null=True + ) # Field name made lowercase. + typeofentity = models.TextField( + db_column="TYPEOFENTITY", blank=True, null=True + ) # Field name made lowercase. + fyenddate = models.TextField( + db_column="FYENDDATE", blank=True, null=True + ) # Field name made lowercase. + audittype = models.TextField( + db_column="AUDITTYPE", blank=True, null=True + ) # Field name made lowercase. + periodcovered = models.TextField( + db_column="PERIODCOVERED", blank=True, null=True + ) # Field name made lowercase. + numbermonths = models.TextField( + db_column="NUMBERMONTHS", blank=True, null=True + ) # Field name made lowercase. + ein = models.TextField( + db_column="EIN", blank=True, null=True + ) # Field name made lowercase. + multipleeins = models.TextField( + db_column="MULTIPLEEINS", blank=True, null=True + ) # Field name made lowercase. + einsubcode = models.TextField( + db_column="EINSUBCODE", blank=True, null=True + ) # Field name made lowercase. + duns = models.TextField( + db_column="DUNS", blank=True, null=True + ) # Field name made lowercase. + multipleduns = models.TextField( + db_column="MULTIPLEDUNS", blank=True, null=True + ) # Field name made lowercase. + auditeename = models.TextField( + db_column="AUDITEENAME", blank=True, null=True + ) # Field name made lowercase. + street1 = models.TextField( + db_column="STREET1", blank=True, null=True + ) # Field name made lowercase. + street2 = models.TextField( + db_column="STREET2", blank=True, null=True + ) # Field name made lowercase. + city = models.TextField( + db_column="CITY", blank=True, null=True + ) # Field name made lowercase. + state = models.TextField( + db_column="STATE", blank=True, null=True + ) # Field name made lowercase. + zipcode = models.TextField( + db_column="ZIPCODE", blank=True, null=True + ) # Field name made lowercase. + auditeecontact = models.TextField( + db_column="AUDITEECONTACT", blank=True, null=True + ) # Field name made lowercase. + auditeetitle = models.TextField( + db_column="AUDITEETITLE", blank=True, null=True + ) # Field name made lowercase. + auditeephone = models.TextField( + db_column="AUDITEEPHONE", blank=True, null=True + ) # Field name made lowercase. + auditeefax = models.TextField( + db_column="AUDITEEFAX", blank=True, null=True + ) # Field name made lowercase. + auditeeemail = models.TextField( + db_column="AUDITEEEMAIL", blank=True, null=True + ) # Field name made lowercase. + auditeedatesigned = models.TextField( + db_column="AUDITEEDATESIGNED", blank=True, null=True + ) # Field name made lowercase. + auditeenametitle = models.TextField( + db_column="AUDITEENAMETITLE", blank=True, null=True + ) # Field name made lowercase. + cpafirmname = models.TextField( + db_column="CPAFIRMNAME", blank=True, null=True + ) # Field name made lowercase. + cpastreet1 = models.TextField( + db_column="CPASTREET1", blank=True, null=True + ) # Field name made lowercase. + cpastreet2 = models.TextField( + db_column="CPASTREET2", blank=True, null=True + ) # Field name made lowercase. + cpacity = models.TextField( + db_column="CPACITY", blank=True, null=True + ) # Field name made lowercase. + cpastate = models.TextField( + db_column="CPASTATE", blank=True, null=True + ) # Field name made lowercase. + cpazipcode = models.TextField( + db_column="CPAZIPCODE", blank=True, null=True + ) # Field name made lowercase. + cpacontact = models.TextField( + db_column="CPACONTACT", blank=True, null=True + ) # Field name made lowercase. + cpatitle = models.TextField( + db_column="CPATITLE", blank=True, null=True + ) # Field name made lowercase. + cpaphone = models.TextField( + db_column="CPAPHONE", blank=True, null=True + ) # Field name made lowercase. + cpafax = models.TextField( + db_column="CPAFAX", blank=True, null=True + ) # Field name made lowercase. + cpaemail = models.TextField( + db_column="CPAEMAIL", blank=True, null=True + ) # Field name made lowercase. + cpadatesigned = models.TextField( + db_column="CPADATESIGNED", blank=True, null=True + ) # Field name made lowercase. + cog_over = models.TextField( + db_column="COG_OVER", blank=True, null=True + ) # Field name made lowercase. + cogagency = models.TextField( + db_column="COGAGENCY", blank=True, null=True + ) # Field name made lowercase. + oversightagency = models.TextField( + db_column="OVERSIGHTAGENCY", blank=True, null=True + ) # Field name made lowercase. + typereport_fs = models.TextField( + db_column="TYPEREPORT_FS", blank=True, null=True + ) # Field name made lowercase. + sp_framework = models.TextField( + db_column="SP_FRAMEWORK", blank=True, null=True + ) # Field name made lowercase. + sp_framework_required = models.TextField( + db_column="SP_FRAMEWORK_REQUIRED", blank=True, null=True + ) # Field name made lowercase. + typereport_sp_framework = models.TextField( + db_column="TYPEREPORT_SP_FRAMEWORK", blank=True, null=True + ) # Field name made lowercase. + goingconcern = models.TextField( + db_column="GOINGCONCERN", blank=True, null=True + ) # Field name made lowercase. + reportablecondition = models.TextField( + db_column="REPORTABLECONDITION", blank=True, null=True + ) # Field name made lowercase. + materialweakness = models.TextField( + db_column="MATERIALWEAKNESS", blank=True, null=True + ) # Field name made lowercase. + materialnoncompliance = models.TextField( + db_column="MATERIALNONCOMPLIANCE", blank=True, null=True + ) # Field name made lowercase. + typereport_mp = models.TextField( + db_column="TYPEREPORT_MP", blank=True, null=True + ) # Field name made lowercase. + dup_reports = models.TextField( + db_column="DUP_REPORTS", blank=True, null=True + ) # Field name made lowercase. + dollarthreshold = models.TextField( + db_column="DOLLARTHRESHOLD", blank=True, null=True + ) # Field name made lowercase. + lowrisk = models.TextField( + db_column="LOWRISK", blank=True, null=True + ) # Field name made lowercase. + reportablecondition_mp = models.TextField( + db_column="REPORTABLECONDITION_MP", blank=True, null=True + ) # Field name made lowercase. + materialweakness_mp = models.TextField( + db_column="MATERIALWEAKNESS_MP", blank=True, null=True + ) # Field name made lowercase. + qcosts = models.TextField( + db_column="QCOSTS", blank=True, null=True + ) # Field name made lowercase. + cyfindings = models.TextField( + db_column="CYFINDINGS", blank=True, null=True + ) # Field name made lowercase. + pyschedule = models.TextField( + db_column="PYSCHEDULE", blank=True, null=True + ) # Field name made lowercase. + totfedexpend = models.TextField( + db_column="TOTFEDEXPEND", blank=True, null=True + ) # Field name made lowercase. + datefirewall = models.TextField( + db_column="DATEFIREWALL", blank=True, null=True + ) # Field name made lowercase. + previousdatefirewall = models.TextField( + db_column="PREVIOUSDATEFIREWALL", blank=True, null=True + ) # Field name made lowercase. + reportrequired = models.TextField( + db_column="REPORTREQUIRED", blank=True, null=True + ) # Field name made lowercase. + multiple_cpas = models.TextField( + db_column="MULTIPLE_CPAS", blank=True, null=True + ) # Field name made lowercase. + auditor_ein = models.TextField( + db_column="AUDITOR_EIN", blank=True, null=True + ) # Field name made lowercase. + facaccepteddate = models.TextField( + db_column="FACACCEPTEDDATE", blank=True, null=True + ) # Field name made lowercase. + cpaforeign = models.TextField( + db_column="CPAFOREIGN", blank=True, null=True + ) # Field name made lowercase. + cpacountry = models.TextField( + db_column="CPACOUNTRY", blank=True, null=True + ) # Field name made lowercase. + entity_type = models.TextField( + db_column="ENTITY_TYPE", blank=True, null=True + ) # Field name made lowercase. + uei = models.TextField( + db_column="UEI", blank=True, null=True + ) # Field name made lowercase. + multipleueis = models.TextField( + db_column="MULTIPLEUEIS", blank=True, null=True + ) # Field name made lowercase. + + class Meta: + managed = False + db_table = "census_gen22" diff --git a/backend/dissemination/migrations/0001_initial.py b/backend/dissemination/migrations/0001_initial.py index d84c1810ee..cd60944111 100644 --- a/backend/dissemination/migrations/0001_initial.py +++ b/backend/dissemination/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.3 on 2023-09-11 19:37 +# Generated by Django 4.2.3 on 2023-09-20 14:33 from django.db import migrations, models @@ -9,6 +9,849 @@ class Migration(migrations.Migration): dependencies = [] operations = [ + migrations.CreateModel( + name="CensusCfda19", + fields=[ + ( + "index", + models.BigIntegerField( + blank=True, primary_key=True, serialize=False + ), + ), + ( + "audityear", + models.TextField(blank=True, db_column="AUDITYEAR", null=True), + ), + ("dbkey", models.TextField(blank=True, db_column="DBKEY", null=True)), + ("ein", models.TextField(blank=True, db_column="EIN", null=True)), + ("cfda", models.TextField(blank=True, db_column="CFDA", null=True)), + ( + "awardidentification", + models.TextField( + blank=True, db_column="AWARDIDENTIFICATION", null=True + ), + ), + ("rd", models.TextField(blank=True, db_column="RD", null=True)), + ( + "federalprogramname", + models.TextField( + blank=True, db_column="FEDERALPROGRAMNAME", null=True + ), + ), + ("amount", models.TextField(blank=True, db_column="AMOUNT", null=True)), + ( + "clustername", + models.TextField(blank=True, db_column="CLUSTERNAME", null=True), + ), + ( + "stateclustername", + models.TextField( + blank=True, db_column="STATECLUSTERNAME", null=True + ), + ), + ( + "programtotal", + models.TextField(blank=True, db_column="PROGRAMTOTAL", null=True), + ), + ( + "clustertotal", + models.TextField(blank=True, db_column="CLUSTERTOTAL", null=True), + ), + ("direct", models.TextField(blank=True, db_column="DIRECT", null=True)), + ( + "passthroughaward", + models.TextField( + blank=True, db_column="PASSTHROUGHAWARD", null=True + ), + ), + ( + "passthroughamount", + models.TextField( + blank=True, db_column="PASSTHROUGHAMOUNT", null=True + ), + ), + ( + "majorprogram", + models.TextField(blank=True, db_column="MAJORPROGRAM", null=True), + ), + ( + "typereport_mp", + models.TextField(blank=True, db_column="TYPEREPORT_MP", null=True), + ), + ( + "typerequirement", + models.TextField( + blank=True, db_column="TYPEREQUIREMENT", null=True + ), + ), + ( + "qcosts2", + models.TextField(blank=True, db_column="QCOSTS2", null=True), + ), + ( + "findings", + models.TextField(blank=True, db_column="FINDINGS", null=True), + ), + ( + "findingrefnums", + models.TextField(blank=True, db_column="FINDINGREFNUMS", null=True), + ), + ("arra", models.TextField(blank=True, db_column="ARRA", null=True)), + ("loans", models.TextField(blank=True, db_column="LOANS", null=True)), + ( + "loanbalance", + models.TextField(blank=True, db_column="LOANBALANCE", null=True), + ), + ( + "findingscount", + models.TextField(blank=True, db_column="FINDINGSCOUNT", null=True), + ), + ( + "elecauditsid", + models.TextField(blank=True, db_column="ELECAUDITSID", null=True), + ), + ( + "otherclustername", + models.TextField( + blank=True, db_column="OTHERCLUSTERNAME", null=True + ), + ), + ( + "cfdaprogramname", + models.TextField( + blank=True, db_column="CFDAPROGRAMNAME", null=True + ), + ), + ], + options={ + "db_table": "census_cfda19", + "managed": False, + }, + ), + migrations.CreateModel( + name="CensusCfda22", + fields=[ + ( + "index", + models.BigIntegerField( + blank=True, primary_key=True, serialize=False + ), + ), + ( + "audityear", + models.TextField(blank=True, db_column="AUDITYEAR", null=True), + ), + ("dbkey", models.TextField(blank=True, db_column="DBKEY", null=True)), + ("ein", models.TextField(blank=True, db_column="EIN", null=True)), + ("cfda", models.TextField(blank=True, db_column="CFDA", null=True)), + ( + "awardidentification", + models.TextField( + blank=True, db_column="AWARDIDENTIFICATION", null=True + ), + ), + ("rd", models.TextField(blank=True, db_column="RD", null=True)), + ( + "federalprogramname", + models.TextField( + blank=True, db_column="FEDERALPROGRAMNAME", null=True + ), + ), + ("amount", models.TextField(blank=True, db_column="AMOUNT", null=True)), + ( + "clustername", + models.TextField(blank=True, db_column="CLUSTERNAME", null=True), + ), + ( + "stateclustername", + models.TextField( + blank=True, db_column="STATECLUSTERNAME", null=True + ), + ), + ( + "programtotal", + models.TextField(blank=True, db_column="PROGRAMTOTAL", null=True), + ), + ( + "clustertotal", + models.TextField(blank=True, db_column="CLUSTERTOTAL", null=True), + ), + ("direct", models.TextField(blank=True, db_column="DIRECT", null=True)), + ( + "passthroughaward", + models.TextField( + blank=True, db_column="PASSTHROUGHAWARD", null=True + ), + ), + ( + "passthroughamount", + models.TextField( + blank=True, db_column="PASSTHROUGHAMOUNT", null=True + ), + ), + ( + "majorprogram", + models.TextField(blank=True, db_column="MAJORPROGRAM", null=True), + ), + ( + "typereport_mp", + models.TextField(blank=True, db_column="TYPEREPORT_MP", null=True), + ), + ( + "typerequirement", + models.TextField( + blank=True, db_column="TYPEREQUIREMENT", null=True + ), + ), + ( + "qcosts2", + models.TextField(blank=True, db_column="QCOSTS2", null=True), + ), + ( + "findings", + models.TextField(blank=True, db_column="FINDINGS", null=True), + ), + ( + "findingrefnums", + models.TextField(blank=True, db_column="FINDINGREFNUMS", null=True), + ), + ("arra", models.TextField(blank=True, db_column="ARRA", null=True)), + ("loans", models.TextField(blank=True, db_column="LOANS", null=True)), + ( + "loanbalance", + models.TextField(blank=True, db_column="LOANBALANCE", null=True), + ), + ( + "findingscount", + models.TextField(blank=True, db_column="FINDINGSCOUNT", null=True), + ), + ( + "elecauditsid", + models.TextField(blank=True, db_column="ELECAUDITSID", null=True), + ), + ( + "otherclustername", + models.TextField( + blank=True, db_column="OTHERCLUSTERNAME", null=True + ), + ), + ( + "cfdaprogramname", + models.TextField( + blank=True, db_column="CFDAPROGRAMNAME", null=True + ), + ), + ], + options={ + "db_table": "census_cfda22", + "managed": False, + }, + ), + migrations.CreateModel( + name="CensusGen19", + fields=[ + ( + "index", + models.BigIntegerField( + blank=True, primary_key=True, serialize=False + ), + ), + ( + "audityear", + models.TextField(blank=True, db_column="AUDITYEAR", null=True), + ), + ("dbkey", models.TextField(blank=True, db_column="DBKEY", null=True)), + ( + "typeofentity", + models.TextField(blank=True, db_column="TYPEOFENTITY", null=True), + ), + ( + "fyenddate", + models.TextField(blank=True, db_column="FYENDDATE", null=True), + ), + ( + "audittype", + models.TextField(blank=True, db_column="AUDITTYPE", null=True), + ), + ( + "periodcovered", + models.TextField(blank=True, db_column="PERIODCOVERED", null=True), + ), + ( + "numbermonths", + models.TextField(blank=True, db_column="NUMBERMONTHS", null=True), + ), + ("ein", models.TextField(blank=True, db_column="EIN", null=True)), + ( + "multipleeins", + models.TextField(blank=True, db_column="MULTIPLEEINS", null=True), + ), + ( + "einsubcode", + models.TextField(blank=True, db_column="EINSUBCODE", null=True), + ), + ("duns", models.TextField(blank=True, db_column="DUNS", null=True)), + ( + "multipleduns", + models.TextField(blank=True, db_column="MULTIPLEDUNS", null=True), + ), + ( + "auditeename", + models.TextField(blank=True, db_column="AUDITEENAME", null=True), + ), + ( + "street1", + models.TextField(blank=True, db_column="STREET1", null=True), + ), + ( + "street2", + models.TextField(blank=True, db_column="STREET2", null=True), + ), + ("city", models.TextField(blank=True, db_column="CITY", null=True)), + ("state", models.TextField(blank=True, db_column="STATE", null=True)), + ( + "zipcode", + models.TextField(blank=True, db_column="ZIPCODE", null=True), + ), + ( + "auditeecontact", + models.TextField(blank=True, db_column="AUDITEECONTACT", null=True), + ), + ( + "auditeetitle", + models.TextField(blank=True, db_column="AUDITEETITLE", null=True), + ), + ( + "auditeephone", + models.TextField(blank=True, db_column="AUDITEEPHONE", null=True), + ), + ( + "auditeefax", + models.TextField(blank=True, db_column="AUDITEEFAX", null=True), + ), + ( + "auditeeemail", + models.TextField(blank=True, db_column="AUDITEEEMAIL", null=True), + ), + ( + "auditeedatesigned", + models.TextField( + blank=True, db_column="AUDITEEDATESIGNED", null=True + ), + ), + ( + "auditeenametitle", + models.TextField( + blank=True, db_column="AUDITEENAMETITLE", null=True + ), + ), + ( + "cpafirmname", + models.TextField(blank=True, db_column="CPAFIRMNAME", null=True), + ), + ( + "cpastreet1", + models.TextField(blank=True, db_column="CPASTREET1", null=True), + ), + ( + "cpastreet2", + models.TextField(blank=True, db_column="CPASTREET2", null=True), + ), + ( + "cpacity", + models.TextField(blank=True, db_column="CPACITY", null=True), + ), + ( + "cpastate", + models.TextField(blank=True, db_column="CPASTATE", null=True), + ), + ( + "cpazipcode", + models.TextField(blank=True, db_column="CPAZIPCODE", null=True), + ), + ( + "cpacontact", + models.TextField(blank=True, db_column="CPACONTACT", null=True), + ), + ( + "cpatitle", + models.TextField(blank=True, db_column="CPATITLE", null=True), + ), + ( + "cpaphone", + models.TextField(blank=True, db_column="CPAPHONE", null=True), + ), + ("cpafax", models.TextField(blank=True, db_column="CPAFAX", null=True)), + ( + "cpaemail", + models.TextField(blank=True, db_column="CPAEMAIL", null=True), + ), + ( + "cpadatesigned", + models.TextField(blank=True, db_column="CPADATESIGNED", null=True), + ), + ( + "cog_over", + models.TextField(blank=True, db_column="COG_OVER", null=True), + ), + ( + "cogagency", + models.TextField(blank=True, db_column="COGAGENCY", null=True), + ), + ( + "oversightagency", + models.TextField( + blank=True, db_column="OVERSIGHTAGENCY", null=True + ), + ), + ( + "typereport_fs", + models.TextField(blank=True, db_column="TYPEREPORT_FS", null=True), + ), + ( + "sp_framework", + models.TextField(blank=True, db_column="SP_FRAMEWORK", null=True), + ), + ( + "sp_framework_required", + models.TextField( + blank=True, db_column="SP_FRAMEWORK_REQUIRED", null=True + ), + ), + ( + "typereport_sp_framework", + models.TextField( + blank=True, db_column="TYPEREPORT_SP_FRAMEWORK", null=True + ), + ), + ( + "goingconcern", + models.TextField(blank=True, db_column="GOINGCONCERN", null=True), + ), + ( + "reportablecondition", + models.TextField( + blank=True, db_column="REPORTABLECONDITION", null=True + ), + ), + ( + "materialweakness", + models.TextField( + blank=True, db_column="MATERIALWEAKNESS", null=True + ), + ), + ( + "materialnoncompliance", + models.TextField( + blank=True, db_column="MATERIALNONCOMPLIANCE", null=True + ), + ), + ( + "typereport_mp", + models.TextField(blank=True, db_column="TYPEREPORT_MP", null=True), + ), + ( + "dup_reports", + models.TextField(blank=True, db_column="DUP_REPORTS", null=True), + ), + ( + "dollarthreshold", + models.TextField( + blank=True, db_column="DOLLARTHRESHOLD", null=True + ), + ), + ( + "lowrisk", + models.TextField(blank=True, db_column="LOWRISK", null=True), + ), + ( + "reportablecondition_mp", + models.TextField( + blank=True, db_column="REPORTABLECONDITION_MP", null=True + ), + ), + ( + "materialweakness_mp", + models.TextField( + blank=True, db_column="MATERIALWEAKNESS_MP", null=True + ), + ), + ("qcosts", models.TextField(blank=True, db_column="QCOSTS", null=True)), + ( + "cyfindings", + models.TextField(blank=True, db_column="CYFINDINGS", null=True), + ), + ( + "pyschedule", + models.TextField(blank=True, db_column="PYSCHEDULE", null=True), + ), + ( + "totfedexpend", + models.TextField(blank=True, db_column="TOTFEDEXPEND", null=True), + ), + ( + "datefirewall", + models.TextField(blank=True, db_column="DATEFIREWALL", null=True), + ), + ( + "previousdatefirewall", + models.TextField( + blank=True, db_column="PREVIOUSDATEFIREWALL", null=True + ), + ), + ( + "reportrequired", + models.TextField(blank=True, db_column="REPORTREQUIRED", null=True), + ), + ( + "multiple_cpas", + models.TextField(blank=True, db_column="MULTIPLE_CPAS", null=True), + ), + ( + "auditor_ein", + models.TextField(blank=True, db_column="AUDITOR_EIN", null=True), + ), + ( + "facaccepteddate", + models.TextField( + blank=True, db_column="FACACCEPTEDDATE", null=True + ), + ), + ( + "cpaforeign", + models.TextField(blank=True, db_column="CPAFOREIGN", null=True), + ), + ( + "cpacountry", + models.TextField(blank=True, db_column="CPACOUNTRY", null=True), + ), + ( + "entity_type", + models.TextField(blank=True, db_column="ENTITY_TYPE", null=True), + ), + ("uei", models.TextField(blank=True, db_column="UEI", null=True)), + ( + "multipleueis", + models.TextField(blank=True, db_column="MULTIPLEUEIS", null=True), + ), + ], + options={ + "db_table": "census_gen19", + "managed": False, + }, + ), + migrations.CreateModel( + name="CensusGen22", + fields=[ + ( + "index", + models.BigIntegerField( + blank=True, primary_key=True, serialize=False + ), + ), + ( + "audityear", + models.TextField(blank=True, db_column="AUDITYEAR", null=True), + ), + ("dbkey", models.TextField(blank=True, db_column="DBKEY", null=True)), + ( + "typeofentity", + models.TextField(blank=True, db_column="TYPEOFENTITY", null=True), + ), + ( + "fyenddate", + models.TextField(blank=True, db_column="FYENDDATE", null=True), + ), + ( + "audittype", + models.TextField(blank=True, db_column="AUDITTYPE", null=True), + ), + ( + "periodcovered", + models.TextField(blank=True, db_column="PERIODCOVERED", null=True), + ), + ( + "numbermonths", + models.TextField(blank=True, db_column="NUMBERMONTHS", null=True), + ), + ("ein", models.TextField(blank=True, db_column="EIN", null=True)), + ( + "multipleeins", + models.TextField(blank=True, db_column="MULTIPLEEINS", null=True), + ), + ( + "einsubcode", + models.TextField(blank=True, db_column="EINSUBCODE", null=True), + ), + ("duns", models.TextField(blank=True, db_column="DUNS", null=True)), + ( + "multipleduns", + models.TextField(blank=True, db_column="MULTIPLEDUNS", null=True), + ), + ( + "auditeename", + models.TextField(blank=True, db_column="AUDITEENAME", null=True), + ), + ( + "street1", + models.TextField(blank=True, db_column="STREET1", null=True), + ), + ( + "street2", + models.TextField(blank=True, db_column="STREET2", null=True), + ), + ("city", models.TextField(blank=True, db_column="CITY", null=True)), + ("state", models.TextField(blank=True, db_column="STATE", null=True)), + ( + "zipcode", + models.TextField(blank=True, db_column="ZIPCODE", null=True), + ), + ( + "auditeecontact", + models.TextField(blank=True, db_column="AUDITEECONTACT", null=True), + ), + ( + "auditeetitle", + models.TextField(blank=True, db_column="AUDITEETITLE", null=True), + ), + ( + "auditeephone", + models.TextField(blank=True, db_column="AUDITEEPHONE", null=True), + ), + ( + "auditeefax", + models.TextField(blank=True, db_column="AUDITEEFAX", null=True), + ), + ( + "auditeeemail", + models.TextField(blank=True, db_column="AUDITEEEMAIL", null=True), + ), + ( + "auditeedatesigned", + models.TextField( + blank=True, db_column="AUDITEEDATESIGNED", null=True + ), + ), + ( + "auditeenametitle", + models.TextField( + blank=True, db_column="AUDITEENAMETITLE", null=True + ), + ), + ( + "cpafirmname", + models.TextField(blank=True, db_column="CPAFIRMNAME", null=True), + ), + ( + "cpastreet1", + models.TextField(blank=True, db_column="CPASTREET1", null=True), + ), + ( + "cpastreet2", + models.TextField(blank=True, db_column="CPASTREET2", null=True), + ), + ( + "cpacity", + models.TextField(blank=True, db_column="CPACITY", null=True), + ), + ( + "cpastate", + models.TextField(blank=True, db_column="CPASTATE", null=True), + ), + ( + "cpazipcode", + models.TextField(blank=True, db_column="CPAZIPCODE", null=True), + ), + ( + "cpacontact", + models.TextField(blank=True, db_column="CPACONTACT", null=True), + ), + ( + "cpatitle", + models.TextField(blank=True, db_column="CPATITLE", null=True), + ), + ( + "cpaphone", + models.TextField(blank=True, db_column="CPAPHONE", null=True), + ), + ("cpafax", models.TextField(blank=True, db_column="CPAFAX", null=True)), + ( + "cpaemail", + models.TextField(blank=True, db_column="CPAEMAIL", null=True), + ), + ( + "cpadatesigned", + models.TextField(blank=True, db_column="CPADATESIGNED", null=True), + ), + ( + "cog_over", + models.TextField(blank=True, db_column="COG_OVER", null=True), + ), + ( + "cogagency", + models.TextField(blank=True, db_column="COGAGENCY", null=True), + ), + ( + "oversightagency", + models.TextField( + blank=True, db_column="OVERSIGHTAGENCY", null=True + ), + ), + ( + "typereport_fs", + models.TextField(blank=True, db_column="TYPEREPORT_FS", null=True), + ), + ( + "sp_framework", + models.TextField(blank=True, db_column="SP_FRAMEWORK", null=True), + ), + ( + "sp_framework_required", + models.TextField( + blank=True, db_column="SP_FRAMEWORK_REQUIRED", null=True + ), + ), + ( + "typereport_sp_framework", + models.TextField( + blank=True, db_column="TYPEREPORT_SP_FRAMEWORK", null=True + ), + ), + ( + "goingconcern", + models.TextField(blank=True, db_column="GOINGCONCERN", null=True), + ), + ( + "reportablecondition", + models.TextField( + blank=True, db_column="REPORTABLECONDITION", null=True + ), + ), + ( + "materialweakness", + models.TextField( + blank=True, db_column="MATERIALWEAKNESS", null=True + ), + ), + ( + "materialnoncompliance", + models.TextField( + blank=True, db_column="MATERIALNONCOMPLIANCE", null=True + ), + ), + ( + "typereport_mp", + models.TextField(blank=True, db_column="TYPEREPORT_MP", null=True), + ), + ( + "dup_reports", + models.TextField(blank=True, db_column="DUP_REPORTS", null=True), + ), + ( + "dollarthreshold", + models.TextField( + blank=True, db_column="DOLLARTHRESHOLD", null=True + ), + ), + ( + "lowrisk", + models.TextField(blank=True, db_column="LOWRISK", null=True), + ), + ( + "reportablecondition_mp", + models.TextField( + blank=True, db_column="REPORTABLECONDITION_MP", null=True + ), + ), + ( + "materialweakness_mp", + models.TextField( + blank=True, db_column="MATERIALWEAKNESS_MP", null=True + ), + ), + ("qcosts", models.TextField(blank=True, db_column="QCOSTS", null=True)), + ( + "cyfindings", + models.TextField(blank=True, db_column="CYFINDINGS", null=True), + ), + ( + "pyschedule", + models.TextField(blank=True, db_column="PYSCHEDULE", null=True), + ), + ( + "totfedexpend", + models.TextField(blank=True, db_column="TOTFEDEXPEND", null=True), + ), + ( + "datefirewall", + models.TextField(blank=True, db_column="DATEFIREWALL", null=True), + ), + ( + "previousdatefirewall", + models.TextField( + blank=True, db_column="PREVIOUSDATEFIREWALL", null=True + ), + ), + ( + "reportrequired", + models.TextField(blank=True, db_column="REPORTREQUIRED", null=True), + ), + ( + "multiple_cpas", + models.TextField(blank=True, db_column="MULTIPLE_CPAS", null=True), + ), + ( + "auditor_ein", + models.TextField(blank=True, db_column="AUDITOR_EIN", null=True), + ), + ( + "facaccepteddate", + models.TextField( + blank=True, db_column="FACACCEPTEDDATE", null=True + ), + ), + ( + "cpaforeign", + models.TextField(blank=True, db_column="CPAFOREIGN", null=True), + ), + ( + "cpacountry", + models.TextField(blank=True, db_column="CPACOUNTRY", null=True), + ), + ( + "entity_type", + models.TextField(blank=True, db_column="ENTITY_TYPE", null=True), + ), + ("uei", models.TextField(blank=True, db_column="UEI", null=True)), + ( + "multipleueis", + models.TextField(blank=True, db_column="MULTIPLEUEIS", null=True), + ), + ], + options={ + "db_table": "census_gen22", + "managed": False, + }, + ), + migrations.CreateModel( + name="AdditionalEin", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "report_id", + models.TextField(verbose_name="; foreign key everywhere else"), + ), + ("additional_ein", models.TextField()), + ], + ), migrations.CreateModel( name="AdditionalUei", fields=[ @@ -393,6 +1236,13 @@ class Migration(migrations.Migration): verbose_name="Note title", ), ), + ( + "contains_chart_or_table", + models.TextField( + help_text="Census mapping: ", + verbose_name="Indicates whether or not the text contained charts or tables that could not be entered due to formatting restrictions", + ), + ), ], ), migrations.CreateModel( diff --git a/backend/dissemination/migrations/0002_additionalein.py b/backend/dissemination/migrations/0002_additionalein.py deleted file mode 100644 index 0869e6907d..0000000000 --- a/backend/dissemination/migrations/0002_additionalein.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.2.3 on 2023-09-12 14:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("dissemination", "0001_initial"), - ] - - operations = [ - migrations.CreateModel( - name="AdditionalEin", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "report_id", - models.TextField(verbose_name="; foreign key everywhere else"), - ), - ("additional_ein", models.TextField()), - ], - ), - ] diff --git a/backend/dissemination/models.py b/backend/dissemination/models.py index 7e19001360..01899ef306 100644 --- a/backend/dissemination/models.py +++ b/backend/dissemination/models.py @@ -2,6 +2,8 @@ from . import docs +from .hist_models import census_2019, census_2022 # noqa: F401 + BIGINT_MAX_DIGITS = 25 REPORT_ID_FK_HELP_TEXT = "; foreign key everywhere else" @@ -204,6 +206,10 @@ class Note(models.Model): ) content = models.TextField("Content of the Note", help_text=docs.content) note_title = models.TextField("Note title", help_text=docs.title) + contains_chart_or_table = models.TextField( + "Indicates whether or not the text contained charts or tables that could not be entered due to formatting restrictions", + help_text=docs.charts_tables_note, + ) class Passthrough(models.Model): diff --git a/backend/load_data.sh b/backend/load_data.sh index 93f019c102..9443b227bd 100755 --- a/backend/load_data.sh +++ b/backend/load_data.sh @@ -6,6 +6,6 @@ git config --global http.proxy "$https_proxy" git clone https://github.com/GSA-TTS/fac-historic-public-csvs.git cd fac-historic-public-csvs -export PATH=/home/vcap/deps/0/apt/usr/lib/postgresql/14/bin:$PATH +export PATH=/home/vcap/deps/0/apt/usr/lib/postgresql/15/bin:$PATH ./create-dumps.sh ./wait-and-load.sh diff --git a/backend/package-lock.json b/backend/package-lock.json index bce297582a..ffa846ff65 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -40,8 +40,8 @@ "stylelint-config-standard-scss": "^6.1.0" }, "engines": { - "node": ">=14.0.0 <19.0.0", - "npm": ">=8.0.0 <9.0.0" + "node": ">=16.0.0 <19.0.0", + "npm": ">=8.0.0 <10.0.0" } }, "node_modules/@4tw/cypress-drag-drop": { diff --git a/backend/package.json b/backend/package.json index b688a9b9b7..d4492de28d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "engines": { - "npm": ">=8.0.0 <9.0.0", - "node": ">=14.0.0 <19.0.0" + "npm": ">=8.0.0 <10.0.0", + "node": ">=16.0.0 <19.0.0" }, "scripts": { "build": "run-p build:*", diff --git a/backend/report_submission/context_processors.py b/backend/report_submission/context_processors.py new file mode 100644 index 0000000000..fa2a0463d3 --- /dev/null +++ b/backend/report_submission/context_processors.py @@ -0,0 +1,5 @@ +def certifiers_emails_must_not_match(request): + emails_must_not_match = ( + "The certifying auditor and auditee should not have the same email address" + ) + return {"certifiers_emails_must_not_match": emails_must_not_match} diff --git a/backend/report_submission/forms.py b/backend/report_submission/forms.py index e9b0f29d67..cde028da5d 100644 --- a/backend/report_submission/forms.py +++ b/backend/report_submission/forms.py @@ -1,9 +1,34 @@ from django import forms +from django.core.validators import RegexValidator from config.settings import STATE_ABBREVS from api.uei import get_uei_info_from_sam_gov +# Regex for words, includes non-[A-Z] ASCII characters like ñ and ī. +# \A and \Z start and terminate the string. +# [^\W\d] - matches values _not_ in W (non-word characters) or d (digits), which is all alphas, including diacritics. +# [^\W\d] is OR'd with \s to allow whitespace instead of another character, to allow spaces. +# [^\W\d]|\s is wrapped in parenthesis and appended by a plus to allow any number of characters. +alpha_validator = RegexValidator( + r"\A([^\W\d]|\s)+\Z", "This field should not include numbers or special characters." +) +date_validator = RegexValidator( + r"^([0-9]{2}/[0-9]{2}/[0-9]{4})|([0-9]{4}-[0-9]{2}-[0-9]{4})$", + "Dates should be in the format 00/00/0000", +) +ein_validator = RegexValidator( + r"^[0-9]{9}$", "EINs should be nine characters long and be made up of only numbers." +) +phone_validator = RegexValidator( + r"^[1-9]{1}[0-9]{9}$", + "Phone numbers should be ten numbers long, cannot include spaces or symbols such as '-' or '+', and cannot begin with a zero.", +) +zip_validator = RegexValidator( + r"^[0-9]{5}(?:[0-9]{4})?$", "Zip codes should be in the format 12345 or 12345-1234." +) + + def validate_uei(value): sam_response = get_uei_info_from_sam_gov(value) if sam_response.get("errors"): @@ -34,38 +59,67 @@ def clean(self): class GeneralInformationForm(forms.Form): + max_string_length = 100 choices_state_abbrevs = list((i, i) for i in STATE_ABBREVS) - audit_type = forms.CharField() - auditee_fiscal_period_end = forms.CharField() - auditee_fiscal_period_start = forms.CharField() - audit_period_covered = forms.CharField() + audit_type = forms.CharField(required=False) + auditee_fiscal_period_end = forms.CharField( + required=False, validators=[date_validator] + ) + auditee_fiscal_period_start = forms.CharField( + required=False, validators=[date_validator] + ) + audit_period_covered = forms.CharField(required=False) audit_period_other_months = forms.CharField(required=False) - ein = forms.CharField() + ein = forms.CharField( + required=False, + validators=[ein_validator], # validators are not run against empty fields + ) ein_not_an_ssn_attestation = forms.BooleanField(required=False) multiple_eins_covered = forms.BooleanField(required=False) - auditee_uei = forms.CharField() + auditee_uei = forms.CharField(required=False) multiple_ueis_covered = forms.BooleanField(required=False) - auditee_name = forms.CharField() - auditee_address_line_1 = forms.CharField() - auditee_city = forms.CharField() - auditee_state = forms.ChoiceField(choices=choices_state_abbrevs) - auditee_zip = forms.CharField() - auditee_contact_name = forms.CharField() - auditee_contact_title = forms.CharField() - auditee_phone = forms.CharField() - auditee_email = forms.CharField() - auditor_firm_name = forms.CharField() - auditor_ein = forms.CharField() + auditee_name = forms.CharField(max_length=max_string_length, required=False) + auditee_address_line_1 = forms.CharField( + max_length=max_string_length, required=False + ) + auditee_city = forms.CharField( + max_length=max_string_length, + required=False, + validators=[alpha_validator], # validators are not run against empty fields + ) + auditee_state = forms.ChoiceField(choices=choices_state_abbrevs, required=False) + auditee_zip = forms.CharField(required=False, validators=[zip_validator]) + auditee_contact_name = forms.CharField(max_length=max_string_length, required=False) + auditee_contact_title = forms.CharField( + max_length=max_string_length, required=False + ) + auditee_phone = forms.CharField(required=False, validators=[phone_validator]) + auditee_email = forms.CharField(max_length=max_string_length, required=False) + auditor_firm_name = forms.CharField(max_length=max_string_length, required=False) + auditor_ein = forms.CharField( + required=False, + validators=[ein_validator], # validators are not run against empty fields + ) auditor_ein_not_an_ssn_attestation = forms.BooleanField(required=False) - auditor_country = forms.CharField() - auditor_international_address = forms.CharField(required=False) - auditor_address_line_1 = forms.CharField(required=False) - auditor_city = forms.CharField(required=False) - auditor_state = forms.CharField(required=False) - auditor_zip = forms.CharField(required=False) - auditor_contact_name = forms.CharField() - auditor_contact_title = forms.CharField() - auditor_phone = forms.CharField() - auditor_email = forms.CharField() + auditor_country = forms.CharField(required=False) + auditor_international_address = forms.CharField( + max_length=max_string_length, required=False + ) + auditor_address_line_1 = forms.CharField( + max_length=max_string_length, required=False + ) + auditor_city = forms.CharField( + max_length=max_string_length, + required=False, + validators=[alpha_validator], # validators are not run against empty fields + ) + auditor_state = forms.ChoiceField(choices=choices_state_abbrevs, required=False) + auditor_zip = forms.CharField(required=False, validators=[zip_validator]) + auditor_contact_name = forms.CharField(max_length=max_string_length, required=False) + auditor_contact_title = forms.CharField( + max_length=max_string_length, required=False + ) + auditor_phone = forms.CharField(required=False, validators=[phone_validator]) + auditor_email = forms.CharField(max_length=max_string_length, required=False) secondary_auditors_exist = forms.BooleanField(required=False) diff --git a/backend/report_submission/templates/report_submission/gen-form.html b/backend/report_submission/templates/report_submission/gen-form.html index f7e0fe037f..5288056f54 100644 --- a/backend/report_submission/templates/report_submission/gen-form.html +++ b/backend/report_submission/templates/report_submission/gen-form.html @@ -20,12 +20,6 @@
  • Type of audit
  • -
  • - Audit period -
  • -
  • - Fiscal period -
  • Auditee information
  • @@ -41,220 +35,145 @@ method="post"> {% csrf_token %}
    - - General information - -

    - *Indicates a required field. -

    -
    - - Fiscal Period - -
    - -

    mm/dd/yyyy

    - - -
    -
    - -

    mm/dd/yyyy

    - - -
    -
    -
    - - Type of audit - -

    - Which type of Uniform Guidance audit are you filing for? * -

    - -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - - Audit period - -

    - How long of an audit period is covered in this report? * -

    - -
    - - -
    -
    - - -
    -
    - - - - -
    + General information +

    All Fields are required before a final submission can be made.

    + {% if errors %} + There were errors when attempting to submit the form. Scroll down for more details. + {% endif %} +
    +
    + Fiscal Period +
    + +

    mm/dd/yyyy

    + + {{ errors.auditee_fiscal_period_start|striptags }} +
    +
    + +

    mm/dd/yyyy

    + + {{ errors.auditee_fiscal_period_end|striptags }} +
    +
    +
    + Type of audit +

    Which type of Uniform Guidance audit are you filing for?

    +
    + + +
    +
    + + +
    + {% comment %} Include after we are able to recieve ACEE submissions. {% endcomment %} + {% comment %}
    + + +
    {% endcomment %} +
    +
    + Audit period +

    How long of an audit period is covered in this report?

    +
    + + +
    +
    + + +
    +
    + + + + +
    +
    -
    -

    - Auditee information -

    +
    +

    Auditee information

    - - Auditee Employer Identification Number (EIN) - + Auditee Employer Identification Number (EIN)
    - - + + aria-required="false" + value="{{ ein | default_if_none:'' }}" /> + {{ errors.ein|striptags }}
    + {% if ein_not_an_ssn_attestation == True %}checked{% endif %} />
    - - Are multiple EINs covered in this report?* - -

    - You'll be able to add additional EINs later. -

    - + Are multiple EINs covered in this report? +

    You'll be able to add additional EINs later.

    - + {% if multiple_eins_covered == True %}checked{% endif %} /> +
    id="multiple-eins-no" name="multiple_eins_covered" value="false" - {% if multiple_eins_covered == False %} checked {% endif %} - data-validate-not-null=""/> - + {% if multiple_eins_covered == False %}checked{% endif %} /> +
    - - Auditee details - + Auditee details
    - - + + aria-required="false" + value="{{ auditee_name | default_if_none:'' }}" />
    - - + + aria-required="false" + value="{{ auditee_address_line_1 | default_if_none:'' }}" />
    - - + + aria-required="false" + value="{{ auditee_city | default_if_none:'' }}" /> + {{ errors.auditee_city|striptags }}
    -
    + aria-required="false" + value="{{ auditee_zip | default_if_none:'' }}" /> + {{ errors.auditee_zip|striptags }}
    - - Auditee Unique Entity Identifier (UEI) - + Auditee Unique Entity Identifier (UEI)
    - - + + {{ auditee_uei }} + aria-required="false" + hidden + value="{{ auditee_uei | default_if_none:'' }}" />
    - - Are multiple UEIs covered in this report?* - -

    - You'll be able to add additional UEIs later. -

    - + Are multiple UEIs covered in this report? +

    You'll be able to add additional UEIs later.

    - + {% if multiple_ueis_covered == True %}checked{% endif %} /> +
    id="multiple-ueis-no" name="multiple_ueis_covered" value="false" - {% if multiple_ueis_covered == False %} checked {% endif %} - data-validate-not-null=""/> - + {% if multiple_ueis_covered == False %}checked{% endif %} /> +
    - - Auditee contact information - + Auditee contact information
    - - + + aria-required="false" + value="{{ auditee_contact_name | default_if_none:'' }}" />
    - - + + aria-required="false" + value="{{ auditee_contact_title | default_if_none:'' }}" />
    - - + + aria-required="false" + value="{{ auditee_phone | default_if_none:'' }}" /> + {{ errors.auditee_phone|striptags }}
    - - + + aria-required="false" + value="{{ auditee_email | default_if_none:'' }}" />
    -
    -

    - Primary auditor information -

    +
    +

    Primary auditor information

    If multiple audit organizations worked on this audit, only include the lead or coordinating auditor's information.

    - - Auditor EIN - + Auditor EIN
    - - + + aria-required="false" + value="{{ auditor_ein | default_if_none:'' }}" /> + {{ errors.auditor_ein|striptags }}
    + {% if auditor_ein_not_an_ssn_attestation == True %}checked{% endif %} />
    - - Auditor details - + Auditor details
    - - + + aria-required="false" + value="{{ auditor_firm_name | default_if_none:'' }}" />
    - - Audit firm/organization address - + Audit firm/organization address
    -
    - + aria-required="false" + value="{{ auditor_address_line_1 | default_if_none:'' }}" />
    - + aria-required="false" + value="{{ auditor_city | default_if_none:'' }}" /> + {{ errors.auditor_city|striptags }}
    -
    + aria-required="false" + value="{{ auditor_zip | default_if_none:'' }}" /> + {{ errors.auditor_zip|striptags }}
    - - Auditor contact information - + Auditor contact information
    - - + + aria-required="false" + value="{{ auditor_contact_name | default_if_none:'' }}" />
    - - + + aria-required="false" + value="{{ auditor_contact_title | default_if_none:'' }}" />
    - - + + aria-required="false" + value="{{ auditor_phone | default_if_none:'' }}" /> + {{ errors.auditor_phone|striptags }}
    - - + + aria-required="false" + value="{{ auditor_email | default_if_none:'' }}" />
    - - Are secondary auditors associated with this report?* - -

    - You'll be able to add secondary auditors later. -

    - -
    - - -
    -
    - - -
    -
    + Are secondary auditors associated with this report? +

    You'll be able to add secondary auditors later.

    +
    + + +
    +
    + + +
    +
    - +
    diff --git a/backend/report_submission/templates/report_submission/step-2.html b/backend/report_submission/templates/report_submission/step-2.html index b0ccad3e85..0b6ffc8f5a 100644 --- a/backend/report_submission/templates/report_submission/step-2.html +++ b/backend/report_submission/templates/report_submission/step-2.html @@ -30,7 +30,7 @@

    - Entity UEI Unique Identity Identifier + Entity UEI Unique Entity Identifier

    12-character alphanumeric ID
    diff --git a/backend/report_submission/templates/report_submission/step-3.html b/backend/report_submission/templates/report_submission/step-3.html index 91a6698114..687d3729c8 100644 --- a/backend/report_submission/templates/report_submission/step-3.html +++ b/backend/report_submission/templates/report_submission/step-3.html @@ -57,6 +57,7 @@ + + data-validate-email="" + data-validate-must-not-match="certifying_auditor_contact_email"/>
    @@ -133,6 +135,7 @@ + + data-validate-email="" + data-validate-must-not-match="certifying_auditee_contact_email"/>
    diff --git a/backend/report_submission/test_views.py b/backend/report_submission/test_views.py index 3652d661a1..857cf9b379 100644 --- a/backend/report_submission/test_views.py +++ b/backend/report_submission/test_views.py @@ -457,9 +457,19 @@ def test_get_valid_audit_returns_populated_form(self): # assert that the text fields are populated in the returned form for field in text_fields: - value = sac.general_information[field] - - self.assertEqual(response.context[field], value) + if ( + field != "auditee_fiscal_period_start" + and field != "auditee_fiscal_period_end" + ): + value = sac.general_information[field] + self.assertEqual(response.context[field], value) + else: + # These are stored in YYYY-MM-DD but get sent as MM/DD/YYYY, so they get special treatment. + stored_value = sac.general_information[field] + formatted_field = datetime.strptime( + response.context[field], "%m/%d/%Y" + ).strftime("%Y-%m-%d") + self.assertEqual(stored_value, formatted_field) def test_post_requires_login(self): """Requests to the POST endpoint require the user to be authenticated""" @@ -522,8 +532,8 @@ def test_post_updates_fields(self, foreign_auditor=False): data = { "audit_type": "single-audit", - "auditee_fiscal_period_start": "2021-11-01", - "auditee_fiscal_period_end": "2022-11-01", + "auditee_fiscal_period_start": "11/01/2021", + "auditee_fiscal_period_end": "11/01/2022", "audit_period_covered": "other", "audit_period_other_months": "10", "ein": "123456780", @@ -546,7 +556,7 @@ def test_post_updates_fields(self, foreign_auditor=False): "auditor_ein_not_an_ssn_attestation": True, "auditor_contact_name": "Qualified Robot Accountant", "auditor_contact_title": "Just an extraordinary person", - "auditor_phone": "0008675310", + "auditor_phone": "9876543210", "auditor_email": "qualified.robot.accountant@dollarauditstore.com", } if foreign_auditor: @@ -574,9 +584,24 @@ def test_post_updates_fields(self, foreign_auditor=False): updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) for field in data: - self.assertEqual( - getattr(updated_sac, field), data[field], f"mismatch for field: {field}" - ) + if ( + field != "auditee_fiscal_period_start" + and field != "auditee_fiscal_period_end" + ): + self.assertEqual( + getattr(updated_sac, field), + data[field], + f"mismatch for field: {field}", + ) + else: + # These are stored in YYYY-MM-DD but are sent as MM/DD/YYYY, so they get special treatment. + stored_value = getattr(updated_sac, field) + formatted_field = datetime.strptime(data[field], "%m/%d/%Y").strftime( + "%Y-%m-%d" + ) + self.assertEqual( + stored_value, formatted_field, f"mismatch for field: {field}" + ) submission_events = SubmissionEvent.objects.filter(sac=sac) @@ -604,12 +629,13 @@ def test_post_requires_fields(self): # submit a bad date format for auditee_fiscal_period_start to verify that the input is being validated data = { - "auditee_fiscal_period_start": "2023-01-01", + "auditee_fiscal_period_start": "Not a date", } + # Post will redirect back to the page with an error message above the relevant date field response = self.client.post(url, data=data) - self.assertEqual(response.status_code, 400) + self.assertContains(response, "Dates should be in the format") def test_post_validates_general_information(self): """When the general information form is submitted, the data should be validated against the general information schema""" @@ -660,6 +686,7 @@ def test_post_validates_general_information(self): "auditor_email": "qualified.robot.accountant@dollarauditstore.com", } + # Post will redirect back to the page with an error message above the relevant date field response = self.client.post(url, data=data) - self.assertEqual(response.status_code, 400) + self.assertContains(response, "Dates should be in the format") diff --git a/backend/report_submission/views.py b/backend/report_submission/views.py index 4d8965ae0e..c719b68b0e 100644 --- a/backend/report_submission/views.py +++ b/backend/report_submission/views.py @@ -1,4 +1,5 @@ import logging +from datetime import datetime from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import BadRequest, PermissionDenied, ValidationError @@ -6,15 +7,16 @@ from django.urls import reverse from django.views import View +import api.views + +from audit.cross_validation.naming import NC, SECTION_NAMES as SN from audit.models import Access, SingleAuditChecklist, LateChangeError, SubmissionEvent from audit.validators import validate_general_information_json -from report_submission.forms import AuditeeInfoForm, GeneralInformationForm - -import api.views - from config.settings import STATIC_SITE_URL, STATE_ABBREVS +from report_submission.forms import AuditeeInfoForm, GeneralInformationForm + logger = logging.getLogger(__name__) @@ -98,11 +100,13 @@ def post(self, post_request): result = api.views.access_and_submission_check( post_request.user, post_request.POST ) + report_id = result.get("report_id") if report_id: return redirect(f"/report_submission/general-information/{report_id}") - return redirect(reverse("report_submission:accessandsubmission")) + else: + return redirect(reverse("report_submission:accessandsubmission")) class GeneralInformationFormView(LoginRequiredMixin, View): @@ -157,6 +161,8 @@ def get(self, request, *args, **kwargs): "state_abbrevs": STATE_ABBREVS, } + context = self._dates_to_slashes(context) + return render(request, "report_submission/gen-form.html", context) except SingleAuditChecklist.DoesNotExist as err: raise PermissionDenied("You do not have access to this audit.") from err @@ -178,13 +184,19 @@ def post(self, request, *args, **kwargs): form = GeneralInformationForm(request.POST) if not form.is_valid(): + context = form.cleaned_data | { + "errors": form.errors, + "report_id": report_id, + "state_abbrevs": STATE_ABBREVS, + } message = "" for field, errors in form.errors.items(): message = f"{message}\n {field}: {errors}" logger.warning(f"Error {field}: {errors}") - raise BadRequest(message) + return render(request, "report_submission/gen-form.html", context) form = self._wipe_auditor_address(form) + form.cleaned_data = self._dates_to_hyphens(form.cleaned_data) general_information = sac.general_information general_information.update(form.cleaned_data) validated = validate_general_information_json(general_information) @@ -202,12 +214,59 @@ def post(self, request, *args, **kwargs): raise PermissionDenied("You do not have access to this audit.") from err except ValidationError as err: message = f"ValidationError for report ID {report_id}: {err.message}" - print(message) raise BadRequest(message) except LateChangeError: return render(request, "audit/no-late-changes.html") + except Exception as err: + message = f"Unexpected error in GeneralInformationFormView post. Report ID {report_id}" + logger.warning(message) + raise err + + def _dates_to_slashes(self, data): + """ + Given a general_information object containging both auditee_fiscal_period_start + and auditee_fiscal_period_start, convert YYYY-MM-DD to MM/DD/YYYY for display. + """ + try: + datetime_object_start = datetime.strptime( + data.get("auditee_fiscal_period_start", ""), "%Y-%m-%d" + ) + datetime_object_end = datetime.strptime( + data.get("auditee_fiscal_period_end", ""), "%Y-%m-%d" + ) + data["auditee_fiscal_period_start"] = datetime_object_start.strftime( + "%m/%d/%Y" + ) + data["auditee_fiscal_period_end"] = datetime_object_end.strftime("%m/%d/%Y") + except Exception: + return data + return data + + def _dates_to_hyphens(self, data): + """ + Given a general_information object containging both auditee_fiscal_period_start + and auditee_fiscal_period_start, convert MM/DD/YYYY to YYYY-MM-DD for storage. + """ + try: + datetime_object_start = datetime.strptime( + data.get("auditee_fiscal_period_start", ""), "%m/%d/%Y" + ) + datetime_object_end = datetime.strptime( + data.get("auditee_fiscal_period_end", ""), "%m/%d/%Y" + ) + data["auditee_fiscal_period_start"] = datetime_object_start.strftime( + "%Y-%m-%d" + ) + data["auditee_fiscal_period_end"] = datetime_object_end.strftime("%Y-%m-%d") + except Exception: + return data + return data def _wipe_auditor_address(self, form): + """ + Given a general_information form object containing auditor_country, wipe + unnecessary address data depending on its value. + """ # If non-USA is selected, wipe USA-specific fields # Else, wipe the non-USA specific field keys_to_wipe = [ @@ -216,7 +275,7 @@ def _wipe_auditor_address(self, form): "auditor_state", "auditor_zip", ] - if form.cleaned_data["auditor_country"] == "non-USA": + if form.cleaned_data.get("auditor_country") == "non-USA": for key in keys_to_wipe: form.cleaned_data[key] = "" else: @@ -238,7 +297,7 @@ def get(self, request, *args, **kwargs): "view_id": "federal-awards", "view_name": "Federal awards", "instructions": "Enter the federal awards you received in the last audit year using the provided worksheet.", - "DB_id": "federal_awards", + "DB_id": SN[NC.FEDERAL_AWARDS].snake_case, "instructions_url": instructions_base_url + "federal-awards/", "workbook_url": workbook_base_url + "federal-awards-workbook.xlsx", }, @@ -246,7 +305,7 @@ def get(self, request, *args, **kwargs): "view_id": "notes-to-sefa", "view_name": "Notes to SEFA", "instructions": "Enter the notes on the Schedule of Expenditures of Federal Awards (SEFA) using the provided worksheet.", - "DB_id": "notes_to_sefa", + "DB_id": SN[NC.NOTES_TO_SEFA].snake_case, "instructions_url": instructions_base_url + "notes-to-sefa/", "workbook_url": workbook_base_url + "notes-to-sefa-workbook.xlsx", }, @@ -254,7 +313,7 @@ def get(self, request, *args, **kwargs): "view_id": "audit-findings", "view_name": "Audit findings", "instructions": "Enter the audit findings for your federal awards using the provided worksheet.", - "DB_id": "findings_uniform_guidance", + "DB_id": SN[NC.FINDINGS_UNIFORM_GUIDANCE].snake_case, "instructions_url": instructions_base_url + "federal-awards-audit-findings/", "no_findings_disclaimer": True, @@ -265,7 +324,7 @@ def get(self, request, *args, **kwargs): "view_id": "audit-findings-text", "view_name": "Audit findings text", "instructions": "Enter the text for your audit findings using the provided worksheet.", - "DB_id": "findings_text", + "DB_id": SN[NC.FINDINGS_TEXT].snake_case, "instructions_url": instructions_base_url + "federal-awards-audit-findings-text/", "no_findings_disclaimer": True, @@ -276,7 +335,7 @@ def get(self, request, *args, **kwargs): "view_id": "cap", "view_name": "Corrective Action Plan (CAP)", "instructions": "Enter your CAP text using the provided worksheet.", - "DB_id": "corrective_action_plan", + "DB_id": SN[NC.CORRECTIVE_ACTION_PLAN].snake_case, "instructions_url": instructions_base_url + "corrective-action-plan/", "no_findings_disclaimer": True, "workbook_url": workbook_base_url @@ -286,6 +345,7 @@ def get(self, request, *args, **kwargs): "view_id": "additional-ueis", "view_name": "Additional UEIs", "instructions": "Enter any additional UEIs using the provided worksheet.", + "DB_id": SN[NC.ADDITIONAL_UEIS].snake_case, "instructions_url": instructions_base_url + "additional-ueis-workbook/", "workbook_url": workbook_base_url + "additional-ueis-workbook.xlsx", }, @@ -293,6 +353,7 @@ def get(self, request, *args, **kwargs): "view_id": "secondary-auditors", "view_name": "Secondary auditors", "instructions": "Enter any additional auditors using the provided worksheet.", + "DB_id": SN[NC.SECONDARY_AUDITORS].snake_case, "instructions_url": instructions_base_url + "secondary-auditors-workbook/", "workbook_url": workbook_base_url + "secondary-auditors-workbook.xlsx", @@ -301,6 +362,7 @@ def get(self, request, *args, **kwargs): "view_id": "additional-eins", "view_name": "Additional EINs", "instructions": "Enter any additional EINs using the provided worksheet.", + "DB_id": SN[NC.ADDITIONAL_EINS].snake_case, "instructions_url": instructions_base_url + "additional-eins-workbook/", "workbook_url": workbook_base_url + "additional-eins-workbook.xlsx", }, diff --git a/backend/requirements.txt b/backend/requirements.txt index ae999d18ef..9c4cfb60b1 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --generate-hashes --output-file=requirements.txt ./requirements/requirements.in +# pip-compile --allow-unsafe --generate-hashes --output-file=requirements.txt ./requirements/requirements.in # annotated-types==0.5.0 \ --hash=sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802 \ @@ -21,7 +21,9 @@ attrs==23.1.0 \ boto3==1.28.27 \ --hash=sha256:8da9621931291b6c261fdaae465f05737c16519b9667d8463181cb8b88444572 \ --hash=sha256:a336cf53a6d86ee6d27b2f6d8b78ec9b320209127e5126359881bbd68f33d0b9 - # via -r ./requirements/requirements.in + # via + # -r ./requirements/requirements.in + # django-storages botocore==1.31.27 \ --hash=sha256:13af1588023750c9bc66d202bb5a934c9412a7dc52587532264ab725c42c2c50 \ --hash=sha256:739d09e13751e3b9b0f341b5ffe5bf8d0452b8769d435c4084ee88739d42b7f7 @@ -230,6 +232,7 @@ django==4.2.4 \ # dj-database-url # django-cors-headers # django-csp + # django-dbbackup # django-storages # djangorestframework # djangorestframework-simplejwt @@ -245,11 +248,15 @@ django-csp==3.7 \ --hash=sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a \ --hash=sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727 # via -r ./requirements/requirements.in +django-dbbackup==4.0.2 \ + --hash=sha256:1874d684abc22260972a67668a6db3331b24d7e1e8af89eaffdcd61eb27dbc2a \ + --hash=sha256:3ccde831f1a8268fb031b37a8e7e2de3abb556623023af1e859cd7104c09ea2a + # via -r ./requirements/requirements.in django-fsm==2.8.1 \ --hash=sha256:e2c02cbf273fb9691aa9a907c29990afdd21a4adea09c5640344c93fbe03f8d9 \ --hash=sha256:fd9f8de9f33188e50f876ce53908fbd7289e5031a44ffdb97d43909e56699ef8 # via -r ./requirements/requirements.in -django-storages==1.13.2 \ +django-storages[boto3]==1.13.2 \ --hash=sha256:31dc5a992520be571908c4c40d55d292660ece3a55b8141462b4e719aa38eab3 \ --hash=sha256:cbadd15c909ceb7247d4ffc503f12a9bec36999df8d0bef7c31e57177d512688 # via -r ./requirements/requirements.in @@ -343,7 +350,9 @@ greenlet==2.0.2 \ --hash=sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19 \ --hash=sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1 \ --hash=sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526 - # via -r ./requirements/requirements.in + # via + # -r ./requirements/requirements.in + # sqlalchemy gunicorn==21.2.0 \ --hash=sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0 \ --hash=sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033 @@ -816,6 +825,7 @@ pytz==2023.3 \ --hash=sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588 \ --hash=sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb # via + # django-dbbackup # djangorestframework # pandas pyyaml==6.0.1 \ @@ -1040,7 +1050,6 @@ typing-extensions==4.7.1 \ --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \ --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2 # via - # asgiref # dj-database-url # pydantic # pydantic-core @@ -1049,7 +1058,9 @@ typing-extensions==4.7.1 \ tzdata==2023.3 \ --hash=sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a \ --hash=sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda - # via pandas + # via + # django + # pandas uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e diff --git a/backend/requirements/dev-requirements.in b/backend/requirements/dev-requirements.in index 06d281e5bc..9c1ce2577d 100644 --- a/backend/requirements/dev-requirements.in +++ b/backend/requirements/dev-requirements.in @@ -14,5 +14,6 @@ toml types-python-slugify types-requests whitenoise +xkcdpass tblib django-debug-toolbar \ No newline at end of file diff --git a/backend/requirements/requirements.in b/backend/requirements/requirements.in index 338e6582fc..6f1d73ce48 100644 --- a/backend/requirements/requirements.in +++ b/backend/requirements/requirements.in @@ -3,7 +3,8 @@ cfenv cryptography>=41.0.2 django-cors-headers django-csp -django-storages +django-dbbackup +django-storages[boto3] Django>=4.2.3 djangorestframework djangorestframework-simplejwt diff --git a/backend/schemas/.gitignore b/backend/schemas/.gitignore deleted file mode 100644 index 28bd9dba47..0000000000 --- a/backend/schemas/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -#*.xlsx -#*.xlsx# diff --git a/backend/schemas/output/excel/json/notes-to-sefa-workbook.json b/backend/schemas/output/excel/json/notes-to-sefa-workbook.json index f8f6049daf..b6cf66d8dd 100644 --- a/backend/schemas/output/excel/json/notes-to-sefa-workbook.json +++ b/backend/schemas/output/excel/json/notes-to-sefa-workbook.json @@ -138,7 +138,7 @@ }, { "header_height": 100, - "hide_col_from": 3, + "hide_col_from": 4, "name": "AdditionalNotes", "open_ranges": [ { @@ -169,6 +169,24 @@ }, "width": 56 }, + { + "help": { + "link": "https://fac.gov/documentation/validation/#yorn", + "text": "This field must be either `Y` or `N`" + }, + "range_name": "contains_chart_or_table", + "title": "Did Text Contain a Chart or Table?", + "title_cell": "C1", + "type": "yorn_range", + "validation": { + "allow_blank": "False", + "custom_error": "Must be 'Y' or 'N'", + "custom_title": "Y/N", + "formula1": "\"Y,N\"", + "type": "list" + }, + "width": 36 + }, { "formula": "=IF(A{0}<>\"\", ROW()-1, \"\")", "help": { @@ -178,7 +196,7 @@ "keep_locked": true, "range_name": "seq_number", "title": "Sequence number (Read Only)", - "title_cell": "C1", + "title_cell": "D1", "type": "open_range", "validation": { "type": "NOVALIDATION" diff --git a/backend/schemas/output/excel/xlsx/additional-eins-workbook.xlsx b/backend/schemas/output/excel/xlsx/additional-eins-workbook.xlsx index 8b6cb9bfb3..4e6300ed6e 100644 Binary files a/backend/schemas/output/excel/xlsx/additional-eins-workbook.xlsx and b/backend/schemas/output/excel/xlsx/additional-eins-workbook.xlsx differ diff --git a/backend/schemas/output/excel/xlsx/additional-ueis-workbook.xlsx b/backend/schemas/output/excel/xlsx/additional-ueis-workbook.xlsx index 454cb16342..b364f222bb 100644 Binary files a/backend/schemas/output/excel/xlsx/additional-ueis-workbook.xlsx and b/backend/schemas/output/excel/xlsx/additional-ueis-workbook.xlsx differ diff --git a/backend/schemas/output/excel/xlsx/audit-findings-text-workbook.xlsx b/backend/schemas/output/excel/xlsx/audit-findings-text-workbook.xlsx index dd372c212a..657d46c20f 100644 Binary files a/backend/schemas/output/excel/xlsx/audit-findings-text-workbook.xlsx and b/backend/schemas/output/excel/xlsx/audit-findings-text-workbook.xlsx differ diff --git a/backend/schemas/output/excel/xlsx/corrective-action-plan-workbook.xlsx b/backend/schemas/output/excel/xlsx/corrective-action-plan-workbook.xlsx index 00ef02f131..99b4a93e66 100644 Binary files a/backend/schemas/output/excel/xlsx/corrective-action-plan-workbook.xlsx and b/backend/schemas/output/excel/xlsx/corrective-action-plan-workbook.xlsx differ diff --git a/backend/schemas/output/excel/xlsx/federal-awards-audit-findings-workbook.xlsx b/backend/schemas/output/excel/xlsx/federal-awards-audit-findings-workbook.xlsx index b6dae3139b..760db8953d 100644 Binary files a/backend/schemas/output/excel/xlsx/federal-awards-audit-findings-workbook.xlsx and b/backend/schemas/output/excel/xlsx/federal-awards-audit-findings-workbook.xlsx differ diff --git a/backend/schemas/output/excel/xlsx/federal-awards-workbook.xlsx b/backend/schemas/output/excel/xlsx/federal-awards-workbook.xlsx index 5455cfd911..19dff6842a 100644 Binary files a/backend/schemas/output/excel/xlsx/federal-awards-workbook.xlsx and b/backend/schemas/output/excel/xlsx/federal-awards-workbook.xlsx differ diff --git a/backend/schemas/output/excel/xlsx/notes-to-sefa-workbook.xlsx b/backend/schemas/output/excel/xlsx/notes-to-sefa-workbook.xlsx index d84fb0e6d9..10769e1016 100644 Binary files a/backend/schemas/output/excel/xlsx/notes-to-sefa-workbook.xlsx and b/backend/schemas/output/excel/xlsx/notes-to-sefa-workbook.xlsx differ diff --git a/backend/schemas/output/excel/xlsx/secondary-auditors-workbook.xlsx b/backend/schemas/output/excel/xlsx/secondary-auditors-workbook.xlsx index 6ceea33de8..590844f688 100644 Binary files a/backend/schemas/output/excel/xlsx/secondary-auditors-workbook.xlsx and b/backend/schemas/output/excel/xlsx/secondary-auditors-workbook.xlsx differ diff --git a/backend/schemas/output/sections/GeneralInformation.schema.json b/backend/schemas/output/sections/GeneralInformation.schema.json index 79d498b89b..fef7349ed9 100644 --- a/backend/schemas/output/sections/GeneralInformation.schema.json +++ b/backend/schemas/output/sections/GeneralInformation.schema.json @@ -71,73 +71,88 @@ "then": { "properties": { "auditor_state": { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ], - "title": "State" + "anyOf": [ + { + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ] + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_zip": { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" + "anyOf": [ + { + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] } } } @@ -169,27 +184,43 @@ "metamodel_version": "1.7.0", "properties": { "audit_period_covered": { - "description": "Period type of audit being submitted", - "enum": [ - "annual", - "biennial", - "other" - ], - "title": "AuditPeriod", - "type": "string" + "oneOf": [ + { + "description": "Period type of audit being submitted", + "enum": [ + "annual", + "biennial", + "other" + ], + "title": "AuditPeriod", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "audit_period_other_months": { + "maxLength": 100, "type": "string" }, "audit_type": { - "description": "Type of audit being submitted", - "enum": [ - "program-specific", - "single-audit", - "alternative-compliance-engagement" - ], - "title": "AuditType", - "type": "string" + "oneOf": [ + { + "description": "Type of audit being submitted", + "enum": [ + "program-specific", + "single-audit" + ], + "title": "AuditType", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditee_address_line_1": { "maxLength": 100, @@ -208,15 +239,39 @@ "type": "string" }, "auditee_email": { - "format": "email", + "oneOf": [ + { + "format": "email" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditee_fiscal_period_end": { - "format": "date", + "oneOf": [ + { + "format": "date" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditee_fiscal_period_start": { - "format": "date", + "oneOf": [ + { + "format": "date" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditee_name": { @@ -224,73 +279,89 @@ "type": "string" }, "auditee_phone": { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" + "oneOf": [ + { + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditee_state": { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ], - "title": "State" + "oneOf": [ + { + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ], + "title": "State" + }, + { + "const": "", + "type": "string" + } + ] }, "auditee_uei": { "allOf": [ @@ -311,8 +382,16 @@ "type": "string" }, "auditee_zip": { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" + "anyOf": [ + { + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_address_line_1": { "maxLength": 100, @@ -340,28 +419,52 @@ "type": "string" }, "auditor_ein": { - "pattern": "^[0-9]{9}$", - "type": "string" + "oneOf": [ + { + "pattern": "^[0-9]{9}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_ein_not_an_ssn_attestation": { "type": "boolean" }, "auditor_email": { - "format": "email", + "maxLength": 100, + "oneOf": [ + { + "format": "email" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditor_firm_name": { - "type": "string" - }, - "auditor_foreign_address": { + "maxLength": 100, "type": "string" }, "auditor_international_address": { + "maxLength": 100, "type": "string" }, "auditor_phone": { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" + "oneOf": [ + { + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_state": { "anyOf": [ @@ -449,8 +552,16 @@ ] }, "ein": { - "pattern": "^[0-9]{9}$", - "type": "string" + "oneOf": [ + { + "pattern": "^[0-9]{9}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "ein_not_an_ssn_attestation": { "type": "boolean" @@ -471,18 +582,26 @@ "type": "boolean" }, "user_provided_organization_type": { - "description": "Org type", - "enum": [ - "state", - "local", - "tribal", - "higher-ed", - "non-profit", - "unknown", - "none" - ], - "title": "OrganizationType", - "type": "string" + "oneOf": [ + { + "description": "Org type", + "enum": [ + "state", + "local", + "tribal", + "higher-ed", + "non-profit", + "unknown", + "none" + ], + "title": "OrganizationType", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] } }, "title": "GeneralInformation", diff --git a/backend/schemas/output/sections/GeneralInformationComplete.schema.json b/backend/schemas/output/sections/GeneralInformationComplete.schema.json new file mode 100644 index 0000000000..618604b04b --- /dev/null +++ b/backend/schemas/output/sections/GeneralInformationComplete.schema.json @@ -0,0 +1,476 @@ +{ + "$id": "http://example.org/generalinformation", + "$schema": "http://json-schema.org/draft/2019-09/schema#", + "additionalProperties": false, + "allOf": [ + { + "anyOf": [ + { + "if": { + "properties": { + "audit_period_covered": { + "const": "annual" + } + } + }, + "then": { + "audit_period_other_months": { + "description": "Empty string or null", + "enum": [ + "", + "null" + ], + "title": "EmptyString_Null" + } + } + }, + { + "if": { + "properties": { + "audit_period_covered": { + "const": "biennial" + } + } + }, + "then": { + "audit_period_other_months": { + "description": "Empty string or null", + "enum": [ + "", + "null" + ], + "title": "EmptyString_Null" + } + } + }, + { + "if": { + "properties": { + "audit_period_covered": { + "const": "other" + } + } + }, + "then": { + "audit_period_other_months": { + "pattern": "^0[0-9]|1[0-8]$", + "type": "string" + } + } + } + ] + }, + { + "if": { + "properties": { + "auditor_country": { + "const": "USA" + } + } + }, + "then": { + "properties": { + "auditor_zip": { + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" + } + } + } + }, + { + "if": { + "properties": { + "auditor_country": { + "not": { + "const": "USA" + } + } + } + }, + "then": { + "properties": { + "auditor_zip": { + "const": "", + "type": "string" + } + } + } + }, + { + "properties": { + "ein_not_an_ssn_attestation": { + "const": true + } + } + }, + { + "properties": { + "auditor_ein_not_an_ssn_attestation": { + "const": true + } + } + } + ], + "metamodel_version": "1.7.0", + "properties": { + "audit_period_covered": { + "description": "Period type of audit being submitted", + "enum": [ + "annual", + "biennial", + "other" + ], + "title": "AuditPeriod", + "type": "string" + }, + "audit_period_other_months": { + "type": "string" + }, + "audit_type": { + "description": "Type of audit being submitted", + "enum": [ + "program-specific", + "single-audit" + ], + "title": "AuditType", + "type": "string" + }, + "auditee_address_line_1": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditee_city": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditee_contact_name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditee_contact_title": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditee_email": { + "format": "email", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditee_fiscal_period_end": { + "format": "date" + }, + "auditee_fiscal_period_start": { + "format": "date" + }, + "auditee_name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditee_phone": { + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" + }, + "auditee_state": { + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ], + "title": "State" + }, + "auditee_uei": { + "allOf": [ + { + "maxLength": 12, + "minLength": 12 + }, + { + "pattern": "^[A-HJ-NP-Z1-9][A-HJ-NP-Z0-9]+$" + }, + { + "pattern": "^(?![A-HJ-NP-Z1-9]+[A-HJ-NP-Z0-9]*?[0-9]{9})[A-HJ-NP-Z0-9]*$" + }, + { + "pattern": "^(?![0-9]{9})" + } + ], + "type": "string" + }, + "auditee_zip": { + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" + }, + "auditor_address_line_1": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditor_city": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditor_contact_name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditor_contact_title": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditor_country": { + "description": "USA or International", + "enum": [ + "USA", + "non-USA" + ], + "title": "CountryType", + "type": "string" + }, + "auditor_ein": { + "pattern": "^[0-9]{9}$", + "type": "string" + }, + "auditor_ein_not_an_ssn_attestation": { + "type": "boolean" + }, + "auditor_email": { + "format": "email", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditor_firm_name": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "auditor_international_address": { + "maxLength": 100, + "type": "string" + }, + "auditor_phone": { + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" + }, + "auditor_state": { + "oneOf": [ + { + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ] + }, + { + "const": "", + "type": "string" + } + ] + }, + "auditor_zip": { + "oneOf": [ + { + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] + }, + "ein": { + "pattern": "^[0-9]{9}$", + "type": "string" + }, + "ein_not_an_ssn_attestation": { + "type": "boolean" + }, + "is_usa_based": { + "type": "boolean" + }, + "met_spending_threshold": { + "type": "boolean" + }, + "multiple_eins_covered": { + "type": "boolean" + }, + "multiple_ueis_covered": { + "type": "boolean" + }, + "secondary_auditors_exist": { + "type": "boolean" + }, + "user_provided_organization_type": { + "description": "Org type", + "enum": [ + "state", + "local", + "tribal", + "higher-ed", + "non-profit", + "unknown", + "none" + ], + "title": "OrganizationType", + "type": "string" + } + }, + "required": [ + "audit_type", + "auditee_address_line_1", + "auditee_city", + "auditee_contact_name", + "auditee_contact_title", + "auditee_email", + "auditee_fiscal_period_end", + "auditee_fiscal_period_start", + "auditee_name", + "auditee_phone", + "auditee_state", + "auditee_uei", + "auditor_contact_name", + "auditor_contact_title", + "auditor_ein", + "auditor_ein_not_an_ssn_attestation", + "auditor_email", + "auditor_firm_name", + "auditor_phone", + "auditor_state", + "auditor_zip", + "ein", + "ein_not_an_ssn_attestation", + "is_usa_based", + "met_spending_threshold", + "multiple_eins_covered", + "multiple_ueis_covered", + "secondary_auditors_exist", + "user_provided_organization_type" + ], + "title": "GeneralInformation", + "type": "object", + "version": null +} diff --git a/backend/schemas/output/sections/GeneralInformationRequired.schema.json b/backend/schemas/output/sections/GeneralInformationRequired.schema.json index fecf6ed821..b5a7137fc8 100644 --- a/backend/schemas/output/sections/GeneralInformationRequired.schema.json +++ b/backend/schemas/output/sections/GeneralInformationRequired.schema.json @@ -71,73 +71,88 @@ "then": { "properties": { "auditor_state": { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ], - "title": "State" + "anyOf": [ + { + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ] + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_zip": { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" + "anyOf": [ + { + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] } } } @@ -169,27 +184,43 @@ "metamodel_version": "1.7.0", "properties": { "audit_period_covered": { - "description": "Period type of audit being submitted", - "enum": [ - "annual", - "biennial", - "other" - ], - "title": "AuditPeriod", - "type": "string" + "oneOf": [ + { + "description": "Period type of audit being submitted", + "enum": [ + "annual", + "biennial", + "other" + ], + "title": "AuditPeriod", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "audit_period_other_months": { + "maxLength": 100, "type": "string" }, "audit_type": { - "description": "Type of audit being submitted", - "enum": [ - "program-specific", - "single-audit", - "alternative-compliance-engagement" - ], - "title": "AuditType", - "type": "string" + "oneOf": [ + { + "description": "Type of audit being submitted", + "enum": [ + "program-specific", + "single-audit" + ], + "title": "AuditType", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditee_address_line_1": { "maxLength": 100, @@ -208,15 +239,39 @@ "type": "string" }, "auditee_email": { - "format": "email", + "oneOf": [ + { + "format": "email" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditee_fiscal_period_end": { - "format": "date", + "oneOf": [ + { + "format": "date" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditee_fiscal_period_start": { - "format": "date", + "oneOf": [ + { + "format": "date" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditee_name": { @@ -224,73 +279,89 @@ "type": "string" }, "auditee_phone": { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" + "oneOf": [ + { + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditee_state": { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ], - "title": "State" + "oneOf": [ + { + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ], + "title": "State" + }, + { + "const": "", + "type": "string" + } + ] }, "auditee_uei": { "allOf": [ @@ -311,8 +382,16 @@ "type": "string" }, "auditee_zip": { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" + "anyOf": [ + { + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_address_line_1": { "maxLength": 100, @@ -340,28 +419,52 @@ "type": "string" }, "auditor_ein": { - "pattern": "^[0-9]{9}$", - "type": "string" + "oneOf": [ + { + "pattern": "^[0-9]{9}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_ein_not_an_ssn_attestation": { "type": "boolean" }, "auditor_email": { - "format": "email", + "maxLength": 100, + "oneOf": [ + { + "format": "email" + }, + { + "const": "", + "type": "string" + } + ], "type": "string" }, "auditor_firm_name": { - "type": "string" - }, - "auditor_foreign_address": { + "maxLength": 100, "type": "string" }, "auditor_international_address": { + "maxLength": 100, "type": "string" }, "auditor_phone": { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" + "oneOf": [ + { + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "auditor_state": { "anyOf": [ @@ -449,8 +552,16 @@ ] }, "ein": { - "pattern": "^[0-9]{9}$", - "type": "string" + "oneOf": [ + { + "pattern": "^[0-9]{9}$", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] }, "ein_not_an_ssn_attestation": { "type": "boolean" @@ -471,18 +582,26 @@ "type": "boolean" }, "user_provided_organization_type": { - "description": "Org type", - "enum": [ - "state", - "local", - "tribal", - "higher-ed", - "non-profit", - "unknown", - "none" - ], - "title": "OrganizationType", - "type": "string" + "oneOf": [ + { + "description": "Org type", + "enum": [ + "state", + "local", + "tribal", + "higher-ed", + "non-profit", + "unknown", + "none" + ], + "title": "OrganizationType", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ] } }, "required": [ diff --git a/backend/schemas/output/sections/NotesToSefa.schema.json b/backend/schemas/output/sections/NotesToSefa.schema.json index 1501678b52..290084edbe 100644 --- a/backend/schemas/output/sections/NotesToSefa.schema.json +++ b/backend/schemas/output/sections/NotesToSefa.schema.json @@ -60,6 +60,13 @@ "items": { "additionalProperties": false, "properties": { + "contains_chart_or_table": { + "enum": [ + "Y", + "N" + ], + "type": "string" + }, "note_content": { "type": "string" }, @@ -72,7 +79,8 @@ }, "required": [ "note_title", - "note_content" + "note_content", + "contains_chart_or_table" ], "title": "NotesToSefaEntry" }, diff --git a/backend/schemas/scripts/parse.py b/backend/schemas/scripts/parse.py index 21da4cb642..6ef0f9f9b9 100644 --- a/backend/schemas/scripts/parse.py +++ b/backend/schemas/scripts/parse.py @@ -15,7 +15,7 @@ Sheet = NT( "Sheet", - "name single_cells meta_cells open_ranges mergeable_cells merged_unreachable header_inclusion text_ranges header_height hide_col_from hide_row_from", + "name single_cells meta_cells open_ranges header_inclusion text_ranges header_height hide_col_from hide_row_from", ) Posn = NT( "Posn", @@ -163,7 +163,7 @@ def parse_text_range(spec): def parse_sheet(spec): # noqa: C901 - sc, mtc, opr, mc, mur, hi, tr = None, None, None, None, None, None, None + sc, mtc, opr, hi, tr = None, None, None, None, None name = get(spec, "name", default="Unnamed Sheet") if "single_cells" in spec: sc = list(map(parse_single_cell, get(spec, "single_cells", default=[]))) @@ -177,14 +177,6 @@ def parse_sheet(spec): # noqa: C901 opr = list(map(parse_open_range, get(spec, "open_ranges", default=[]))) else: opr = [] - if "mergeable_cells" in spec: - mc = list(map(parse_mergeable_cell, get(spec, "mergeable_cells", default=[]))) - else: - mc = [] - if "merged_unreachable" in spec: - mur = parse_merged_unreachable(get(spec, "merged_unreachable", default=None)) - else: - mur = [] if "header_inclusion" in spec: hi = parse_header_inclusion(get(spec, "header_inclusion")) else: @@ -205,7 +197,7 @@ def parse_sheet(spec): # noqa: C901 hrf = get(spec, "hide_row_from") else: hrf = None - return Sheet(name, sc, mtc, opr, mc, mur, hi, tr, hh, hcf, hrf) + return Sheet(name, sc, mtc, opr, hi, tr, hh, hcf, hrf) def parse_spec(spec): diff --git a/backend/schemas/scripts/render.py b/backend/schemas/scripts/render.py index 50ae81fce1..7fa5183502 100644 --- a/backend/schemas/scripts/render.py +++ b/backend/schemas/scripts/render.py @@ -26,7 +26,7 @@ "name,column,label_row,range_start_row,range_start,abs_range_start,full_range", ) -MAX_ROWS = 3000 +MAX_ROWS = 5000 XLSX_MAX_ROWS = 1048576 # Excel has a maximum of 1048576 rows XLSX_MAX_COLS = 16384 # Excel has a maximum of 16384 columns @@ -83,8 +83,6 @@ def process_spec(WBNT): print(f"## Processing sheet {ndx+1}") print("########################") ws = create_protected_sheet(wb, sheet, password, ndx) - if sheet.mergeable_cells is not None: - merge_adjacent_columns(ws, sheet.mergeable_cells) if sheet.hide_col_from is not None: hide_all_columns_from(ws, sheet.hide_col_from) if sheet.hide_row_from is not None: @@ -144,19 +142,6 @@ def create_protected_sheet(wb, sheet, password, ndx): return ws -def merge_adjacent_columns(ws, cell_ranges): - """ - Merges cells in adjacent columns for each row within the specified range. - Largely for cosmetic reasons in any given sheet. - """ - print("---- mergeable_cells ----") - for cell_range in cell_ranges: - start_row, end_row, start_column, end_column = cell_range - for row in range(start_row, end_row): - merge_range = f"{start_column}{row}:{end_column}{row}" - ws.merge_cells(merge_range) - - def hide_all_columns_from(ws, start_column): """Hides all columns from the specified column to the end of the sheet.""" for col_idx in range(start_column, XLSX_MAX_COLS + 1): @@ -479,14 +464,6 @@ def unlock_data_entry_cells(header_row, ws, sheet): cell_reference = f"${coords.column}${rowndx}" cell = ws[cell_reference] cell.protection = Protection(locked=False) - if sheet.merged_unreachable not in [None, []]: - print(sheet.merged_unreachable) - data_row_index = header_row + 1 - for column in sheet.merged_unreachable.columns: - for rowndx in range(data_row_index, MAX_ROWS): - cell_reference = f"${column}${rowndx}" - cell = ws[cell_reference] - cell.protection = Protection(locked=False) def calculate_average_width(ws): diff --git a/backend/schemas/scripts/xlrows.py b/backend/schemas/scripts/xlrows.py new file mode 100644 index 0000000000..f1f438d47e --- /dev/null +++ b/backend/schemas/scripts/xlrows.py @@ -0,0 +1,12 @@ +import openpyxl +import glob + +folder_path = "../output/excel/xlsx" + +for filename in glob.glob(f"{folder_path}/*.xlsx"): + print(f"File: {filename}") + wb = openpyxl.load_workbook(filename) + + for sheet in wb._sheets: + nrows = sheet.max_row + print(f"Sheet {sheet.title} has {nrows} rows") diff --git a/backend/schemas/source/base/Base.libsonnet b/backend/schemas/source/base/Base.libsonnet index a2eaae8003..979052564c 100644 --- a/backend/schemas/source/base/Base.libsonnet +++ b/backend/schemas/source/base/Base.libsonnet @@ -164,7 +164,8 @@ local Enum = { enum: [ 'program-specific', 'single-audit', - 'alternative-compliance-engagement', + // Include after we are able to recieve ACEE submissions. + // 'alternative-compliance-engagement', ], title: 'AuditType', }, diff --git a/backend/schemas/source/excel/libs/InstructionsNotestoSEFA.libsonnet b/backend/schemas/source/excel/libs/InstructionsNotestoSEFA.libsonnet index dbeec2726a..c0957b72a9 100644 --- a/backend/schemas/source/excel/libs/InstructionsNotestoSEFA.libsonnet +++ b/backend/schemas/source/excel/libs/InstructionsNotestoSEFA.libsonnet @@ -30,5 +30,10 @@ instructions: [ 'Enter the full text of the note. Do not include charts, tables, or footnotes.', ], - } -} \ No newline at end of file + }, + contains_chart_or_table: { + instructions: [ + 'Indicates whether or not the text contained charts or tables that could not be entered due to formatting restrictions.', + ], + }, +} diff --git a/backend/schemas/source/excel/libs/Sheets.libsonnet b/backend/schemas/source/excel/libs/Sheets.libsonnet index 46b99c4f95..012ec8f0b7 100644 --- a/backend/schemas/source/excel/libs/Sheets.libsonnet +++ b/backend/schemas/source/excel/libs/Sheets.libsonnet @@ -40,9 +40,6 @@ local section_names = { NOTES_TO_SEFA: 'NotesToSefa', }; -// MAX_ROWS here is equal to MAX_ROWS in render.py plus 1 to account for the header row. -local MAX_ROWS = 3001; - // All workbooks should get the same version number. local WORKBOOKS_VERSION = '1.0.0'; @@ -51,7 +48,6 @@ local WORKBOOKS_VERSION = '1.0.0'; meta_cell: meta_cell, open_range: open_range, y_or_n_range: y_or_n_range, - MAX_ROWS: MAX_ROWS, WORKBOOKS_VERSION: WORKBOOKS_VERSION, section_names: section_names } diff --git a/backend/schemas/source/excel/templates/notes-to-sefa-workbook.jsonnet b/backend/schemas/source/excel/templates/notes-to-sefa-workbook.jsonnet index d07e4436a8..5b777c00e1 100644 --- a/backend/schemas/source/excel/templates/notes-to-sefa-workbook.jsonnet +++ b/backend/schemas/source/excel/templates/notes-to-sefa-workbook.jsonnet @@ -106,6 +106,15 @@ local open_ranges_defns = [ 'Note content', 'note_content', ], + [ + Sheets.y_or_n_range { + width: 36, + help: Help.yorn, + }, + SV.YoNValidation, + 'Did Text Contain a Chart or Table?', + 'contains_chart_or_table', + ], [ Sheets.open_range { keep_locked: true, @@ -131,7 +140,7 @@ local sheets = [ name: sefaAdditionalSheet, open_ranges: Fun.make_open_ranges(title_row, open_ranges_defns), header_height: 100, - hide_col_from: 3, + hide_col_from: 4, }, ]; diff --git a/backend/schemas/source/sections/GeneralInformation.schema.jsonnet b/backend/schemas/source/sections/GeneralInformation.schema.jsonnet index e5b1a58f5e..538d4a1b53 100644 --- a/backend/schemas/source/sections/GeneralInformation.schema.jsonnet +++ b/backend/schemas/source/sections/GeneralInformation.schema.jsonnet @@ -2,97 +2,172 @@ local Base = import '../base/Base.libsonnet'; local Func = import '../base/Functions.libsonnet'; local Types = Base.Types; +/* +Typechecks fields, but allows for empty data as well. Contains conditional Checks. +*/ { '$id': 'http://example.org/generalinformation', '$schema': Base.Const.SCHEMA_VERSION, additionalProperties: false, metamodel_version: '1.7.0', properties: { + // Audit information + auditee_fiscal_period_start: Types.string { + oneOf: [ + { + format: 'date', + }, + Base.Compound.EmptyString, + ] + }, + auditee_fiscal_period_end: Types.string { + oneOf: [ + { + format: 'date', + }, + Base.Compound.EmptyString, + ] + }, + audit_type: { + oneOf: [ + Base.Enum.AuditType, + Base.Compound.EmptyString, + ] + }, + audit_period_covered: { + oneOf: [ + Base.Enum.AuditPeriod, + Base.Compound.EmptyString, + ] + }, + audit_period_other_months: Types.string { + maxLength: 100, + }, + + // Auditee information + auditee_uei: Base.Compound.UniqueEntityIdentifier, + ein: { + oneOf: [ + Base.Compound.EmployerIdentificationNumber, + Base.Compound.EmptyString, + ], + }, + ein_not_an_ssn_attestation: Types.boolean, + auditee_name: Types.string { + maxLength: 100, + }, auditee_address_line_1: Types.string { maxLength: 100, }, auditee_city: Types.string { maxLength: 100, }, + auditee_state: { + oneOf: [ + Base.Enum.UnitedStatesStateAbbr { + title: 'State', + }, + Base.Compound.EmptyString, + ] + }, + auditee_zip: { + anyOf: [ + Base.Compound.Zip, + Base.Compound.EmptyString, + ], + }, + auditee_contact_name: Types.string { maxLength: 100, }, auditee_contact_title: Types.string { maxLength: 100, }, - auditee_email: Types.string { - format: 'email', - }, - auditee_name: Types.string { - maxLength: 100, + auditee_phone: { + oneOf: [ + Base.Compound.UnitedStatesPhone, + Base.Compound.EmptyString, + ] }, - auditee_phone: Base.Compound.UnitedStatesPhone, - auditee_state: Base.Enum.UnitedStatesStateAbbr { - title: 'State', + auditee_email: Types.string { + oneOf: [ + { + format: 'email', + }, + Base.Compound.EmptyString, + ] }, - auditee_uei: Base.Compound.UniqueEntityIdentifier, - auditee_zip: Base.Compound.Zip, - ein: Base.Compound.EmployerIdentificationNumber, - - auditor_phone: Base.Compound.UnitedStatesPhone, - auditor_state: { - anyOf: [ - Base.Enum.UnitedStatesStateAbbr { - title: 'State', - }, + // Auditor information + auditor_ein: { + oneOf: [ + Base.Compound.EmployerIdentificationNumber, Base.Compound.EmptyString, ], }, - auditor_city: Types.string { + auditor_ein_not_an_ssn_attestation: Types.boolean, + auditor_firm_name: Types.string { maxLength: 100, }, - auditor_contact_title: Types.string { + auditor_country: Base.Enum.CountryType, + auditor_international_address: Types.string { maxLength: 100, }, auditor_address_line_1: Types.string { maxLength: 100, }, + auditor_city: Types.string { + maxLength: 100, + }, + auditor_state: { + anyOf: [ + Base.Enum.UnitedStatesStateAbbr { + title: 'State', + }, + Base.Compound.EmptyString, + ], + }, auditor_zip: { anyOf: [ Base.Compound.Zip, Base.Compound.EmptyString, ], }, - auditor_country: Base.Enum.CountryType, + auditor_contact_name: Types.string { maxLength: 100, }, - auditor_international_address: Types.string, - - auditor_email: Types.string { - format: 'email', + auditor_contact_title: Types.string { + maxLength: 100, }, - auditor_firm_name: Types.string, - auditor_foreign_address: Types.string, - auditor_ein: Base.Compound.EmployerIdentificationNumber, - - auditee_fiscal_period_start: Types.string { - format: 'date', + auditor_phone: { + oneOf: [ + Base.Compound.UnitedStatesPhone, + Base.Compound.EmptyString, + ] }, - auditee_fiscal_period_end: Types.string { - format: 'date', + auditor_email: Types.string { + oneOf: [ + { + format: 'email', + }, + Base.Compound.EmptyString + ], + maxLength: 100, }, - audit_type: Base.Enum.AuditType, - user_provided_organization_type: Base.Enum.OrganizationType, - audit_period_other_months: Types.string, - audit_period_covered: Base.Enum.AuditPeriod, - - auditor_ein_not_an_ssn_attestation: Types.boolean, - ein_not_an_ssn_attestation: Types.boolean, + // Others is_usa_based: Types.boolean, met_spending_threshold: Types.boolean, - + user_provided_organization_type: { + oneOf: [ + Base.Enum.OrganizationType, + Base.Compound.EmptyString, + ], + }, multiple_eins_covered: Types.boolean, multiple_ueis_covered: Types.boolean, secondary_auditors_exist: Types.boolean, - }, allOf: [ { @@ -145,10 +220,18 @@ local Types = Base.Types; }, 'then': { properties: { - auditor_zip: Base.Compound.Zip, - auditor_state: Base.Enum.UnitedStatesStateAbbr { - title: 'State', + auditor_zip: { + anyOf: [ + Base.Compound.Zip, + Base.Compound.EmptyString, + ], }, + auditor_state: { + anyOf: [ + Base.Enum.UnitedStatesStateAbbr, + Base.Compound.EmptyString, + ], + } }, }, }, diff --git a/backend/schemas/source/sections/GeneralInformationComplete.schema.jsonnet b/backend/schemas/source/sections/GeneralInformationComplete.schema.jsonnet new file mode 100644 index 0000000000..8995183c93 --- /dev/null +++ b/backend/schemas/source/sections/GeneralInformationComplete.schema.jsonnet @@ -0,0 +1,244 @@ +local Base = import '../base/Base.libsonnet'; +local Func = import '../base/Functions.libsonnet'; +local Types = Base.Types; + +/* +Checks all the fields to answer the question, "Is the form complete?" +Requires most fields, has consitional checks for conditional fields. +*/ +{ + '$id': 'http://example.org/generalinformation', + '$schema': 'http://json-schema.org/draft/2019-09/schema#', + additionalProperties: false, + metamodel_version: '1.7.0', + properties: { + // Audit information + auditee_fiscal_period_start: { + format: 'date', + }, + auditee_fiscal_period_end: { + format: 'date', + }, + audit_type: Base.Enum.AuditType, + audit_period_covered: Base.Enum.AuditPeriod, + audit_period_other_months: Types.string, // Conditional, no min/max length + + // Auditee information + auditee_uei: Base.Compound.UniqueEntityIdentifier, + ein: Base.Compound.EmployerIdentificationNumber, + ein_not_an_ssn_attestation: Types.boolean, + auditee_name: Types.string { + maxLength: 100, + minLength: 1, + }, + auditee_address_line_1: Types.string { + maxLength: 100, + minLength: 1, + }, + auditee_city: Types.string { + maxLength: 100, + minLength: 1, + }, + auditee_state: Base.Enum.UnitedStatesStateAbbr { + title: 'State', + }, + auditee_zip: Base.Compound.Zip, + + auditee_contact_name: Types.string { + maxLength: 100, + minLength: 1, + }, + auditee_contact_title: Types.string { + maxLength: 100, + minLength: 1, + }, + auditee_phone: Base.Compound.UnitedStatesPhone, + auditee_email: Types.string { + format: 'email', + maxLength: 100, + minLength: 1, + }, + + // Auditor information + // Auditor address information is conditional based on country + auditor_ein: Base.Compound.EmployerIdentificationNumber, + auditor_ein_not_an_ssn_attestation: Types.boolean, + auditor_firm_name: Types.string { + maxLength: 100, + minLength: 1, + }, + auditor_country: Base.Enum.CountryType, + auditor_international_address: Types.string { + maxLength: 100, + }, + auditor_address_line_1: Types.string { + maxLength: 100, + minLength: 1, + }, + auditor_city: Types.string { + maxLength: 100, + minLength: 1, + }, + auditor_state: { + oneOf: [ + Base.Enum.UnitedStatesStateAbbr, + Base.Compound.EmptyString, + ], + }, + auditor_zip: { + oneOf: [ + Base.Compound.Zip, + Base.Compound.EmptyString, + ], + }, + + auditor_contact_name: Types.string { + maxLength: 100, + minLength: 1, + }, + auditor_contact_title: Types.string { + maxLength: 100, + minLength: 1, + }, + auditor_phone: Base.Compound.UnitedStatesPhone, + auditor_email: Types.string { + format: 'email', + maxLength: 100, + minLength: 1, + }, + + // Others + is_usa_based: Types.boolean, + met_spending_threshold: Types.boolean, + user_provided_organization_type: Base.Enum.OrganizationType, + multiple_eins_covered: Types.boolean, + multiple_ueis_covered: Types.boolean, + secondary_auditors_exist: Types.boolean, + }, + allOf: [ + // If audit_period_covered is 'other', then audit_period_other_months should + // have a value. Otherwise, it should have no value. + { + anyOf: [ + { + 'if': { + properties: { + audit_period_covered: { + const: 'annual', + }, + }, + }, + 'then': { + audit_period_other_months: Base.Enum.EmptyString_Null, + }, + }, + { + 'if': { + properties: { + audit_period_covered: { + const: 'biennial', + }, + }, + }, + 'then': { + audit_period_other_months: Base.Enum.EmptyString_Null, + }, + }, + { + 'if': { + properties: { + audit_period_covered: { + const: 'other', + }, + }, + }, + 'then': { + audit_period_other_months: Base.Compound.MonthsOther, + }, + }, + ], + }, + // If auditor is from the USA, address info should be included. + { + 'if': { + properties: { + auditor_country: { + const: 'USA', + }, + }, + }, + 'then': { + properties: { + auditor_zip: Base.Compound.Zip, + }, + }, + }, + // If auditor is NOT from the USA, the zip should be empty. + { + 'if': { + properties: { + auditor_country: { + not: { + const: 'USA', + }, + }, + }, + }, + 'then': { + properties: { + auditor_zip: Base.Compound.EmptyString, + }, + }, + }, + // The auditee EIN attestation should always be true. + { + properties: { + ein_not_an_ssn_attestation: { + const: true, + }, + }, + }, + // The auditor EIN attestation should always be true. + { + properties: { + auditor_ein_not_an_ssn_attestation: { + const: true, + }, + }, + } + ], + required: [ + 'audit_type', + 'auditee_address_line_1', + 'auditee_city', + 'auditee_contact_name', + 'auditee_contact_title', + 'auditee_email', + 'auditee_fiscal_period_end', + 'auditee_fiscal_period_start', + 'auditee_name', + 'auditee_phone', + 'auditee_state', + 'auditee_uei', + 'auditor_contact_name', + 'auditor_contact_title', + 'auditor_ein', + 'auditor_ein_not_an_ssn_attestation', + 'auditor_email', + 'auditor_firm_name', + 'auditor_phone', + 'auditor_state', + 'auditor_zip', + 'ein', + 'ein_not_an_ssn_attestation', + 'is_usa_based', + 'met_spending_threshold', + 'multiple_eins_covered', + 'multiple_ueis_covered', + 'secondary_auditors_exist', + 'user_provided_organization_type' + ], + title: 'GeneralInformation', + type: 'object', + version: null, +} diff --git a/backend/schemas/source/sections/NotesToSefa.schema.jsonnet b/backend/schemas/source/sections/NotesToSefa.schema.jsonnet index 1e118c135a..0f5fbfd9b9 100644 --- a/backend/schemas/source/sections/NotesToSefa.schema.jsonnet +++ b/backend/schemas/source/sections/NotesToSefa.schema.jsonnet @@ -25,9 +25,10 @@ local NotesToSefaEntry = { properties: { note_title: Types.string, note_content: Types.string, + contains_chart_or_table: Base.Enum.YorN, seq_number: Types.integer, }, - required: ['note_title', 'note_content'], + required: ['note_title', 'note_content', 'contains_chart_or_table'], title: 'NotesToSefaEntry', }; diff --git a/backend/static/js/check-access.js b/backend/static/js/check-access.js index 9a573afc42..0fa8e1a15f 100644 --- a/backend/static/js/check-access.js +++ b/backend/static/js/check-access.js @@ -9,7 +9,7 @@ function setFormDisabled(shouldDisable) { } function allResponsesValid() { - const inputsWithErrors = document.querySelectorAll('[class *="--error"]'); + const inputsWithErrors = document.querySelectorAll('[class *="-error"]'); return inputsWithErrors.length === 0; } @@ -104,8 +104,8 @@ function attachEventHandlers() { ); certifyingInputs.forEach((q) => { - q.addEventListener('blur', (e) => { - performValidations(e.target); + q.addEventListener('focusout', (e) => { + performValidations([e.target]); }); }); diff --git a/backend/static/js/check-ueid.js b/backend/static/js/check-ueid.js index 19fd90bd15..3eb08d0dad 100644 --- a/backend/static/js/check-ueid.js +++ b/backend/static/js/check-ueid.js @@ -2,6 +2,7 @@ import { checkValidity } from './validate.js'; import { queryAPI } from './api'; const FORM = document.forms[0]; +var isUEIValidated = false; function handleUEIDResponse({ valid, response, errors }) { if (valid) { @@ -60,6 +61,8 @@ function showValidUeiInfo() { function setupFormWithValidUei() { hideUeiStuff(); showValidUeiInfo(); + isUEIValidated = true; + setFormDisabled(false); } function resetModal() { @@ -208,7 +211,18 @@ function validateFyStartDate(fyInput) { function setFormDisabled(shouldDisable) { const continueBtn = document.getElementById('continue'); - continueBtn.disabled = shouldDisable; + // If we want to disable the button, do it. + if (shouldDisable) { + continueBtn.disabled = true; + return; + } + + // If we want to enable the button, the UEI validation should be done. + if (!shouldDisable && isUEIValidated) { + continueBtn.disabled = false; + } else { + continueBtn.disabled = true; + } } function allResponsesValid() { @@ -262,6 +276,7 @@ function attachDatePickerHandlers() { function init() { attachEventHandlers(); window.addEventListener('load', attachDatePickerHandlers, false); // Need to wait for date-picker text input to render. + setFormDisabled(true); // Disabled initially, re-enables after filling everything out. } init(); diff --git a/backend/static/js/sf-sac.js b/backend/static/js/sf-sac.js index eced0950ff..3ca3007bd0 100644 --- a/backend/static/js/sf-sac.js +++ b/backend/static/js/sf-sac.js @@ -25,9 +25,10 @@ function performValidations(field) { setFormDisabled(errors.length > 0); } +// Fieldset elements with attribute "navitem" are watched. When scolled past, the applicable navLink is set to current. function highlightActiveNavSection() { let currentFieldsetId; - const fieldsets = document.querySelectorAll('fieldset[id]'); + const fieldsets = document.querySelectorAll('fieldset[navitem]'); const navLinks = document.querySelectorAll('li .usa-sidenav__item a'); fieldsets.forEach((f) => { @@ -49,6 +50,8 @@ function highlightActiveNavSection() { } function attachEventHandlers() { + // These fields no longer exist. The gen form is submittable in an incomplete state, so non-null data is okay. + // Left alone for potential future enhancements on the form. const fieldsNeedingValidation = Array.from( document.querySelectorAll('.sf-sac input[data-validate-not-null]') ); @@ -82,37 +85,27 @@ function attachEventHandlers() { window.addEventListener('scroll', highlightActiveNavSection); } + const foreignFields = document.querySelectorAll('[name="foreign_address"]'); const domesticFields = document.querySelectorAll('[name="domestic_address"]'); function setupAddress() { if (countrySelect.value == 'USA') { foreignFields.forEach((input) => { input.setAttribute('hidden', true); - input.querySelectorAll('textarea').forEach((element) => { - element.removeAttribute('required'); - }); }); domesticFields.forEach((input) => { input.removeAttribute('hidden'); - input.querySelectorAll('input').forEach((element) => { - element.setAttribute('required', true); - }); }); } else { foreignFields.forEach((input) => { input.removeAttribute('hidden'); - input.querySelectorAll('textarea').forEach((element) => { - element.setAttribute('required', true); - }); }); domesticFields.forEach((input) => { input.setAttribute('hidden', true); - input.querySelectorAll('input').forEach((element) => { - element.removeAttribute('required'); - }); }); } } + function init() { attachEventHandlers(); setupAddress(); diff --git a/backend/static/js/validate.js b/backend/static/js/validate.js index 8ee41dbff1..fc6673bb05 100644 --- a/backend/static/js/validate.js +++ b/backend/static/js/validate.js @@ -139,6 +139,19 @@ export const validations = { : result; }, + validateMustNotMatch: (field, matchField) => { + const matchFieldEl = document.querySelector(`input#${matchField}`); + const result = { + error: false, + fieldId: field.id, + validation: 'must-not-match', + }; + + return field.value == matchFieldEl.value + ? { ...result, error: true } + : result; + }, + validateLength: (field, compStr) => { const [comparator, compValue] = compStr.split(' '); const valueLength = field.value.length; @@ -157,4 +170,20 @@ export const validations = { : result; } }, + + validateDateComesAfter: (field) => { + let comparisonField = field.dataset['validateDateComesAfter'], + comparisonFieldValue = document.getElementById(comparisonField).value; + + let endDate = new Date(field.value), + startDate = new Date(comparisonFieldValue); + + const result = { + error: false, + fieldId: field.id, + validation: 'date-order', + }; + + return startDate >= endDate ? { ...result, error: true } : result; + }, }; diff --git a/backend/support/__init__.py b/backend/support/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/support/admin.py b/backend/support/admin.py new file mode 100644 index 0000000000..1d8a776349 --- /dev/null +++ b/backend/support/admin.py @@ -0,0 +1,108 @@ +from django.contrib import admin + +from audit.models import SingleAuditChecklist +from .models import CognizantBaseline, CognizantAssignment, AssignmentTypeCode + + +class SupportAdmin(admin.ModelAdmin): + def has_view_permission(self, request, obj=None): + if request.user.is_staff: + return True + + def has_delete_permission(self, request, obj=None): + return False + + +@admin.register(CognizantBaseline) +class CognizantBaselineAdmin(SupportAdmin): + list_display = [ + "uei", + "cognizant_agency", + "date_assigned", + "ein", + "dbkey", + "is_active", + ] + list_filter = [ + "is_active", + "uei", + "cognizant_agency", + "ein", + ] + + def has_change_permission(self, request, obj=None): + return False + + def has_add_permission(self, request, obj=None): + return False + + +@admin.register(CognizantAssignment) +class CognizantAssignmentAdmin(SupportAdmin): + date_hierarchy = "date_assigned" + ordering = ["date_assigned"] + list_display = [ + "report_id", + "uei", + "cognizant_agency", + "date_assigned", + "assignor_email", + "override_comment", + "assignment_type", + ] + list_filter = [ + "assignment_type", + "cognizant_agency", + ] + fields = [ + "report_id", + ( + "uei", + "last_assigned_by", + "current_cog", + ), + "cognizant_agency", + "override_comment", + ] + readonly_fields = [ + "uei", + "last_assigned_by", + "current_cog", + ] + + def uei(self, obj): + return SingleAuditChecklist.objects.get(report_id=obj.report_id).auditee_uei + + def current_cog(self, obj): + return SingleAuditChecklist.objects.get( + report_id=obj.report_id + ).cognizant_agency + + def last_assigned_by(self, obj): + return obj.assignor_email + + save_as = True + save_as_continue = False + + def change_view(self, request, object_id, form_url="", extra_context=None): + """customize edit form""" + extra_context = extra_context or {} + extra_context["show_save_and_continue"] = False + extra_context["show_save"] = False + extra_context[ + "show_save_and_add_another" + ] = False # this not works if has_add_permision is True + return super().change_view(request, object_id, extra_context=extra_context) + + def save_model(self, request, obj, form, change): + obj.assignor_email = request.user.email + obj.assignment_type = AssignmentTypeCode.MANUAL + super().save_model(request, obj, form, change) + + def has_change_permission(self, request, obj=None): + if request.user.is_staff: + return True + + def has_add_permission(self, request, obj=None): + if request.user.is_staff: + return True diff --git a/backend/support/apps.py b/backend/support/apps.py new file mode 100644 index 0000000000..bc171b758c --- /dev/null +++ b/backend/support/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class SupportConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "support" + + def ready(self): + from . import signals # noqa diff --git a/backend/support/cog_over.py b/backend/support/cog_over.py new file mode 100644 index 0000000000..7dd5f6f0d8 --- /dev/null +++ b/backend/support/cog_over.py @@ -0,0 +1,187 @@ +from collections import defaultdict +import logging + +from django.db.models.functions import Cast +from django.db.models import BigIntegerField, Q + +from dissemination.hist_models.census_2019 import CensusGen19, CensusCfda19 +from dissemination.hist_models.census_2022 import CensusGen22 +from support.models import CognizantBaseline, CognizantAssignment + +logger = logging.getLogger(__name__) + + +COG_LIMIT = 50_000_000 +DA_THRESHOLD_FACTOR = 0.25 + + +def compute_cog_over(federal_awards, submission_status, auditee_ein, auditee_uei): + """ + Compute cog or oversight agency for the sac. + Return tuple (cog_agency, oversight_agency) + + WIP: + - sac.federal_awards + - sac.submission_status + - sac.ein + - sac.auditee_uei + """ + if not federal_awards: + logger.warning( + f"Trying to determine cog_over for a sac with zero awards with status = {submission_status}." + ) + return (None, None) + awards = federal_awards["FederalAwards"] + total_amount_expended = awards.get("total_amount_expended") + cognizant_agency = oversight_agency = None + (max_total_agency, max_da_agency) = calc_award_amounts(awards) + + agency = determine_agency( + total_amount_expended, + max_total_agency, + max_da_agency, + ) + + if total_amount_expended <= COG_LIMIT: + oversight_agency = agency + # logger.warning("Assigning an oversight agenct", oversight_agency) + return (cognizant_agency, oversight_agency) + cognizant_agency = determine_hist_agency(auditee_ein, auditee_uei) + if cognizant_agency: + return (cognizant_agency, oversight_agency) + cognizant_agency = agency + return (cognizant_agency, oversight_agency) + + +def calc_award_amounts(awards): + total_amount_agency = defaultdict(lambda: 0) + total_da_amount_agency = defaultdict(lambda: 0) + for award in awards["federal_awards"]: + agency = award["program"]["federal_agency_prefix"] + total_amount_agency[agency] += award["program"]["amount_expended"] + if award["direct_or_indirect_award"]["is_direct"] == "Y": + total_da_amount_agency[agency] += award["program"]["amount_expended"] + return ( + prune_dict_to_max_values(total_amount_agency), + prune_dict_to_max_values(total_da_amount_agency), + ) + + +def determine_agency(total_amount_expended, max_total_agency, max_da_agency): + tie_breaker = defaultdict() + for key, value in max_da_agency.items(): + if value >= DA_THRESHOLD_FACTOR * total_amount_expended: + tie_breaker[key] = value + max_total_agency.get(key, 0) + for agency in prune_dict_to_max_values(tie_breaker).keys(): + return agency + for agency in max_total_agency.keys(): + return agency + + +def determine_hist_agency(ein, uei): + dbkey = get_dbkey(ein, uei) + + cog_agency = lookup_baseline(ein, uei, dbkey) + if cog_agency: + return cog_agency + (gen_count, total_amount_expended) = get_2019_gen(ein, dbkey) + if gen_count != 1: + return None + cfdas = get_2019_cfdas(ein, dbkey) + if not cfdas: + # logger.warning("Found no cfda data for dbkey {dbkey} in 2019") + return None + (max_total_agency, max_da_agency) = calc_cfda_amounts(cfdas) + cognizant_agency = determine_agency( + total_amount_expended, + max_total_agency, + max_da_agency, + ) + return cognizant_agency + + +def get_dbkey(ein, uei): + try: + dbkey = CensusGen22.objects.values_list("dbkey", flat=True).get( + Q(ein=ein), Q(uei=uei) | Q(uei=None) + )[:] + except (CensusGen22.DoesNotExist, CensusGen22.MultipleObjectsReturned): + dbkey = None + return dbkey + + +def lookup_baseline(ein, uei, dbkey): + try: + cognizant_agency = CognizantBaseline.objects.values_list( + "cognizant_agency", flat=True + ).get(Q(is_active=True) & ((Q(ein=ein) & Q(dbkey=dbkey)) | Q(uei=uei)))[:] + except (CognizantBaseline.DoesNotExist, CognizantBaseline.MultipleObjectsReturned): + cognizant_agency = None + return cognizant_agency + + +def get_2019_gen(ein, dbkey): + gens = CensusGen19.objects.annotate( + amt=Cast("totfedexpend", output_field=BigIntegerField()) + ).filter(Q(ein=ein), Q(dbkey=dbkey) | Q(dbkey=None)) + + if len(gens) != 1: + return (len(gens), 0) + gen = gens[0] + return (1, gen.amt) + + +def get_2019_cfdas(ein, dbkey): + cfdas = CensusCfda19.objects.annotate( + amt=Cast("amount", output_field=BigIntegerField()) + ).filter(Q(ein=ein), Q(dbkey=dbkey) | Q(dbkey=None)) + + if len(cfdas) == 0: + return None + baseline_cfdas = [] + for row in cfdas: + baseline_cfdas.append((row.cfda, row.amt, row.direct)) + return baseline_cfdas + + +def calc_cfda_amounts(cfdas): + total_amount_agency = defaultdict(lambda: 0) + total_da_amount_agency = defaultdict(lambda: 0) + for cfda in cfdas: + agency = cfda[0][:2] + amount = cfda[1] or 0 + direct = cfda[2] + total_amount_agency[agency] += amount + if direct == "Y": + total_da_amount_agency[agency] += amount + return ( + prune_dict_to_max_values(total_amount_agency), + prune_dict_to_max_values(total_da_amount_agency), + ) + + +def prune_dict_to_max_values(data: dict): + """ + prune_dict_to_max_values({"a": 5, "b": 10, "c": 10}) => {"b": 10, "c": 10} + """ + if len(data) == 0: + return data + + pruned_dict = {} + max_value = max(data.values()) + for key, value in data.items(): + if value == max_value: + pruned_dict[key] = value + + return pruned_dict + + +def record_cog_assignment(report_id, user, cognizant_agency): + """ + To be unvoked by app to persist the computed cog agency + """ + CognizantAssignment( + report_id=report_id, + cognizant_agency=cognizant_agency, + assignor_email=user.email, + ).save() diff --git a/backend/support/fixtures/census_baseline.csv b/backend/support/fixtures/census_baseline.csv new file mode 100644 index 0000000000..cfab1f41b1 --- /dev/null +++ b/backend/support/fixtures/census_baseline.csv @@ -0,0 +1,2339 @@ +DBKEY,EIN,UEI,AUDITEENAME,COGAGENCY,DATE_ADDED +8503,111631788,,THE JAMAICA HOSPITAL,10,8/8/2022 +8512,111631837,,WYCKOFF HEIGHTS MEDICAL CENTER,10,10/5/2022 +13163,133179546,,FOOD BANK FOR NEW YORK CITY AND SUBSIDIARY,10,4/10/2022 +20576,221487602,,"ST. JOSEPH'S HEALTH, INC.",10,12/3/2022 +44372,341517671,,PROMEDICA HEALTH SYSTEM AND SUBSIDIARIES,10,10/3/2022 +49722,362971864,,GREATER CHICAGO FOOD DEPOSITORY,10,12/16/2021 +56674,382515765,,"FOOD BANK COUNCIL OF MICHIGAN, INC.",10,3/25/2021 +78063,530242652,,THE NATURE CONSERVANCY,10,3/25/2021 +83408,570314408,,PRESBYTERIAN COLLEGE,10,3/25/2021 +85453,581376648,,"ATLANTA COMMUNITY FOOD BANK, INC.",10,12/10/2021 +95627,640169075,RH39MK9L1MC8,COAST ELECTRIC POWER ASSOCIATION,10,4/21/2023 +97664,710236857,SAFBJDMBB9A6,ARKANSAS CHILDRENS HOSPITAL,10,3/20/2023 +102442,742181456,,THE HOUSTON FOOD BANK AND SUBSIDIARIES,10,3/25/2021 +105204,751785357,,NORTH TEXAS FOOD BANK,10,3/23/2022 +105242,751822473,,TARRANT AREA FOOD BANK,10,1/15/2022 +114986,911352172,,MULTICARE HEALTH SYSTEM,10,10/3/2022 +125252,953135649,,LOS ANGELES REGIONAL FOOD BANK,10,6/13/2022 +130457,866000484,,WASHINGTON ELEMENTARY SCHOOL DISTRICT NO. 6,10,1/14/2022 +131824,946000511,,COUNTY OF EL DORADO,10,3/25/2021 +134025,956000120,,ANAHEIM UNION HIGH SCHOOL DISTRICT,10,2/23/2022 +134053,956002823,,SANTA ANA UNIFIED SCHOOL DISTRICT,10,3/25/2021 +134153,956002542,,RIALTO UNIFIED SCHOOL DISTRICT,10,3/4/2022 +137690,586000227,,DEKALB COUNTY BOARD OF EDUCATION,10,3/25/2021 +137732,586000264,,HOUSTON COUNTY BOARD OF EDUCATION,10,4/10/2022 +146487,616001059,,FAYETTE COUNTY PUBLIC SCHOOLS,10,12/17/2021 +154114,436004252,E3Y7LEMLMH66,HAZELWOOD SCHOOL DISTRICT,10,12/21/2022 +161024,566001021,,DURHAM PUBLIC SCHOOLS,10,1/14/2022 +161204,566001131,PRC9ZC7J56X7,WAYNE COUNTY BOARD OF EDUCATION,10,12/8/2022 +171977,746000420,FKJNNPQQMKM1,"CAMERON COUNTY, TEXAS",10,6/9/2023 +171989,756000873,,"COLLIN COUNTY, TEXAS",10,4/28/2022 +174124,756001854,,IRVING INDEPENDENT SCHOOL DISTRICT,10,2/10/2022 +174324,746000895,SGQXJ6JPSA73,GALENA PARK INDEPENDENT SCHOOL DISTRICT,10,1/23/2023 +180895,910577129,,VOLUNTEERS OF AMERICA WESTERN WASHINGTON AND SUBSIDIARY,10,3/24/2022 +181600,856000565,PBWFLKLC8Q84,NEW MEXICO DEPARTMENT OF FINANCE AND ADMINISTRATION,10,4/13/2023 +181645,356005697,,"HEALTH AND HOSPITAL CORPORATION OF MARION COUNTY, INDIANA",10,3/25/2021 +182474,344428214,FRH2FFME3RM7,FULTON COUNTY HEALTH CENTER,10,6/9/2023 +183628,346004382,,THE METROHEALTH SYSTEM,10,4/19/2022 +184893,061259539,,THE MOHEGAN TRIBE OF INDIANS OF CONNECTICUT,10,7/6/2022 +186518,036024497,,VERMONT ECONOMIC DEVELOPMENT AUTHORITY,10,3/25/2021 +186988,522003078,F3MHWBVMBFD8,NATIONAL EXPERIENCED WORKFORCE SOLUTIONS,10,5/2/2023 +194286,586000312,KMVGJJU8P5H4,ROCKDALE COUNTY BOARD OF EDUCATION,10,1/12/2023 +198614,942191905,DVZ6S7VKGRK7,PORTERVILLE UNIFIED SCHOOL DISTRICT,10,6/6/2023 +210703,520795508,,"APPALACHIAN REGIONAL HEALTHCARE, INC",10,3/25/2021 +211645,586000212,,CLAYTON COUNTY BOARD OF EDUCATION,10,7/6/2023 +219041,640239641,LB93DH1VACJ3,SINGING RIVER ELECTRIC COOPERATIVE,10,4/20/2023 +221345,592097520,,"FEEDING SOUTH FLORIDA, INC.",10,4/19/2022 +222245,640195613,,MAGNOLIA ELECTRIC POWER ASSOCIATION,10,5/4/2022 +223101,237353532,,ST. MARY'S FOOD BANK ALLIANCE AND SMFB FOUNDATION,10,12/21/2021 +226122,222534389,,"RUTLAND REGIONAL HEALTH SERVICES, INC",10,6/9/2022 +227622,840399209,CBPFNNJYY9Q1,MEMORIAL REGIONAL HEALTH,10,6/8/2023 +229203,010639602,QC59ZX49LPJ1,"KIPP TEXAS, INC.",10,12/6/2022 +232272,222423882,,"COMMUNITY FOOD BANK OF NEW JERSEY, INC.",10,2/28/2022 +234600,446005758,,OZARKS MEDICAL CENTER,10,6/21/2023 +240754,460454373,E7YMVCB28565,SOUTHEASTERN ELECTRIC COOPERATIVE,10,4/28/2023 +240985,452893839,,EL PASOANS FIGHTING HUNGER,10,10/6/2022 +241118,560845796,KJ3FUJ4JD2K9,CUMBERLAND COUNTY HOSPITAL SYSTEM INC.,10,7/6/2023 +242278,611566644,,"GEORGIA CHILDCARE RESOURCES, INC.",10,10/5/2022 +245337,860181654,,HONORHEALTH,10,10/3/2022 +245423,475280523,,SRUF CAMPUS HOUSING INC. AND SUBSIDIARY,10,3/25/2021 +245548,274385692,,AVITA HEALTH SYSTEM,10,3/25/2021 +246806,586001719,,TIFT COUNTY HOSPITAL AUTHORITY,10,3/25/2021 +247269,610458376,,"PIKEVILLE MEDICAL CENTER, INC.",10,7/2/2022 +247412,474302258,,PARTNERS IN NUTRITION,10,2/2/2023 +248314,416005815,,WELIA HEALTH,10,3/25/2021 +248730,812092154,GKYNWL1LBAL8,RESIDENTIAL REVIVAL INDIANA,10,11/1/2022 +249970,815323275,,"ONE BROOKLYN HEALTH SYSTEM, INC.",10,10/10/2022 +252272,680620773,,CAMPBELL UNIVERSITY FOUNDATION INC,10,2/15/2022 +252682,562217808,,CAHEC PROPERTIES CORPORATION AND AFFILIATES,10,8/16/2022 +279,010281875,,EASTERN MAINE DEVELOPMENT CORPORATION,11,6/16/2022 +99460,726012779,,GULF STATES MARINE FISHERIES COMMISSION,11,8/16/2022 +184959,936002376,,PACIFIC STATES MARINE FISHERIES COMMISSION,11,3/25/2021 +2164,042103594,,MASSACHUSETTS INSTITUTE OF TECHNOLOGY,12,3/25/2021 +2932,042239742,,THE MITRE CORPORATION,12,3/25/2021 +3379,042505372,,"THE CHARLES STARK DRAPER LABORATORY, INC.",12,3/25/2021 +11634,132593244,HRH1XLHY1XJ3,RIVERSIDE RESEARCH INSTITUTE,12,6/21/2023 +18232,150589832,,"SRC, INC.",12,3/25/2021 +35809,250969449,,CARNEGIE MELLON UNIVERSITY,12,3/25/2021 +57098,382761126,,NATIONAL CENTER FOR MANUFACTURING SCIENCES AND SUBSIDIARY AND AFFILIAT,12,3/25/2021 +68000,440545878,,MRIGLOBAL,12,3/25/2021 +72985,520595110,,THE JOHNS HOPKINS UNIVERSITY,12,3/25/2021 +73200,520741393,,LOGISTICS MANAGEMENT INSTITUTE,12,3/25/2021 +75117,521317896,,HENRY M. JACKSON FOUNDATION FOR THE ADVANCEMENT OF MILITARY MEDI,12,3/25/2021 +77832,530196605,,AMERICAN NATIONAL RED CROSS,12,12/10/2021 +78019,530233247,,INSTITUTE FOR DEFENSE ANALYSES,12,3/25/2021 +78670,540695125,MEHNDWCSBKD5,ANALYTIC SERVICES INC.,12,5/23/2023 +79792,541558882,,CNA,12,3/25/2021 +109945,840865803,FR89NXNNSDX3,NATIONAL MARROW DONOR PROGRAM,12,1/31/2023 +118387,941160950,,SRI INTERNATIONAL,12,3/25/2021 +124066,951958142,,THE RAND CORPORATION,12,3/25/2021 +124199,952102389,,THE AEROSPACE CORPORATION,12,4/28/2023 +166432,936001832,,PORT OF PORTLAND,12,11/9/2021 +188008,911593913,TR9RX33188T4,THE GENEVA FOUNDATION,12,7/13/2023 +189490,916001299,,CLARK COUNTY,12,10/3/2022 +198458,571067151,,ADVANCED TECHNOLOGY INTERNATIONAL,12,3/25/2021 +211446,756037853,,CIVIL AIR PATROL,12,3/25/2021 +216666,314379427,,BATTELLE MEMORIAL INSTITUTE,12,3/25/2021 +216862,742678229,,FLEXTECH ALLIANCE INC. DBA NEXTFLEX,12,7/1/2022 +219797,660540236,,GUAM WATERWORKS AUTHORITY,12,4/19/2022 +221323,760727663,,NATIONAL CENTER FOR DEFENSE MANUFACTURING AND MACHINING,12,6/30/2022 +245327,465718220,,BEAUMONT HEALTH,12,10/10/2022 +246440,474960128,,MEDICAL TECHNOLOGY ENTERPRISE CONSORTIUM,12,9/2/2022 +251628,262571197,,NATIONAL ADVANCED MOBILITY CONSORTIUM,12,4/14/2022 +3122,042382027,,"COMMUNITY TEAMWORK, INC.",14,3/25/2021 +3141,042389659,,"SOUTH MIDDLESEX OPPORTUNITY COUNCIL, INC. AND AFFILIATES",14,4/6/2022 +3235,042454675,C1MZSNM5PNJ6,"RCAP SOLUTIONS, INC.",14,1/9/2023 +3416,042518368,,"WAY FINDERS, INC. AND SUBSIDIARIES",14,12/7/2021 +3471,042543731,,"HRCA HOUSING FOR ELDERLY, INC D/B/A JACK SATTER HOUSE",14,12/26/2021 +4261,042775991,,"METROPOLITAN BOSTON HOUSING PARTNERSHIP, INC. DBA METRO HOUSING/BOSTON",14,3/25/2021 +4902,043145070,,THE PELHAM II CORPORATION,14,4/22/2022 +8474,111630755,,THE BROOKLYN HOSPITAL CENTER,14,10/6/2022 +8527,111635081,,MAIMONIDES MEDICAL CENTER,14,3/25/2021 +8862,112077747,DA4DKV6LGFK9,ISRAEL SENIOR CITIZENS HOUSING DEVELOPMENT FUND CORP,14,11/27/2022 +11022,131740122,,ST BARNABAS HOSPITAL,14,3/25/2021 +11980,132738818,,METROPOLITAN COUNCIL ON JEWISH POVERTY AND AFFILIATES,14,3/25/2021 +12543,132997301,,THE ST. LUKE'S-ROOSEVELT HOSPITAL CENTER AND AFFILIATES,14,3/25/2021 +17092,141641730,,ALBANY MEDICAL CENTER,14,3/25/2021 +20500,221487173,,ENGLEWOOD HOSPITAL AND MEDICAL CENTER AND SUBSIDIARIES,14,3/25/2021 +20543,262019056,,SAINT PETER'S HEALTHCARE SYSTEM,14,10/10/2022 +45589,270581133,,UNION HEALTH SYSTEM,14,3/25/2021 +50485,363166895,,SINAI HEALTH SYSTEM AND AFFILIATES,14,3/25/2021 +59460,391249426,,"COMMUNITY ADVOCATES, INC. AND AFFILIATES",14,7/21/2022 +72628,510116653,,DELAWARE STATE HOUSING AUTHORITY,14,3/25/2021 +87073,582044503,,"MEADOWS HEALTHCARE ALLIANCE, INC. DBA MEADOWS HEALTH",14,3/25/2021 +93339,620752586,,"DOUGLAS-CHEROKEE ECONOMIC AUTHORITY, INC",14,3/25/2021 +98545,720475545,,GENERAL HEALTH SYSTEM,14,3/25/2021 +122035,943155150,,LOW INCOME HOUSING INSTITUTE,14,10/3/2022 +127155,954382752,CGJWB981V7W5,UNITED STATES VETERANS INITIATIVE AND SUBSIDIARIES,14,1/17/2023 +129006,630980480,,ALABAMA HOUSING FINANCE AUTHORITY,14,1/6/2022 +129064,636001579,,JEFFERSON COUNTY COMMISSION,14,6/5/2022 +129234,636001201,E6FCXGL5FC43,CITY OF BIRMINGHAM,14,5/30/2023 +129511,636000390,,HOUSING AUTHORITY OF THE BIRMINGHAM DISTRICT,14,3/25/2021 +130250,866000252,,"CITY OF MESA, ARIZONA",14,2/25/2022 +130252,866000256,,"CITY OF PHOENIX, ARIZONA",14,3/25/2021 +130278,866000266,,"CITY OF TUCSON, ARIZONA",14,3/25/2021 +131805,680181576,,CALIFORNIA HOUSING FINANCE AGENCY,14,3/25/2021 +131848,946000519,,COUNTY OF MARIN,14,3/25/2021 +131882,956000939,,COUNTY OF SAN LUIS OBISPO,14,3/25/2021 +131925,946027361,Q3MWNS85DEF8,CITY OF FREMONT,14,3/31/2023 +132044,956000733,,CITY OF LONG BEACH,14,3/25/2021 +132045,956000735,,CITY OF LOS ANGELES,14,3/25/2021 +132056,956000759,,CITY OF PASADENA,14,3/25/2021 +132057,956000764,QLK7V1HAF1J2,CITY OF POMONA,14,3/31/2023 +132062,956000790,,CITY OF SANTA MONICA,14,4/12/2022 +132155,956000666,,CITY OF ANAHEIM,14,3/25/2021 +132161,956005848,,CITY OF GARDEN GROVE,14,3/26/2022 +132169,956000785,,CITY OF SANTA ANA,14,6/29/2022 +132185,952759391,WANRBCLK1AD7,CITY OF IRVINE,14,12/6/2022 +132202,956000769,,CITY OF RIVERSIDE,14,12/23/2021 +132219,946000759,,SACRAMENTO HOUSING AND REDEVELOPMENT AGENCY,14,3/25/2021 +132248,956000690,KVBYLRZMAGJ9,CITY OF CHULA VISTA,14,3/31/2023 +132271,946000436,YV3KNQAKJLQ7,CITY OF STOCKTON,14,5/17/2023 +132318,946000419,,CITY OF SAN JOSE,14,3/25/2021 +132350,946000428,MPMQPX61FMW7,CITY OF SANTA ROSA,14,5/15/2023 +132402,946000758,,"HOUSING AUTHORITY OF THE CITY OF OAKLAND, CALIFORNIA",14,3/25/2021 +132417,942784334,,HOUISNG AUTHORITY OF THE COUNTY OF ALAMEDA,14,3/25/2021 +132478,946000746,,HOUSING AUTHORITY OF THE COUNTY OF CONTRA COSTA,14,3/25/2021 +132630,956001629,,HOUSING AUTHORITY OF THE COUNTY OF KERN,14,4/8/2022 +132734,956001623,,HOUSING AUTHORITY OF THE CITY OF LOS ANGELES,14,3/25/2021 +133100,956001632,,HOUSING AUTHORITY OF THE COUNTY OF SAN BERNARDINO,14,3/25/2021 +133274,946000761,,HOUSING AUTHORITY OF THE COUNTY OF SAN MATEO,14,3/25/2021 +133295,956001634,,HOUSING AUTHORITY OF THE COUNTY OF SANTA BARBARA,14,3/25/2021 +133348,941699385,,HOUSING AUTHORITY OF THE COUNTY OF SANTA CRUZ,14,3/25/2021 +133426,946003023,,STANISLAUS REGIONAL HOUSING AUTHORITY,14,6/30/2022 +135066,846002414,,HOUSING AUTHORITY OF THE CITY AND COUNTY OF DENVER,14,3/25/2021 +135673,066001870,,"CITY OF HARTFORD, CONNECTICUT",14,3/25/2021 +135844,237060999,,"PARK CITY COMMUNITIES, INC. FKA HA OF THE CITY OF BRIDGEPORT",14,3/25/2021 +135912,066000413,,HOUSING AUTHORITY OF THE CITY OF NEW HAVEN D/B/A ELM CITY COMMUNITIES,14,3/25/2021 +135998,516000160,,"NEW CASTLE COUNTY, DELAWARE",14,10/10/2022 +136109,596000695,,"LAKE COUNTY, FL",14,5/26/2022 +136122,596000773,,"ORANGE COUNTY, FLORIDA",14,3/25/2021 +136123,596000780,,OSCEOLA COUNTY BOARD OF COUNTY COMMISSIONERS,14,3/23/2022 +136125,596000793,,"PASCO COUNTY, FL",14,3/25/2021 +136225,596000375,,"CITY OF MIAMI, FL",14,5/17/2022 +136246,596000406,UBMRAF87HQF5,CITY OF PENSACOLA,14,3/30/2023 +136274,591101138,,CITY OF TAMPA,14,5/3/2022 +136350,596000396,CYLDC3FVBLJ9,CITY OF ORLANDO,14,4/28/2023 +136521,591562393,,BROWARD COUNTY HOUSING AUTHORITY,14,3/25/2021 +136538,591220360,,HIALEAH HOUSING AUTHORITY,14,3/25/2021 +136904,586000802,LUJ2LTXZBX26,CLAYTON COUNTY BOARD OF COMMISSIONERS,14,7/24/2023 +136918,586000814,,"DEKALB COUNTY, GA",14,10/10/2022 +136943,586000835,,GWINNETT COUNTY BOARD OF COMMISSIONERS,14,7/22/2022 +137893,996000567,,COUNTY OF HAWAII,14,10/6/2022 +137894,996000658,,COUNTY OF KAUAI,14,3/15/2022 +137895,996000618,,COUNTY OF MAUI,14,3/25/2021 +137896,996001257,,CITY AND COUNTY OF HONOLULU,14,3/25/2021 +138538,362708817,,ILLINOIS HOUSING DEVELOPMENT AUTHORITY,14,3/25/2021 +138628,366006551,,"DUPAGE COUNTY, ILLINOIS",14,9/1/2022 +140543,366001313,,HOUSING AUTHORITY OF COOK COUNTY,14,3/25/2021 +142228,351485172,,INDIANA HOUSING AND COMMUNITY DEVELOPMENT AUTHORITY,14,3/25/2021 +142675,356001063,,CITY OF INDIANAPOLIS,14,7/26/2022 +145235,486000653,,CITY OF WICHITA,14,3/25/2021 +146140,320049006,,LOUISVILLE/JEFFERSON COUNTY METRO GOVERNMENT,14,3/25/2021 +146735,726001371,,TANGIPAHOA PARISH GOVERNMENT,14,8/10/2022 +146970,726001390,T16NA5YA1WH3,TERREBONNE PARISH CONSOLIDATED GOVERNMENT,14,6/26/2023 +147725,526000889,,"BALTIMORE COUNTY, MD",14,3/25/2021 +147735,526000943,,"FREDERICK COUNTY, MARYLAND",14,5/24/2022 +147738,526000959,YAALVYBSJWY1,HARFORD COUNTY,14,4/13/2023 +147739,526000965,,HOWARD COUNTY,14,5/26/2022 +147741,526000980,,MONTGOMERY COUNTY,14,3/25/2021 +147743,526000998,,"PRINCE GEORGE'S COUNTY, MD",14,3/25/2021 +147758,526000769,,"CITY OF BALTIMORE, MARYLAND",14,3/25/2021 +147884,526001394,,HOUSING AUTHORITY OF BALTIMORE CITY,14,10/10/2022 +147919,042443980,,MASSACHUSETTS HOUSING FINANCE AGENCY,14,3/25/2021 +147937,046001402,,CITY OF NEW BEDFORD,14,3/25/2021 +147952,046001383,JJ3JKMU5K4L1,CITY OF CAMBRIDGE MASSACHUSETTS,14,3/31/2023 +147965,046001380,,"CITY OF BOSTON, MASSACHUSETTS",14,3/25/2021 +148729,386004862,,"COUNTY OF KENT, MICHIGAN",14,6/22/2022 +148751,386004876,,"OAKLAND COUNTY, MICHIGAN",14,1/4/2023 +148881,386004611,,CITY OF FLINT,14,4/9/2022 +148931,386004628,VARBEGGYV751,CITY OF LANSING,14,4/28/2023 +149215,386004606,,"CITY OF DETROIT, MICHIGAN",14,3/25/2021 +150667,411599130,,MINNESOTA HOUSING FINANCE AGENCY,14,3/25/2021 +151542,411677709,,MINNEAPOLIS PUBLIC HOUSING AUTHORITY,14,3/25/2021 +152747,430979983,,MISSOURI HOUSING DEVELOPMENT COMMISSION,14,3/25/2021 +153460,446000716,,"HOUSING AUTHORITY OF KANSAS CITY, MISSOURI",14,3/25/2021 +153624,436003234,,HOUSING AUTHORITY OF ST. LOUIS COUNTY,14,3/25/2021 +155086,476006304,,"CITY OF OMAHA, NEBRASKA",14,9/22/2022 +155160,476006256,,"CITY OF LINCOLN, NEBRASKA",14,4/15/2022 +155946,886000720,,"CITY OF HENDERSON, NEVADA",14,10/10/2022 +155947,886000198,,CITY OF LAS VEGAS,14,1/29/2022 +155960,886000201,,"CITY OF RENO, NEVADA",14,2/26/2022 +155991,886000260,,HOUSING AUTHORITY OF THE CITY OF RENO,14,6/14/2022 +156041,026046088,,NEW HAMPSHIRE HOUSING FINANCE AUTHORITY,14,3/25/2021 +156513,226002426,,COUNTY OF BERGEN,14,1/23/2023 +156754,226002138,,CITY OF NEWARK,14,8/16/2023 +156782,226002501,,JERSEY CITY HOUSING AUTHORITY,14,3/25/2021 +158023,850252748,,NEW MEXICO MORTGAGE FINANCE AUTHORITY,14,3/25/2021 +158041,856000642,,UNIVERSITY OF NEW MEXICO,14,3/25/2021 +158056,856000202,,STATE OF NEW MEXICO COUNTY OF BERNALILLO,14,1/7/2022 +158089,856000102,,CITY OF ALBUQUERQUE,14,3/25/2021 +158537,166002533,,CITY OF BUFFALO,14,1/13/2022 +158643,160791244,,ROCHESTER HOUSING AUTHORITY,14,3/25/2021 +158767,156000416,TXMLVC8GKRV9,"CITY OF SYRACUSE, NEW YORK",14,3/30/2023 +158993,136007340,DA3CE2FN4MW1,CITY OF YONKERS,14,1/19/2023 +160937,561700536,,NORTH CAROLINA HOUSING FINANCE AGENCY,14,3/25/2021 +161739,566000560,,"HOUSING AUTHORITY OF THE CITY OF CHARLOTTE, NORTH CAROLINA",14,3/25/2021 +162629,346000646,,CITY OF CLEVELAND,14,3/25/2021 +162757,316000064,,CITY OF CINCINNATI,14,2/11/2022 +162957,316000175,,CITY OF DAYTON,14,8/9/2022 +163900,316401164,,COLUMBUS METROPOLITAN HOUSING AUTHORITY,14,3/25/2021 +163917,316000188,,CINCINNATI METROPOLITAN HOUSING AUTHORITY,14,3/25/2021 +163962,346401430,,LUCAS METROPOLITAN HOUSING AUTHORITY,14,3/25/2021 +164035,346000029,,AKRON METROPOLITAN HOUSING AUTHORITY,14,3/25/2021 +165114,736005359,,THE CITY OF OKLAHOMA CITY,14,5/6/2022 +165198,736005470,,CITY OF TULSA OKLAHOMA,14,4/12/2022 +166054,936002316,,"WASHINGTON COUNTY, OREGON",14,3/25/2021 +166150,936002160,QC6XU4HJEZW3,CITY OF EUGENE,14,12/21/2022 +166198,936002236,,"CITY OF PORTLAND, OREGON",14,3/25/2021 +166796,231733024,,PENNSYLVANIA HOUSING FINANCE AGENCY,14,3/25/2021 +169402,236003266,,PHILADELPHIA HOUSING AUTHORITY,14,3/25/2021 +170323,056000193,,HOUSING AUTHORITY OF THE CITY OF PROVIDENCE,14,6/2/2022 +170356,591585639,,SOUTH CAROLINA STATE HOUSING FINANCE AND DEVELOPMENT AUTHORITY,14,3/25/2021 +171819,626001582,,KNOXVILLE COMMUNITY DEVELOPMENT CORPORATION,14,3/25/2021 +171869,626001584,,MEMPHIS HOUSING AUTHORITY,14,3/25/2021 +172003,756000905,,DALLAS COUNTY,14,3/25/2021 +172116,746000558,,"MONTGOMERY COUNTY, TEXAS",14,4/22/2022 +172167,756001170,,TARRANT COUNTY,14,3/25/2021 +172246,746002070,,CITY OF SAN ANTONIO,14,3/25/2021 +172393,756000508,,"CITY OF DALLAS, TEXAS",14,3/25/2021 +172399,756000543,,CITY OF GRAND PRAIRIE TEXAS,14,4/26/2022 +172814,756000444,NV4JC28TLJL6,"CITY OF AMARILLO, TEXAS",14,3/9/2023 +172891,756000528,,CITY OF FORT WORTH,14,3/25/2021 +173037,746002108,,HOUSING AUTHORITY OF THE CITY OF SAN ANTONIO,14,3/25/2021 +173203,746000787,,HOUSING AUTHORITY OF THE CITY OF EL PASO,14,3/25/2021 +173806,746000117,,HOUSING AUTHORITY OF THE CITY OF AUSTIN,14,3/25/2021 +176036,546001123,,"ARLINGTON COUNTY, VIRGINIA",14,3/25/2021 +176394,916001368,,SNOHOMISH COUNTY,14,10/3/2022 +176397,916001375,GU94D6PX5KT1,THURSTON COUNTY,14,7/13/2023 +176492,916001275,,CITY OF SEATTLE,14,3/25/2021 +176569,916001283,,CITY OF TACOMA,14,3/16/2023 +176795,916000978,,HOUSING AUTHORITY OF THE COUNTY OF KING,14,3/25/2021 +176796,916000977,,HOUSING AUTHORITY OF THE CITY OF SEATTLE,14,3/25/2021 +176851,916000962,,THE HOUSING AUTHORITY OF THE CITY OF BREMERTON,14,3/25/2021 +176997,911061936,,HOUSING AUTHORITY OF SNOHOMISH COUNTY,14,3/25/2021 +177869,391209764,,WISCONSIN HOUSING AND ECONOMIC DEVELOPMENT AUTHORITY,14,3/25/2021 +178024,396005507,,CITY OF MADISON,14,7/12/2022 +178208,396005532,,"CITY OF MILWAUKEE, WISCONSIN",14,3/25/2021 +180177,660433539,DE37L8G4Q8M7,MUNICIPALITY OF CAROLINA,14,4/3/2023 +180266,660378410,,VIRGIN ISLANDS HOUSING AUTHORITY,14,3/30/2023 +180847,161030027,,BUFFALO URBAN RENEWAL AGENCY,14,10/21/2021 +181063,521934645,,DISTRICT OF COLUMBIA HOUSING AUTHORITY,14,3/25/2021 +181497,010312916,,MAINE STATE HOUSING AUTHORITY,14,3/25/2021 +181501,136007014,,MUNICIPAL HOUSING AUTHORITY FOR THE CITY OF YONKERS,14,3/25/2021 +181506,546001564,,RICHMOND REDEVELOPMENT AND HOUSING AUTHORITY,14,3/25/2021 +181811,362170826,,ADMIN & SERVICE DELIVER OPERATIONS OF THE ARCHDIOCESE OF CHICAGO,14,3/25/2021 +182042,820302333,,IDAHO HOUSING AND FINANCE ASSOCIATION,14,3/25/2021 +182086,956000714,,CITY OF GLENDALE,14,3/25/2021 +182143,550515944,,WEST VIRGINIA HOUSING DEVELOPMENT FUND,14,3/25/2021 +182187,481194075,,UNIFIED GOVERNMENT OF WYANDOTTE COUNTY / KANSAS CITY KANSAS,14,12/15/2022 +182279,141967871,,HAWAII HOUSING FINANCE AND DEVELOPMENT CORPORATION,14,1/6/2022 +182305,226002481,,COUNTY OF UNION,14,2/6/2023 +182368,521332044,,HOUSING AUTHORITY OF PRINCE GEORGE'S COUNTY,14,3/25/2021 +182640,521527664,UFN9T95N4GQ9,OHIO HOUSING FINANCE AGENCY,14,10/17/2022 +182827,581222605,,GEORGIA HOUSING & FINANCE AUTHORITY,14,10/7/2021 +182986,061267528,,CONNECTICUT HOUSING FINANCE AUTHORITY,14,3/25/2021 +183000,660433574,,MUNICIPALITY OF BAYAMON,14,3/25/2021 +183029,953777596,,LOS ANGELES COUNTY DEVELOPMENT AUTHORITY FKA COMMUNITY DEV COMMISSION,14,3/25/2021 +183038,030221655,,VERMONT STATE HOUSING AUTHORITY,14,3/25/2021 +184259,726000536,,HOUSING AUTHORITY OF NEW ORLEANS,14,3/25/2021 +184465,132946111,,"GRAND STREET GUILD HOUSING DEVELOPMENT FUND COMPANY, INC.",14,3/25/2021 +184655,660427034,,MUNICIPALITY OF SAN JUAN,14,3/25/2021 +184677,386000134,,MICHIGAN STATE HOUSING DEVELOPMENT AUTHORITY,14,3/25/2021 +185800,136400571,,NEW YORK CITY HOUSING AUTHORITY,14,3/25/2021 +185803,256001768,,HOUSING AUTHORITY OF THE CITY OF PITTSBURGH,14,3/25/2021 +186308,416005375,,CITY OF MINNEAPOLIS,14,8/16/2022 +186694,411309192,,PUBLIC HOUSING AGENCY OF THE CITY OF SAINT PAUL,14,3/25/2021 +186867,226002507,,HOUSING AUTHORITY OF THE CITY OF NEWARK,14,3/25/2021 +186914,730999618,,OKLAHOMA HOUSING FINANCE AGENCY,14,3/25/2021 +187125,540921892,,VIRGINIA HOUSING DEVELOPMENT AUTHORITY,14,3/25/2021 +187226,946002988,,HOUSING AUTHORITY OF THE COUNTY OF MARIN,14,10/3/2022 +187645,586000511,,CITY OF ATLANTA,14,3/25/2021 +187776,116001929,,TOWN OF HEMPSTEAD,14,10/20/2022 +187886,226002454,,COUNTY OF MIDDLESEX,14,10/10/2022 +188017,351939038,,INDIANAPOLIS HOUSING AGENCY,14,3/25/2021 +188422,236003050,,"COUNTY OF YORK, PENNSYLVANIA",14,3/25/2021 +188641,593278450,,JACKSONVILLE HOUSING AUTHORITY,14,3/25/2021 +188774,953390896,,SAN DIEGO HOUSING COMMISSION,14,3/25/2021 +188871,946000749,,HOUSING AUTHORITY OF THE CITY OF FRESNO,14,3/25/2021 +189287,222045817,,NEW JERSEY ECONOMIC DEVELOPMENT AUTHORITY,14,3/25/2021 +189854,626001585,,METROPOLITAN DEVELOPMENT AND HOUSING AGENCY,14,3/25/2021 +190105,942158408,,SONOMA COUNTY COMMUNITY DEVELOPMENT COMMISSION,14,12/16/2021 +190261,520859090,,HOUSING OPPORTUNITIES COMMISSION OF MONTGOMERY COUNTY,14,3/25/2021 +190356,256000022,,ALLEGHENY COUNTY HOUSING AUTHORITY,14,3/25/2021 +190376,132679034,,"SOUTHEAST GRAND STREET GUILD HOUSING DEVELOPMENT FUND COMPANY, INC.",14,3/25/2021 +191539,916000980,,HOUSING AUTHORITY OF THE CITY OF TACOMA,14,10/3/2022 +191678,746001164,,CITY OF HOUSTON,14,3/25/2021 +191679,446000201,,"CITY OF KANSAS CITY, MISSOURI",14,5/5/2022 +191815,046003214,UQE5LKJA6E47,LYNN HOUSING AUTHORITY,14,12/31/2022 +191887,366006541,,"COOK COUNTY, ILLINIOS",14,3/25/2021 +191925,256000879,,CITY OF PITTSBURGH,14,9/20/2022 +192010,646010364,,MISSISSIPPI REGIONAL HOUSING AUTHORITY VIII,14,10/6/2022 +192516,956000776,,CITY OF SAN DIEGO,14,3/25/2021 +192579,954498834,,LOS ANGELES HOMELESS SERVICES AUTHORITY,14,12/31/2022 +192789,611447539,,LOUISVILLE METRO HOUSING AUTHORITY,14,3/25/2021 +192900,946002959,,HOUSING AUTHORITY OF CITY AND COUNTY OF SAN FRANCISCO,14,3/25/2021 +192992,046001907,,BOSTON HOUSING AUTHORITY,14,3/25/2021 +193178,132688369,,NEW YORK CITY HOUSING DEVELOPMENT CORPORATION,14,3/25/2021 +193278,866000247,CRE4N8H1X6J5,"CITY OF GLENDALE, ARIZONA",14,4/4/2023 +193318,941667160,,SANTA CLARA COUNTY HOUSING AUTHORITY,14,3/16/2022 +193337,586002356,,"THE HOUSING AUTHORITY OF THE CITY OF ATLANTA, GEORGIA",14,3/25/2021 +193346,956001631,,HOUSING AUTHORITY OF THE COUNTY OF RIVERSIDE,14,3/25/2021 +193354,946000299,KC7DYLV9EF25,CITY OF BERKELEY,14,3/31/2023 +194174,436003231,,"CITY OF ST. LOUIS, MISSOURI",14,3/25/2021 +194206,436003254,,ST. LOUIS HOUSING AUTHORITY,14,3/25/2021 +194247,846000762,,EAGLE COUNTY,14,3/25/2021 +194480,756001817,,"THE HOUSING AUTHORITY OF THE CITY OF DALLAS, TEXAS",14,10/5/2022 +194793,960001279,,GUAM HOUSING AND URBAN RENEWAL AUTHORITY,14,3/25/2021 +195537,226002466,,COUNTY OF PASSAIC,14,11/1/2022 +195591,366000618,,CHICAGO HOUSING AUTHORITY,14,3/25/2021 +195678,860185412,,NAVAJO HOUSING AUTHORITY,14,3/25/2021 +196157,046004472,,WORCESTER HOUSING AUTHORITY,14,12/27/2021 +196782,391159751,,HOUSING AUTHORITY OF THE CITY OF MILWAUKEE,14,3/25/2021 +196974,596001289,,HOUSING AUTHORITY OF THE CITY OF TAMPA,14,3/25/2021 +196991,132946271,,"GRAND STREET GUILD EAST HOUSING DEVELOPMENT FUND COMPANY, INC.",14,3/25/2021 +197644,610864674,,KENTUCKY HOUSING CORPORATION,14,3/25/2021 +197664,990334987,,HAWAII PUBLIC HOUSING AUTHORITY,14,3/25/2021 +197817,161533232,,KALEIDA HEALTH,14,3/25/2021 +197819,346000703,,CUYAHOGA METROPOLITAN HOUSING AUTHORITY,14,3/25/2021 +198212,830208667,,WYOMING BUSINESS COUNCIL,14,5/13/2022 +198353,521699886,,IOWA FINANCE AUTHORITY,14,3/25/2021 +198588,223548695,,"CAPITAL HEALTH SYSTEM, INC. AND SUBSIDIARIES",14,3/25/2021 +199966,133957095,,THE NEW YORK AND PRESBYTERIAN HOSPITAL,14,3/25/2021 +200364,660466229,,PUERTO RICO PUBLIC HOUSING ADMINISTRATION,14,3/25/2021 +200579,216000881,,COUNTY OF MONMOUTH,14,3/25/2021 +200897,210634462,,THE COOPER HEALTH SYSTEM,14,10/10/2022 +201893,546001466,,NORFOLK REDEVELOPMENT AND HOUSING AUTHORITY,14,3/25/2021 +202639,946000760,,HOUSING AUTHORITY OF THE COUNTY OF SAN JOAQUIN,14,5/24/2022 +203375,730781666,VP1JKMDPCXC8,HOUSING AUTHORITY OF THE CHEROKEE NATION,14,4/25/2023 +204222,756001818,,FORT WORTH HOUSING SOLUTIONS,14,3/25/2021 +205004,382142140,CLUCTHFSJKP3,UNITED COMMUNITY HOUSING COALITION,14,6/13/2023 +205354,042732439,,"NEIGHBORHOOD HOUSING SERVICES OF THE SOUTH SHORE, INC.",14,12/16/2021 +207657,141338428,,ELLIS HOSPITAL (D/B/A ELLIS MEDICINE),14,3/25/2021 +207970,456002413,,BANK OF NORTH DAKOTA,14,3/25/2021 +208656,363971142,,"LAKEVIEW TOWERS RESIDENTS ASSOCIATION, INC.",14,3/25/2021 +210104,450371338,,NORTH DAKOTA HOUSING FINANCE AGENCY,14,3/25/2021 +210452,582489635,,"GEORGIA HAP ADMINISTRATORS, INC. DBA NATIONAL HOUSING COMPLIANCE",14,3/25/2021 +210661,593451366,,FLORIDA HOUSING FINANCE CORPORATION,14,3/25/2021 +212132,113257656,,358-74 VERNON AVENUE HOUSING DEVELOPMENT FUND CORPORATION,14,3/25/2021 +215856,310929576,,KNOX COMMUNITY HOSPITAL AND SUBSIDIARIES,14,3/25/2021 +215944,231664406,,PHILADELPHIA HOUSING DEVELOPMENT CORPORATION,14,4/6/2022 +217330,043358566,,"UMASS MEMORIAL HEALTH CARE, INC.",14,7/1/2022 +217432,222551209,,ROCHESTER REGIONAL HEALTH,14,3/25/2021 +217653,300002040,,WISCONSIN HOUSING PRESERVATION CORP.,14,3/25/2021 +218144,651176352,,HARRIS COUNTY HOUSING AUTHORITY,14,3/25/2021 +218150,383617958,,DETROIT HOUSING COMMISSION,14,3/25/2021 +218774,132710179,,"EAST HARLEM PILOT BLOCK HDFC, INC. 012-44096",14,3/25/2021 +220827,582669483,,"CALIFORNIA AFFORDABLE HOUSING INITIATIVES, INC",14,3/25/2021 +220829,113636256,,CATHERINE SHERIDAN HOUSING DEVELOPMENT FUND CO INC,14,9/28/2021 +221283,741303720,,BAPTIST HOSPITALS OF SOUTHEAST TEXAS AND SUBSIDIARIES,14,3/25/2021 +221552,840676451,,COLORADO HOUSING AND FINANCE AUTHORITY,14,3/25/2021 +222011,571098556,,MEDICAL UNIVERSITY HOSPITAL AUTHORITY,14,3/25/2021 +222978,593123362,,NORTH TAMPA HOUSING DEVELOPMENT CORPORATION,14,3/25/2021 +224269,470646706,,"MERCY HOUSING, INC",14,3/25/2021 +224512,221838278,,NEW JERSEY HOUSING AND MORTGAGE FINANCE AGENCY,14,3/25/2021 +226802,640644578,Q23XVN4NV7T3,MISSISSIPPI HOME CORPORATION,14,1/4/2023 +229449,237379161,,"SISTERS OF CHARITY OF LEAVENWORTH HEALTH SYSTEM, INC",14,3/25/2021 +230355,160984913,,PATHSTONE CORPORATION AND AFFILIATES,14,3/25/2021 +231858,660219758,,"MENNONITE GENERAL HOSPITAL, INC",14,3/25/2021 +233668,020222150,,LRGHEALTHCARE,14,3/25/2021 +234054,270910670,,SOUTHERN NEVADA REGIONAL HOUSING AUTHORITY,14,3/25/2021 +236573,550357057,,"WHEELING HOSPITAL, INC.",14,10/3/2022 +236700,362170152,,MERCY HEALTH SYSTEM OF CHICAGO AND SUBSIDIARIES,14,3/25/2021 +237045,454619102,,LOUISIANA HOUSING CORPORATION,14,3/25/2021 +237149,770301242,,HOUSING AUTHORITY OF FRESNO COUNTY,14,3/25/2021 +237460,581259426,,HOUSING ASSISTANCE DIVISION OF GEORGIA DEPT. OF COMMUNITY AFFAIRS,14,3/25/2021 +237652,453615582,,DIVISION HOUSING CORPORATION,14,3/25/2021 +238623,952082686,,RIDGECREST REGIONAL HOSPITAL,14,3/25/2021 +238927,273335876,,PARISH HOSPITAL SERVICE DISTRICT FOR THE PARISH OF ORLEANS- DISTRICT A,14,10/10/2022 +240442,132951474,,THE ASSOCIATED BLIND HOUSING DEVELOPMENT FUND CORP. NY 012-35712,14,3/25/2021 +241168,461215609,LDF2JEF4NNU1,"BALTIMORE REGIONAL HOUSING PARTNERSHIP, INC.",14,7/18/2023 +241631,630985617,,"NAVIGATE AFFORDABLE HOUSING PARTNERS, INC.",14,3/25/2021 +242802,201282261,,THE SYLACAUGA HEALTH CARE AUTHORITY,14,5/4/2022 +242988,463176429,,JOHNSTON HEALTH SERVICES CORPORATION D/B/A JOHNSTON HEALTH,14,3/25/2021 +243191,364272272,VZ2XWYQHNYU7,ALL CHICAGO MAKING HOMELESSNESS HISTORY,14,7/26/2023 +243645,133398657,,ELIZABETH SETON PEDIATRIC CENTER,14,3/25/2021 +245568,742805097,,SOUTHWEST HOUSING COMPLIANCE CORPORATION,14,3/25/2021 +246455,391866425,,GUNDERSEN HEALTH SYSTEM,14,10/3/2022 +246499,952126937,,TRI-CITY HEALTHCARE DISTRICT,14,3/25/2021 +247053,046002081,,CAMBRIDGE HOUSING AUTHORITY,14,11/1/2022 +247377,203165478,,WILLOW BROOK SENIOR COOPERATIVE,14,4/7/2022 +247649,460318666,NQW1BHKPK8U3,SOUTH DAKOTA HOUSING DEVELOPMENT AUTHORITY,14,11/1/2022 +247800,971667160,,SANTA CLARA COUNTY HOUSING AUTHORITY,14,3/25/2021 +248241,421215609,,"BALTIMORE REGIONAL HOUSING PARTNERSHIP, INC.",14,3/25/2021 +248455,936001547,,HOME FORWARD,14,1/9/2023 +248578,591912330,,"NORTH FLORIDA RETIREMENT VILLAGE, INC.",14,3/25/2021 +248721,571090154,,"ATLANTIC HOUSING FOUNDATION, INC.",14,7/10/2022 +249438,596000573,,MIAMI-DADE PUBLIC HOUSING AND COMMUNITY DEVELOPMENT DEPARTMENT,14,3/25/2021 +249449,736006419,,TULSA COUNTY,14,10/14/2022 +249948,382542859,,SPARROW HEALTH SYSTEM,14,9/22/2022 +252569,840469270,,COLORADO WEST HEALTHCARE SYSTEM DBA COMMUNITY HOSPITAL,14,5/2/2022 +255712,951683892,,COMMUNITY MEMORIAL HEALTH SYSTEM,14,10/27/2022 +258050,760454514,JFMKAENLGN81,HARRIS COUNTY- 7 MONTH AUDIT ,14,5/1/2023 +15445,135643799,DDCMMXZ95TQ5,"DUCKS UNLIMITED, INC",15,12/8/2022 +75348,521384139,,NATIONAL FISH AND WILDLIFE FOUNDATION,15,3/25/2021 +115819,920047009,HKXNQ68KHVL9,"KAWERAK, INC.",15,7/11/2023 +115999,920094184,,"COOK INLET TRIBAL COUNCIL, INC.",15,6/29/2022 +129029,636001408,,BALDWIN COUNTY COMMISSION,15,9/16/2022 +130053,920036505,,CENTRAL COUNCIL OF THE TLINGIT AND HAIDA INDIAN TRIBES OF ALASKA,15,4/15/2022 +130584,860107023,,GILA RIVER INDIAN COMMUNITY - DEPARTMENT OF TRIBAL PROGRAMS AND ADMIN,15,3/25/2021 +152117,411661577,KR5CGFZ5W2R4,MILLE LACS BAND OF CHIPPEWA INDIANS,15,6/30/2023 +154763,810230409,,CONFEDERATED SALISH AND KOOTENAI TRIBES OF THE FLATHEAD NATION - GSD,15,3/25/2021 +154769,810292623,,ASSINIBOINE & SIOUX TRIBES OF THE FORT PECK INDIAN RESERVATION,15,6/27/2022 +162367,450220519,,STANDING ROCK SIOUX TRIBE,15,7/6/2022 +177403,910557683,,CONFEDERATED TRIBES OF THE COLVILLE RESERVATION,15,5/11/2023 +180127,660433481,,COMMONWEALTH OF PR DEPARTMENT OF NATURAL AND ENVIRONMENTAL RESOURCES,15,3/7/2023 +180253,980076103,,REPUBLIC OF THE MARSHALL ISLANDS,15,3/25/2021 +181737,810216424,,FORT BELKNAP INDIAN COMMUNITY,15,7/2/2022 +182916,910606339,Q4XPD6M7TNE7,SPOKANE TRIBE OF INDIANS,15,6/29/2023 +185670,980018947,,GOVERNMENT OF GUAM,15,3/25/2021 +186306,460248724,,ROSEBUD SIOUX TRIBE,15,4/28/2023 +187702,820197554,,THE SHOSHONE-BANNOCK TRIBES,15,7/2/2022 +193075,980050061,,FEDERATED STATES OF MICRONESA NATIONAL GOVERNMENT,15,3/25/2021 +193694,860092030,HCBFFF3D5F16,WHITE MOUNTAIN APACHE TRIBE GOVERNMENTAL PROGRAMS AND ADMINISTRATION,15,4/13/2023 +194704,970000676,,TERRITORY OF AMERICAN SAMOA,15,3/25/2021 +199046,910955402,,PUYALLUP TRIBE OF INDIANS,15,6/23/2022 +200742,870267073,,CENTRAL UTAH WATER CONSERVANCY DISTRICT,15,10/28/2021 +203160,521086761,,NATIONAL PARK FOUNDATION,15,4/12/2022 +247301,850098966,,MESCALERO APACHE TRIBE GOVERNMENTAL SERVICES DEPARTMENT,15,2/6/2023 +75269,521362793,,UNIVERSITY OF MARYLAND MEDICAL SYSTEM CORPORATION,16,2/14/2023 +158052,856002585,,NEW MEXICO CORRECTIONS DEPARTMENT,16,1/27/2022 +164892,736006400,,OKLAHOMA COUNTY,16,7/10/2022 +194355,990246363,JLCCLDD3KAV1,HAWAII PACIFIC HEALTH,16,2/9/2023 +11343,131932384,KYYSGM2KW1S3,"NATIONAL COUNCIL ON AGING, INC",17,12/21/2022 +73286,520794300,,AARP FOUNDATION,17,3/25/2021 +77241,526048236,,"SENIOR SERVICE AMERICA, INC.",17,3/25/2021 +137875,990252020,,"STATE OF HAWAII, DEPARTMENT OF ACCOUNTING AND GENERAL SERVICES",17,3/25/2021 +138705,366006672,,WILL COUNTY,17,1/2/2023 +158018,856000577,,STATE OF NEW MEXICO WORKFORCE SOLUTIONS DEPARTMENT,17,3/25/2021 +182738,576000286,,SOUTH CAROLINA DEPARTMENT OF EMPLOYMENT AND WORKFORCE,17,10/20/2022 +192601,576000356,,GREENVILLE COUNTY,17,4/12/2022 +203477,366006585,,KANE COUNTY,17,9/1/2022 +204880,456002490,,JOB SERVICE NORTH DAKOTA,17,3/25/2021 +219566,364122225,,CHICAGO COOK WORKFORCE PARTNERSHIP,17,3/25/2021 +246336,660654753,,PUERTO RICO DEPARTMENT OF ECONOMIC DEVELOPMENT AND COMMERCE,17,5/15/2023 +1368,030179592,,"WORLD LEARNING, INC.",19,3/25/2021 +10633,131624046,,"INSTITUTE OF INTERNATIONAL EDUCATION, INC.",19,3/25/2021 +24011,223087809,,"INTERNATIONAL RESEARCH & EXCHANGES BOARD, INC.",19,3/25/2021 +28540,236393344,C2TKJFL5CKP7,WORLD RELIEF CORPORATION OF NATIONAL ASSOCIATION OF EVANGELICALS,19,4/13/2023 +52434,366110299,L6R3PLXVANN9,AMERICAN BAR ASSOCIATION,19,11/29/2022 +73608,520914250,,NATIONAL CENTER FOR STATE COURTS,19,3/25/2021 +75070,521308986,PLKJMJKQVMM6,"ECDC ETHIOPIAN COMMUNITY DEVELOPMENT COUNCIL, INC",19,6/30/2023 +75207,521344831,,NATIONAL ENDOWMENT FOR DEMOCRACY,19,3/25/2021 +93650,621049974,,THE AMERICAN INSTITUTE IN TAIWAN,19,3/25/2021 +118431,941191246,,THE ASIA FOUNDATION,19,2/15/2022 +198499,521067256,CACNQQMMQ1S1,AMERICAN COUNCILS FOR INTERNATIONAL EDUCATION,19,1/27/2023 +209589,134080201,KNKPMDF9EUB8,"CHURCH WORLD SERVICE, INC",19,4/4/2023 +249317,522302253,K32ZM369MCL6,"MAG AMERICA, INC.",19,8/28/2023 +77849,530196932,,NATIONAL ACADEMY OF SCIENCES,20,3/25/2021 +87296,586002324,,ATLANTA REGIONAL COMMISSION,20,3/25/2021 +124865,952784997,,SAN DIEGO ASSOCIATION OF GOVERNMENTS,20,3/25/2021 +127348,954489711,,ACCESS SERVICES,20,3/25/2021 +129306,636001318,,"CITY OF MOBILE, ALABAMA",20,7/1/2022 +129836,920059987,,"MUNICIPALITY OF ANCHORAGE, ALASKA",20,10/3/2022 +129864,920038816,,"CITY AND BOROUGH OF JUNEAU, ALASKA",20,4/14/2022 +131972,946000338,,CITY OF FRESNO,20,3/25/2021 +132411,941492636,,ALAMEDA CONTRA COSTA TRANSIT DISTRICT,20,3/25/2021 +132422,941552685,,SAN FRANCISCO BAY AREA RAPID TRANSIT,20,3/25/2021 +132750,954351663,,SOUTHERN CALIFORNIA REGIONAL RAIL AUTHORITY/METROLINK,20,1/20/2022 +133082,941338218,,SACRAMENTO REGIONAL TRANSIT DISTRICT,20,12/26/2021 +133121,953035112,,OMNITRANS,20,2/3/2022 +133128,330478781,,SAN BERNARDINO COUNTY TRANSPORTATION AUTHORITY,20,3/25/2021 +133185,953041463,,SAN DIEGO METROPOLITAN TRANSIT SYSTEM,20,3/25/2021 +134855,846000573,,CITY OF COLORADO SPRINGS,20,3/25/2021 +136060,520847040,,WASHINGTON METROPOLITAN AREA TRANSIT AUTHORITY,20,3/25/2021 +136077,596000531,,BROWARD COUNTY FLORIDA,20,3/25/2021 +136110,596000702,,"LEE COUNTY, FLORIDA",20,6/30/2022 +136138,596000885,,COUNTY OF VOLUSIA,20,3/25/2021 +136672,591395059,,PINELLAS SUNCOAST TRANSIT AUTHORITY,20,4/14/2022 +136906,586000804,,COBB COUNTY GOVERNMENT,20,10/24/2022 +137490,580964286,,METROPOLITAN ATLANTA RAPID TRANSIT AUTHORITY,20,3/25/2021 +138010,826000165,HHBYKBT1RJQ5,CITY OF BOISE,20,3/30/2023 +142676,351065397,KLA2ZBMMWA64,INDIANAPOLIS AIRPORT AUTHORITY,20,4/17/2023 +143130,351522844,,NORTHERN INDIANA COMMUTER TRANSPORTATION DISTRICT,20,4/21/2022 +144054,426004336,NDSYD8WM9RL7,"CITY OF CEDAR RAPIDS, IOWA",20,2/16/2023 +146794,726000641,,CITY OF LAKE CHARLES,20,4/27/2022 +146818,726000137,,CITY OF BATON ROUGE - PARISH OF EAST BATON ROUGE,20,3/25/2021 +147017,720899720,,REGIONAL TRANSIT AUTHORITY,20,7/26/2022 +147709,010238864,,GREATER PORTLAND COUNCIL OF GOVERNMENTS,20,12/26/2021 +147916,046006429,,MASSACHUSETTS PORT AUTHORITY,20,2/11/2022 +148509,042323989,,MASSACHUSETTS BAY TRANSPORTATION AUTHORITY,20,3/25/2021 +151616,416007123,,MINNEAPOLIS ST. PAUL METROPOLITAN AIRPORTS COMMISSION,20,6/9/2022 +152842,436003242,,"ST. LOUIS COUNTY, MISSOURI",20,3/25/2021 +153631,436004283,,BI-STATE DEVELOPMENT AGENCY OF THE MO-IL METROPOLITAN DISTRICT,20,1/15/2022 +155930,886000028,,CLARK COUNTY DEPARTMENT OF AVIATION,20,2/15/2023 +156500,222281352,,NEW JERSEY TRANSIT CORPORATION,20,3/25/2021 +158015,856000581,,NEW MEXICO DEPARTMENT OF TRANSPORTATION,20,3/25/2021 +158028,856000565,,NM DEPARTMENT OF PUBLIC SAFETY,20,2/1/2022 +160020,136400654,,THE PORT AUTHORITY OF NEW YORK AND NEW JERSEY,20,3/25/2021 +161378,566000241,E8LMSJ41W817,CITY OF WINSTON-SALEM,20,12/31/2022 +161412,566000230,CGWAKNW9BAD5,CITY OF GREENSBORO,20,11/14/2022 +161476,521333483,,CITY OF CHARLOTTE,20,3/25/2021 +161632,566000236,,CITY OF RALEIGH,20,11/23/2021 +162056,456002186,,CITY OF WILLISTON,20,3/25/2021 +163887,341170830,,GREATER CLEVELAND REGIONAL TRANSIT AUTHORITY,20,7/28/2022 +163904,310800546,,CENTRAL OHIO TRANSIT AUTHORITY,20,8/22/2022 +163922,310834121,LM81VYPTNQL9,SOUTHWEST OHIO REGIONAL TRANSIT AUTHORITY,20,6/30/2023 +163998,237182735,,GREATER DAYTON REGIONAL TRANSIT AUTHORITY,20,8/2/2022 +165981,730978251,,COMANCHE NATION GOVERNMENTAL PROGRAMS DEPARTMENT,20,2/18/2022 +166437,930579353,,TRI-COUNTY METROPOLITAN TRANSPORTATION DISTRICT OF OREGON (TRIMET),20,3/25/2021 +170355,576000954,,SOUTH CAROLINA DEPARTMENT OF TRANSPORTATION,20,3/25/2021 +170419,576000365,,"HORRY COUNTY, SOUTH CAROLINA",20,1/27/2022 +171871,621262331,,MEMPHIS SHELBY COUNTY AIRPORT AUTHORITY,20,12/1/2021 +172025,746001969,,"FORT BEND COUNTY, TEXAS",20,3/25/2021 +172719,756000590,,"CITY OF LUBBOCK, TEXAS",20,4/8/2022 +172924,746000085,,CITY OF AUSTIN,20,3/25/2021 +173040,741330327,,VIA METROPOLITAN TRANSIT,20,3/23/2022 +173406,741998278,,"METROPOLITAN TRANSIT AUTHORITY OF HARRIS COUNTY, TEXAS",20,3/25/2021 +173789,752793063,,FORT WORTH TRANSPORTATION AUTHORITY,20,3/25/2021 +173810,742355740,,CAPITAL METROPOLITAN TRANSPORTATION AUTHORITY,20,3/25/2021 +175159,876000266,EYDMXDL7KP25,PROVO CITY CORPORATION,20,3/30/2023 +175221,870284459,,UTAH TRANSIT AUTHORITY,20,3/25/2021 +176058,540787833,,"COUNTY OF FAIRFAX, VIRGINIA",20,3/25/2021 +176274,546022059,,"CITY OF NEWPORT NEWS, VIRGINIA",20,12/14/2021 +176299,521513553,,METROPOLITAN WASHINGTON AIRPORTS AUTHORITY,20,3/25/2021 +176313,541486769,,POTOMAC AND RAPPAHANNOCK TRANSPORTATION COMMISSION,20,12/2/2021 +176395,916001370,,SPOKANE COUNTY,20,11/1/2022 +177016,911151502,,SPOKANE TRANSIT AUTHORITY,20,7/14/2022 +179767,836000263,,WYOMING DEPARTMENT OF TRANSPORTATION,20,3/25/2021 +180123,660433808,,PUERTO RICO HIGHWAYS AND TRANSPORTATION AUTHORITY,20,4/9/2022 +181547,596000573,,MIAMI-DADE AVIATION DEPARTMENT,20,6/9/2022 +181552,990258224,,HIGHWAYS DIVISION DEPARTMENT OF TRANSPORTATION STATE OF HAWAII,20,3/25/2021 +181885,650002789,,SOUTH FLORIDA REGIONAL TRANSPORTATION AUTHORITY,20,3/25/2021 +181946,990257540,,"DEPARTMENT OF TRANSPORTATION - AIRPORTS DIVISION, STATE OF HAWAII",20,4/9/2022 +182201,941746312,LWYHDXYRZCU8,PORT OF OAKLAND,20,4/3/2023 +182281,237119049,,ORANGE COUNTY TRANSPORTATION AUTHORITY,20,3/25/2021 +182440,616000300,,CINCINNATI/NORTHERN KENTUCKY INTERNATIONAL AIRPORT,20,7/21/2022 +182557,231642972,,SOUTHEASTERN PENNSYLVANIA TRANSPORTATION AUTHORITY,20,3/25/2021 +182598,363306845,,"PACE, THE SUBURBAN BUS DIVISION OF THE RTA",20,6/22/2022 +182654,592982959,,CENTRAL FLORIDA REGIONAL TRANSPORATION AUTHORITY D/B/A LYNX,20,4/12/2022 +182669,946000696,,"GOLDEN GATE BRIDGE, HIGHWAY & TRANSPORTATION DISTRICT",20,3/25/2021 +182813,941749911,,METROPOLITAN TRANSPORTATION COMMISSION,20,3/25/2021 +182830,880209587,,REGIONAL TRANSPORTATION COMMISSION,20,12/10/2021 +182941,876000279,,SALT LAKE CITY CORPORATION,20,1/13/2022 +182960,953009680,,NORTH COUNTY TRANSIT DISTRICT,20,12/27/2021 +183178,256011888,,PORT AUTHORITY OF ALLEGHENY COUNTY,20,3/25/2021 +183342,942186907,,SANTA CLARA VALLEY TRANSPORTATION AUTHORITY,20,3/25/2021 +183850,416008898,,METROPOLITAN COUNCIL OF THE TWIN CITIES AREA,20,3/25/2021 +183954,954401975,,LOS ANGELES COUNTY METROPOLITAN TRANSPORTATION AUTHORITY,20,3/25/2021 +183956,943152903,,PENINSULA CORRIDOR JOINT POWERS BOARD,20,3/25/2021 +184233,132624287,,NEW YORK STATE URBAN DEVELOPMENT CORPORATION AND SUBSIDIARIES,20,7/1/2021 +184309,132552035,,METROPOLITAN TRANSPORTATION AUTHORITY,20,3/25/2021 +185703,860557533,,VALLEY METRO REGIONAL PUBLIC TRANSPORTATION AUTHORITY,20,1/13/2022 +186063,620910914,MATA10910914,MEMPHIS AREA TRANSIT AUTHORITY,20,3/31/2023 +186616,362164842,,CHICAGO TRANSIT AUTHORITY,20,3/25/2021 +187806,916001327,,KING COUNTY,20,3/25/2021 +188391,746000749,,CITY OF EL PASO,20,3/25/2021 +189355,381876249,,SUBURBAN MOBILITY AUTHORITY FOR REGIONAL TRANSPORTATION,20,12/2/2021 +189554,840597392,,REGIONAL TRANSPORTATION DISTRICT,20,3/25/2021 +190125,541781521,,"NOBLIS, INC.",20,3/25/2021 +190624,956000735,,LOS ANGELES WORLD AIRPORTS,20,12/1/2021 +190643,596001253,HU5MVK2S6C73,HILLSBOROUGH COUNTY AVIATION AUTHORITY,20,4/6/2023 +190764,595916967,,GREATER ORLANDO AVIATION AUTHORITY,20,3/22/2022 +192087,956000672,,"CITY OF BAKERSFIELD, CALIFORNIA",20,3/25/2021 +192208,751279194,,DALLAS FORT WORTH INTERNATIONAL AIRPORT,20,3/25/2021 +192501,596000848,K4SXBE88XP88,"SARASOTA COUNTY, FLORIDA",20,4/6/2023 +192534,746001650,,"CITY OF MCALLEN, TEXAS",20,5/4/2022 +192666,166008839,,NIAGARA FRONTIER TRANSPORTATION AUTHORITY,20,9/2/2021 +194481,540948306,,"COUNTY OF LOUDOUN, VIRGINIA",20,3/25/2021 +195543,916001025,,PORT OF SEATTLE,20,5/18/2022 +195595,430992477,DADDJ2EKZ5L8,KANSAS CITY AREA TRANSPORTATION AUTHORITY,20,5/23/2023 +195945,351324569,,INDIANAPOLIS PUBLIC TRANSPORTATION CORPORATION,20,3/25/2021 +196069,954668218,,FOOTHILL TRANSIT AUTHORITY,20,1/20/2022 +196758,160967419,,ROCHESTER-GENESSEE REGIONAL TRANSPORTATION AUTHORITY,20,7/26/2021 +197411,911628275,,CENTRAL PUGET SOUND REGIONAL TRANSIT AUTHORITY,20,3/25/2021 +198163,942325976,,SAN MATEO COUNTY TRANSIT DISTRICT,20,11/12/2021 +198757,596018367,,JACKSONVILLE TRANSPORTATION AUTHORITY,20,5/2/2022 +199044,363126147,,METRA - COMMUTER RAIL DIVISION & COMMUTER RAILROAD CORPORATION,20,3/25/2021 +199947,146003955,,NEW YORK STATE THRUWAY AUTHORITY,20,3/25/2021 +201778,751813169,,DALLAS AREA RAPID TRANSIT,20,3/25/2021 +202168,330072823,,RIVERSIDE COUNTY TRANSPORTATION COMMISSION,20,3/25/2021 +215840,753019713,,SAN DIEGO COUNTY REGIONAL AIRPORT AUTHORITY,20,11/19/2021 +215905,562296529,,"VALLEY METRO RAIL, INC.",20,3/25/2021 +217393,596000573,,MIAMI-DADE TRANSIT ENTERPRISE FUND,20,3/25/2021 +224731,591021557,,CENTRAL FLORIDA EXPRESSWAY AUTHORITY FKA ORLANDO-ORANGE CO EXPRESSWAY,20,3/25/2021 +225260,352198574,,CENTRAL TEXAS REGIONAL MOBILITY AUTHORITY,20,3/25/2021 +229340,900036752,,REGIONAL TRANSPORTATION COMMISSION OF SOUTHERN NEVADA,20,3/25/2021 +229893,351602316,,INDIANA FINANCE AUTHORITY,20,3/25/2021 +231133,520910053,,NATIONAL RAILROAD PASSENGER CORPORATION & SUBSIDIARIES (AMTRAK),20,3/25/2021 +232693,946000417,,SAN FRANCISCO INTERNATIONAL AIRPORT,20,9/16/2022 +233179,941160893,,SAN FRANCISCO MUNICIPAL TRANSPORTATION AGENCY,20,3/25/2021 +244060,452593877,V45ADERKJAY8,CONNECTICUT AIRPORT AUTHORITY,20,11/15/2022 +247806,141515330,,CAPITAL DISTRICT TRANSPORTATION AUTHORITY,20,12/21/2021 +248514,920020624,,ALASKA RAILROAD CORPORATION,20,9/20/2022 +249248,746001573,,"CITY OF LAREDO, TEXAS",20,4/24/2022 +249484,820579904,,WAYNE COUNTY AIRPORT AUTHORITY,20,5/20/2022 +250092,520001005,T768HKZ8PLC2,MARYLAND TRANSPORTATION AUTHORITY,20,3/31/2023 +255982,471742163,NEUHFP6RELY3,HAMPTON ROADS TRANSPORTATION ACCOUNTABILITY COMMISSION,20,11/27/2022 +258028,736060890,GTLHGBUGLWF3,OKLAHOMA TURNPIKE AUTHORITY,20,4/27/2023 +12660,133030229,,LOCAL INITIATIVES SUPPORT CORPORATION,21,3/25/2021 +22772,043246555,,"BLUEHUB CAPITAL, INC. AND AFFILIATES",21,3/25/2021 +58055,381458751,EUSURN9MJR17,MICHIGAN HEALTH & HOSPITAL ASSOCIATION AND CONTROLLED ENTITIES,21,11/29/2022 +74286,521148078,,NEIGHBORHOOD REINVESTMENT CORPORATION (DBA NEIGHBORWORKS AMERICA),21,3/25/2021 +74643,521231931,,"ENTERPRISE COMMUNITY PARTNERS, INC.",21,3/25/2021 +74976,521290127,,CAPITAL IMPACT PARTNERS AND SUBSIDIARIES,21,3/25/2021 +82593,561552375,,"RURAL ECONOMIC DEVELOPMENT CENTER, INC.",21,2/11/2022 +82670,561620516,,"DREAMKEY PARTNERS, INC.",21,6/13/2022 +180945,946000501,Z2JTEQA62JV7,ALAMEDA COUNTY HOUSING AND COMMUNITY DEVELOPMENT DEPARTMENT,21,3/31/2023 +198070,956000736,YVCBUGKDEUF7,DEPARTMENT OF WATER AND POWER OF THE CITY OF LOS ANGELES POWER SYSTEM,21,3/23/2023 +200809,942952578,,LOW INCOME INVESTMENT FUND,21,3/25/2021 +215664,366006551,W7KRN7E54898,"DUPAGE COUNTY, ILLINOIS",21,8/31/2023 +216160,363656836,,IFF,21,5/19/2022 +219042,596002482,K21GR3WGNYT4,CANAVERAL PORT AUTHORITY,21,4/13/2023 +221087,630338569,XWCKHU8NAGJ8,ALABAMA HOSPITAL ASSOCIATION,21,12/8/2022 +227824,920130785,CCGWKQWMJSB6,"VALLEY CHARITIES, INC.",21,4/12/2023 +228066,521954196,,"RAZA DEVELOPMENT FUND, INC.",21,3/25/2021 +230310,205189202,,OPPORTUNITY FINANCE NETWORK,21,3/25/2021 +231775,520591543,VDMQJRLN4LQ3,UNITED WAY OF CENTRAL MARYLAND INC,21,4/5/2023 +233817,232331946,,"REINVESTMENT FUND, INC. AND AFFILIATES",21,3/25/2021 +236094,910313383,,COUNCIL FOR NATIVE HAWAIIAN ADVANCEMENT,21,7/6/2022 +237356,136532871,,"NATIONAL COUNCIL FOR COMMUNITY DEVELOPMENT, INC.",21,10/10/2022 +240587,383315978,G1QCQA9QRJK5,HOMELESS ACTION NETWORK OF DETROIT,21,3/24/2023 +242068,411616861,,"COMMUNITY REINVESTMENT FUND, INC. AND SUBSIDIARIES",21,3/25/2021 +250671,926001185,,ALASKA INDUSTRIAL DEVELOPMENT AND EXPORT AUTHORITY,21,12/27/2021 +251563,825258187,DPDDX2J3K735,LIFT TO RISE,21,6/8/2023 +251723,237181456,WRCPSYBRB5N6,CENTRO LEGAL DE LA RAZA,21,1/5/2023 +251777,461549875,EL6UD5XNAHK6,ALLIANCE FOR HOUSING OAKLAND COUNTY CONTINUUM OF CARE,21,2/16/2023 +252205,541760384,,"COMMUNITY BUSINESS PARTNERSHIP, INC.",21,2/7/2022 +253206,232920364,,PENNSYLVANIA CDFI NETWORK,21,5/16/2022 +253952,731396320,,COMMUNITIES FOUNDATION OF OKLAHOMA,21,7/19/2022 +254037,920042304,,"COOK INLET REGION, INC.",21,7/27/2022 +254327,330047994,TWL3WEV59TH4,ORANGE COUNTY'S UNITED WAY,21,2/1/2023 +254787,562379862,,BRILLIANT CORNERS,21,9/28/2022 +256678,871237752,GM5HSAC69XE5,CALHFA HOMEOWNER RELIEF CORPORATION,21,4/20/2023 +257083,920044137,,ARCTIC SLOPE REGIONAL CORPORATION,21,2/23/2023 +258151,222593378,P49TYES2Z113,"BLUEHUB CAPITAL, INC. AND AFFILIATES",21,5/17/2023 +101078,741070544,,SOUTHWEST RESEARCH INSTITUTE,43,3/25/2021 +111596,860138043,,"ASSOCIATION OF UNIVERSITIES FOR RESEARCH IN ASTRONOMY, INC.",43,3/25/2021 +193965,951643307,,JET PROPULSION LABORATORY,43,3/25/2021 +203062,530206027,,SMITHSONIAN INSTITUTION,43,3/25/2021 +225398,520892064,,UNIVERSITIES SPACE RESEARCH ASSOCIATION,43,3/25/2021 +2550,042105850,,WOODS HOLE OCEANOGRAPHIC INSTITUTION,47,3/25/2021 +8489,111630900,,"ASSOCIATED UNIVERSITIES, INC.",47,3/25/2021 +20066,210634501,,PRINCETON UNIVERSITY,47,3/25/2021 +101106,741109620,,WILLIAM MARSH RICE UNIVERSITY,47,3/25/2021 +101351,741238434,,TEXAS A&M RESEARCH FOUNDATION,47,3/25/2021 +109269,840412668,,UNIVERSITY CORPORATION FOR ATMOSPHERIC RESEARCH,47,3/25/2021 +123473,951643307,,CALIFORNIA INSTITUTE OF TECHNOLOGY,47,3/25/2021 +64934,421114803,,BLACK HAWK ECONOMIC DEVELOPMENT INC,59,3/25/2021 +85763,581496388,,"SMALL BUSINESS ACCESS PARTNERS, INC.",59,3/25/2021 +109754,840732616,,PIKES PEAK REGIONAL DEVELOPMENT CORPORATION,59,3/25/2021 +120189,942548556,,MAIN STREET LAUNCH,59,4/28/2022 +183597,742712770,ZW2LP2T9CKF2,LIFTFUND INC.,59,6/21/2023 +192718,421150060,,SIOUXLAND ECONOMIC DEVELOPMENT CORPORATION,59,3/25/2021 +208943,363966573,,ALLIES FOR COMMUNITY BUSINESS,59,6/2/2022 +220957,222309023,,REGIONAL BUSINESS ASSISTANCE CORPORATION,59,3/25/2021 +240047,356000158,,INDIANA ECONOMIC DEVELOPMENT CORPORATION,59,2/25/2022 +245753,341897225,,REGIONAL BUSINESS ASSISTANCE CORPORATION,59,3/1/2022 +258066,860108839,FNLTCQ8TL4L4,THE PHOENIX THEATRE COMPANY,59,5/4/2023 +258477,560946437,RDPDXKHU5BC8,RICHLAND-LEXINGTON RIVERBANKS PARK DISTRICT,59,7/6/2023 +106316,756000450,,CITY OF ARLINGTON,66,7/2/2022 +146075,610858140,K4A4SNZG2YY9,LEXINGTON-FAYETTE URBAN COUNTY GOVERNMENT,66,2/14/2023 +147138,016000032,,CITY OF PORTLAND MAINE,66,7/6/2022 +153630,436011991,,THE METROPOLITAN ST. LOUIS SEWER DISTRICT,66,11/3/2021 +161932,456002069,,CITY OF FARGO,66,7/12/2022 +163886,341128332,,NORTHEAST OHIO REGIONAL SEWER DISTRICT,66,3/25/2021 +166236,936002183,KGJ9RMCYNNM3,CITY OF HILLSBORO,66,12/27/2022 +171671,626000361,,CITY OF MEMPHIS,66,4/6/2022 +172278,746028909,,"CITY OF PEARLAND, TEXAS",66,4/10/2022 +181549,581667498,,GEORGIA ENVIRONMENTAL FINANCE AUTHORITY,66,3/25/2021 +183049,526002033,,MARYLAND WATER QUALITY FINANCING ADMINISTRATION,66,10/31/2021 +185273,541288444,,VIRGINIA RESOURCE AUTHORITY,66,3/25/2021 +185545,141499804,,NEW YORK STATE ENVIRONMENTAL FACILITIES CORPORATION,66,3/25/2021 +187010,946000410,,CITY OF SACRAMENTO,66,4/9/2022 +187744,346000020,,"CITY OF AKRON, OHIO",66,2/9/2023 +188341,956000736,YVCBUGKDEUF7,DEPARTMENT OF WATER AND POWER OF THE CITY OF LOS ANGELES WATER SYSTEM,66,3/23/2023 +190083,526002653,,WASHINGSTON SUBURBAN SANITARY COMMISSION,66,12/27/2021 +196487,043119080,,MASSACHUSETTS CLEAN WATER TRUST,66,3/25/2021 +197066,416007162,,MINNESOTA PUBLIC FACILITIES AUTHORITY,66,3/25/2021 +197945,850432350,,NEW MEXICO FINANCE AUTHORITY,66,2/23/2022 +201102,626000290,,"CITY OF FRANKLIN, TENNESSEE",66,1/7/2022 +204306,956002277,PBDCJHMN11V5,ORANGE COUNTY WATER DISTRICT,66,11/16/2022 +204521,952303211,,H-DESERT WATER DISTRICT,66,3/25/2021 +230430,066000532,,THE METROPOLITAN DISTRICT,66,3/25/2021 +243901,680281986,TGFTZM2DN5Z2,CA STATE WATER RESOURCES CONTROL BOARD WATER POLLUTION REVOLVING FUND,66,2/15/2023 +245385,680281986,,CA ST WATER RESOURCES CONTROL BD SAFE DRINKING WATER ST REVOLVING FUND,66,3/25/2021 +253438,526002033,S734YDNLSHW5,MARYLAND WATER INFRASTRUCTURE FINANCING ADMINISTRATION,66,11/29/2022 +253614,756004258,GWLYHAYMS3K4,NORTH TEXAS MUNICIPAL WATER DISTRICT,66,2/24/2023 +257559,521566252,LJ2NNMA3JZZ7,NEW JERSEY INFRASTRUCTURE BANK,66,3/30/2023 +92977,620476816,,"OAK RIDGE ASSOCIATED UNIVERSITIES, INCORPORATED",81,3/25/2021 +166777,930624734,,CONFEDERATED TRIBES OF THE UMATILLA INDIAN RESERVATION,81,9/28/2022 +127,010211810,,UNIVERSITY OF NEW ENGLAND,84,3/25/2021 +1062,020368776,,NEW HAMPSHIRE HIGHER EDUCATION LOAN CORPORATION,84,3/25/2021 +1231,026007687,,NEW HAMPSHIRE HIGHER EDUCATION ASSISTANCE FOUNDATION,84,3/25/2021 +1354,030179414,N3UTNKGNEZK9,BENNINGTON COLLEGE AND SUBSIDIARY,84,12/21/2022 +1366,030179440,,UNIVERSITY OF VERMONT AND STATE AGRICULTURAL COLLEGE,84,3/25/2021 +1815,041286950,,EMERSON COLLEGE,84,11/9/2021 +1942,041679980,,NORTHEASTERN UNIVERSITY,84,3/25/2021 +2130,042103545,,TRUSTEES OF BOSTON COLLEGE,84,3/25/2021 +2131,042103547,,BOSTON UNIVERSITY,84,3/25/2021 +2135,042103552,,BRANDEIS UNIVERSITY,84,3/25/2021 +2187,042103629,,SIMMONS UNIVERSITY,84,3/25/2021 +2190,042103634,,TUFTS UNIVERSITY,84,3/25/2021 +2256,042103865,,BAY PATH UNIVERSITY,84,3/25/2021 +2385,042104329,,SPRINGFIELD COLLEGE,84,3/25/2021 +2443,042104700,,MCPHS UNIVERSITY,84,3/25/2021 +2619,042108376,,WESTERN NEW ENGLAND UNIVERSITY,84,3/25/2021 +2667,042121659,,WORCESTER POLYTECHNIC INSTITUTE,84,3/25/2021 +2723,042133255,,SUFFOLK UNIVERSITY,84,3/25/2021 +3013,042300472,,"BERKLEE COLLEGE OF MUSIC, INC.",84,3/25/2021 +5982,050277222,,ROGER WILLIAMS UNIVERSITY,84,3/25/2021 +6016,050306206,,JOHNSON & WALES UNIVERSITY,84,3/25/2021 +6652,060646701,,QUINNIPIAC UNIVERSITY,84,3/25/2021 +6713,060646936,,UNIVERSITY OF BRIDGEPORT,84,3/25/2021 +6983,060731360,,UNIVERSITY OF HARTFORD,84,3/25/2021 +7022,060761704,,UNIVERSITY OF NEW HAVEN,84,3/25/2021 +7055,060776644,,"SACRED HEART UNIVERSITY, INC.",84,3/25/2021 +8469,111630741,,ADELPHI UNIVERSITY,84,3/25/2021 +8486,111630822,,PRATT INSTITUTE,84,3/25/2021 +8488,111630830,,ST. JOHN'S UNIVERSITY,84,3/25/2021 +8490,111630906,,HOFSTRA UNIVERSITY,84,3/25/2021 +8518,111633516,,LONG ISLAND UNIVERSITY,84,3/25/2021 +8650,111788788,,NEW YORK INSTITUTE OF TECHNOLOGY,84,3/25/2021 +8656,111797182,,MOLLOY COLLEGE,84,3/25/2021 +8813,262415339,,SUFFOLK COUNTY COMMUNITY COLLEGE,84,4/20/2022 +10394,131099420,,NEW YORK MEDICAL COLLEGE,84,3/25/2021 +10689,131624202,,"TEACHERS COLLEGE, COLUMBIA UNIVERSITY",84,3/25/2021 +10698,131624225,,YESHIVA UNIVERSITY,84,3/25/2021 +11508,132501829,,"AMDA, INC. AND SUBSIDIARY (THE AMERICAN MUSICAL AND DRAMATIC ACADEMY)",84,3/25/2021 +11852,132676570,,TOURO COLLEGE,84,3/25/2021 +13468,133297197,,THE NEW SCHOOL,84,3/25/2021 +15175,135562308,,NEW YORK UNIVERSITY,84,3/25/2021 +15177,135562314,,PACE UNIVERSITY,84,3/25/2021 +15234,135564934,,BETH ISRAEL MEDICAL CENTER AND AFFILIATES,84,10/10/2022 +15535,132556338,,FASHION INSTITUTE OF TECHNOLOGY,84,2/18/2022 +17157,141340095,,RENSSELAER POLYTECHNIC INSTITUTE,84,3/25/2021 +17331,141442493,,MARIST COLLEGE AND AFFILIATES,84,3/25/2021 +18063,150532081,,SYRACUSE UNIVERSITY,84,3/25/2021 +18082,150532204,,ITHACA COLLEGE,84,3/25/2021 +18167,150543659,,CLARKSON UNIVERSITY,84,3/25/2021 +18543,160743140,,ROCHESTER INSTITUTE OF TECHNOLOGY,84,3/25/2021 +18626,160746864,,ST. JOHN FISHER COLLEGE AND SUBSIDIARIES,84,3/25/2021 +18798,160849590,,MONROE COMMUNITY COLLEGE,84,3/25/2021 +19715,161476258,,UTICA COLLEGE,84,3/25/2021 +20056,210634479,,EDUCATIONAL TESTING SERVICE,84,1/4/2022 +20084,210634584,,MONMOUTH UNIVERSITY,84,3/25/2021 +20145,210650678,,RIDER UNIVERSITY,84,8/4/2022 +20541,221487322,,"HOLY NAME MEDICAL CENTER, INC. & SUBSIDIARIES",84,10/10/2022 +20545,221487354,,STEVENS INSTITUTE OF TECHNOLOGY,84,3/25/2021 +20627,221500645,,SETON HALL UNIVERSITY,84,3/25/2021 +23549,222872262,,"COMMUNITY LOAN FUND OF NEW JERSEY, INC.",84,6/2/2022 +25413,231352204,,TOWER HEALTH,84,9/27/2022 +25515,231352630,,DREXEL UNIVERSITY,84,3/25/2021 +25526,231352651,,THOMAS JEFFERSON UNIVERSITY,84,3/25/2021 +25528,231352654,,LA SALLE UNIVERSITY,84,3/25/2021 +25529,231352655,T59JHM69NCZ3,LINCOLN UNIVERSITY OF THE COMMONWEALTH SYSTEM OF HIGHER EDUCATION,84,3/16/2023 +25540,231352674,,SAINT JOSEPH'S UNIVERSITY,84,3/25/2021 +25547,231352688,,VILLANOVA UNIVERSITY,84,3/25/2021 +25567,231355135,,PHILADELPHIA COLLEGE OF OSTEOPATIC MEDICINE FOUNDATION AND RELATED ENT,84,3/25/2021 +25638,231365971,,TEMPLE UNIVERSITY,84,3/25/2021 +25705,231386178,,WIDENER UNIVERSITY AND AFFILIATE,84,3/25/2021 +25774,231413680,,SALUS UNIVERSITY AND ITS FOUNDATION,84,3/25/2021 +27760,232564508,,PENNSYLVANIA COLLEGE OF TECHNOLOGY,84,3/25/2021 +29125,237025386,,PANHANDLE-PLAINS HIGHER EDUCATION AUTHORITY,84,3/25/2021 +31045,237133739,,"NORTH TEXAS HIGHER EDUCATION AUTHORITY, INC",84,3/25/2021 +31596,237172306,,NATIONAL UNIVERSITY,84,3/25/2021 +33196,340896630,,CUYAHOGA COMMUNITY COLLEGE,84,3/25/2021 +35195,240795445,,LEHIGH UNIVERISTY,84,3/25/2021 +35206,240795495,,THE UNIVERSITY OF SCRANTON,84,3/25/2021 +35212,240795506,U8BCVKFYFJZ2,WILKES UNIVERSITY,84,1/19/2023 +35469,250496976,,GANNON UNIVERSITY,84,7/14/2022 +35683,250965219,,AMERICAN INSTITUTES FOR RESEARCH IN THE BEHAVIORAL SCIENCES,84,3/25/2021 +35732,250965430,,MERCYHURST UNIVERSITY,84,6/9/2022 +35942,251035663,,DUQUESNE UNIVERSITY OF THE HOLY SPIRIT,84,3/25/2021 +37223,251698677,,LAKE ERIE COLLEGE OF OSTEOPATHIC MEDICINE,84,3/25/2021 +37834,310536640,,ANTIOCH UNIVERSITY,84,3/25/2021 +37861,310536715,,UNIVERSITY OF DAYTON,84,3/25/2021 +37918,310537516,,XAVIER UNIVERSITY,84,3/25/2021 +38025,310621866,,KETTERING HEALTH NETWORK,84,10/10/2022 +38105,310707369,,FRANKLIN UNIVERSITY AND SUBSIDIARY,84,11/23/2021 +42354,340714626,,ASHLAND UNIVERSITY,84,3/2/2022 +44389,344429091,,OHIO NORTHERN UNIVERSITY,84,3/25/2021 +45426,350845258,,"INDIANA INSTITUTE OF TECHNOLOGY, INC.",84,3/25/2021 +45463,350867977,,BUTLER UNIVERSITY,84,3/25/2021 +45486,350868107,,UNIVERSITY OF INDIANAPOLIS,84,3/25/2021 +45506,350868175,,MARIAN UNIVERSITY,84,3/25/2021 +45510,350868188,,UNIVERSITY OF NOTRE DAME DU LAC,84,3/25/2021 +45608,350885591,,INDIANA WESLEYAN UNIVERSITY,84,3/25/2021 +47703,366112087,,COLUMBIA COLLEGE CHICAGO,84,3/25/2021 +47882,361408475,,LOYOLA UNIVERSITY OF CHICAGO,84,3/25/2021 +48272,362166964,,AURORA UNIVERSITY,84,3/25/2021 +48297,362167048,,DEPAUL UNIVERSITY,84,3/25/2021 +48313,362167725,R3T3SZHLYJY7,THE ART INSTITUTE OF CHICAGO,84,11/17/2022 +48335,362167773,,LEWIS UNIVERSITY,84,3/25/2021 +48426,362170136,,ILLINOIS INSTITUTE OF TECHNOLOGY,84,3/25/2021 +48517,362181973,,ROSALIND FRANKLIN UNIVERSITY OF MEDICINE AND SCIENCE AND AFFILIATE,84,3/25/2021 +49357,362722198,,BENEDICTINE UNIVERSITY AND AFFILIATES,84,3/25/2021 +49782,363005527,,THE CHICAGO SCHOOL-CAL INC DBA THE CHICAGO SCH OF PRO PSYCHOLOGY,84,3/25/2021 +50642,363377698,,MIDWESTERN UNIVERSITY,84,3/25/2021 +50976,363514573,,"NEBRASKA STUDENT LOAN PROGRAM, INC. DBA NATIONAL STUDENT LOAN PROGRAM",84,3/25/2021 +53254,370661494,,BRADLEY UNIVERSITY,84,3/25/2021 +54157,376005961,,SOUTHERN ILLINOIS UNIVERSITY,84,3/25/2021 +54309,380321740,,BAKER COLLEGE AND SUBSIDIARIES,84,3/25/2021 +54829,381359083,,ALMA COLLEGE,84,7/26/2022 +54901,381360586,,UNIVERSITY OF DETROIT MERCY,84,3/25/2021 +55659,381945965,,DAVENPORT UNIVERSITY,84,3/25/2021 +55736,381988915,,THOMAS M. COOLEY LAW SCHOOL DBA WESTERN MICHIGAN UNIVERSITY THOMAS M C,84,3/25/2021 +58639,390806251,,MARQUETTE UNIVERSITY,84,3/25/2021 +58834,390833608,,"CONCORDIA UNIVERSITY, INC. AND AFFILIATE",84,3/25/2021 +59643,396003459,,MILWAUKEE AREA TECHNICAL COLLEGE,84,3/25/2021 +61396,410693970,,UNIVERSITY OF ST. THOMAS,84,3/25/2021 +61412,410695509,,ST. CATHERINE UNIVERSITY,84,3/25/2021 +61414,410695527,,SAINT MARY'S UNIVERSITY OF MINNESOTA,84,3/25/2021 +64966,421137531,,IOWA STUDENT LOAN LIQUIDITY CORPORATION,84,3/25/2021 +65449,421435199,,IOWA HEALTH SYSTEM AND SUBSIDIARIES DBA UNITYPOINT HEALTH,84,10/10/2022 +65539,426081293,,PALMER COLLEGE FOUNDATION AND PALMER COLLEGE OF CHIROPRACTIC - WEST,84,3/25/2021 +65687,430356250,,A.T. STILL UNIVERSITY OF HEALTH SCIENCES,84,3/25/2021 +65809,430652649,,LINDENWOOD UNIVERSITY,84,3/25/2021 +65841,430653369,,MARYVILLE UNIVERSITY,84,3/25/2021 +65863,430654872,,SAINT LOUIS UNIVERSITY,84,3/25/2021 +65867,430655867,,COLUMBIA COLLEGE,84,3/25/2021 +65887,430662529,,WEBSTER UNIVERSITY,84,3/25/2021 +65989,430746185,,"LOGAN UNIVERSITY, INC.",84,3/25/2021 +67983,440545280,,KANSAS CITY UNIVERSITY OF MEDICINE AND BIOSCIENCES,84,3/25/2021 +68053,440562048,,PARK UNIVERSITY,84,3/25/2021 +68080,471087427,,COXHEALTH,84,6/23/2022 +69267,470376583,,CREIGHTON UNIVERSITY,84,3/25/2021 +69276,470639839,,NEBRASKA METHODIST HEALTH SYSTEM,84,8/24/2022 +69462,470491571,,BELLEVUE UNIVERSITY,84,3/25/2021 +71253,510107088,,"WILMINGTON UNIVERSITY, INC",84,3/25/2021 +72950,520591623,,"LOYOLA UNIVERSITY MARYLAND, INC",84,3/25/2021 +74735,521245746,,"RIVERSIDE HEALTHCARE ASSOCIATION, INC.",84,10/6/2022 +77809,530196549,,AMERICAN UNIVERSITY,84,3/25/2021 +77823,530196583,,THE CATHOLIC UNIVERSITY OF AMERICA AND SUBSIDIARIES,84,3/25/2021 +77824,530196584,,THE GEORGE WASHINGTON UNIVERSITY,84,3/25/2021 +77831,530196603,,GEORGETOWN UNIVERSITY,84,3/25/2021 +77883,530204707,,THE HOWARD UNIVERSITY,84,3/25/2021 +78383,540505990,,HAMPTON UNIVERSITY AND SUBSIDIARIES,84,3/25/2021 +78442,540525605,,SHENANDOAH UNIVERSITY,84,3/25/2021 +78689,540715569,,"CENTRA HEALTH, INC.",84,10/5/2022 +79002,540946734,,"LIBERTY UNIVERSITY, INC.",84,3/25/2021 +79142,541061178,,REGENT UNIVERSITY AND AFFILIATED ORGANIZATIONS,84,3/25/2021 +79772,521271901,,SENTARA HEALTHCARE AND SUBSIDIARIES,84,10/5/2022 +80286,546055378,,EASTERN VIRGINIA MEDICAL SCHOOL,84,3/25/2021 +81316,560529940,,CAMPBELL UNIVERSITY INCORPORATED,84,3/25/2021 +81341,560529990,,PFEIFFER UNIVERSITY,84,1/28/2022 +81346,560529999,,HIGH POINT UNIVERSITY,84,3/25/2021 +81375,560532303,,ELON UNIVERSITY,84,3/25/2021 +83091,566046937,,"COLLEGE FOUNDATION, INC.",84,3/25/2021 +83404,570314402,ZE7BFE5NNDC3,LIMESTONE UNIVERSITY,84,5/9/2023 +84632,580566143,H13ZRHGUJFJ7,BRENAU UNIVERSITY,84,3/31/2023 +84650,580566205,,MOREHOUSE COLLEGE,84,3/25/2021 +84663,580566243,,SPELMAN COLLEGE,84,3/25/2021 +85388,581357177,,"THE SAVANNAH COLLEGE OF ART AND DESIGN, INC.",84,3/25/2021 +85608,581438873,,"MOREHOUSE SCHOOL OF MEDICINE, INC AND AFFILIATE",84,3/25/2021 +86359,581757969,,"EDUCATIONAL FUNDING OF THE SOUTH, INC. AND SUBSIDIARIES",84,3/25/2021 +86512,581825259,,CLARK ATLANTA UNIVERSITY,84,3/25/2021 +87864,590624364,,BARRY UNIVERSITY,84,3/25/2021 +87886,590624416,,"STETSON UNIVERSITY, INC.",84,3/25/2021 +87899,590624458,,UNIVERSITY OF MIAMI,84,3/25/2021 +87900,590624459,,"THE UNIVERSITY OF TAMPA, INC.",84,3/25/2021 +88048,590704726,,"BETHUNE-COOKMAN UNIVERSITY, INC.",84,3/25/2021 +88084,590722789,,"SOUTHEASTERN UNIVERSITY, INC. AND SUBSIDIARY",84,3/25/2021 +88388,590949880,,"ST. THOMAS UNIVERSITY, INC.",84,3/25/2021 +88532,591083502,,"NOVA SOUTHEASTERN UNIVERSITY, INC.",84,3/25/2021 +88747,591237047,,"SAINT LEO UNIVERSITY, INC",84,3/25/2021 +91200,596046500,,"FLORIDA INSTITUTE OF TECHNOLOGY, INC.",84,3/25/2021 +91651,610444650,,BEREA COLLEGE,84,3/25/2021 +91678,610444788,,UNIVERSITY OF PIKEVILLE,84,3/25/2021 +91751,610470593,,"UNIVERSITY OF THE CUMBERLANDS, INC.",84,3/25/2021 +92927,620465076,,BELMONT UNIVERSITY,84,3/25/2021 +92979,620476822,,VANDERBILT UNIVERSITY,84,3/25/2021 +92997,620479542,,LINCOLN MEMORIAL UNIVERSITY,84,10/31/2022 +93013,620484185,WLC1GBMGN9N7,TUSCULUM UNIVERSITY,84,4/5/2023 +93016,620485733,,LIPSCOMB UNIVERSITY,84,3/25/2021 +94539,630288878,,TUSKEGEE UNIVERSITY,84,3/25/2021 +94582,630312914,,SAMFORD UNIVERSITY,84,3/25/2021 +95676,610303069,,BELHAVEN UNIVERSITY,84,11/23/2021 +95684,640303086,,MISSISSIPPI COLLEGE,84,3/25/2021 +95734,640329300,,WILLIAM CAREY UNIVERSITY,84,3/25/2021 +96882,660177776,,"UNIVERSIDAD INTERAMERICANA DE PUERTO RICO, INC.",84,3/25/2021 +96890,660201206,,SISTEMA UNIVERSITARIO ANA G MENDEZ,84,3/25/2021 +97663,710236856,,BAPTIST HEALTH,84,10/3/2022 +98449,720408946,,LOYOLA UNIVERSITY NEW ORLEANS,84,3/25/2021 +98491,720423889,,THE ADMINISTRATORS OF THE TULANE EDUCATIONAL FUND,84,3/25/2021 +98680,720635884,,XAVIER UNIVERSITY OF LOUISIANA,84,3/25/2021 +99762,730579298,,THE UNIVERSITY OF TULSA,84,4/12/2022 +99942,730739626,,ORAL ROBERTS UNIVERSITY,84,3/25/2021 +101129,741109661,,UNIVERSITY OF THE INCARNATE WORD,84,3/25/2021 +101232,741159753,,BAYLOR UNIVERSITY,84,3/25/2021 +102318,742094204,,"TEXAS GUARANTEED STUDENT LOAN CORPORATION, DBA TRELLIS COMPANY",84,3/25/2021 +102323,742099402,,"BRAZOS HIGHER EDUCATION AUTHORITY, INC.",84,3/25/2021 +104198,750800689,,SOUTHERN METHODIST UNIVERSITY,84,3/25/2021 +104248,750827465,,TEXAS CHRISTIAN UNIVERSITY,84,3/25/2021 +104277,750851900,,ABILENE CHRISTIAN UNIVERSITY,84,8/24/2021 +105010,751596658,,PARKER UNIVERSITY,84,3/25/2021 +109213,840402707,,REGIS UNIVERSITY,84,3/25/2021 +109221,840404231,,UNIVERSITY OF DENVER (COLORADO SEMINARY),84,3/25/2021 +109321,840442429,,COLORADO CHRISTIAN UNIVERSITY AND CCU ENDOWMENT FOUNDATION,84,3/25/2021 +111084,850291313,,NEW MEXICO EDUCATIONAL ASSISTANCE FOUNDATION,84,5/2/2022 +111571,472507725,,GRAND CANYON UNIVERSITY,84,3/25/2021 +113416,910236600,,THE CORPORATION OF GONZAGA UNIVERSITY,84,3/25/2021 +113680,910565006,,SEATTLE UNIVERSITY,84,3/25/2021 +116380,930386858,,LEWIS & CLARK COLLEGE,84,3/25/2021 +116391,930386892,,PACIFIC UNIVERSITY,84,3/25/2021 +116417,930391563,,CONCORDIA UNIVERSITY,84,3/25/2021 +117072,930575187,,PORTLAND COMMUNITY COLLEGE,84,3/25/2021 +118294,941156266,,UNIVERSITY OF THE PACIFIC,84,3/25/2021 +118364,941156617,,SANTA CLARA UNIVERSITY,84,3/25/2021 +118369,941156628,,UNIVERSITY OF SAN FRANCISCO,84,3/25/2021 +120730,942788907,,SUTTER HEALTH AND AFFILIATES,84,3/25/2021 +123150,950948050,K91FAN8WHBM9,SOUTHERN CALIFORNIA UNIVERSITY OF HEALTH SCIENCES,84,2/20/2023 +123265,951246140,EVHMJGKEXNH8,SOUTHWESTERN LAW SCHOOL,84,12/21/2022 +123466,951642394,,UNIVERSITY OF SOUTHERN CALIFORNIA,84,3/25/2021 +123503,951643389,,UNIVERSITY OF REDLANDS,84,3/25/2021 +123517,951643992,,CHAPMAN UNIVERSITY AND AFFILIATES,84,3/25/2021 +123525,951644026,,UNIVERSITY OF LA VERNE,84,3/25/2021 +123802,951744369,,AZUSA PACIFIC UNIVERSITY AND SUBSIDIARY,84,3/25/2021 +123893,951816009,,LOMA LINDA UNIVERSITY,84,3/25/2021 +123976,951890710,,CALIFORNIA BAPTIST UNIVERSITY,84,3/25/2021 +124616,952544535,,UNIVERSITY OF SAN DIEGO,84,3/25/2021 +125231,953127273,,WESTERN UNIVERSITY OF HEALTH SCIENCES,84,3/25/2021 +128842,636000724,,AUBURN UNIVERSITY,84,3/25/2021 +128910,636001099,,JACKSONVILLE STATE UNIVERSITY,84,3/25/2021 +128921,636001100,,THE UNIVERSITY OF WEST ALABAMA,84,3/25/2021 +128977,636001102,,TROY UNIVERSITY,84,3/25/2021 +128979,630520830,,THE UNIVERSITY OF ALABAMA IN HUNTSVILLE,84,3/25/2021 +128985,636000893,,UNIVERSITY OF NORTH ALABAMA,84,3/2/2022 +128986,630477348,,UNIVERSITY OF SOUTH ALABAMA,84,3/25/2021 +129020,636001138,,THE UNIVERSITY OF ALABAMA,84,3/25/2021 +129643,636000750,DMHNANKDJPB7,BALDWIN COUNTY BOARD OF EDUCATION,84,6/26/2023 +129709,636000945,,JEFFERSON COUNTY BOARD OF EDUCATION,84,8/19/2022 +129727,636000813,,HUNTSVILLE CITY BOARD OF EDUCATION,84,7/1/2022 +129738,636000774,,MOBILE COUNTY BOARD OF SCHOOL COMMISSIONERS,84,3/25/2021 +129740,636000997,,MONTGOMERY COUNTY BOARD OF EDUCATION,84,5/27/2022 +129795,920056756,VS2WT8MAJ688,LOWER KUSKOKWIM SCHOOL DISTRICT,84,11/8/2022 +129822,926000034,TB8KBAUF7N68,MATANUSKA-SUSITNA BOROUGH SCHOOL DISTRICT,84,12/1/2022 +130410,866000510,MFFQJLG4KY23,ALHAMBRA ELEMENTARY SCHOOL DISTRICT NO. 68,84,2/6/2023 +130416,866000517,,CARTWRIGHT ELEMENTARY SCHOOL DISTRICT NO. 83,84,2/24/2022 +130418,866000515,,CHANDLER UNIFIED SCHOOL DISTRICT NO. 80,84,12/29/2021 +130434,866000481,,MESA UNIFIED SCHOOL DISTRICT NO. 4,84,3/25/2021 +130442,866000488,QF5ELNJFBVS3,PEORIA UNIFIED SCHOOL DISTRICT NO. 11,84,1/19/2023 +130444,866000534,,PHOENIX UNION HIGH SCHOOL DISTRICT NO. 210,84,4/21/2022 +130461,866004178,LQQ4SRYSCKM1,DEER VALLEY UNIFIED SCHOOL DISTRICT NO. 97,84,2/8/2023 +130463,860185552,,MARICOPA COUNTY COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +130498,866000792,TKBSF37VK8J8,SUNNYSIDE UNIFIED SCHOOL DISTRICT NO. 12,84,3/30/2023 +130501,866000551,,TUCSON UNIFIED SCHOOL DISTRICT NO. 1,84,3/25/2021 +130503,860208787,,PIMA COUNTY COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +131710,716014717,,LITTLE ROCK SCHOOL DISTRICT,84,5/10/2022 +131767,716021364,Y1GUR2JJCH18,SPRINGDALE SCHOOL DISTRICT # 50,84,4/3/2023 +131807,912155587,,CALIFORNIA STATE UNIVERSITY,84,3/25/2021 +131825,942317114,VZ9VH8SQVRN9,EL DORADO COUNTY OFFICE OF EDUCATION,84,3/1/2023 +131879,956000935,,SAN DIEGO COUNTY OFFICE OF EDUCATION,84,3/25/2021 +131912,942191905,,TULARE COUNTY OFFICE OF EDUCATION,84,3/25/2021 +133559,941693499,,HAYWARD UNIFIED SCHOOL DISTRICT,84,3/26/2022 +133572,941590799,DSLHRLFLNN89,PERALTA COMMUNITY COLLEGE DISTRICT,84,4/4/2023 +133615,680091157,R8L1DRLVBNX8,MOUNT DIABLO UNIFIED SCHOOL DISTRICT,84,1/25/2023 +133619,680000495,,WEST CONTRA COSTA UNIFIED SCHOOL DISTRICT,84,2/11/2022 +133649,946002206,,FRESNO UNIFIED SCHOOL DISTRICT,84,3/25/2021 +133733,956006350,XX5DV4Q28A71,IMPERIAL COMMUNITY COLLEGE DISTRICT,84,3/14/2023 +133742,956000671,,BAKERSFIELD CITY SCHOOL DISTRICT,84,3/25/2021 +133756,956001764,,KERN HIGH SCHOOL DISTRICT,84,3/25/2021 +133770,956000412,P6A1KWZZ9PF1,PANAMA BUENA VISTA UNION SCHOOL DISTRICT,84,6/9/2023 +133787,956006644,,KERN COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +133823,956000127,RVLCKGNB9FF1,ANTELOPE VALLEY UNION HIGH SCHOOL DISTRICT,84,4/6/2023 +133836,952650551,,COMPTON UNIFIED SCHOOL DISTRICT,84,2/4/2022 +133840,956006586,,DOWNEY UNIFIED SCHOOL DISTRICT,84,2/3/2022 +133844,956001060,,EL CAMINO COMMUNITY COLLEGE DISTRICT,84,8/22/2022 +133849,956001464,,GLENDALE UNIFIED SCHOOL DISTRICT,84,12/17/2021 +133858,956001815,F7MEGL17QWN9,LANCASTER SCHOOL DISTRICT,84,3/14/2023 +133863,956001886,,LONG BEACH UNIFIED SCHOOL DISTRICT,84,3/25/2021 +133864,956001908,,LOS ANGELES UNIFIED SCHOOL DISTRICT,84,3/25/2021 +133865,952587353,,LOS ANGELES COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +133871,956002104,,MONTEBELLO UNIFIED SCHOOL DISTRICT,84,2/8/2022 +133873,956002131,,MT. SAN ANTONIO COMMUNITY COLLEGE DISTRICT,84,12/16/2021 +133875,956002228,,NORWALK-LA MIRADA UNIFIED SCHOOL DISTRICT,84,2/8/2022 +133876,966002342,,PALMDALE SCHOOL DISTRICT,84,3/25/2021 +133880,952505000,,PASADENA AREA COMMUNITY COLLEGE DISTRICT,84,1/15/2022 +133907,956005521,,CERRITOS COMMUNITY COLLEGE DISTRICT,84,12/18/2021 +133914,952668744,,GLENDALE COMMUNITY COLLEGE DISTRICT,84,5/25/2022 +133915,952654140,,LONG BEACH COMMUNITY COLLEGE DISTRICT,84,2/8/2022 +133916,952767537,,SANTA MONICA COMMUNITY COLLEGE DISTRICT,84,1/13/2022 +133922,946002320,,MADERA UNIFIED SCHOOL DISTRICT,84,4/12/2022 +134028,952321055,VZPNFN6ZBXH3,CAPISTRANO UNIFIED SCHOOL DISTRICT,84,10/27/2022 +134034,952394131,,NORTH ORANGE COUNTY COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +134044,956002272,,COAST COMMUNITY COLLEGE DISTRICT,84,12/9/2021 +134046,952696799,,RANCHO SANTIAGO COMMUNITY COLLEGE DISTRICT,84,12/17/2021 +134055,952479872,,SOUTH ORANGE COUNTY COMMUNITY COLLEGE DISTRICT,84,2/3/2022 +134076,946031260,VJKEUF53D8M1,SIERRA JOINT COMMUNITY COLLEGE DISTRICT,84,12/9/2022 +134080,951794390,KS11SUNKT9K6,ALVORD UNIFIED SCHOOL DISTRICT,84,3/31/2023 +134083,330765218,,COACHELLA VALLEY UNIFIED SCHOOL DISTRICT,84,5/6/2022 +134084,330277305,,CORONA-NORCO UNIFIED SCHOOL DISTRICT,84,2/10/2022 +134087,521527174,,HEMET UNIFIED SCHOOL DISTRICT,84,1/6/2022 +134088,330743985,,DESERT SANDS UNIFIED SCHOOL DISTRICT,84,12/16/2021 +134092,521527179,,PALM SPRINGS UNIFIED SCHOOL DISTRICT,84,2/8/2022 +134096,952883296,,RIVERSIDE UNIFIED SCHOOL DISTRICT,84,2/7/2022 +134100,581997491,,VAL VERDE UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134103,330813228,L1QXZ1FQXSX6,MT. SAN JACINTO COMMUNITY COLLEGE DISTRICT,84,2/6/2023 +134105,521770792,,MORENO VALLEY UNIFIED SCHOOL DISTRICT,84,5/19/2022 +134110,946002501,,ELK GROVE UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134119,946002491,,SACRAMENTO CITY UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134120,946002533,,SAN JUAN UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134122,941576340,,LOS RIOS COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +134136,956000560,G6NAHBZF3AL3,CHAFFEY JOINT UNION HIGH SCHOOL DISTRICT,84,1/9/2023 +134137,956000558,Q9QRTJMGP7A8,CHAFFEY COMMUNITY COLLEGE DISTRICT,84,2/24/2023 +134138,956000586,MT8RFPNUGL93,CHINO VALLEY UNIFIED SCHOOL DISTRICT,84,2/16/2023 +134139,952414439,,COLTON JOINT UNIFIED SCHOOL DISTRICT,84,2/11/2022 +134142,956001357,,FONTANA UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134144,930944528,JFARC4LMCRH7,HESPERIA UNIFIED SCHOOL DISTRICT,84,12/27/2022 +134150,956002267,,ONTARIO-MONTCLAIR SCHOOL DISTRICT,84,2/26/2022 +134152,952254572,,REDLANDS UNIFIED SCHOOL DISTRICT,84,3/3/2022 +134155,952285577,,SAN BERNARDINO CITY UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134184,956001517,JMG7LBBS3DY6,GROSSMONT UNION HIGH SCHOOL DISTRICT,84,1/19/2023 +134193,956002227,FNCMV513G321,PALOMAR COMMUNITY COLLEGE DISTRICT,84,2/6/2023 +134200,956002781,,SAN DIEGO UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134208,956003082,,SWEETWATER UNION HIGH SCHOOL DISTRICT,84,3/2/2022 +134213,956006652,L3LUC5Y7B188,GROSSMONT-CUYAMACA COMMUNITY COLLEGE DISTRICT,84,1/18/2023 +134215,956006662,K5P1TBMK2943,MIRACOSTA COMMUNITY COLLEGE DISTRICT,84,2/8/2023 +134217,946000416,,SAN FRANCISCO UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134233,946002661,,STOCKTON UNIFIED SCHOOL DISTRICT,84,3/25/2021 +134236,911044400,J1M3XMMKNH73,SAN JOAQUIN DELTA COMMUNITY COLLEGE DISTRICT,84,2/15/2023 +134323,946002606,FL1ADFUSL9J4,SAN JOSE UNIFIED SCHOOL DISTRICT,84,5/18/2023 +134346,770375541,,PAJARO VALLEY UNIFIED SCHOOL DISTRICT,84,3/4/2022 +134458,770195326,,MODESTO CITY SCHOOLS,84,2/25/2022 +134460,521566989,DLVKVBMZME64,YOSEMITE COMMUNITY COLLEGE DISTRICT,84,12/21/2022 +134561,770531549,TGDTBR9R5L51,VISALIA UNIFIED SCHOOL DISTRICT,84,2/6/2023 +134596,952224338,,VENTURA COUNTY COMMUNITY COLLEGE DISTRICT,84,1/15/2022 +135326,846000822,,ADAMS 12 FIVE STAR SCHOOLS,84,2/15/2022 +135331,846000861,,CHERRY CREEK SCHOOL DISTRICT NO 5,84,4/21/2022 +135336,846000870,,JOINT SCHOOL DISTRICT NO 28-J OF THE COUNTIES OF ADAMS AND ARAPAHOE,84,11/26/2021 +135361,846001099,,SCHOOL DISTRICT NO. 1 IN THE CITY AND COUNTY OF DENVER AND STATE OF CO,84,3/25/2021 +135363,846011446,,DOUGLAS COUNTY SCHOOL DISTRICT RE.1,84,3/9/2022 +135400,846002817,,JEFFERSON COUNTY SCHOOL DISTRICT NO R-1,84,3/14/2022 +135434,846002839,,MESA COUNTY VALLEY SCHOOL DISTRICT NO. 51,84,3/22/2022 +135667,066001865,,"CITY OF BRIDGEPORT, CONNECTICUT",84,3/25/2021 +135683,066001900,,"CITY OF WATERBURY, CONNECTICUT",84,1/18/2022 +136721,596000522,,BREVARD COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136728,596000552,GN5LCCG51JM6,CLAY COUNTY DISTRICT SCHOOL BOARD,84,3/31/2023 +136729,596000557,,"DISTRICT SCHOOL BOARD OF COLLIER COUNTY, FLORIDA",84,3/25/2021 +136732,596000572,,MIAMI-DADE COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136736,596000589,,DUVAL COUNTY PUBLIC SCHOOLS,84,3/25/2021 +136738,596000597,,DISTRICT SCHOOL BOARD OF ESCAMBIA COUNTY,84,3/25/2021 +136752,596000660,,HILLSBOROUGH COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136760,596000694,,LAKE COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136762,596000701,,SCHOOL DISTRICT OF LEE COUNTY,84,3/25/2021 +136764,596000709,,LEON COUNTY DISTRICT SCHOOL BOARD,84,3/1/2022 +136770,596000728,,"SCHOOL DISTRICT OF MANATEE COUNTY, FLORIDA",84,3/25/2021 +136772,596000734,,MARION COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136778,596000764,KCMMFYM5LUW6,OKALOOSA COUNTY DISTRICT SCHOOL BOARD,84,3/30/2023 +136781,596000771,,ORANGE COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136783,596000779,,OSCEOLA COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136784,596000783,,PALM BEACH COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136786,596000092,,DISTRICT SCHOOL BOARD OF PASCO COUNTY,84,3/25/2021 +136788,596000799,,DISTRICT SCHOOL BOARD OF PINELLAS COUNTY,84,3/25/2021 +136790,596000807,,POLK COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136795,596000832,,ST. LUCIE COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136797,596000845,JG4LLWLFK4M7,SANTA ROSA COUNTY DISTRICT SCHOOL BOARD,84,1/23/2023 +136798,596000847,,SARASOTA COUNTY DISTRICT SCHOOL BOARD,84,12/21/2021 +136799,596000855,,SEMINOLE COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +136805,596000884,,"DISTRICT SCHOOL BOARD OF VOLUSIA COUNTY, FLORIDA",84,3/25/2021 +137671,586011458,RG6JN4TD5XH3,CHEROKEE COUNTY BOARD OF EDUCATION,84,4/3/2023 +137677,586000214,,COBB COUNTY SCHOOL DISTRICT,84,3/25/2021 +137694,586000231,CNRYFGU8KTM7,DOUGHERTY COUNTY BOARD OF EDUCATION,84,8/23/2023 +137707,586000243,DBPSHJAXNPJ5,FORSYTH COUNTY BOARD OF EDUCATION,84,3/14/2023 +137721,586000254,,GWINNETT COUNTY BOARD OF EDUCATION,84,3/25/2021 +137724,586000256,G775DZH3VKC5,HALL COUNTY BOARD OF EDUCATION,84,9/7/2023 +137769,586000143,,MUSCOGEE COUNTY SCHOOL DISTRICT,84,2/25/2022 +137785,586000310,,RICHMOND COUNTY BOARD OF EDUCATION,84,3/25/2021 +137874,996000354,,UNIVERSITY OF HAWAII,84,3/25/2021 +137901,820290701,,BOISE STATE UNIVERSITY,84,3/25/2021 +137921,826000924,,IDAHO STATE UNIVERSITY,84,3/25/2021 +138350,826001213,FE7DGWDMFZE1,JOINT SCHOOL DISTRICT NO. 2,84,12/3/2022 +138509,376013590,,EASTERN ILLINOIS UNIVERSITY,84,7/6/2022 +138532,362684803,,GOVERNORS STATE UNIVERSITY,84,3/25/2021 +138560,366008480,,NORTHERN ILLINOIS UNIVERISTY,84,3/25/2021 +138590,376000511,,UNIVERSITY OF ILLINOIS,84,3/25/2021 +138595,370910458,,WESTERN ILLINOIS UNIVERSITY,84,3/25/2021 +141344,366005821,,CHICAGO PUBLIC SCHOOLS / CHICAGO BOARD OF EDUCATION,84,3/25/2021 +141358,362606236,,CITY COLLEGES OF CHICAGO COMMUNITY COLLEGE DISTRICT NO. 508,84,3/25/2021 +141433,362594972,,COLLEGE OF DUPAGE COMMUNITY COLLEGE DISTRICT 502,84,12/27/2021 +141569,366004736,,SCHOOL DISTRICT U-46,84,9/1/2022 +141865,376001759,C83FD7GDF7B1,BOARD OF EDUCATION OF THE CITY OF PEORIA,84,3/7/2023 +141961,376004615,LVM3BAVZFAN8,SPRINGFIELD PUBLIC SCHOOL DISTRICT 186,84,6/22/2023 +142117,366009416,FBLNJNGFM713,ROCKFORD PUBLIC SCHOOLS DISTRICT 205,84,11/29/2022 +142179,356000221,,BALL STATE UNIVERSITY,84,3/25/2021 +142234,356001670,,INDIANA STATE UNIVERSITY,84,3/25/2021 +142235,356001673,,TRUSTEES OF INDIANA UNIVERSITY,84,3/25/2021 +142292,351308176,,UNIVERSITY OF SOUTHERN INDIANA,84,2/24/2022 +142300,351180631,,IVY TECH COMMUNITY COLLEGE OF INDIANA,84,3/25/2021 +143278,356006351,,FORT WAYNE COMMUNITY SCHOOLS,84,3/25/2021 +143452,356002486,,INDIANAPOLIS PUBLIC SCHOOLS,84,3/25/2021 +143559,351071682,,EVANSVILLE VANDERBURGH SCHOOL CORPORATION,84,3/25/2021 +144594,420924685,,KIRKWOOD COMMUNITY COLLEGE,84,12/16/2021 +144663,426001433,,DES MOINES INDEPENDENT COMMUNITY SCHOOL DISTRICT,84,3/25/2021 +144671,420926354,,DES MOINES AREA COMMUNITY COLLEGE,84,12/3/2021 +145652,480697986,F8GTKBANM2B4,"OLATHE PUBLIC SCHOOLS, UNIFIED SCHOOL DISTRICT NO 233",84,12/31/2022 +145798,486000351,,WICHITA PUBLIC SCHOOLS UNIFIED SCHOOL DISTRICT NO 259,84,3/25/2021 +145808,486030115,,WASHBURN UNIVERSITY OF TOPEKA,84,1/14/2022 +145849,486031181,VNN2HKYDQU77,UNIFIED SCHOOL DISTRICT NO. 500,84,1/26/2023 +145869,616055628,,WESTERN KENTUCKY UNIVERSITY,84,3/25/2021 +145871,611005783,,MURRAY STATE UNIVERSITY,84,3/25/2021 +145872,616001218,,UNIVERSITY OF KENTUCKY,84,3/25/2021 +145873,611014029,,MOREHEAD STATE UNIVERSITY,84,3/25/2021 +146521,616001316,,JEFFERSON COUNTY BOARD OF EDUCATION,84,3/25/2021 +146599,616001247,MMVUBL3ECMX4,WARREN COUNTY SCHOOL DISTRICT,84,12/12/2022 +147031,726000185,QGV5Q4V3FK85,BOSSIER PARISH SCHOOL BOARD,84,3/7/2023 +147032,726000224,,CADDO PARISH SCHOOL BOARD,84,3/25/2021 +147033,726000235,,CALCASIEU PARISH SCHOOL BOARD,84,4/8/2022 +147040,726000353,,EAST BATON ROUGE PARISH SCHOOL SYSTEM,84,3/25/2021 +147051,726000625,,LAFAYETTE PARISH SCHOOL BOARD,84,2/3/2022 +147055,720882480,W7LSK2MCPA95,LIVINGSTON PARISH SCHOOL BOARD,84,1/24/2023 +147064,726001133,,RAPIDES PARISH SCHOOL BOARD,84,2/11/2022 +147077,726001372,MYDNDM6MCTF4,TAGIPAHOA PARISH SCHOOL SYSTEM,84,1/31/2023 +147111,010416015,,MAINE COMMUNITY COLLEGE SYSTEM,84,3/25/2021 +147112,010392006,,FINANCE AUTHORITY OF MAINE,84,3/25/2021 +147114,016000769,,UNIVERSITY OF MAINE SYSTEM,84,3/25/2021 +147742,526000989,,MONTGOMERY COUNTY PUBLIC SCHOOLS,84,3/25/2021 +147942,046001394,EFC2AWA1NQC9,CITY OF LAWRENCE,84,3/14/2023 +147949,046001415,,CITY OF SPRINGFIELD,84,3/25/2021 +147954,046001396,,CITY OF LOWELL,84,4/26/2022 +147963,046001409,,CITY OF QUINCY,84,10/14/2022 +147964,046001382,,CITY OF BROCKTON,84,3/24/2023 +148653,386004447,,CENTRAL MICHIGAN UNIVERSITY,84,3/25/2021 +148655,386005986,,EASTERN MICHIGAN UNIVERSITY,84,3/25/2021 +148656,386005159,,FERRIS STATE UNIVERISTY,84,3/25/2021 +148657,381684280,,GRAND VALLEY STATE UNIVERSITY,84,3/25/2021 +148661,386005984,,MICHIGAN STATE UNIVERSITY,84,3/25/2021 +148662,386005955,,MICHIGAN TECHNOLOGICAL UNIVERSITY,84,3/25/2021 +148663,386029206,,NORTHERN MICHIGAN UNIVERSITY,84,3/25/2021 +148665,381714400,,OAKLAND UNIVERSITY,84,3/25/2021 +148666,381798800,,SAGINAW VALLEY STATE UNIVERSITY,84,3/25/2021 +148671,386007327,,WESTERN MICHIGAN UNIVERSITY,84,3/25/2021 +150206,386001599,ESQJNJ5LU3R5,LANSING SCHOOL DISTRICT,84,10/14/2022 +150266,386002019,ZKKMEZC272F7,GRAND RAPIDS PUBLIC SCHOOLS,84,12/12/2022 +150277,382980195,,GRAND RAPIDS COMMUNITY COLLEGE,84,7/7/2022 +150340,381714601,,MACOMB INTERMEDIATE SCHOOL DISTRICT,84,11/13/2021 +150450,381751522,,OAKLAND COMMUNITY COLLEGE,84,11/4/2021 +150606,381909530,,WAYNE COUNTY REGIONAL EDUCATIONAL SERVICE AGENCY,84,3/25/2021 +150611,386157869,FN98L9WF61M3,WAYNE COUNTY COMMUNITY COLLEGE DISTRICT,84,3/31/2023 +150676,416007513,,REGENTS OF THE UNIVERSITY OF MINNESOTA,84,3/25/2021 +151681,416008267,KBAWKXFM32L7,INDEPENDENT SCHOOL DISTRICT NO. 11,84,1/13/2023 +151808,410851980,,MINNEAPOLIS SPECIAL SCHOOL DISTRICT NO. 1,84,3/25/2021 +151978,410901311,,INDEPENDENT SCHOOL DISTRICT NO. 625,84,3/25/2021 +152599,646000505,,JACKSON PUBLIC SCHOOL DISTRICT,84,9/19/2022 +152737,446000308,,MISSOURI STATE UNIVERSITY,84,3/25/2021 +152738,436003859,,UNIVERSITY OF MISSOURI SYSTEM,84,3/25/2021 +152741,446000293,,UNIVERSITY OF CENTRAL MISSOURI,84,3/25/2021 +152745,446000301,,NORTHWEST MISSOURI STATE UNIVERSITY,84,12/18/2021 +152746,436003569,,SOUTHEAST MISSOURI STATE UNIVERSITY,84,3/25/2021 +153845,446005539,EMUDM5DEZMH1,THE SCHOOL DISTRICT OF SPRINGFIELD R-XII,84,12/12/2022 +153896,446003031,WN2MVJNM9624,INDEPENDENCE 30 SCHOOL DISTRICT,84,2/14/2023 +153897,446003108,HGM6RN7EW395,"THE SCHOOL DISTRICT OF KANSAS CITY, MISSOURI",84,1/4/2023 +153902,430813703,,METROPOLITAN COMMUNITY COLLEGE,84,12/7/2021 +154131,436003220,,SCHOOL DISTRICT OF THE CITY OF ST. LOUIS,84,1/18/2022 +154881,470049123,,UNIVERSITY OF NEBRASKA,84,3/25/2021 +154885,611573095,UXMZXKLPV9M2,NEBRASKA STATE COLLEGE SYSTEM,84,12/31/2022 +155652,476002629,,DOUGLAS COUNTY SCHOOL DISTRICT #0001,84,3/25/2021 +156000,886000030,,CLARK COUNTY SCHOOL DISTRICT,84,3/25/2021 +156014,886000919,,WASHOE COUNTY SCHOOL DISTRICT,84,3/25/2021 +156501,222830882,,NEW JERSEY CITY UNIVERSITY,84,3/25/2021 +156504,222960726,,KEAN UNIVERSITY,84,3/25/2021 +156505,521558006,,RAMAPO COLLEGE OF NEW JERSEY,84,5/16/2022 +156506,222797398,,THE COLLEGE OF NEW JERSEY,84,5/4/2022 +156507,222781603,,THE WILLIAM PATERSON UNIVERSITY OF NEW JERSEY,84,3/25/2021 +156508,222764819,,ROWAN UNIVERSITY,84,3/25/2021 +156543,226088440,J7EFDKQD4D97,ROWAN COLLEGE OF SOUTH JERSEY,84,5/16/2023 +156801,222832788,,STOCKTON UNIVERSITY (A COMPONENT UNIT OF THE STATE OF NEW JERSEY),84,3/25/2021 +156810,216000326,N84LD49GR6G1,TRENTON BOARD OF EDUCATION,84,3/30/2023 +157585,216000154,E6HKN3MD1G21,CITY OF CAMDEN BOARD OF EDUCATION,84,3/31/2023 +158024,856000401,,NEW MEXICO STATE UNIVERSITY,84,3/25/2021 +158048,856000565,,STATE OF NEW MEXICO PUBLIC EDUCATION DEPARTMENT,84,3/25/2021 +158218,856000101,,ALBUQUERQUE MUNICIPAL SCHOOL DISTRICT NO. 12,84,3/25/2021 +158219,850172915,EN21ZJ5KXAF8,STATE OF NM CENTRAL NEW MEXICO COMMUNITY COLLEGE,84,1/18/2023 +158238,856002445,KXMCLLUKZWF5,LAS CRUCES PUBLIC SCHOOL DISTRICT NO. 2,84,2/20/2023 +158723,131616880,,THE CITY UNIVERSITY OF NEW YORK,84,3/25/2021 +160651,136006910,H6HKL3HS2KU3,EAST RAMAPO CENTRAL SCHOOL DISTRICT,84,7/6/2023 +160969,566000994,MB5UHS1RFA98,BUNCOMBE COUNTY BOARD OF EDUCATION,84,12/27/2022 +160975,566000997,VHKTMJGPA8L1,CABARRUS COUNTY BOARD OF EDUCATION,84,11/10/2022 +161008,566001015,,CUMBERLAND COUNTY BOARD OF EDUCATION,84,3/25/2021 +161029,560795164,,WINSON-SALEM FORSYTH COUNTY SCHOOLS,84,3/25/2021 +161034,566001032,,GASTON COUNTY BOARD OF EDUCATION,84,12/7/2021 +161045,566000522,,GUILFORD COUNTY BOARD OF EDUCATION,84,3/25/2021 +161052,566001044,XKGNL17FKW65,HARNETT COUNTY BOARD OF EDUCATION,84,2/6/2023 +161071,561744267,CKDGXLL4NN87,IREDELL-STATESVILLE BOARD OF EDUCATION,84,4/4/2023 +161079,566001055,HP51MNJMFQ61,JOHNSTON COUNTY BOARD OF EDUCATION,84,11/16/2022 +161102,566001074,,CHARLOTTE MECKLENBURG BOARD OF EDUCATION,84,3/25/2021 +161117,566001085,H9KFKBENBPL3,NEW HANOVER COUNTY BOARD OF EDUCATION,84,1/4/2023 +161141,566001097,ECKDEHPCB6D4,"PITT COUNTY BOARD OF EDUCATION, NORTH CAROLINA",84,1/2/2023 +161151,566001104,,PUBLIC SCHOOLS OF ROBESON COUNTY,84,4/28/2022 +161185,566001123,MJ7JT5N2UN17,UNION COUNTY BOARD OF EDUCATION,84,1/4/2023 +161191,561137759,,WAKE COUNTY BOARD OF EDUCATION,84,3/25/2021 +162392,316025986,,THE OHIO STATE UNIVERSITY,84,3/25/2021 +162393,316402113,,OHIO UNIVERSITY,84,3/25/2021 +162395,341055865,CJRNG45SK9D1,STARK STATE COLLEGE,84,3/1/2023 +162401,341015707,,EASTERN GATEWAY COMMUNITY COLLEGE,84,3/10/2022 +162405,316402089,,MIAMI UNIVERSITY,84,3/25/2021 +162408,341011998,,YOUNGSTOWN STATE UNIVERSITY,84,3/25/2021 +162411,316402079,,KENT STATE UNIVERSITY,84,3/25/2021 +162412,346401483,,THE UNIVERSITY OF TOLEDO,84,3/25/2021 +162413,310732831,,WRIGHT STATE UNIVERSITY,84,3/25/2021 +162414,316000989,,UNIVERSITY OF CINCINNATI,84,3/25/2021 +162415,346402018,,BOWLING GREEN STATE UNIVERSITY,84,3/25/2021 +162418,346002924,,THE UNIVERSITY OF AKRON,84,3/25/2021 +162420,340966056,,CLEVELAND STATE UNIVERSITY,84,3/25/2021 +164186,346000662,,CLEVELAND MUNICIPAL SCHOOL DISTRICT,84,3/25/2021 +164252,316400416,,COLUMBUS CITY SCHOOL DISTRICT,84,3/25/2021 +164437,346401449,,TOLEDO CITY SCHOOL DISTRICT,84,3/25/2021 +164498,316000784,JETRRRCCKH75,DAYTON CITY SCHOOL DISTRICT,84,3/2/2023 +164514,310723444,,SINCLAIR COMMUNITY COLLEGE,84,1/11/2022 +164655,346000033,,AKRON CITY SCHOOL DISTRICT,84,6/29/2022 +164836,611770436,,OKLAHOMA STATE REGENTS FOR HIGHER EDUCATION,84,3/25/2021 +165774,736021175,,"INDEPENDENT SCHOOL DISTRICT NO. 89 OF OKLAHOMA COUNTY, OKLAHOMA",84,3/25/2021 +165941,736021242,,TULSA PUBLIC SCHOOLS INDEPENDENT SCHOOL DISTRICT NO. I-1,84,3/25/2021 +166673,936000763,,SALEM-KEIZER SCHOOL DISTRICT NO. 24J,84,12/9/2021 +166683,930585134,VKNDQDCD3AU9,CHEMEKETA COMMUNITY COLLEGE,84,2/24/2023 +166693,936000830,,"SCHOOL DISTRICT NO.1J, MULTNOMAH COUNTY OREGON",84,12/14/2021 +166744,936001065,E9UDY9JX78X9,BEAVERTON SCHOOL DISTRICT,84,12/20/2022 +166793,231693362,,PENNSYLVANIA HIGHER EDUCATION ASSISTANCE AGENCY (PHEAA),84,3/25/2021 +166794,232250505,,"STATE SYSTEM OF HIGHER EDUCATION, COMMONWEALTH OF PENNSYLVANIA",84,3/25/2021 +166795,246000376,,THE PENNSYLVANIA STATE UNIVERSITY,84,3/25/2021 +169600,251157808,,SCHOOL DISTRICT OF PITTSBURGH,84,3/25/2021 +169627,256075057,,COMMUNITY COLLEGE OF ALLEGHENY COUNTY,84,12/17/2021 +169839,231685591,UQEFRLKYSEA3,DELAWARE COUNTY COMMUNITY COLLEGE,84,4/3/2023 +169847,256001265,MJNTFJNX6S65,"SCHOOL DISTRICT OF THE CITY OF ERIE,PA",84,3/31/2023 +169929,231740422,,LANCASTER-LEBANON INTERMEDIATE UNIT 13,84,12/17/2021 +169951,236003488,YVMSJ93LHNC6,ALLENTOWN SCHOOL DISTRICT,84,6/30/2023 +170068,236391672,,COMMUNITY COLLEGE OF PHILADELPHIA,84,3/25/2021 +170374,570427788,JJWEM8GBJ9L9,MIDLANDS TECHNICAL COLLEGE,84,10/25/2022 +170375,570440170,JTNDE6EBHL41,TRIDENT TECHNICAL COLLEGE,84,3/30/2023 +170721,576000300,LU7JSJ4R3MM9,THE CONSOLIDATED SCHOOL DISTRICT OF AIKEN COUNTY,84,12/7/2022 +170734,576000310,KMALQBCRCNN5,BEAUFORT COUNTY SCHOOL DISTRICT,84,12/6/2022 +170735,576000313,ZFRDSBNFLEY9,BERKELEY COUNTY SCHOOL DISTRICT,84,12/9/2022 +170755,576000231,Q2KVV935WJE3,FLORENCE SCHOOL DISTRICT ONE,84,12/7/2022 +170761,576000234,,THE SCHOOL DISTRICT OF GREENVILLE COUNTY,84,3/25/2021 +170768,576000364,,HORRY COUNTY SCHOOLS,84,12/7/2021 +170800,578000364,UKJCDJAFLBL3,RICHLAND COUNTY SCHOOL DISTRICT ONE,84,12/21/2022 +171279,466002586,,SIOUX FALLS SCHOOL DISTRICT 49-5,84,4/12/2022 +171391,626000636,,"HAMILTON COUNTY, TENNESSEE",84,3/25/2021 +171405,626007979,,KNOX COUNTY GOVERNMENT,84,3/25/2021 +171415,626000729,MYX9B6NDMS25,"MADISON COUNTY, TENNESSEE",84,2/6/2023 +171421,626000764,,"MONTGOMERY COUNTY, TENNESSEE",84,3/18/2022 +171432,626000818,,RUTHERFORD COUNTY TENNESSEE,84,12/29/2021 +173929,746001505,,KILLEEN INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +173943,746015301,,NORTH EAST INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +173944,746015904,,NORTHSIDE INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +173946,746002173,,ALAMO COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +173947,746002167,,SAN ANTONIO INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174016,746000418,,BROWNSVILLE INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174017,746001053,ZZSKMJLNWBA7,HARLINGEN CONSOLIDATED INDEPENDENT SCHOOL DISTRICT,84,1/24/2023 +174022,746002224,VUQKWNGGJEK1,SAN BENITO CONSOLIDATED INDEPENDENT SCHOOL DISTRICT,84,12/12/2022 +174070,756001636,YLM8Y2HQ6TD8,FRISCO INDEPENDENT SCHOOL DISTRICT,84,12/1/2022 +174074,756002252,DERLA1LAC6H8,PLANO INDEPENDENT SCHOOL DISTRICT,84,12/21/2022 +174078,752037156,,COLLIN COUNTY COMMUNITY COLLEGE DISTRICT,84,12/23/2021 +174118,756001278,,DALLAS INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174121,756001650,,GARLAND INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174126,756002054,EDUJZELNL9A4,MESQUITE INDEPENDENT SCHOOL DISTRICT,84,12/9/2022 +174131,751213149,,DALLAS COUNTY COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +174145,756001955,,LEWISVILLE INDEPENDENT SCHOOL DISTRICT,84,12/16/2021 +174175,756001362,,ECTOR COUNTY INDEPENDENT SCHOOL DISTRICT,84,12/1/2021 +174192,746029385,ZB1CF1UCVVQ7,SOCORRO INDEPENDENT SCHOOL DISTRICT,84,1/24/2023 +174195,746002473,,YSLETA INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174197,741690850,,EL PASO COUNTY COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +174198,746000769,,EL PASO INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174228,746002016,LJ1TM4C8X411,LAMAR CONSOLIDATED INDEPENDENT SCHOOL DISTRICT,84,2/6/2023 +174230,746025253,,FORT BEND INDEPENDENT SCHOOL DISTRICT,84,11/17/2021 +174242,746001592,JTUENMMSMWF6,CLEAR CREEK INDEPENDENT SCHOOL DISTRICT,84,1/31/2023 +174317,746001110,,ALDINE INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174318,746000019,,ALIEF INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174321,746000654,,CYPRESS-FAIRBANKS INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174325,746000251,,GOOSE CREEK CONSOLIDATED INDEPENDENT SCHOOL DISTRICT,84,3/26/2022 +174326,746001255,,HOUSTON INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174329,746001484,,KATY INDEPENDENT SCHOOL DISTRICT,84,2/16/2022 +174332,746001850,,PASADENA INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174334,746001379,,SPRING BRANCH INDEPENDENT SCHOOL DISTRICT,84,12/10/2021 +174335,746002339,YGX3UJR6N6U6,SPRING INDEPENDENT SCHOOL DISTRICT,84,1/24/2023 +174337,741589572,,REGION 4 EDUCATION SERVICE CENTER,84,1/11/2022 +174338,741709152,,HOUSTON COMMUNITY COLLEGE SYSTEM,84,3/25/2021 +174339,746028285,,SAN JACINTO COMMUNITY COLLEGE DISTRICT,84,1/14/2022 +174341,741734884,,LONE STAR COLLEGE SYSTEM,84,3/25/2021 +174369,746000715,,EDINBURG CONSOLIDATED INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174374,746001742,CF41ED1ALVQ4,MISSION CONSOLIDATED INDEPENDENT SCHOOL DISTRICT,84,12/6/2022 +174376,746001876,,PHARR-SAN JUAN-ALAMO I.S.D.,84,3/25/2021 +174379,746001550,,LA JOYA INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174561,756001989,,LUBBOCK INDEPENDENT SCHOOL DISTRICT,84,12/7/2021 +174605,746000701,RGALM64S41C4,EAGLE PASS INDEPENDENT SCHOOL DISTRICT,84,12/20/2022 +174635,746000556,,CONROE INDEPENDENT SCHOOL DISTRICT,84,1/29/2022 +174674,746000581,,CORPUS CHRISTI INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174725,756000031,,AMARILLO COLLEGE,84,12/16/2021 +174726,756000036,,AMARILLO INDEPENDENT SCHOOL DISTRICT,84,12/7/2021 +174800,756002675,T9KLBZLQGU76,TYLER INDEPENDENT SCHOOL DISTRICT,84,1/26/2023 +174807,746003668,PNRNGL9YQLD3,RIO GRANDE CITY GRULLA INDEPENDENT SCHOOL DISTRICT,84,12/1/2022 +174817,756000119,,ARLINGTON INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174824,756001613,,FORT WORTH INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174829,756002005,R4SYTR7G6PJ6,MANSFIELD INDEPENDENT SCHOOL DISTRICT,84,1/19/2023 +174833,751217163,,TARRANT COUNTY COLLEGE DISTRICT,84,3/25/2021 +174839,756000004,JN8BGSBPWNZ4,ABILENE INDEPENDENT SCHOOL DISTRICT,84,1/11/2023 +174859,746000064,,AUSTIN INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174864,741742036,,AUSTIN COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +174912,746000400,,BLINN COLLEGE DISTRICT,84,3/25/2021 +174916,746001580,,LAREDO INDEPENDENT SCHOOL DISTRICT,84,12/23/2021 +174918,746028859,,UNITED INDEPENDENT SCHOOL DISTRICT,84,3/25/2021 +174953,746002018,JSQ9N6EHLUA1,ROUND ROCK INDEPENDENT SCHOOL DISTRICT,84,12/8/2022 +175253,876000487,,DAVIS SCHOOL DISTRICT,84,12/9/2021 +175266,876000494,,GRANITE SCHOOL DISTRICT,84,3/25/2021 +175267,876000497,MYCXQMLWYKB4,JORDAN SCHOOL DISTRICT,84,11/29/2022 +175279,876000478,,ALPINE SCHOOL DISTRICT,84,11/11/2021 +175283,876000531,KXXQFLKEQAV8,WASHINGTON COUNTY SCHOOL DISTRICT,84,3/30/2023 +175301,030213787,,VERMONT STATE COLLEGES,84,3/25/2021 +175651,036000783,SDJZAAPVXR36,WINOOSKI SCHOOL DISTRICT,84,2/8/2023 +176050,546001208,,"CHESTERFIELD COUNTY, VIRGINIA",84,3/25/2021 +176072,546001344,,"COUNTY OF HENRICO, VIRGINIA",84,3/25/2021 +176104,546001531,,PRINCE WILLIAM COUNTY,84,3/25/2021 +176116,546001622,,SPOTSYLVANIA COUNTY,84,10/6/2022 +176258,540721442,,CITY OF CHESAPEAKE,84,3/25/2021 +176268,546001336,,CITY OF HAMPTON,84,12/18/2021 +176272,546001405,,"CITY OF LYNCHBURG, VIRGINIA",84,12/18/2021 +176278,546001512,,"CITY OF PORTSMOUTH, VIRGINIA",84,5/3/2022 +176285,546001636,PFBEDV4G5MF3,"CITY OF SUFFOLK, VIRGINIA",84,12/22/2022 +177114,916001600,TS7KCJ776E59,EVERGREEN SCHOOL DISTRICT NO. 114,84,4/17/2023 +177119,916001540,MV9DMZYCZLM9,VANCOUVER SCHOOL DISTRICT NO. 37,84,6/1/2023 +177142,916001752,NWW9NTJ3ABU7,PASCO SCHOOL DISTRICT NO. 1,84,5/16/2023 +177181,916001624,U96DJWC6FGZ4,FEDERAL WAY SCHOOL DISTRICT NO. 210,84,6/1/2023 +177189,916001541,,SEATTLE SCHOOL DISTRICT NO. 1,84,5/26/2022 +177277,916001553,,TACOMA SCHOOL DISTRICT NO. 10,84,6/1/2022 +177320,916001582,XSVJKNZ9N263,SPOKANE SCHOOL DISTRICT NO 81,84,6/1/2023 +177387,916001550,YXD3EXL7BZMS,YAKIMA SCHOOL DISTRICT NO. 7,84,5/25/2023 +177820,556000337,,KANAWHA COUNTY BOARD OF EDUCATION,84,5/2/2022 +179284,396002329,KTMFQB98UHH5,GREEN BAY AREA PUBLIC SCHOOLS,84,1/11/2023 +179346,391086718,TK2SQD3B8M44,MADISON AREA TECHNICAL COLLEGE DISTRICT,84,12/27/2022 +179495,396003457,,MILWAUKEE PUBLIC SCHOOLS,84,3/25/2021 +179770,836000331,,UNIVERSITY OF WYOMING,84,3/25/2021 +180102,980032933,,UNIVERSITY OF GUAM,84,4/27/2022 +180113,660446193,,COMM OF THE NO MARIANA ISLANDS PUBLIC SCH SYSTEM,84,5/1/2023 +180138,660433481,,PUERTO RICO DEPARTMENT OF EDUCATION,84,3/25/2021 +180143,660433767,,UNIVERSITY OF PUERTO RICO,84,3/25/2021 +180800,226002012,,JERSEY CITY PUBLIC SCHOOLS,84,4/12/2022 +180812,526000886,,BOARD OF EDUCATION OF BALTIMORE COUNTY,84,3/25/2021 +180840,911898417,,NORTHEASTERN STATE UNIVERSITY,84,3/25/2021 +180845,156010157,,"CITY SCHOOL DISTRICT OF SYRACUSE, NEW YORK",84,3/25/2021 +180857,710236896,,"HARDING UNIVERSITY, INC.",84,3/25/2021 +180875,561957903,KPTDLJRNNE31,ALAMANCE-BURLINGTON BOARD OF EDUCATION,84,12/6/2022 +180880,731353314,,UNIVERSITY OF CENTRAL OKLAHOMA,84,3/25/2021 +180900,886000024,,NEVADA SYSTEM OF HIGHER EDUCATION,84,3/25/2021 +180901,926000096,JK2MKNSUTB39,FAIRBANKS NORTH STAR BOROUGH SCHOOL DISTRICT,84,11/29/2022 +180950,946002320,CPLCLKQTK2W5,MADERA UNIFIED SCHOOL DISTRICT,84,1/26/2023 +180960,166001554,,"BOARD OF EDUCATION, CITY OF BUFFALO, NEW YORK",84,3/25/2021 +180985,356002041,,PURDUE UNIVERSITY,84,3/25/2021 +180995,660191965,,PONTIFICAL CATHOLIC UNIVERSITY OF PUERTO RICO,84,3/25/2021 +181003,611010545,,NORTHERN KENTUCKY UNIVERSITY,84,3/25/2021 +181010,820207699,,BRIGHAM YOUNG UNIVERSITY - IDAHO,84,3/25/2021 +181034,526000992,,BOARD OF EDUCATION OF PRINCE GEORGE'S COUNTY MARYLAND,84,3/25/2021 +181037,526001035,H5NPA6BV2363,WASHINGTON COUNTY BOARD OF EDUCATION,84,1/6/2023 +181048,341131512,,NORTHEAST OHIO MEDICAL UNIVERSITY,84,11/23/2021 +181071,136007340,XN4BA2UMNY17,YONKERS CITY SCHOOL DISTRICT,84,1/2/2023 +181086,956000931,,SAN BERNARDINO COUNTY SUPERINTENDENT OF SCHOOLS,84,3/25/2021 +181090,742683499,,SOUTH TEXAS COLLEGE DISTRICT,84,3/25/2021 +181178,386028429,,WAYNE STATE UNIVERSITY,84,3/25/2021 +181225,926000078,,ANCHORAGE SCHOOL DISTRICT,84,3/25/2021 +181251,943233542,,WESTED,84,3/25/2021 +181292,113682643,CNSNH6LM6NY7,SUNY ERIE,84,6/2/2023 +181298,226002140,,NEWARK BOARD OF EDUCATION,84,3/25/2021 +181306,530199507,,GALLAUDET UNIVERSITY,84,3/25/2021 +181330,526000955,WE2EJHNL6LL5,HARFORD COUNTY PUBLIC SCHOOLS,84,12/31/2022 +181377,222912682,,MONTCLAIR STATE UNIVERSITY,84,3/25/2021 +181395,352544200,,TULSA COMMUNITY COLLEGE,84,12/14/2021 +181427,870217280,,BRIGHAM YOUNG UNIVERSITY,84,3/25/2021 +181601,731180305,,OKLAHOMA STUDENT LOAN AUTHORITY,84,3/25/2021 +181646,990266482,,"DEPARTMENT OF EDUCATION, STATE OF HAWAII",84,3/25/2021 +181925,131740451,,FORDHAM UNIVERSITY,84,3/25/2021 +182089,581216007,,"LIFE UNIVERSITY, INC.",84,3/25/2021 +182145,066001876,,CITY OF NEW HAVEN,84,3/25/2021 +182312,226000910,,NEW JERSEY INSTITUTE OF TECHNOLOGY,84,3/25/2021 +182630,526000941,FJJQCCXBQBK5,FREDERICK COUNTY BOARD OF EDUCATION/PUBLIC SCHOOLS,84,12/8/2022 +182926,226001086,,"RUTGERS, THE STATE UNIVERSITY OF NEW JERSEY",84,3/25/2021 +183200,526000882,,ANNE ARUNDEL COUNTY BOARD OF EDUCATION,84,3/25/2021 +184216,596000530,,BROWARD COUNTY DISTRICT SCHOOL BOARD,84,3/25/2021 +184257,046001418,,"CITY OF WORCESTER, MASSACHUSETTS",84,3/25/2021 +184274,256007669,V8NCPGDCHZ41,ALLEGHENY INTERMEDIATE UNIT 3,84,12/27/2022 +184292,520891845,M2ENSJTABU43,MONTGOMERY COLLEGE,84,1/4/2023 +184315,611011211,,EASTERN KENTUCKY UNIVERSITY,84,3/25/2021 +184321,431549458,,COMM COLLEGE DIST OF CNTRL SW MOI - OZARKS TECH C,84,1/13/2022 +184331,526000968,LBUJLL83NF95,HOWARD COUNTY BOARD OF EDUCATION,84,2/2/2023 +184509,344428219,DZW4S1HT2EL9,HEIDELBERG UNIVERSITY,84,1/24/2023 +184530,566172047,,NORTH CAROLINA STATE EDUCATION ASSISTANCE AUTHORITY,84,3/25/2021 +184565,131967321,,MERCY COLLEGE,84,3/25/2021 +184609,726000592,,JEFFERSON PARISH PUBLIC SCHOOL SYSTEM,84,3/25/2021 +184805,381713563,,OAKLAND SCHOOLS,84,3/25/2021 +185713,756002311,KSJKFACJJLB5,RICHARDSON INDEPENDENT SCHOOL DISTRICT,84,12/1/2022 +185746,146013200,,NYS HIGHER EDUCATION SERVICES CORP,84,3/25/2021 +185848,540722061,,CITY OF VIRGINIA BEACH,84,3/25/2021 +185899,226002199,,PATERSON BOARD OF EDUCATION,84,3/25/2021 +186066,626000834,,SHELBY COUNTY BOARD OF EDUCATION,84,3/25/2021 +186428,231738850,,MONTGOMERY COUNTY INTERMEDIATE UNIT,84,1/28/2022 +186541,826000945,,UNIVERSITY OF IDAHO,84,3/25/2021 +186728,586000191,,BIBB COUNTY SCHOOL DISTRICT,84,3/22/2022 +186782,951644035,,POINT LOMA NAZARENE UNIVERSITY AND AFFILIATE,84,3/25/2021 +186787,746001658,JUFXAND8BUG9,MCALLEN INDEPENDENT SCHOOL DISTRICT,84,12/6/2022 +186979,330386669,FG8HAYKLHGN7,LAKE ELSINORE UNIFIED SCHOOL DISTRICT,84,4/25/2023 +187036,226002193,,PASSAIC PUBLIC SCHOOLS,84,4/9/2022 +187203,236004102,,SCHOOL DISTRICT OF PHILADELPHIA,84,3/25/2021 +187283,952644299,,SAN DIEGO COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +187375,746000691,GD9NKJUFC2D3,DONNA INDEPENDENT SCHOOL DISTRICT,84,1/13/2023 +187547,431261525,,HIGHER EDUCATION LOAN AUTHORITY OF THE STATE OF MISSOURI,84,3/25/2021 +187661,946000385,,OAKLAND UNIFIED SCHOOL DISTRICT,84,3/25/2021 +187998,952378800,,GARDEN GROVE UNIFIED SCHOOL DISTRICT,84,3/25/2021 +188134,756002676,,TYLER JUNIOR COLLEGE,84,12/16/2021 +189247,576000322,,CHARLESTON COUNTY SCHOOL DISTRICT,84,3/25/2021 +189616,376014070,,ILLINOIS STATE UNIVERSITY,84,3/25/2021 +189671,741556846,V3J6KE9L9YL7,JUDSON INDEPENDENT SCHOOL DISTRICT,84,12/21/2022 +189818,942840774,,CLOVIS UNIFIED SCHOOL DISTRICT,84,6/27/2022 +189897,956006659,,SOUTHWESTERN COMMUNITY COLLEGE DISTRICT,84,3/3/2022 +189904,710503641,,ARKANSAS DEVELOPMENT FINANCE AUTHORITY,84,3/25/2021 +189911,370813229,,OSF HEALTHCARE SYSTEM,84,7/6/2022 +190159,356007261,HJ7RDM3Q45X7,VIGO COUNTY SCHOOL CORPORATION,84,3/31/2023 +190213,391853833,,"ASCENDIUM EDUCATION SOLUTIONS, INC.",84,3/25/2021 +190346,941054700,,LODI UNIFIED SCHOOL DISTRICT,84,12/17/2021 +191889,221494434,,FAIRLEIGH DICKINSON UNIVERSITY,84,3/25/2021 +192142,951643334,,LOYOLA MARYMOUNT UNIVERSITY,84,3/25/2021 +192442,546001455,,"CITY OF NORFOLK, VIRGINIA",84,3/25/2021 +192446,043167352,,UNIVERSITY OF MASSACHUSETTS,84,3/25/2021 +192477,586000246,,FULTON COUNTY BOARD OF EDUCATION,84,3/25/2021 +192511,586000206,GM1WDDKTGNJ1,SAVANNAH-CHATHAM COUNTY PUBLIC SCHOOL SYSTEM,84,4/6/2023 +192542,586000134,,ATLANTA INDEPENDENT SCHOOL SYSTEM,84,3/25/2021 +192565,731377584,,UNIVERSITY OF OKLAHOMA NORMAN CAMPUS,84,3/25/2021 +192639,646000964,,MISSISSIPPI GULF COAST COMMUNITY COLLEGE,84,8/8/2022 +192649,746001421,,HUMBLE INDEPENDENT SCHOOL DISTRICT,84,12/9/2021 +193043,646000783,,STATE OF MISSISSIPPI INSTITUTIONS OF HIGHER LEARNING,84,3/25/2021 +193279,310729591,,COLUMBUS STATE COMMUNITY COLLEGE,84,3/25/2021 +193349,522064235,,BALTIMORE CITY PUBLIC SCHOOL SYSTEM,84,3/25/2021 +193434,316000758,,CINCINNATI CITY SCHOOL DISTRICT,84,3/25/2021 +193461,646000453,,HINDS COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +193596,366009515,,NORTHEASTERN ILLINOIS UNIVERSITY,84,3/25/2021 +193670,596000500,,ALACHUA COUNTY DISTRICT SCHOOL BOARD,84,4/7/2022 +193671,952962604,UTQ7GBV8BLL8,CALIFORNIA LUTHERAN UNIVERSITY & AFFILIATE,84,10/25/2022 +193991,620694743,,"THE METROPOLITAN GOVERNMENT OF NASHVILLE & DAVIDSON COUNTY, TENNESSEE",84,3/25/2021 +194459,386004193,,"SCHOOL DISTRICT OF THE CITY OF DEARBORN, MICHIGAN",84,3/25/2021 +194874,586000232,WCMNR7WC8JF9,DOUGLAS COUNTY BOARD OF EDUCATION,84,4/4/2023 +195021,956000613,,CHULA VISTA ELEMENTARY SCHOOL DISTRICT,84,2/23/2022 +195427,636000767,,BIRMINGHAM CITY BOARD OF EDUCATION,84,12/6/2022 +196078,636001101,,ALABAMA STATE UNIVERSITY,84,3/25/2021 +196886,726001305,,ST. TAMMANY PARISH SCHOOL BOARD,84,1/29/2022 +196892,430786590,CH6QTM2K95W1,COMMUNITY COLLEGE DISTRICT OF ST. LOUIS,84,11/27/2022 +197070,731383996,,OKLAHOMA STATE UNIVERSITY,84,3/25/2021 +197262,751249185,,EDUCATION SERVICE CENTER REGION 10,84,3/25/2021 +197499,330831357,,RIVERSIDE COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +197652,841383926,,WESTERN GOVERNORS UNIVERSITY,84,3/25/2021 +197851,946003004,MUNJKC8LKBJ1,COLLEGE OF THE SEQUOIAS COMMUNITY COLLEGE DISTRICT,84,3/31/2023 +197868,741143128,,ST. MARY'S UNIVERSITY,84,3/25/2021 +198446,521977456,,THE COMMUNITY COLLEGE OF BALTIMORE COUNTY,84,3/25/2021 +198496,516000297,,UNIVERSITY OF DELAWARE,84,3/25/2021 +198517,680342035,,CONTRA COSTA COMMUNITY COLLEGE DISTRICT,84,7/26/2022 +198615,956000025,HNEAQFHNYBB3,ALHAMBRA UNIFIED SCHOOL DISTRICT,84,2/23/2023 +199507,660234412,,UNIVERSIDAD CARLOS ALBIZU INC,84,2/3/2022 +199828,351330472,,"FRANCISCAN ALLIANCE, INC.",84,10/10/2022 +199844,420730347,,DES MOINES UNIVERSITY OSTEOPATHIC MEDICAL CENTER,84,3/25/2021 +200095,610600439,,KENTUCKY HIGHER EDUCATION ASSISTANCE AUTHORITY/ STUDENT LOAN CORP.,84,3/25/2021 +200449,636001097,,ALABAMA A&M UNIVERSITY,84,3/25/2021 +200576,226002304,C5VYV9ZLXBT1,CITY OF ELIZABETH SCHOOL DISTRICT-EIN NOTED,84,3/20/2023 +200795,231639151,,HARRISBURG AREA COMMUNITY COLLEGE,84,3/25/2021 +201044,952228663,W92UJUVFJYC4,ANTELOPE VALLEY COMMUNITY COLLEGE DISTRICT,84,4/3/2023 +201583,362703832,U6U3MSJA34N6,WAUKEGAN COMMUNITY UNIT SCHOOL DISTRICT NO. 60,84,4/6/2023 +201693,611320380,,KENTUCKY COMMUNITY AND TECHNICAL COLLEGE SYSTEM,84,3/25/2021 +202042,941574802,,STATE CENTER COMMUNITY COLLEGE DISTRICT,84,3/25/2021 +202092,351443425,,TRINITY HEALTH,84,6/3/2022 +202105,236291113,,GEISINGER HEALTH,84,3/25/2021 +202471,951644037,,PEPPERDINE UNIVERSITY,84,3/25/2021 +203744,756001697,SSFUTC29RJC7,GRAND PRAIRIE INDEPENDENT SCHOOL DISTRICT,84,2/23/2023 +203869,431617558,,BJC HEALTHCARE,84,10/5/2022 +205901,856005550,MZC6N49RJKA8,GALLUP-MCKINLEY COUNTY SCHOOLS,84,1/11/2023 +209135,522063496,,CHARTER SCHOOL DEVELOPMENT CORPORATION,84,6/2/2022 +209224,411778617,,EDUCATIONAL CREDIT MANAGEMENT CORPORATION,84,3/25/2021 +209451,576000265,,THE COLLEGE OF CHARLESTON,84,3/25/2021 +209715,570977955,,COASTAL CAROLINA UNIVERSITY,84,3/25/2021 +210445,576001204,,WINTHROP UNIVERSITY,84,3/25/2021 +211692,576000254,,CLEMSON UNIVERSITY,84,3/25/2021 +211695,576001153,,UNIVERSITY OF SOUTH CAROLINA,84,3/25/2021 +212257,760615245,J943LULZXK64,HARMONY PUBLIC SCHOOLS,84,12/12/2022 +212490,650216638,,"EVERGLADES COLLEGE, INC.",84,3/25/2021 +212629,576000950,,SOUTH CAROLINA STATE UNIVERSITY,84,3/25/2021 +213514,431747502,,"SAINT LUKE'S HEALTH SYSTEM, INC",84,9/26/2022 +214366,752659683,YESVC9SDGFK7,UPLIFT EDUCATION,84,12/9/2022 +214499,660491518,,GUAM DEPARTMENT OF EDUCATION,84,3/25/2021 +215447,590936101,,"EMBRY-RIDDLE AERONAUTICAL UNIVERSITY, INC.",84,3/25/2021 +218327,542052107,,EDWARD VIA VIRGINIA COLLEGE OF OSTEOPATHIC MEDICINE AND AFFILIATES,84,3/25/2021 +218734,362167854,,ROOSEVELT UNIVERSITY,84,3/25/2021 +219643,510305893,,DELAWARE STATE UNIVERSITY,84,3/25/2021 +220190,611014882,,UNIVERSITY OF LOUISVILLE,84,3/25/2021 +221966,340714538,,AULTMAN HEALTH FOUNDATION,84,10/5/2022 +223716,273339369,,REMINGTON COLLEGE,84,3/25/2021 +225629,061627882,GX7UG6VHDHC9,"GOODWIN UNIVERSITY, INC. AND SUBSIDIARIES",84,3/1/2023 +225665,880435559,,ROSEMAN UNIVERSITY OF HEALTH SCIENCES,84,3/25/2021 +228272,166002010,,ROCHESTER CITY SCHOOL DISTRICT,84,3/25/2021 +229253,261773196,,TWIN RIVERS UNIFIED SCHOOL DISTRICT,84,1/20/2022 +229592,742948339,,"IDEA PUBLIC SCHOOLS, INC.",84,3/25/2021 +229596,541398784,GD54V5XED254,"THE SCHOOL BOARD OF THE CITY OF NEWPORT NEWS, VIRGINIA",84,3/30/2023 +229737,743068519,NKYUBMDD67H3,COMMONWEALTH CHARTER ACADEMY CHARTER SCHOOL,84,2/14/2023 +230189,300686273,,NASSAU COMMUNITY COLLEGE,84,6/13/2022 +230427,621586836,,"EDUCATION LOAN FINANCE, INC.",84,3/25/2021 +230948,223524939,,VIRTUA HEALTH INC.,84,9/26/2022 +231704,546001570,ZU3QCHKBMW13,ROANOKE CITY PUBLIC SCHOOLS,84,1/12/2023 +231934,263770988,KL5JDE23TN48,CANYONS SCHOOL DISTRICT,84,12/21/2022 +232275,371058692,,HOSPITAL SISTERS HEALTH SYSTEM,84,10/5/2022 +235613,621056632,K49KP3ZCGW63,"CITY OF LAKELAND, TENNESSEE",84,12/8/2022 +237887,470617373,,COMMONSPIRIT HEALTH,84,10/6/2022 +237982,042492727,,QUINSIGAMOND COMMUNITY COLLEGE,84,9/22/2022 +238045,900531902,,COMMUNITY COLLEGE SYSTEM OF NEW HAMPSHIRE,84,3/25/2021 +238098,042325342,,SALEM STATE UNIVERSITY,84,3/25/2021 +238280,043010428,,BRIDGEWATER STATE UNIVERSITY,84,3/25/2021 +238343,042710011,,BUNKER HILL COMMUNITY COLLEGE,84,4/14/2022 +238347,900171867,,NORTHCENTRAL UNIVERSITY FKA WESTMED COLLEGE,84,3/25/2021 +239636,721028323,,FRANCISCAN MISSIONARIES OF OUR LADY HEALTH SYSTEM,84,7/25/2022 +240303,050395355,,RHODE ISLAND STUDENT LOAN AUTHORITY,84,3/25/2021 +240925,420680460,,DRAKE UNIVERSITY,84,3/25/2021 +242667,216000219,,TOWNSHIP OF LAKEWOOD SCHOOL DISTRICT,84,3/17/2022 +242736,026000937,,UNIVERSITY SYSTEM OF NEW HAMPSHIRE,84,3/25/2021 +244560,611730890,,OREGON STATE UNIVERSITY,84,3/25/2021 +244592,464727800,,UNIVERSITY OF OREGON,84,3/25/2021 +244593,364776757,,PORTLAND STATE UNIVERSITY,84,3/25/2021 +244668,362167804,,NATIONAL LOUIS UNIVERSITY,84,3/25/2021 +245063,272280465,,CLINICAL & PATIENT EDUCATORS ASSOC & UMA EDUCATION INC DBA ULTIMATE ME,84,3/25/2021 +245195,271503981,,"HERZING UNIVERSITY, LTD.",84,3/25/2021 +245476,731563627,,THE UNIVERSITY OF OKLAHOMA HEALTH SCIENCES CENTER,84,3/25/2021 +245842,541689909,,SCHOOL BOARD CITY OF RICHMOND,84,3/25/2021 +246537,812847693,,DETROIT PUBLIC SCHOOLS COMMUNITY DISTRICT,84,3/25/2021 +248006,821319401,,"PURDUE UNIVERSITY GLOBAL, INC.",84,3/25/2021 +248303,042254705,,AMERICAN STUDENT ASSISTANCE,84,3/25/2021 +248565,746002248,,TEXAS STATE UNIVERSITY,84,3/25/2021 +248573,208091013,,"CENTER FOR EXCELLENCE IN HIGHER EDUCATION, INC.",84,3/25/2021 +248703,521301088,,BON SECOURS MERCY HEALTH,84,9/28/2022 +249242,586000263,NP93D6WFDKG1,HENRY COUNTY BOARD OF EDUCATION,84,7/25/2023 +249257,056000329,,"CITY OF PROVIDENCE, RHODE ISLAND",84,3/25/2021 +249261,020274509,,SOUTHERN NEW HAMPSHIRE UNIVERSITY,84,3/25/2021 +249308,580566167,,THE CORPORATION OF MERCER UNIVERSITY,84,3/25/2021 +249510,815128459,,SOUTH UNIVERSITY - MEMBER LLC,84,3/25/2021 +249837,815126041,,THE ARTS INSTITUTES INTERNATIONAL LLC,84,3/25/2021 +251288,746001399,,UNIVERSITY OF HOUSTON - DOWNTOWN,84,2/18/2022 +252793,566001834,WXA2ESWNHJ83,ROWAN-SALISBURY BOARD OF EDUCATION,84,1/9/2023 +255162,460459671,,HARVEY W PETERS RESEARCH FOUNDATION,84,10/5/2022 +256201,260152908,MB5GWJCFBGC7,UNIVERSITY OF MASSACHUSETTS GLOBAL-9 MONTH AUDIT,84,12/12/2022 +258054,844813183,C9L1ZHMMTNH5,"BRYANT & STRATTON COLLEGE, INC.",84,5/1/2023 +258431,426004333,PLZ1BS2RC597,UNIVERSITY OF NORTHERN IOWA,84,6/29/2023 +258457,426004813,Z1H9VJS8NG16,STATE UNIVERSITY OF IOWA,84,6/30/2023 +258472,426004224,DQDBM7FGJPC5,IOWA STATE UNIVERSITY-EIN NOTED,84,7/6/2023 +73971,521068522,JNXVYWEXC959,"RFE/RL, INC.",90,3/25/2021 +197690,521968145,G22JVJGLK725,RADIO FREE ASIA,90,3/25/2021 +221792,421591205,,"MIDDLE EAST BRAODCASTING NETWORKS, INC.",90,3/25/2021 +85,010527066,,EASTERN MAINE HEALTHCARE SYSTEMS D/B/A NORTHERN LIGHT HEALTH,93,6/30/2022 +202,010238552,,MAINEHEALTH,93,6/23/2022 +511,010424969,L8KDMW1J82M9,"COMMUNITY CONCEPTS, INCORPORATED AND ITS AFFILIATES",93,3/16/2023 +658,016023748,FLJGNNHJJ6B3,"PENQUIS C.A.P., INC. AND ITS AFFILIATES",93,5/11/2023 +738,020222111,,TRUSTEES OF DARTMOUTH COLLEGE,93,3/25/2021 +895,020268285,,"SOUTHERN NEW HAMPHSIRE SERVICES, INC.",93,4/10/2022 +2155,042103580,,HARVARD UNIVERSITY,93,3/25/2021 +2262,042103881,,BETH ISRAEL DEACONESS MEDICAL CENTER,93,3/25/2021 +2961,042263040,,"DANA-FARBER CANCER INSTITUTE, INC. AND SUBSIDIARIES",93,3/25/2021 +3019,042304133,,"ACTION FOR BOSTON COMMUNITY DEVELOPMENT, INC.",93,3/25/2021 +3226,042452600,,"HARVARD PILGRIM HEALTH CARE, INC AND AFFILIATES",93,10/10/2022 +3278,042473133,,"COMMUNITY DAY CARE CENTER OF LAWRENCE, INC.",93,3/25/2021 +4292,042790311,,"BAYSTATE MEDICAL CENTER, INC.",93,6/16/2022 +5865,050258809,,BROWN UNIVERSITY,93,3/25/2021 +6735,060646973,,YALE UNIVERSITY,93,3/25/2021 +7194,060872959,,"NEW ENGLAND FARM WORKERS' COUNCIL, INC.",93,3/25/2021 +8827,112013303,,COLD SPRING HARBOR LABORATORY,93,3/25/2021 +8851,112047151,YZHQV1DK1NP3,"CATHOLIC CHARITIES NEIGHBORHOOD SERVICES, INC.",93,3/30/2023 +9532,113418133,,"NORTHWELL HEALTH, INC.",93,3/25/2021 +9899,113050340,NJBXJYAP8AA5,"YELED V'YALDA EARLY CHILDHOOD CENTER, INC.",93,1/5/2023 +10650,131624096,,THE MOUNT SINAI HOSPITAL,93,10/10/2022 +10670,131624135,,NEW YORK SOCIETY FOR THE RELIEF OF THE RUPTURED AND CRIPPLED,93,10/10/2022 +10677,131624158,,THE ROCKEFELLER UNIVERSITY,93,3/25/2021 +11261,131878704,,"U.S. COMMITTEE FOR REFUGEES AND IMMIGRANTS, INC. AND RELATED ENTITY",93,3/25/2021 +11448,131974191,,BRONXCARE HEALTH SYSTEM,93,10/5/2022 +11478,131988190,,RESEARCH FOUNDATION OF THE CITY UNIVERSITY OF NEW YORK,93,3/25/2021 +11754,132637308,UKCPHXSY7MB8,"THE MENTAL HEALTH ASSOCIATION OF NEW YORK CITY, INC",93,3/7/2023 +13106,133164477,JV7ZPKLK3NP6,"WOMEN IN NEED, INC.",93,3/31/2023 +15303,135596796,,BASSETT HEALTHCARE NETWORK,93,10/10/2022 +15322,135598093,,COLUMBIA UNIVERSITY,93,3/25/2021 +15516,135669201,,PUBLIC HEALTH SOLUTIONS,93,3/25/2021 +17225,141368361,,RESEARCH FOUNDATION FOR THE STATE UNIVERSITY OF NEW YORK,93,3/25/2021 +17262,141402155,,"HEALTH RESEARCH, INC.",93,3/25/2021 +17274,141410842,,"RESEARCH FOUNDATION FOR MENTAL HYGIENE, INC.14-1410842",93,3/25/2021 +18064,150532082,,CORNELL UNIVERSITY,93,3/25/2021 +18565,160743209,,UNIVERSITY OF ROCHESTER,93,3/25/2021 +19020,830382654,,ERIE COUNTY MEDICAL CENTER CORPORATION,93,9/22/2022 +20075,210634549,,"ATLANTICARE HEALTH SYSTEM, INC AND AFFILIATES",93,10/10/2022 +20567,221487576,,"HACKENSACK MERIDIAN HEALTH, INC.",93,10/10/2022 +23463,222842846,,"CHILD CARE CONSULTANTS, INC.",93,3/25/2021 +23522,222861978,,LIFESPAN CORPORATION AND AFFILIATES,93,3/25/2021 +25374,231352104,,CARING PEOPLE ALLIANCE,93,3/25/2021 +25395,231352166,,THE CHILDREN'S HOSPITAL OF PHILADELPHIA FOUNDATION & CONTROLLED AFFILI,93,3/25/2021 +25418,231352213,,"ST. LUKE'S HEALTH NETWORK, INC. AND CONTROLLED ENTITIES",93,6/23/2022 +25545,231352685,,UNIVERSITY OF PENNSYLVANIA,93,3/25/2021 +26234,222458317,,LEHIGH VALLEY HEALTH NETWORK AND SUBSIDIARIES,93,8/16/2022 +27118,232204725,,"COMMUNITY SERVICES FOR CHILDREN, INC",93,3/25/2021 +27282,232290323,,ALBERT EINSTEIN HEALTHCARE NETWORK,93,6/22/2022 +27372,232331531,,MAIN LINE HEALTH SYSTEM AND AFFILIATES,93,10/6/2022 +28643,236434390,DW1XZMGNFBL4,THE WISTAR INSTITUTE OF ANATOMY AND BIOLOGY,93,6/13/2023 +29479,237046393,EK81Q7HQA8N3,URBAN AFFAIRS COALITION,93,7/13/2023 +29588,237052541,,"CALIFORNIA RURAL INDIAN HEALTH BOARD, INC.",93,3/25/2021 +29737,237062976,,BAKERRIPLEY,93,3/25/2021 +31329,237156071,,FRED HUTCHINSON CANCER RESEARCH CENTER,93,3/25/2021 +31937,237203666,,THE J. DAVID GLADSTONE INSTITUTES,93,7/2/2022 +32124,237221025,,PUBLIC HEALTH MANAGEMENT CORPORATION,93,3/25/2021 +33339,237334012,E6RZE38JHS41,"CATHOLIC CHARITIES, DIOCESE OF SAN DIEGO",93,2/23/2023 +34557,237426300,ZY63XPTXNZC3,LEGACY HEALTH,93,12/21/2022 +35765,250965591,,UNIVERSITY OF PITTSBURGH OF THE COMMONWEALTH SYSTEM OF HIGHER EDUCATIO,93,3/25/2021 +35792,250965639,,YOUNG WOMEN'S CHRISTIAN ASSOCIATION GREATER PITTSBURGH,93,3/25/2021 +36006,251094911,,MERAKEY ALLEGHENY VALLEY SCHOOL,93,3/25/2021 +39110,316056230,,THE RESEARCH INSTITUTE AT NATIONWIDE CHILDREN'S HOSPITAL,93,3/25/2021 +40830,316053035,,CINCINNATI-HAMILTON COUNTY COMMUNITY ACTION AGENCY,93,10/10/2022 +41447,330328688,,LA JOLLA INSTITUTE FOR IMMUNOLOGY,93,7/14/2022 +41588,330435954,,THE SCRIPPS RESEARCH INSTITUTE,93,3/25/2021 +42338,340714585,,CLEVELAND CLINIC FOUNDATION DBA CLEVELAND CLINIC HEALTH SYSTEM,93,3/25/2021 +42780,341018992,,CASE WESTERN RESERVE UNIVERSITY,93,3/25/2021 +43333,341346763,,CHN HOUSING PARTNERS,93,7/10/2022 +47025,351859562,GEL5PMVKJYK8,GEMINUS CORPORATION,93,2/9/2023 +48063,361877640,,HEARTLAND ALLIANCE FOR HUMAN NEEDS & HUMAN RIGHTS,93,3/25/2021 +48300,362167060,,NORTHSHORE UNIVERSITY HEALTHSYSTEM,93,7/6/2022 +48349,362167808,,NATIONAL OPINION RESEARCH CENTER,93,3/25/2021 +48352,362167817,,NORTHWESTERN UNIVERSITY,93,3/25/2021 +48401,824184596,,"ADVOCATE AURORA HEALTH, INC.",93,9/8/2022 +48480,362174823,,RUSH SYSTEM FOR HEALTH,93,3/25/2021 +48485,362177139,,THE UNIVERSITY OF CHICAGO,93,3/25/2021 +49171,362597741,,"COMMUNITY AND ECONOMIC DEVELOPMENT ASSOCIATION OF COOK COUNTY, INC.",93,3/25/2021 +49340,362712912,,ILLINOIS ACTION FOR CHILDREN,93,3/25/2021 +50324,363261413,,ALLINA HEALTH SYSTEM,93,9/23/2022 +50593,362170833,,CHILDREN'S HOSPITAL OF CHICAGO MEDICAL CENTER AND AFFILIATED CORPORATI,93,5/13/2022 +53625,363152959,,NORTHWESTERN MEMORIAL HEALTHCARE,93,6/1/2022 +53832,371110690,,MEMORIAL HEALTH SYSTEM AND SUBSIDIARIES,93,7/6/2022 +55033,381405282,,BETHANY CHRISTIAN SERVICES AND ITS SUBSIDIARIES,93,3/25/2021 +55715,381976979,,WAYNE METROPOLITAN COMMUNITY ACTION AGENCY AND AFFILIATES,93,5/25/2022 +58433,390452970,,"MARSHFIELD CLINIC HEALTH SYSTEM, INC.",93,10/3/2022 +58643,390806261,,"THE MEDICAL COLLEGE OF WISCONSIN, INC",93,3/25/2021 +58758,391500074,,"CHILDREN'S HOSPITAL AND HEALTH SYSTEM, INC",93,7/19/2022 +63579,416011702,,MAYO CLINIC,93,3/25/2021 +65853,430653611,,WASHINGTON UNIVERSITY,93,3/25/2021 +68101,440605373,N52NHWMBZNG5,THE CHILDREN'S MERCY HOSPITAL,93,4/5/2023 +68155,440661018,,"TRUMAN MEDICAL CENTER, INCORPORATED",93,10/10/2022 +69071,460420063,DT3GJW3JNMN5,GREAT PLAINS TRIBAL LEADERS HEALTH BOARD,93,7/6/2023 +70117,480543809,,"SAINT FRANCIS MINISTRIES, INC.",93,7/29/2022 +70282,480680117,,UNIVERSITY OF KANSAS CENTER FOR RESEARCH,93,3/25/2021 +71243,510103684,,CHRISTIANA CARE HEALTH SYSTEM AND AFFILIATES,93,7/2/2022 +72105,510197108,,SANFORD BURNHAM PREBYS MEDICAL DISCOVERY INSTITUTE,93,3/25/2021 +76672,521800436,,"ASSOCIATION OF PUBLIC HEALTH LABORATORIES, INC.",93,3/25/2021 +78587,540620889,,INOVA HEALTH SYSTEM,93,12/8/2022 +80920,550665758,,WEST VIRGINIA UNIVERSITY RESEARCH CORPORATION,93,3/25/2021 +81362,560532129,,DUKE UNIVERSITY,93,3/25/2021 +81366,560532138,,WAKE FOREST UNIVERSITY,93,3/25/2021 +81635,560686338,,RESEARCH TRIANGLE INSTITUTE,93,3/25/2021 +87167,582106707,,NATIONAL FOUNDATION FOR THE CENTERS FOR DISEASE CONTROL AND PREVENTION,93,2/8/2022 +87913,590634433,,THE NEMOURS FOUNDATION,93,9/7/2022 +87947,590638499,,"MIAMI CHILDREN'S HEALTH SYSTEM, INC. D/B/A NICKLAUS CHILDREN'S HEALTH ",93,6/9/2022 +88612,591146765,,"EPISCOPAL CHILDREN'S SERVICES, INC.",93,12/1/2021 +88715,591221966,,"REDLANDS CHRISTIAN MIGRANT ASOCIATION, INC.",93,3/25/2021 +88898,591371754,,"COMMUNITY COORDINATED CARE FOR CHILDREN, INC & THE 4C FOUNDATION, INC",93,3/25/2021 +89272,591726273,,"ORLANDO HEALTH, INC",93,7/7/2022 +89485,591865751,,"CITRUS HEALTH NETWORK, INC.",93,1/20/2022 +89865,592142859,TN6UN1R1Y6C3,"SHANDS JACKSONVILLE MEDICAL CENTER, INC.",93,3/31/2023 +90240,592451713,,"H. LEE MOFFITT CANCER CENTER & RESEARCH INSTITUTE, INC.",93,10/12/2021 +90356,592551416,,"ECKERD YOUTH ALTERNATIVES, INC.",93,3/25/2021 +90878,593032613,,FLORIDA HEALTHY KIDS CORPORATION,93,3/25/2021 +91183,596012065,,NORTH BROWARD HOSPITAL DISTRICT,93,1/14/2022 +91926,610650121,VM74MG6JG5K3,"COMMUNITY ACTION COUNCIL FOR LEXINGTON-FAYETTE, BOURBON, HARRISON, AND",93,4/5/2023 +92121,610865554,,"COMMUNITY ACTION KENTUCKY, INC.",93,4/10/2022 +92994,620479367,,METHODIST LE BONHEUR HEALTHCARE,93,7/21/2022 +93019,620488046,,MEHARRY MEDICAL COLLEGE,93,3/25/2021 +93230,620646012,,"ST. JUDE CHILDREN'S RESEARCH HOSPITAL, INC. AND SUBSIDIARIES",93,3/25/2021 +94212,626000101,,ERLANGER HEALTH SYSTEM,93,4/7/2022 +94568,630307306,,THE CHILDREN'S HOSPITAL OF ALABAMA,93,9/26/2022 +94765,630644300,HPY7M5C5NBK8,TALLADEGA CLAY RANDOLPH CHILD CARE CORPORATION,93,3/31/2023 +95844,640440578,VD9RYD7FAFT1,"MISSISSIPPI ACTION FOR PROGRESS, INC.",93,8/31/2023 +96036,640653269,,"NORTH MISSISSIPPI HEALTH SERVICES, INC.",93,7/6/2022 +98571,720502505,,OCHSNER CLINIC FOUNDATION,93,10/5/2022 +101101,760461578,EYJCLM9UCLX5,TEXAS CHILDREN'S,93,6/22/2023 +101136,741109733,,CATHOLIC CHARITIES OF THE ARCHDIOCE OF GALVESTON-HOUSTON,93,12/16/2021 +101209,741152597,,MEMORIAL HERMANN HEALTH SYSTEM,93,10/6/2022 +101267,741180155,,THE HOUSTON METHODIST HOSPITAL SYSTEM DBA HOUSTON METHODIST,93,9/13/2022 +101742,741613878,,BAYLOR COLLEGE OF MEDICINE,93,3/25/2021 +102253,742044647,,NATIONAL JEWISH HEALTH,93,5/10/2022 +102701,742327454,RTLBXFXU7GN6,WORKFORCE SOLUTIONS - CAPITAL AREA WORKFORCE BOARD,93,6/27/2023 +103008,742481167,,"SOUTHWEST KEY PROGRAMS, INC. AND AFFILIATES",93,3/25/2021 +104609,751230219,,"SOUTH PLAINS COMMUNITY ACTION ASSOCIATION, INC.",93,12/3/2021 +106315,756000439,FV5JX1HNMDH3,TARRANT COUNTY HOSPITAL DISTRICT,93,6/21/2023 +109056,840166760,,CHILDREN'S HOSPITAL COLORADO,93,9/6/2022 +109522,840587601,,CHILD CARE ASSOCIATES,93,3/25/2021 +110753,850105601,,PRESBYTERIAN HEALTHCARE SERVICES,93,9/28/2022 +111758,860227210,,"CHICANOS POR LA CAUSA, INC. AND SUBSIDIARIES AND AFFILIATES",93,1/15/2022 +113665,910564748,,SEATTLE CHILDRENS,93,3/25/2021 +114207,910873623,,"ARCTIC SLOPE NATIVE ASSOCIATION, LIMITED",93,3/25/2021 +114425,911019392,XPMYYG76MMD5,YAKIMA VALLEY FARM WORKERS CLINIC,93,11/8/2022 +114429,911020139,HDKAKDQ1KLQ6,SEA MAR COMMUNITY HEALTH CENTERS,93,11/17/2022 +115770,920037322,ZNJZMWJ84HU5,"CATHOLIC SOCIAL SERVICES, INC.",93,3/1/2023 +115787,920040308,,TANANA CHIEFS CONFERENCE,93,3/25/2021 +115791,920041414,,YUKON-KUSKOKWIM HEALTH CORPORATION,93,3/25/2021 +115793,920041461,,MANIILAQ ASSOCIATION,93,3/25/2021 +115796,920041488,,NORTON SOUND HEALTH CORPORATION,93,3/25/2021 +115810,920044965,,BRISTOL BAY AREA HEALTH CORPORATION,93,3/15/2022 +115836,920056274,,SOUTHEAST ALASKA REGIONAL HEALTH CONSORTIUM,93,3/25/2021 +115971,920086076,,SOUTHCENTRAL FOUNDATION,93,3/25/2021 +116713,930591240,,"OREGON CHILD DEVELOPMENT COALITION, INC.",93,3/25/2021 +118297,941156276,,COMMUNITY HOSPITALS OF CENTRAL CA DBA COMMUNITY HEALTH SYSTEM,93,5/20/2022 +118321,941156365,,STANFORD UNIVERSITY,93,3/25/2021 +118676,941105628,,"KAISER FOUNDATION HEALTH PLAN, INC AND SUBSIDIARIES AND KAISER FOUNDAT",93,3/25/2021 +119054,941606519,,FRESNO ECONOMIC OPPORTUNITIES COMMISSION,93,3/25/2021 +119146,941646278,,PUBLIC HEALTH INSTITUTE,93,3/25/2021 +119575,942219349,,"TARZANA TREATMENT CENTERS, INC.",93,1/14/2022 +120912,942854057,,"IHC HEALTH SERVICES, INC.",93,10/6/2022 +122362,946000533,,COUNTY OF SANTA CLARA,93,3/25/2021 +123574,951648184,,NEIGHBORHOOD HOUSE ASSOCIATION,93,3/25/2021 +123653,951683878,,CITY OF HOPE AND AFFILIATES,93,3/25/2021 +123693,951690976,,CHILDREN'S HOME SOCIETY OF CALIFORNIA,93,10/12/2021 +123694,951690977,,CHILDRENS HOSPITAL LOS ANGELES,93,6/1/2022 +124143,952039198,,YMCA OF SAN DIEGO COUNTY,93,12/10/2021 +124278,952160097,,SALK INSTITUTE FOR BIOLOGICAL STUDIES,93,3/25/2021 +124464,952402760,,COMMUNITY ACTION PARTNERSHIP OF KERN,93,3/25/2021 +124481,952410253,,"COMMUNITY ACTION PARTNERSHIP OF SAN LUIS OBISPO COUNTY, INC.",93,3/25/2021 +124640,952557063,,"PUBLIC HEALTH FOUNDATION ENTERPRISES, INC. DBA HELUNA HEALTH",93,3/25/2021 +124691,952594166,,MEXICAN AMERICAN OPPORTUNITY FOUNDATION,93,1/18/2022 +124821,952707101,WMC4EN344RB6,CLINICA SIERRA VISTA,93,8/17/2023 +124934,952846605,,"RIVERSIDE-SAN BERNARDINO COUNTY INDIAN HEALTH, INC.",93,9/20/2022 +125751,953510046,,CRYSTAL STAIRS INC,93,3/25/2021 +125770,953522679,K5TMAM98KHD3,LOMA LINDA UNIVERSITY MEDICAL CENTER AND AFFILIATES,93,2/8/2023 +125871,953602641,,OPTIONS FOR LEARNING,93,3/25/2021 +127595,956042721,,SAN DIEGO STATE UNIVERSITY RESEARCH FOUNDATION,93,3/25/2021 +127959,956151774,,CHARLES R. DREW UNIVERSITY OF MEDICINE AND SCIENCE,93,3/25/2021 +128828,636000619,,STATE OF ALABAMA,93,3/25/2021 +128980,636005396,,THE UNIVERSITY OF ALABAMA AT BIRMINGHAM,93,3/25/2021 +129688,636001120,,TALLASSEE CITY BOARD OF EDUCATION,93,6/29/2022 +129769,630705119,X3G1ZDHMASN5,POARCH BAND OF CREEK INDIANS,93,7/6/2023 +129783,926001185,,STATE OF ALASKA,93,9/14/2022 +130060,920069243,,KENAITZE INDIAN TRIBE,93,4/24/2022 +130209,866000472,,MARICOPA COUNTY,93,3/25/2021 +130213,866000543,,PIMA COUNTY,93,4/20/2022 +130571,860093307,,SAN CARLOS APACHE TRIBE DEPARTMENT OF GOVERNMENTAL PROGRAMS,93,3/25/2021 +130579,860203228,,PASCUA YAQUI TRIBE GOVERNMENTAL OPERATIONS,93,6/29/2022 +130749,716046242,,UNIVERSITY OF ARKANSAS FOR MEDICAL SCIENCES,93,3/25/2021 +131803,680284175,,STATE OF CALIFORNIA,93,3/25/2021 +131804,943067788,,UNIVERSITY OF CALIFORNIA,93,3/25/2021 +131810,946000501,,"COUNTY OF ALAMEDA, CALIFORNIA",93,3/25/2021 +131816,946000506,,COUNTY OF BUTTE,93,3/25/2021 +131822,946000509,,COUNTY OF CONTRA COSTA,93,3/25/2021 +131826,946000512,,COUNTY OF FRESNO,93,3/25/2021 +131830,946000513,,COUNTY OF HUMBOLDT,93,3/25/2021 +131832,956000924,,COUNTY OF IMPERIAL,93,3/25/2021 +131837,956000925,,COUNTY OF KERN,93,3/25/2021 +131838,956000941,,KERN COUNTY SUPERINTENDENT OF SCHOOLS,93,3/25/2021 +131845,956000927,,COUNTY OF LOS ANGELES,93,3/25/2021 +131846,956000942,,LOS ANGELES COUNTY OFFICE OF EDUCATION,93,3/25/2021 +131847,946000518,,COUNTY OF MADERA,93,5/10/2022 +131851,946000520,,COUNTY OF MENDOCINO,93,10/6/2022 +131853,946002379,,MERCED COUNTY OFFICE OF EDUCATION,93,4/21/2022 +131858,946000524,,COUNTY OF MONTEREY,93,3/25/2021 +131860,946000525,,COUNTY OF NAPA,93,3/25/2021 +131866,956000943,,ORANGE COUNTY DEPARTMENT OF EDUCATION,93,3/25/2021 +131867,946000527,,COUNTY OF PLACER,93,3/25/2021 +131871,956000930,,COUNTY OF RIVERSIDE,93,3/25/2021 +131872,330830818,,RIVERSIDE COUNTY OFFICE OF EDUCATION,93,3/25/2021 +131873,946000529,,COUNTY OF SACRAMENTO,93,3/25/2021 +131877,956002748,,COUNTY OF SAN BERNARDINO,93,3/25/2021 +131878,956000934,,COUNTY OF SAN DIEGO,93,3/25/2021 +131880,946000531,,COUNTY OF SAN JOAQUIN,93,3/25/2021 +131884,946000532,,COUNTY OF SAN MATEO,93,3/25/2021 +131885,956002833,,COUNTY OF SANTA BARBARA,93,3/25/2021 +131888,770272168,,SANTA CLARA COUNTY OFFICE OF EDUCATION,93,3/25/2021 +131891,946000535,,COUNTY OF SHASTA,93,3/25/2021 +131898,946000538,,COUNTY OF SOLANO,93,3/25/2021 +131903,946000540,,COUNTY OF STANISLAUS,93,3/25/2021 +131904,946002388,,STANISLAUS COUNTY OFFICE OF EDUCATION,93,3/25/2021 +131915,956000944,,COUNTY OF VENTURA,93,3/25/2021 +131917,946000548,,COUNTY OF YOLO,93,3/25/2021 +131928,946027360,J2JJNJAU6E26,CITY OF NEWARK,93,4/4/2023 +131929,946000384,,CITY OF OAKLAND,93,3/25/2021 +132266,946000417,,CITY AND COUNTY OF SAN FRANCISCO,93,3/25/2021 +133079,946000529,,SACRAMENTO EMPLOYMENT AND TRAINING AGENCY,93,3/25/2021 +133826,956000213,,BALDWIN PARK UNIFIED SCHOOL DISTRICT,93,4/12/2022 +133881,956002457,,POMONA UNIFIED SCHOOL DISTRICT,93,3/25/2021 +134720,840644739,,STATE OF COLORADO,93,3/25/2021 +134732,846000732,,"ADAMS COUNTY, COLORADO",93,3/25/2021 +134734,846000740,,"ARAPAHOE COUNTY, COLORADO",93,3/25/2021 +134738,846000748,,"BOULDER COUNTY, COLORADO",93,2/14/2023 +134751,846000764,,EL PASO COUNTY,93,3/25/2021 +134760,846000774,,JEFFERSON COUNTY GOVERNMENT,93,3/25/2021 +134765,846000779,,"LARIMER COUNTY, COLORADO",93,7/21/2022 +134792,846000813,,WELD COUNTY GOVERNMENT,93,7/29/2022 +135992,516000279,,STATE OF DELAWARE,93,3/25/2021 +136063,596001874,,STATE OF FLORIDA,93,3/25/2021 +136086,591713947,,"PUBLIC HEALTH TRUST OF MAIMI-DADE COUNTY, FLORIDA",93,5/27/2022 +136102,596000661,,"HILLSBOROUGH COUNTY, FLORIDA",93,3/25/2021 +136126,596000800,,PINELLAS COUNTY,93,4/14/2022 +136810,591415030,,SEMINOLE TRIBE OF FLORIDA,93,7/6/2022 +136829,580973190,,STATE OF GEORGIA/STATE ACCOUNTING OFFICE,93,3/25/2021 +137883,996000449,,STATE OF HAWAII DEPARTMENT OF HEALTH,93,3/25/2021 +137884,996001089,,DEPARTMENT OF HUMAN SERVICES,93,3/25/2021 +137965,826000952,,STATE OF IDAHO,93,3/25/2021 +138467,820255928,,NEZ PERCE TRIBE,93,3/25/2021 +138655,366006600,,"LAKE COUNTY, ILLINOIS",93,9/19/2022 +138815,366005820,,CITY OF CHICAGO,93,3/25/2021 +139586,366006082,,CITY OF ROCKFORD,93,9/22/2022 +143605,420933966,,STATE OF IOWA,93,3/25/2021 +143683,426004519,K32TGXKSEL64,"POLK COUNTY, IOWA",93,1/27/2023 +144797,481124839,,STATE OF KANSAS,93,3/25/2021 +144884,486000798,,SEDGWICK COUNTY,93,6/23/2022 +146632,726000720,,STATE OF LOUISIANA,93,3/25/2021 +146694,726000223,,CADDO PARISH COMMISSION,93,8/11/2022 +147110,016000001,,STATE OF MAINE,93,3/25/2021 +147719,526002033,,STATE OF MARYLAND,93,3/25/2021 +147913,046002284,,COMMONWEALTH OF MASSACHUSETTS,93,3/25/2021 +148713,386004849,,"GENESEE COUNTY, MICHIGAN",93,7/26/2022 +148738,386004868,,"MACOMB COUNTY, MICHIGAN",93,3/25/2021 +150622,237249643,,SAULT STE. MARIE TRIBE OF CHIPPEWA INDIANS,93,10/5/2022 +150662,416007162,,STATE OF MINNESOTA,93,3/25/2021 +150696,416005786,,DAKOTA COUNTY,93,10/10/2022 +150704,416005801,,HENNEPIN COUNTY MINNESOTA,93,3/25/2021 +150739,416005875,,RAMSEY COUNTY,93,3/25/2021 +150746,416005890,JLKFMAQAN397,ST LOUIS COUNTY,93,7/11/2023 +152109,411737979,,WHITE EARTH RESERVATION,93,7/6/2022 +152111,410965719,,GOVERNMENTAL DEPT OF THE FOND DU LAC BAND OF LAKE SUPERIOR CHIPPEWA,93,6/30/2022 +152133,646000749,,THE STATE OF MISSISSIPPI,93,3/25/2021 +152717,640345731,,MISSISSIPPI BAND OF CHOCTAW INDIANS,93,10/14/2022 +152734,446000987,,STATE OF MISSOURI,93,3/25/2021 +154226,810302402,,STATE OF MONTANA,93,3/25/2021 +154770,810432358,WQV6F3GXKPR8,NORTHERN CHEYENNE TRIBE,93,6/30/2023 +154800,470491233,,STATE OF NEBRASKA,93,3/25/2021 +154916,476006455,,"DOUGLAS COUNTY, NEBRASKA",93,1/21/2022 +155928,886000022,,STATE OF NEVADA,93,3/25/2021 +155943,886000138,,WASHOE COUNTY,93,12/27/2021 +156039,026000618,,STATE OF NEW HAMPSHIRE,93,3/25/2021 +156544,226002443,,COUNTY OF HUDSON,93,3/25/2021 +156567,216000954,,COUNTY OF OCEAN,93,10/14/2022 +158049,856000565,,NEW MEXICO DEPARTMENT OF HEALTH,93,3/25/2021 +158054,856000565,,"NEW MEXICO CHILDREN, YOUTH AND FAMILIES DEPARTMENT",93,3/25/2021 +158354,146002563,,COUNTY OF ALBANY,93,3/25/2021 +158355,166002554,,"COUNTY OF ALLEGANY, STATE OF NEW YORK",93,3/25/2021 +158366,156000453,,"COUNTY OF DELAWARE, NEW YORK",93,3/25/2021 +158368,166002558,,ERIE COUNTY,93,3/25/2021 +158385,166002564,,"COUNTY OF NIAGARA, NEW YORK",93,3/25/2021 +158388,156000461,,"COUNTY OF ONONDAGA, NEW YORK",93,3/25/2021 +158391,146002567,,COUNTY OF ORANGE,93,3/25/2021 +158397,146002569,LPXDBJAJRMU5,"COUNTY OF RENSSELAER, NEW YORK",93,7/6/2023 +158399,136007344,,COUNTY OF ROCKLAND,93,3/25/2021 +158408,116000464,,"SUFFOLK COUNTY, NEW YORK",93,3/25/2021 +158418,136007353,,WESTCHESTER COUNTY,93,3/25/2021 +158720,136400434,,THE CITY OF NEW YORK,93,3/25/2021 +160912,160786768,,SENECA NATION OF INDIANS,93,7/1/2022 +160913,161007650,G9GLDN3W57D1,SAINT REGIS MOHAWK TRIBE,93,8/31/2023 +160966,566000279,W5TCDKMLHE69,"BUNCOMBE COUNTY, NC",93,1/5/2023 +161007,566000291,,COUNTY OF CUMBERLAND,93,12/21/2021 +161023,566000297,LJ5BA6U2HLM7,DURHAM COUNTY,93,12/31/2022 +161027,566000450,ZTVELM361423,"FORSYTH COUNTY, NORTH CAROLINA",93,3/31/2023 +161044,566000305,,GUILFORD COUNTY,93,1/13/2022 +161100,566000319,,MECKLENBURG COUNTY,93,3/25/2021 +161105,566000321,,COUNTY OF MONTGOMERY,93,2/23/2022 +161115,566000324,F7TLT2GMEJE1,NEW HANOVER COUNTY,93,12/31/2022 +161190,566000347,,"WAKE COUNTY, NORTH CAROLINA",93,3/25/2021 +161800,560572090,,EASTERN BAND OF CHEROKEE INDIANS,93,3/25/2021 +162380,311334820,,STATE OF OHIO,93,3/25/2021 +162442,346000817,,CUYAHOGA COUNTY,93,9/19/2022 +162449,316400067,,FRANKLIN COUNTY,93,3/25/2021 +162455,316000063,,HAMILTON COUNTY,93,3/25/2021 +162474,346000177,,MAHONING COUNTY,93,8/24/2022 +162501,346002767,,SUMMIT COUNTY,93,3/25/2021 +165971,730717979,,CHOCTAW NATION OF OKLAHOMA,93,3/25/2021 +165989,730932018,,MUSCOGEE CREEK NATION,93,3/25/2021 +166003,730772869,,ABSENTEE SHAWNEE TRIBE OF INDIANS OF OKLAHOMA,93,3/25/2021 +166006,730801256,KJYDNF7PDEF1,SEMINOLE NATION OF OKLAHOMA GOV'T SERVICES DEPARTMENT,93,7/6/2023 +166019,931070707,,STATE OF OREGON,93,3/25/2021 +166023,936002286,,"CLACKAMAS COUNTY, OREGON",93,3/25/2021 +166040,936002303,,"LANE COUNTY, OREGON",93,6/29/2022 +166046,936002309,,MULTNOMAH COUNTY OREGON,93,3/25/2021 +166791,010661737,,COMMONWEALTH OF PENNSYLVANIA,93,3/25/2021 +166805,256001017,,"COUNTY OF ALLEGHENY, PENNSYLVANIA",93,3/25/2021 +166818,236003040,,COUNTY OF CHESTER,93,9/28/2022 +166825,236003043,,COUNTY OF DAUPHIN,93,10/6/2022 +166828,256001027,,"COUNTY OF ERIE, PENNSYLVANIA",93,10/6/2022 +166842,231663078,,COUNTY OF LEHIGH,93,9/26/2022 +166849,236003126,,COUNTY OF MONTGOMERY,93,3/25/2021 +169669,231740825,,BERKS COUNTY INTERMEDIATE UNIT,93,3/25/2021 +169837,231736237,,DELAWARE COUNTY INTERMEDIATE UNIT,93,2/17/2022 +170255,056000522,,STATE OF RHODE ISLAND AND PROVIDENCE PLANTATIONS,93,3/25/2021 +170349,576000286,,STATE OF SOUTH CAROLINA,93,3/25/2021 +170831,466000364,,STATE OF SOUTH DAKOTA,93,4/6/2022 +171359,626001445,,STATE OF TENNESSEE,93,3/25/2021 +171436,626000841,,"SHELBY COUNTY, TENNESSEE",93,3/25/2021 +171548,626000259,,"CITY OF CHATTANOOGA, TENNESSEE",93,8/4/2022 +171944,746000089,,STATE OF TEXAS C/O COMPTROLLER OF PUBLIC ACCOUNTS,93,3/25/2021 +171961,746002039,WGCSBPYDLS78,"BEXAR COUNTY, TEXAS",93,4/28/2023 +172047,760454514,,HARRIS COUNTY,93,3/25/2021 +172054,746000717,,"COUNTY OF HIDALGO, TEXAS",93,3/25/2021 +172174,746000192,,TRAVIS COUNTY TEXAS,93,3/26/2022 +174196,741588856,,REGION 19 EDUCATION SERVICE CENTER,93,1/6/2022 +175005,756049012,,NORTH CENTRAL TEXAS COUNCIL OF GOVERNMENTS,93,3/25/2021 +175013,876000545,,STATE OF UTAH,93,3/25/2021 +175032,876000316,,SALT LAKE COUNTY,93,7/14/2022 +175300,036000264,,STATE OF VERMONT,93,3/25/2021 +175887,546001736,,COMMONWEALTH OF VIRGINIA,93,3/25/2021 +176254,546001103,,CITY OF ALEXANDRIA,93,7/12/2022 +176280,546001556,,CITY OF RICHMOND,93,10/6/2022 +176362,916001089,,STATE OF WASHINGTON C/O OFFICE OF FINANCIAL MANAGEMENT,93,3/25/2021 +177397,910760952,,QUINAULT INDIAN NATION,93,6/29/2022 +177452,550526580,,STATE OF WEST VIRGINIA,93,3/25/2021 +177867,396028867,,STATE OF WISCONSIN,93,3/25/2021 +177883,396005684,,COUNTY OF DANE,93,3/25/2021 +179719,396081138,,ONEIDA NATION,93,3/25/2021 +179738,830208667,,STATE OF WYOMING,93,3/25/2021 +180174,660433568,L46HH5KH8CA1,AUTONOMOUS MUNICIPALITY OF CAGUAS,93,4/5/2023 +180849,450314494,,SPIRIT LAKE TRIBE,93,6/30/2022 +181657,356000158,,STATE OF INDIANA,93,3/25/2021 +181658,066000798,,STATE OF CONNECTICUT,93,3/25/2021 +181661,561611588,,STATE OF NORTH CAROLINA,93,3/25/2021 +181663,736017987,,STATE OF OKLAHOMA,93,3/25/2021 +181664,866004791,,STATE OF ARIZONA,93,3/25/2021 +181667,216000928,,STATE OF NEW JERSEY,93,3/25/2021 +181744,596000573,,MIAMI-DADE COUNTY,93,3/25/2021 +181745,156000460,,"ONEIDA COUNTY, NEW YORK",93,3/25/2021 +181758,043314093,,"BMC HEALTH SYSTEM, INC.",93,3/25/2021 +181790,521640403,,CHILDREN'S NATIONAL MEDICAL CENTER AND SUBSIDIARIES,93,6/13/2022 +181798,350983617,,"COMMUNITY HEALTH NETWORK, INC.",93,6/22/2022 +181877,310833936,,CHILDREN'S HOSPITAL MEDICAL CENTER,93,3/25/2021 +181984,391140880,,HO-CHUNK NATION,93,3/23/2023 +182191,526000878,,"ANNE ARUNDEL COUNTY, MARYLAND",93,8/22/2022 +182237,386004895,,"CHARTER COUNTY OF WAYNE, MICHIGAN",93,3/25/2021 +182318,316000172,,MONTGOMERY COUNTY,93,3/25/2021 +182353,730945447,,CITIZEN POTAWATOMI NATION,93,3/25/2021 +182415,060995554,VKELG69WZM38,MASHANTUCKET PEQUOT TRIBAL NATION,93,2/6/2023 +182432,043320571,,CAMBRIDGE PUBLIC HEALTH COMMISSION/CAMBRIDGE HEALTH ALLIANCE,93,8/30/2022 +182608,410989737,G11HL1HD82Z4,SHAKOPEE MDEWAKANTON SIOUX COMMUNITY,93,2/14/2023 +182945,386178758,,SAGINAW CHIPPEWA INDIAN TRIBE OF MICHIGAN,93,7/6/2022 +183299,741603950,,THE HARRIS CENTER FOR MENTAL HEALTH AND IDD,93,3/25/2021 +183356,042774441,,BOSTON CHILDRENS HOSPITAL AND SUBSIDIARIES,93,3/25/2021 +183392,750808769,TW3HQTG7SHM5,"CATHOLIC CHARITIES DIOCESE OF FORT WORTH, INC.",93,7/21/2023 +183414,043293659,FFEEFY1RB5B5,SEVEN HILLS FOUNDATION AND AFFILIATES,93,12/20/2022 +183686,911631806,,HARBORVIEW MEDICAL CENTER,93,9/26/2022 +183705,951691330,,VOLUNTEERS OF AMERICA OF LOS ANGELES,93,3/25/2021 +183782,250965469,EJRKDPC1R9P8,ACTION HOUSING INC. AND SUBSIDIARIES,93,6/30/2023 +183970,530196617,,UNITED STATES CONFERENCE OF CATHOLIC BISHOPS,93,3/25/2021 +184345,470744117,,PONCA TRIBE OF NEBRASKA,93,6/30/2022 +184511,746002164,JTALGHD9SUH5,BEXAR COUNTY HOSPITAL DISTRICT D/B/A UNIVERSITY HEALTH ,93,8/29/2023 +184744,946000539,,COUNTY OF SONOMA,93,3/25/2021 +184937,910838426,,MUCKLESHOOT INDIAN TRIBE,93,12/8/2022 +185003,042777810,,"PEOPLE ACTING IN COMMUNITY ENDEAVORS, INC.",93,3/25/2021 +185026,156000449,,BROOME COUNTY,93,3/25/2021 +186051,753159126,KT4MLLHK99V6,ATCC GLOBAL & SUBSIDIARIES,93,5/16/2023 +186071,746000756,,EL PASO COUNTY HOSPITAL DISTRICT D/B/A UNIVERSITY MEDICAL CENTER,93,7/2/2022 +186354,880151573,,SOUTHERN NEVADA HEALTH DISTRICT,93,3/15/2022 +186634,132574854,,LUTHERAN IMMIGRATION AND REFUGEE SERVICE,93,3/25/2021 +187129,236003047,,CITY OF PHILADELPHIA,93,4/5/2023 +187160,236003046,,COUNTY OF DELAWARE,93,3/25/2021 +187214,886000028,,"CLARK COUNTY, NEVADA",93,3/25/2021 +187253,146002635,,"COUNTY OF WASHINGTON, NEW YORK",93,3/25/2021 +187679,381357020,,HENRY FORD HEALTH SYSTEM,93,10/6/2022 +187697,351955872,,"INDIANA UNIVERSITY HEALTH, INC. AND SUBSIDIARIES",93,10/6/2022 +187704,916001359,,PIERCE COUNTY,93,10/3/2022 +187746,846000580,,CITY AND COUNTY OF DENVER,93,3/25/2021 +187804,586001729,,"FULTON COUNTY, GEORGIA",93,2/2/2023 +187823,236003049,,"COUNTY OF BERKS, PENNSYLVANIA",93,10/6/2022 +187841,166002557,ZSPKQ36C6GD4,CHEMUNG COUNTY TREASURER,93,8/7/2023 +187844,841343242,,DENVER HEALTH AND HOSPITAL AUTHORITY,93,10/10/2022 +187899,116000463,,"COUNTY OF NASSAU, NEW YORK",93,3/25/2021 +187997,946000521,,MERCED COUNTY,93,3/25/2021 +188084,236003055,,"COUNTY OF LANCASTER, PENNSYLVANIA",93,10/6/2022 +188209,953081695,,"CHILD CARE RESOURCE CENTER, INC.",93,3/25/2021 +188494,156000459,,"COUNTY OF MADISON, NEW YORK",93,3/25/2021 +188874,166002563,,"COUNTY OF MONROE, NEW YORK",93,3/25/2021 +189454,731374986,,CHICKASAW NATION,93,3/25/2021 +189799,660437470,,DEPARTMENT OF HEALTH OF THE COMMONWEALTH OF PUERTO RICO,93,3/25/2021 +189875,860044545,,TOHONO O'ODHAM NATION GOVERNMENT SERVICES DEPARTMENT,93,3/25/2021 +189972,946000534,,COUNTY OF SANTA CRUZ,93,3/25/2021 +190096,916000986,,KING COUNTY PUBLIC HOSPITAL DISTRICT NO. 1,93,4/10/2022 +191859,226002433,,COUNTY OF ESSEX,93,10/7/2022 +191897,911004074,,LUMMI INDIAN BUSINESS COUNCIL,93,3/25/2021 +192079,610600439,,COMMONWEALTH OF KENTUCKY,93,6/30/2022 +192115,860092335,,THE NAVAJO NATION,93,3/25/2021 +192613,146002566,,DUTCHESS COUNTY,93,3/25/2021 +192783,910557816,,GOVERNMENTAL DEPARTMENTS OF THE TULALIP TRIBES OF WASHINGTON,93,10/3/2022 +192799,946000545,,COUNTY OF TULARE,93,3/25/2021 +192840,386006309,,REGENTS OF THE UNIVERSITY OF MICHIGAN,93,3/25/2021 +192887,450223071,,TURTLE MOUNTAIN BAND OF CHIPPEWA INDIANS,93,7/6/2022 +193062,146013200,,STATE OF NEW YORK,93,3/25/2021 +193137,920162721,,ALASKA NATIVE TRIBAL HEALTH CONSORTIUM,93,3/25/2021 +193401,316400223,,"CITY OF COLUMBUS, OHIO",93,3/25/2021 +193452,383278535,,POKAGON BAND OF POTAWATOMI INDIANS,93,1/2/2023 +193590,346000817,,CUYAHOGA COUNTY,93,3/25/2021 +193634,931176109,,OREGON HEALTH & SCIENCE UNIVERSITY,93,3/25/2021 +193698,450309764,KADSKJ11BLH5,STATE OF NORTH DAKOTA,93,3/31/2023 +193854,756000920,,"DENTON COUNTY, TEXAS",93,7/2/2022 +194208,586001198,,THE FULTON - DEKALB HOSPITAL AUTHORITY,93,11/16/2022 +194244,396005720,,MILWAUKEE COUNTY,93,3/25/2021 +194823,590624424,,"MOUNT SINAI MEDICAL CENTER OF FLORIDA, INC",93,7/6/2022 +195540,741557575,,HOUSTON-GALVESTON AREA COUNCIL,93,3/25/2021 +195773,561376950,,"NOVANT HEALTH, INC.",93,10/6/2022 +195844,953484589,,ADVENTIST HEALTH SYSTEM/WEST,93,10/3/2022 +195946,660055941,,PUERTO RICO MENTAL HEALTH AND ANTI-ADDICTION SERVICE ADMINISTRATION,93,3/25/2021 +196000,952833205,TL1GXSM5USD7,FAMILY HEALTH CENTERS OF SAN DIEGO,93,4/4/2023 +196303,592198911,,"LUTHERAN SERVICES FLORIDA, INC. AND SUBSIDIARIES",93,3/25/2021 +197012,364096312,,ILLINOIS NETWORK OF CHILD CARE RESOURCE AND REFERRAL AGENCIES,93,12/9/2021 +197107,731509406,,OSAGE NATION GOVERNMENTAL PROGRAMS DEPARTMENT,93,7/6/2022 +197363,146002571,,"COUNTY OF SARATOGA, NEW YORK",93,3/25/2021 +197527,561022483,,TELAMON CORPORATION,93,3/25/2021 +197537,580566256,,EMORY UNIVERSITY,93,3/25/2021 +197622,680006282,,SAN JOAQUIN COUNTY OFFICE OF EDUCATION,93,3/25/2021 +197856,410991680,,FAIRVIEW HEALTH SERVICES,93,9/22/2022 +198009,856000570,,NEW MEXICO HUMAN SERVICES DEPARTMENT,93,2/9/2022 +198369,886000436,,UNIVERSITY MEDICAL CENTER OF SOUTHERN NEVADA,93,2/7/2022 +198810,730757033,,CHEROKEE NATION,93,3/25/2021 +198973,133964321,,WESTCHESTER COUNTY HEALTH CARE CORPORATION,93,10/10/2022 +199123,391205576,,MENOMINEE INDIAN TRIBE OF WISCONSIN,93,12/27/2022 +199136,593467610,,"CENTRAL FLORIDA BEHAVIORAL HEALTH NETWORK, INC.",93,3/25/2021 +199484,222405279,,"RWJ BARNABAS HEALTH, INC.",93,9/22/2022 +199567,131624182,,MEMORIAL SLOAN KETTERING CANCER CENTER & AFFILIATED CORPORATIONS,93,3/25/2021 +199644,860143787,,SALT RIVER PIMA-MARICOPA INDIAN COMMUNITY,93,4/12/2022 +200900,751924974,,WORKFORCE SOLUTIONS GREATER DALLAS,93,3/25/2021 +201149,316000061,,BUTLER COUNTY,93,10/10/2022 +201368,270139424,,CALIFORNIA AUTOMATED CONSORTIUM ELIGIBILITY SYSTEM,93,3/25/2021 +201767,752681216,,TARRANT COUNTY WORKFORCE DEVELOPMENT BOARD,93,3/25/2021 +202468,132828349,,"HUDSON RIVER HEALTHCARE, INC. (D/B/A SUN RIVER HEALTH)",93,8/2/2022 +202931,966000814,,COUNTY OF KINGS,93,12/27/2022 +203014,521402373,,"LIFE BRIDGE HEALTH, INC.",93,9/2/2022 +203015,371320188,,STATE OF ILLINOIS,93,3/25/2021 +203568,742940085,,LOWER RIO GRANDE VALLEY WORKFORCE DEVELOPMENT BOARD,93,3/25/2021 +203794,521958352,,AHS HOSPITAL CORP.,93,8/30/2022 +203850,223601678,,TRINITAS REGIONAL MEDICAL CENTER,93,7/6/2022 +203859,150329043,,EXCELLUS HEALTH PLAN,93,3/25/2021 +203870,582367819,,CHILDREN'S HEALTHCARE OF ATLANTA,93,8/8/2022 +204059,450233470,,BANNER HEALTH,93,1/26/2023 +204571,536001131,,GOVERNMENT OF THE DISTRICT OF COLUMBIA,93,3/25/2021 +204659,951643381,,MEMORIAL HEALTH SERVICES AND SUBSIDIARIES,93,9/26/2022 +205501,233048942,,"PHILADELPHIA WORKS, INC.",93,3/25/2021 +205726,431423050,,"MERCY HEALTH FKA SISTERS OF MERCY HEALTH SYSTEMS, INC",93,10/10/2022 +206165,731192764,,"INTEGRIS HEALTH, INC.",93,8/22/2022 +206285,232825881,,TEMPLE UNIVERSITY HEALTH SYSTEM,93,9/26/2022 +206506,750800628,,CHILDREN'S HEALTH CLINICAL OPERATIONS,93,7/22/2022 +206676,596014973,CM6ZXKDCWEJ7,SOUTH BROWARD HOSPITAL DISTRICT D/B/A MEMORIAL HEALTHCARE SYSTEM,93,12/9/2022 +206851,576000722,,MEDICAL UNIVERSITY OF SOUTH CAROLINA,93,3/25/2021 +207008,581588823,,"THE MOSES H. CONE MEMORIAL HOSPITAL OPERATING CORPORATION, INC.",93,7/6/2022 +207050,582001014,,"PHOEBE PUTNEY HEALTH SYSTEM, INC",93,5/2/2022 +207628,146002626,,COUNTY OF FULTON,93,3/25/2021 +207677,741260710,,BCFS HEALTH AND HUMAN SERVICES,93,3/25/2021 +207994,651122406,,"EARLY LEARNING COALITION OF MIAMI-DADE/MONROE, INC.",93,3/25/2021 +208421,593688924,,"EARLY LEARNING COALITION OF DUVAL, INC",93,12/30/2021 +208872,466029223,,SSM HEALTH CARE CORPORATION,93,10/10/2022 +209282,113465690,,NASSAU HEALTH CARE CORPORATION,93,10/10/2022 +209471,340714775,,"UNIVERSITY HOSPITALS HEALTH SYSTEM, INC.",93,10/10/2022 +210565,593380599,,SOUTH FLORIDA BEHAVIORAL HEALTH NETWORK INC,93,3/25/2021 +210648,133783732,,"HEALTHFIRST PHSP, INC.",93,3/25/2021 +211212,651060848,,"EARLY LEARNING COALITION OF BROWARD COUNTY, INC.",93,3/14/2022 +211653,650974035,,"EARLY LEARNING COALITION OF PALM BEACH COUNTY, INC.",93,2/8/2022 +211669,311759186,,"ORANGE COUNTY SCHOOL READINESS COALITION, INC.",93,4/15/2022 +211671,593726679,,"EARLY LEARNING COALITION OF PINELLAS COUNTY, INC.",93,2/3/2022 +211899,581698648,L9DLXYHZ3TM8,"TASK FORCE FOR GLOBAL HEALTH, INC.",93,1/5/2023 +212004,752702388,,TEXAS HEALTH RESOURCES,93,8/4/2022 +212155,596001217,,HALIFAX HOSPITAL MEDICAL CENTER,93,7/6/2022 +212445,562570681,,"ST. LUKE'S HEALTH SYSTEM, LTD. AND SUBSIDIARIES",93,7/6/2022 +212675,956077327,,SHARP HEALTHCARE,93,7/1/2022 +212942,710847443,,STATE OF ARKANSAS,93,3/25/2021 +214017,651149351,,"CHILDNET, INC. AND AFFILIATE",93,3/25/2021 +215470,550753754,,CAMC HEALTH SYSTEM INC. AND SUBSIDIARIES,93,7/14/2022 +215599,951684089,,SCRIPPS HEALTH AND AFFILIATES,93,7/6/2022 +216236,930951989,,"SAMARITAN HEALTH SERVICES, INC.",93,10/10/2022 +217342,222529464,,YALE - NEW HAVEN HEALTH SERVICES CORPORATION,93,7/6/2022 +217422,860422559,,PHOENIX CHILDRENS HOSPITAL,93,10/3/2022 +217428,954191698,,ELIZABETH GLASER PEDIATRIC AIDS FOUNDATION,93,3/25/2021 +217851,043651340,,TUBA CITY REGIONAL HEALTH CARE CORPORATION,93,3/25/2021 +217857,111631781,,FLUSHING HOSPITAL MEDICAL CENTER,93,8/9/2022 +218267,030423156,ULDJB39ERZU8,"BIG BEND COMMUNITY BASED CARE, INC. D/B/A NORTHWEST FLORIDA HEALTH NET",93,3/22/2023 +218860,571140890,,"OUR KIDS OF MIAMI-DADE/MONROE, INC.",93,3/25/2021 +218914,411813221,,CENTRACARE HEALTH SYSTEM,93,9/28/2022 +218994,810549382,,"WINSLOW INDIAN HEALTH CARE CENTER, INC.",93,5/13/2022 +219000,043369649,,MAINEGENERAL HEALTH,93,9/23/2022 +219107,481108830,,"UNIVERSITY OF KANSAS MEDICAL CENTER RESEARCH INSTITUTE, INC.",93,3/25/2021 +219340,200360007,,ESSENTIA HEALTH,93,4/21/2022 +219459,860810876,,GILA RIVER HEALTH CARE CORPORATION,93,3/25/2021 +219579,621646734,,COVENANT HEALTH,93,7/25/2022 +219891,592796965,,"BAYCARE HEALTH SYSTEM, INC. AND AFFILIATES",93,8/5/2022 +219918,222517863,,WELLSPAN HEALTH,93,10/6/2022 +220008,920144231,,ALASKA REINSURANCE PROGRAM/ALASKA COMPREHENSIVE HEALTH INSURANCE ASSOC,93,3/25/2021 +220160,111665825,,EPISCOPAL HEALTH SERVICES INC. AND SUBSIDIARIES,93,11/17/2022 +220999,311339322,,OHIO CHILD CARE RESOURCE & REFERRAL ASSOCIATION,93,12/26/2021 +221064,391835630,,UNIVERSITY OF WISCONSIN HOSPITALS & CLINICS AUTHORITY,93,6/29/2022 +221072,050539199,SEA8ANNY16M5,"FUND FOR PUBLIC HEALTH IN NEW YORK, INC.",93,6/30/2023 +221266,521465301,,JOHNS HOPKINS HEALTH SYSTEM,93,1/28/2022 +221303,910939479,,PEACEHEALTH NETWORKS,93,8/26/2022 +221530,043567502,,"PARTNERS IN HEALTH, A NONPROFIT CORPORATION",93,12/9/2021 +222035,020222140,,DARTMOUTH HITCHCOCK HEALTH AND SUBSIDIARIES,93,10/10/2022 +222279,136171197,,ICAHN SCHOOL OF MEDICINE AT MOUNT SINAI,93,3/25/2021 +222378,411314595,,MINNESOTA COMPREHENSIVE HEALTH ASSOCIATION,93,9/27/2022 +222380,222565278,,"CATHOLIC HEALTH SYSTEM, INC. & SUBSIDIARIES",93,10/5/2022 +222561,951644036,,"COLLIS P. AND HOWARD HUNTINGTON MEMORIAL HOSPITAL TRUST, PASADENA HOSP",93,5/26/2022 +222716,311626179,,"UNIVERSITY HEALTH SYSTEM, INC.",93,10/6/2022 +223420,770097156,,"THE CHILDREN'S CABINET, INC.",93,2/7/2022 +223772,660500678,,PUERTO RICO HEALTH INSURANCE ADMINISTRATION (ASES),93,3/25/2021 +223800,382640544,,MUNSON HEALTHCARE AND SUBSIDIARIES,93,9/15/2022 +224072,912155317,NFHEUCKBFMU4,ALLEN INSTITUTE,93,7/13/2023 +224542,200873314,,LAS VEGAS - CLARK COUNTY URBAN LEAGUE,93,3/25/2021 +224672,521532556,,"ADVENTIST HEALTHCARE, INC AND CONTROLLED ENTITIES",93,4/21/2023 +225185,237204495,,"JOHN H. BONER COMMUNITY CENTER, INC.",93,8/30/2022 +226112,756004221,,DALLAS COUNTY HOSPITAL DISTRICT DBA PARKLAND HEALTH & HOSPITAL SYSTEM,93,7/2/2022 +226559,351972384,,"PARKVIEW HEALTH SYSTEM, INC. DBA PARKVIEW HEALTH",93,7/7/2022 +226636,202508411,DC4UE8MU2ZE8,"SUNSET PARK HEALTH COUNCIL, INC. DBA FAMILY HEALTH CENTERS AT NYU LANG",93,6/1/2023 +227312,460422673,,AVERA HEALTH,93,10/3/2022 +227687,203796994,,"QLARANT INTEGRITY SOLUTIONS, LLC",93,3/25/2021 +228058,650267668,,"BAPTIST HEALTH SOUTH FLORIDA, INC. AND AFFILIATES",93,7/6/2022 +228496,208022336,,"NEW YORK EHEALTH COLLABORATIVE, INC.",93,10/6/2021 +228533,743177454,,RICHMOND UNIVERSITY MEDICAL CENTER,93,10/6/2022 +228550,396005507,FS3AZ3FV8JG8,CITY OF MADISON,93,6/28/2023 +230070,990073547,,CATHOLIC CHARITIES HAWAII,93,6/1/2022 +230344,411693838,,"HEALTHPARTNERS, INC.",93,10/6/2022 +230423,050490997,,CARE NEW ENGLAND HEALTH SYSTEM,93,6/27/2022 +230928,610445850,,"ST. ELIZABETH MEDICAL CENTER, INC.",93,9/22/2022 +231074,260076866,,"ACACIA NETWORK HOUSING, INC.",93,3/25/2021 +231143,911935159,TJFZLPP6NYL6,FRED HUTCHINSON CANCER CENTER,93,11/27/2022 +231358,263428781,,"THE BROAD INSTITUTE, INC.",93,3/25/2021 +231533,264059542,,RAINBOW LEARNING ENRICHMENT,93,3/24/2022 +232531,222508425,,"INSPIRA HEALTH NETWORK, INC.",93,10/25/2022 +233569,043230035,,"PARTNERS HEALTHCARE SYSTEM, INC. AND AFFILIATES",93,3/25/2021 +233916,860817397,,FORT DEFIANCE INDIAN HOSPITAL BOARD DBA TSEHOOTSOOI MEDICAL CENTER,93,3/25/2021 +233938,611028725,,"NORTON HEALTHCARE, INC. AND AFFILIATES",93,10/5/2022 +235872,311662309,,ASCENSION HEALTH ALLIANCE D/B/A ASCENSION,93,9/20/2022 +236686,010211513,,THE JACKSON LABORATORY,93,3/25/2021 +237461,010274725,XA9EPK1VPLQ6,THE OPPORTUNITY ALLIANCE AND AFFILIATE,93,11/3/2022 +237465,481202402,,UNIVERSITY OF KANSAS HOSPITAL AUTHORITY,93,10/10/2022 +238304,841548541,,COMMUNITY DEVELOPMENT INSTITUTE - HEAD START,93,3/25/2021 +238692,451294709,,NEW MEXICO HEALTH CONNECTIONS,93,3/25/2021 +238711,453416923,,MAINE COMMUNITY HEALTH OPTIONS,93,3/25/2021 +238746,451295465,,MONTANA HEALTH COOPERATIVE,93,3/25/2021 +238799,386000134,,STATE OF MICHIGAN,93,3/25/2021 +238808,222672834,,HARTFORD HEALTHCARE CORPORATION,93,6/14/2022 +238890,346400806,,LUCAS COUNTY,93,3/25/2021 +238898,453309488,,COMMON GROUND HEALTHCARE COOPERATIVE,93,3/25/2021 +238923,351361243,,HENDRICKS REGIONAL HEALTH,93,6/1/2022 +239022,346002718,,STARK COUNTY,93,3/25/2021 +239979,610444707,,BAPTIST HEALTHCARE SYSTEM INC AND AFFILIATES,93,5/27/2022 +240167,383382353,,SPECTRUM HEALTH SYSTEM & AFFILIATES,93,9/15/2022 +240238,453675836,,BROWARD BEHAVIORAL HEALTH COALITION INC,93,3/25/2021 +241068,455282243,QLL1T1MQ4VN7,SIERRA HEALTH FOUNDATION: CENTER FOR HEALTH PROGRAM MANAGEMENT,93,5/23/2023 +241630,201615393,,"MONTEFIORE HEALTH SYSTEM, INC.",93,3/25/2021 +241730,113403968,,CATHOLIC HEALTH SERVICES OF LONG ISLAND,93,10/10/2022 +241733,830254253,,NORTHERN ARAPAHO TRIBE OF INDIANS,93,7/21/2023 +242271,626010402,,JMC GEN HOSP DIST DBA WEST TENNESSEE HEALTHCARE AND RELATED AFFILIATES,93,5/25/2022 +242487,150532087,,"CAYUGA HOME FOR CHILDREN, INC. D/B/A CAYUGA CENTERS",93,3/25/2021 +242795,593626765,,EARLY LEARNING COALITION OF HILLSBOROUGH COUNTY,93,4/8/2022 +242882,382397643,,MCLAREN HEALTH CARE CORPORATION AND SUBSIDIARIES,93,6/30/2022 +243232,943480131,,LOUISIANA CHILDREN'S MEDICAL CENTER,93,10/5/2022 +243303,581954432,,"NORTHSIDE HOSPITAL, INC.",93,6/22/2022 +244245,453674900,,HIGHMARK HEALTH,93,3/25/2021 +244411,463130985,,BAYLOR SCOTT & WHITE HOLDINGS AND ITS CONTROLLED AFFILIATES,93,5/19/2022 +244616,452880726,,THE UNIVERSITY OF VERMONT HEALTH NETWORK INC,93,6/30/2022 +244758,473255211,,SAN CARLOS APACHE HEALTHCARE CORPORATION,93,12/1/2022 +244798,541190771,MREQZTLUYLY9,CARILION CLINIC,93,6/14/2023 +244946,750800634,,CHILDCAREGROUP,93,3/25/2021 +245299,830621846,,ALBERT EINSTEIN COLLEGE OF MEDICINE-EIN NOTED,93,3/25/2021 +245542,382080820,G9UZX5M53LV6,"SER-METRO DETROIT JOBS FOR PROGRESS, INC.",93,3/7/2023 +245647,742911834,MAMHT8BAPS95,"WORKFORCE SOLUTIONS BORDERPLEX, INC.",93,6/30/2023 +245895,472158680,,MERCY HEALTH CORPORATION,93,9/20/2022 +246110,521357729,,VALLEY HEALTH SYSTEM AND SUBSIDIARIES,93,10/3/2022 +246131,481102008,,KANSAS ASSOCIATION OF CHILD CARE RESOURCE AND REFERRAL AGENCIES,93,7/14/2022 +246145,061491191,,"TRINITY HEALTH OF NEW ENGLAND CORPORATION, INC.",93,10/13/2022 +246340,742709309,,"ALAMO WORKFORCE DEVELOPMENT, INC. DBA WORKFORCE SOLUTIONS - ALAMO",93,3/25/2021 +246449,811244422,,PROVIDENCE ST. JOSEPH HEALTH,93,10/3/2022 +246678,352528741,,VANDERBILT UNIVERSITY MEDICAL CENTER,93,3/25/2021 +246684,200072992,,"CENTERSTONE OF AMERICA, INC.",93,3/25/2021 +246812,581521475,,BAPTIST MEMORIAL HEALTH CARE CORPORATION,93,6/30/2022 +247064,300820570,,CHILDRENS COMMUNITY SERVICES INC.,93,3/25/2021 +247480,251423657,,UPMC,93,3/25/2021 +247662,272999718,,"AU HEALTH SYSTEM, INC.",93,10/5/2022 +247699,813158267,,NEBRASKA MEDICINE,93,10/5/2022 +247889,951644600,,CEDARS-SINAI HEALTH SYSTEM,93,3/25/2021 +247948,472250732,,WELLFORCE INC,93,6/30/2022 +248106,926001185,,STATE OF ALASKA,93,3/25/2021 +248133,946000549,,COUNTY OF YUBA,93,3/25/2021 +248139,611771290,,BALLAD HEALTH,93,12/1/2022 +248192,271218956,,SANFORD,93,7/2/2022 +248410,521020023,,EAST COAST MIGRANT HEAD START PROJECT,93,8/24/2022 +248990,061488219,,GARNET HEALTH,93,10/10/2022 +249009,856000570,,NEW MEXICO HUMAN SERVICES DEPARTMENT,93,3/25/2021 +249302,466000364,,STATE OF SOUTH DAKOTA,93,3/25/2021 +249310,956000928,,"COUNTY OF ORANGE, CALIFORNIA",93,3/25/2021 +249331,610600439,,COMMONWEALTH OF KENTUCKY,93,3/25/2021 +249463,454331075,,MAINE GUARANTEED ACCESS REINSURANCE ASSOCIATION,93,3/25/2021 +249502,822595551,,PRISMA HEALTH AND SUBSIDIARIES,93,6/30/2022 +249505,952810095,,ALTAMED HEALTH SERVICES CORPORATION,93,10/6/2022 +250587,431853499,,"VISION FOR CHILDREN AT RISK, INC.",93,5/16/2022 +250736,522087445,,"MEDSTAR HEALTH, INC.",93,5/10/2022 +250744,592487136,VKMFGLCR5E65,"BAPTIST HEALTH SYSTEM, INC.",93,6/29/2023 +250987,562141073,,"UNIVERSITY HEALTH SYSTEMS OF EASTERN CAROLINA, INC. DBA VIDANT HEALTH",93,3/23/2022 +251133,311438846,E3MRTEM17PM8,TRIHEALTH,93,4/4/2023 +251143,581649541,,"WELLSTAR HEALTH SYSTEM, INC. AND AFFILIATES",93,7/29/2022 +251165,731501972,,SAINT FRANCIS HEALTH SYSTEM,93,10/6/2022 +251180,363513954,,EDWARD-ELMHURST HEALTHCARE,93,9/28/2022 +251188,382459948,,MIDMICHIGAN HEALTH,93,6/30/2022 +251198,510190238,,WAKE FOREST BAPTIST,93,10/6/2022 +251209,660433481,,SOCIOECONOMIC AND COMMUNITY DEVELOPMENT OFFICE,93,4/15/2022 +251250,222482803,,CENTRASTATE HEALTHCARE SYSTEM INC.,93,10/10/2022 +251285,340965350,VRKYFZKLPTC6,STEP FORWARD,93,11/1/2022 +251672,742161737,RHL3SKDB2GV1,"UNIVERSITY PHYSICIANS, INC DBA UNIVERSITY OF COLORADO MEDICINE",93,1/11/2023 +251776,185600056,,NEW MEXICO EARLY CHILDHOOD EDUCATION AND CARE DEPARTMENT,93,12/21/2021 +251833,832671600,,BETH ISRAEL LAHEY HEALTH,93,6/30/2022 +251882,510473500,,MCLEOD HEALTH,93,7/1/2022 +252147,410729979,,NORTH MEMORIAL HEALTH CARE,93,9/27/2022 +252375,550754713,,WEST VIRGINIA UNIVERSITY HEALTH SYSTEM AND CONTROLLED ENTITIES,93,10/3/2022 +252550,930602940,DE4AJXJ7KPW5,"ST. CHARLES HEALTH SYSTEM, INC.",93,8/3/2023 +252622,592170012,,ADVENTIST HEALTH SYSTEM SUNBELT HEALTHCARE CORPORATION,93,10/10/2022 +252657,316059784,,OHIOHEALTH CORPORATION,93,9/26/2022 +252662,311446699,,PREMIER HEALTH PARTNERS,93,9/20/2022 +252795,581581102,,"UNIVERSITY HEALTH, INC.",93,10/10/2022 +252823,540505989,,VIRGINIA HOSPITAL CENTER ARLINGTON HEALTH SYSTEM,93,10/6/2022 +252831,822081292,,"CARE PLUS BERGEN, INC.",93,9/1/2022 +253329,946174066,,STANFORD HEALTH CARE,93,6/2/2022 +253632,750800661,,METHODIST HEALTH SYSTEM,93,6/29/2022 +253672,630841123,C7K6WD3GFUP6,INFIRMARY HEALTH SYSTEM INC,93,12/31/2022 +253695,581694090,,"NORTHEAST GEORGIA HEALTH SYSTEM, INC.",93,6/30/2022 +253765,550675666,,"CABELL HUNTINGTON HOSPITAL, INC. AND SUBSIDIARIES",93,7/2/2022 +253791,953619388,,"PIH HEALTH, INC.",93,7/2/2022 +253824,361564290,,"HUMBOLDT PARK HEALTH, INC.",93,7/6/2022 +253848,720400933,,WILLIS-KNIGHTON MEDICAL CENTER,93,7/6/2022 +254078,392014409,,"FROEDTERT HEALTH, INC. AND AFFILIATES",93,8/2/2022 +254228,221487307,,THE VALLEY HOSPITAL,93,8/18/2022 +254251,350868133,,"THE METHODIST HOSPITALS, INC.",93,10/6/2022 +254570,454552631,,UNIVERSITY OF COLORADO HEALTH,93,9/22/2022 +254586,943270595,,HAWAII HEALTH SYSTEMS CORPORATION,93,9/22/2022 +254836,831605004,L7U3M1K8PCH3,OCHSNER LSU HEALTH SYSTEM OF NORTH LOUISIANA,93,3/31/2023 +254984,208065139,,"PRIME HEALTHCARE FOUNDATION, INC. AND SUBSIDIARIES",93,10/6/2022 +254992,941461843,,JOHN MUIR HEALTH,93,10/3/2022 +255007,362167060,,NORTHSHORE UNIVERSITY HEALTHSYSTEM-3 MONTH AUDIT,93,10/5/2022 +255067,350892672,,"REID HOSPITAL & HEALTHCARE SERVICES, INC.",93,10/5/2022 +255093,473769205,GLK7YEX4CN16,PENN STATE HEALTH,93,3/30/2023 +255110,273850988,E127PEKAAV49,UC HEALTHCARE SYSTEM,93,2/2/2023 +255171,843178470,,UNIVERSITY OF LOUISVILLE HEALTH,93,10/6/2022 +255489,843647453,,"ATRIUM HEALTH, INC.",93,10/10/2022 +255494,912135195,,"BON SECOURS CHARITY HEALTH SYSTEM, INC.",93,10/10/2022 +255507,821883948,,"OU MEDICINE, INC.",93,10/10/2022 +258327,834214573,,NUVANCE HEALTH AND SUBSIDIARIES,93,8/7/2023 +258496,953435919,KM7XTKHC9D19,CITY OF HOPE ,93,7/6/2023 +258601,222476636,,"STAMFORD HEALTH, INC.",93,8/1/2023 +258692,581503902,CJTEQKKB6SF8,"PIEDMONT HEALTHCARE, INC.",93,8/15/2023 +904661,946002388,,STANISLAUS COUNTY OFFICE OF EDUCATION,93,9/3/2022 +136118,596000749,,"MONROE COUNTY, FLORIDA",95,4/14/2022 +172017,746000762,UCXEL13TK6W5,"COUNTY OF EL PASO, TEXAS",95,6/13/2023 +188902,716049083,,STATE OF ARKANSAS DISABILITY DETERMINATION FOR SOCIAL SECURITY,96,7/1/2022 +88071,590714812,,LEE MEMORIAL HEALTH SYSTEM,97,11/3/2022 +136074,596000512,,"BAY COUNTY, FLORIDA",97,3/25/2021 +136076,596000523,,"BREVARD COUNTY, FLORIDA",97,4/22/2022 +136090,596000598,LLHWX867E5J6,"ESCAMBIA COUNTY, FLORIDA",97,3/16/2023 +136115,596000727,,"MANATEE COUNTY, FLORIDA",97,4/12/2022 +136116,596000735,,"MARION COUNTY, FL",97,6/16/2022 +136124,596000785,,"PALM BEACH COUNTY, FLORIDA",97,3/25/2021 +136127,596000809,,"POLK COUNTY, FLORIDA",97,6/27/2022 +136129,596000825,DKTCSFFFL3Q8,ST JOHNS COUNTY,97,6/30/2023 +136133,596000856,JPJLF4QHYR13,"SEMINOLE COUNTY, FLORIDA",97,5/16/2023 +136242,596000344,,CITY OF JACKSONVILLE,97,3/25/2021 +136718,596000511,,BAY COUNTY DISTRICT SCHOOL BOARD,97,4/10/2022 +146695,726000234,,CALCASIEU PARISH POLICE JURY,97,7/21/2022 +146710,726013920,,JEFFERSON PARISH,97,3/25/2021 +146898,726000969,,CITY OF NEW ORLEANS,97,2/24/2023 +158022,856000411,,NEW MEXICO INSTITUTE OF MINING & TECHNOLOGY,97,3/25/2021 +172790,746000574,XETBTPPKL895,"CITY OF CORPUS CHRISTI, TEXAS",97,4/28/2023 +181375,726001323,,SEWERAGE AND WATER BOARD OF NEW ORLEANS,97,7/12/2022 +189733,131740011,,WILDLIFE CONSERVATION SOCIETY,97,3/25/2021 +192502,596000558,,COLLIER COUNTY,97,3/25/2021 +192969,596000708,,LEON COUNTY,97,6/9/2022 +193128,726000093,YWJ7LKSNL6S6,ASCENSION PARISH SCHOOL BOARD,97,1/27/2023 +197821,112879302,,LONG ISLAND POWER AUTHORITY,97,3/25/2021 +198800,760590551,,CHRISTUS HEALTH,97,10/14/2022 +202333,660558579,,DEPARTMENT OF HOUSING OF THE COMMONWEALTH OF PUERTO RICO,97,1/12/2023 +203800,271110568,,EMERGENCY FOOD AND SHELTER PROGRAM,97,3/25/2021 +213554,660412508,,VIRGIN ISLANDS HOUSING FINANCE AUTHORITY,97,3/25/2021 +219086,111352310,,SOUTH NASSAU COMMUNITIES HOSPITAL AND SUBSIDIARIES,97,10/10/2022 +219874,226002471,,PASSAIC VALLEY SEWERAGE COMMISSION,97,9/26/2022 +222290,746019452,,HARRIS COUNTY FLOOD CONTROL DISTRICT,97,3/25/2021 +222946,660436728,,DEPARTMENT OF TRANSPORTATION AND PUBLIC WORKS,97,3/25/2021 +227852,300426073,,NEW MEXICO DEPARTMENT OF HOMELAND SECURITY & EMERGENCY MANAGEMENT,97,3/17/2022 +242833,221775306,,UNIVERSITY HOSPITAL,97,9/15/2022 +251453,720362325,,"JEFFERSON DAVIS ELECTRIC COOPERATIVE, INC.",97,8/25/2022 +258112,810216629,KEDLA4RDUX55,"MCCONE ELECTRIC CO-OP, INC.",97,5/9/2023 +2940,042241718,,"EDUCATION DEVELOPMENT CENTER, INC.",98,3/25/2021 +3308,042482188,,"MANAGEMENT SCIENCES FOR HEALTH, INC.",98,3/25/2021 +3886,042679824,,"JSI RESEARCH AND TRAINING INSTITUTE, INC.",98,3/25/2021 +10860,131685039,,CARE USA AND SUBSIDIARIES,98,3/25/2021 +11916,132702768,,"PACT, INC.",98,3/25/2021 +14648,133712030,,"CONCERN WORLDWIDE (U.S.), INC",98,10/14/2022 +15491,135660870,,"INTERNATIONAL RESCUE COMMITTEE, INC.",98,3/25/2021 +16489,136183605,,"COUNTERPART INTERNATIONAL, INC.",98,3/25/2021 +73339,520811461,,ACDI/VOCA AND AFFILIATES,98,3/25/2021 +73390,520846183,,GLOBAL COMMUNITIES,98,3/25/2021 +75188,521338892,,NATIONAL DEMOCRATIC INSTITUTE FOR INTERNATIONAL AFFAIRS,98,3/25/2021 +75191,521340267,,INTERNATIONAL REPUBLICAN INSTITUTE,98,3/25/2021 +75514,521447902,,CNFA,98,3/25/2021 +75735,521527835,,INTERNATIONAL FOUNDATION FOR ELECTORAL SYSTEMS,98,6/28/2022 +78028,530235320,,PATHFINDER INTERNATIONAL AND SUBSIDIARIES,98,3/25/2021 +81941,560942853,,POPULATION SERVICES INTERNATIONAL,98,3/25/2021 +85605,581437002,,SAMARITAN'S PURSE,98,8/5/2022 +98107,710603560,,WINROCK INTERNATIONAL INSTITUTE FOR AGRICULTURAL DEVELOPMENT,98,3/25/2021 +114648,911148123,,MERCY CORPS AND AFFILIATES,98,3/25/2021 +114674,911157127,,PATH,98,3/25/2021 +121369,943027961,,INTERNEWS NETWORK,98,3/25/2021 +124020,951922279,,WORLD VISION INTERNATIONAL AND WORLD VISION INC.(USA),98,3/25/2021 +124798,952680390,,FOOD FOR THE HUNGRY,98,3/25/2021 +126425,953949646,,INTERNATIONAL MEDICAL CORPS,98,3/25/2021 +126995,954300662,,RELIEF INTERNATIONAL,98,9/28/2022 +182396,521943638,,CONSORTIUM FOR ELECTIONS AND POLITICAL PROCESS STRENGHTENING,98,3/25/2021 +187669,135563422,,CATHOLIC RELIEF SERVICES - US CONFERENCE OF CATHOLIC BISHOPS,98,3/25/2021 +188206,526054268,,PAN AMERICAN DEVELOPMENT FOUNDATION,98,6/22/2022 +188372,237413005,,FAMILY HEALTH INTERNATIONAL,98,3/25/2021 +197541,521314847,,ADRA INTERNATIONAL,98,3/25/2021 +198686,521984713,FGHSZZVE59R8,AMERICAN CENTER FOR INTERNATIONAL LABOR SOLIDARITY,98,8/22/2023 +213388,133870223,,"INTERNATIONAL AIDS VACCINE INITIATIVE, INC.",98,7/28/2022 +221191,550825466,,"INTRAHEALTH INTERNATIONAL, INC.",98,3/25/2021 +224703,043478123,,"ROOT CAPITAL, INC.",98,4/28/2022 +241642,060726487,,"SAVE THE CHILDREN FEDERATION, INC AND RELATED ENTITIES",98,3/25/2021 +247573,810888072,,"BLUMONT, INC.",98,3/25/2021 +253956,911157127,PD3VNAABZJL7,PATH,98,6/14/2023 diff --git a/backend/support/management/commands/run_2022.py b/backend/support/management/commands/run_2022.py new file mode 100644 index 0000000000..73790243ad --- /dev/null +++ b/backend/support/management/commands/run_2022.py @@ -0,0 +1,125 @@ +from django.core.management.base import BaseCommand +from django.db.models.functions import Cast +from django.db.models import BigIntegerField + + +from dissemination.hist_models.census_2022 import CensusGen22, CensusCfda22 +from audit.models import SingleAuditChecklist, User +from support.models import CognizantAssignment + + +class Command(BaseCommand): + help = """ + Analyze cog/over for 20122 submissions + Beware! Deletes any existing rows in SingleAuditChecklist + """ + + def handle(self, *args, **kwargs): + initialize_db() + gens = CensusGen22.objects.annotate( + amt=Cast("totfedexpend", output_field=BigIntegerField()) + ).all() + print(f"Count of 2022 submissions: {len(gens)}") + processed = cog_mismatches = over_mismatches = 0 + + for gen in gens: + sac = self.make_sac(gen) + sac.submission_status = sac.STATUS.SUBMITTED + sac.save() + sac.disseminate() + processed += 1 + if gen.cogagency != sac.cognizant_agency: + cog_mismatches += 1 + print(f"Cog mismatch. Expected {gen.cogagency}") + self.show_mismatch(sac) + if gen.oversightagency != sac.oversight_agency: + over_mismatches += 1 + print(f"Oversight mismatch. Expected {gen.oversightagency}") + self.show_mismatch(sac) + if processed % 1000 == 0: + print( + f""" + Processed {processed} rows so far. + Found {cog_mismatches} cog and {over_mismatches} over mismatches. + ...""" + ) + print( + f""" + Processed all {processed} rows. + Found {cog_mismatches} cog and {over_mismatches} over mismatches. + """ + ) + + def show_mismatch(self, sac): + print( + sac.ein, + sac.auditee_uei, + sac.cognizant_agency, + sac.oversight_agency, + sac.federal_awards["FederalAwards"]["total_amount_expended"], + ) + for award in sac.federal_awards["FederalAwards"]["federal_awards"]: + print( + "Award:", + award["award_reference"], + award["program"]["amount_expended"], + award["program"]["federal_agency_prefix"], + award["program"]["three_digit_extension"], + award["direct_or_indirect_award"]["is_direct"], + ) + + def make_sac(self, gen: CensusGen22): + general_information = { + "auditee_fiscal_period_start": "2022-11-01", + "auditee_fiscal_period_end": "2023-11-01", + "met_spending_threshold": True, + "is_usa_based": True, + } + general_information["ein"] = gen.ein + general_information["auditee_uei"] = gen.uei + federal_awards = self.make_awards(gen) + sac = SingleAuditChecklist.objects.create( + submitted_by=User.objects.first(), + submission_status="in_progress", + general_information=general_information, + federal_awards=federal_awards, + ) + return sac + + def make_awards(self, gen: CensusGen22): + awards = { + "FederalAwards": { + "auditee_uei": gen.uei, + "federal_awards": [], + "total_amount_expended": gen.amt, + } + } + cfdas = CensusCfda22.objects.annotate( + amt=Cast("amount", output_field=BigIntegerField()) + ).filter(ein=gen.ein, dbkey=gen.dbkey) + for cfda in cfdas: + awards["FederalAwards"]["federal_awards"].append( + { + "award_reference": cfda.index, + "program": { + "program_name": cfda.cfdaprogramname, + "amount_expended": cfda.amt, + "federal_agency_prefix": cfda.cfda[:2], + "three_digit_extension": cfda.cfda[2:], + }, + "direct_or_indirect_award": {"is_direct": cfda.direct}, + }, + ) + return awards + + +def initialize_db(): + """ + This will delete existing data, and should only be run in a dev env + """ + SingleAuditChecklist.objects.all().delete() + CognizantAssignment.objects.all().delete() + User.objects.get_or_create( + username="foo", + email="g22.foobar.com", + ) diff --git a/backend/support/management/commands/seed_cog_baseline.py b/backend/support/management/commands/seed_cog_baseline.py new file mode 100644 index 0000000000..3085c745bf --- /dev/null +++ b/backend/support/management/commands/seed_cog_baseline.py @@ -0,0 +1,49 @@ +import pandas as pd +from os import path + +from django.core.management.base import BaseCommand +from support.models import CognizantBaseline +from config.settings import BASE_DIR +from collections import defaultdict + + +class Command(BaseCommand): + help = """ + Populates CognizantBaseline using Cognizant_Agencies_2021_2025.csv + obtained from https://facdissem.census.gov/PublicDataDownloads.aspx. + """ + + def handle(self, *args, **kwargs): + count = load_cog_2021_2025() + print(f"Loaded {count} rows to baseline table") + + +def load_cog_2021_2025(): + dtypes = defaultdict(lambda: str) + file_path = path.join(BASE_DIR, "support/fixtures/", "census_baseline.csv") + df = pd.read_csv(file_path, dtype=dtypes) + df = df.drop(columns=["AUDITEENAME"]) + df = df.rename( + columns={ + "DBKEY": "dbkey", + "EIN": "ein", + "UEI": "uei", + "COGAGENCY": "cognizant_agency", + "DATE_ADDED": "date_assigned", + } + ) + df["date_assigned"] = pd.to_datetime(df["date_assigned"], utc=True) + df["is_active"] = True + CognizantBaseline.objects.all().delete() + data = df.to_dict("records") + + for item in data: + CognizantBaseline( + dbkey=item["dbkey"], + ein=item["ein"], + uei=item["uei"], + cognizant_agency=item["cognizant_agency"], + date_assigned=item["date_assigned"], + is_active=item["is_active"], + ).save() + return CognizantBaseline.objects.count() diff --git a/backend/support/migrations/0001_initial.py b/backend/support/migrations/0001_initial.py new file mode 100644 index 0000000000..d32cba7509 --- /dev/null +++ b/backend/support/migrations/0001_initial.py @@ -0,0 +1,81 @@ +# Generated by Django 4.2.3 on 2023-09-20 14:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + operations = [ + migrations.CreateModel( + name="CognizantAssignment", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("report_id", models.CharField(max_length=17)), + ( + "cognizant_agency", + models.CharField(max_length=2, verbose_name="Cog Agency"), + ), + ( + "date_assigned", + models.DateTimeField( + auto_now_add=True, verbose_name="Date Assigned" + ), + ), + ( + "assignor_email", + models.EmailField(max_length=254, verbose_name="Email"), + ), + ("override_comment", models.TextField(verbose_name="Comment")), + ( + "assignment_type", + models.CharField( + choices=[ + ("computed", "Computed by FAC"), + ("manual", "Manual Override"), + ], + default="computed", + max_length=20, + verbose_name="Type", + ), + ), + ], + ), + migrations.CreateModel( + name="CognizantBaseline", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "dbkey", + models.CharField(max_length=20, null=True, verbose_name="dbkey"), + ), + ("ein", models.CharField(max_length=30, null=True, verbose_name="EIN")), + ("uei", models.CharField(max_length=30, null=True, verbose_name="UEI")), + ( + "cognizant_agency", + models.CharField(max_length=2, verbose_name="Cog Agency"), + ), + ( + "date_assigned", + models.DateTimeField(null=True, verbose_name="Date Assigned"), + ), + ("is_active", models.BooleanField(default=True, verbose_name="Active")), + ], + ), + ] diff --git a/backend/support/migrations/__init__.py b/backend/support/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/support/models.py b/backend/support/models.py new file mode 100644 index 0000000000..0009f18202 --- /dev/null +++ b/backend/support/models.py @@ -0,0 +1,76 @@ +from django.db import models + + +class CognizantBaseline(models.Model): + dbkey = models.CharField( + # help_text = "Identifier for a submission along with audit_year in Census FAC", + null=True, + max_length=20, + verbose_name="dbkey", + ) + ein = models.CharField( + # help_text = "Primary Employer Identification Number", + null=True, + max_length=30, + verbose_name="EIN", + ) + uei = models.CharField( + # help_text = "Unique Employer Identification Number", + null=True, + max_length=30, + verbose_name="UEI", + ) + cognizant_agency = models.CharField( + # help_text = "Two digit Federal agency prefix of the cognizant agency", + max_length=2, + verbose_name="Cog Agency", + ) + date_assigned = models.DateTimeField( + # help_text = "Time when the cog agency was assigned to the entity", + null=True, # allow nulls in case history has nulls + verbose_name="Date Assigned", + ) + is_active = models.BooleanField( + # help_text = "Record to be ignored if this field is False", + default=True, + verbose_name="Active", + ) + + +class AssignmentTypeCode: + COMPUTED = "computed" + MANUAL = "manual" + + +ASSIGNMENT_TYPES = ( + (AssignmentTypeCode.COMPUTED, "Computed by FAC"), + (AssignmentTypeCode.MANUAL, "Manual Override"), +) + + +class CognizantAssignment(models.Model): + report_id = models.CharField(max_length=17) + cognizant_agency = models.CharField( + # "Two digit Federal agency prefix of the cognizant agency", + max_length=2, + verbose_name="Cog Agency", + ) + date_assigned = models.DateTimeField( + # "Time when the cog agency was assigned to the entity", + auto_now_add=True, + verbose_name="Date Assigned", + ) + assignor_email = models.EmailField( + # "Who originally set the cog agency or who overrode it", + verbose_name="Email", + ) + override_comment = models.TextField( + # "Comment by assignor explaining the justification for an override", + verbose_name="Comment", + ) + assignment_type = models.CharField( + max_length=20, + choices=ASSIGNMENT_TYPES, + default=AssignmentTypeCode.COMPUTED, + verbose_name="Type", + ) diff --git a/backend/support/signals.py b/backend/support/signals.py new file mode 100644 index 0000000000..c2bcd94d83 --- /dev/null +++ b/backend/support/signals.py @@ -0,0 +1,32 @@ +from django.db.models.signals import post_save +from django.db.models import Q +from django.dispatch import receiver + +from audit.models import SingleAuditChecklist +from dissemination.models import General +from support.models import CognizantAssignment, CognizantBaseline + + +@receiver(post_save, sender=CognizantAssignment) +def post_cog_assignment(sender, instance, created, **kwargs): + """ + If a CognizantAssignment instance is saved, handle the effects this should + have on other tables. + """ + if created: + sac = SingleAuditChecklist.objects.get(report_id=instance.report_id) + cognizant_agency = sac.cognizant_agency + + ein, uei = sac.auditee_uei, sac.ein + baselines = CognizantBaseline.objects.filter(Q(ein=ein) | Q(uei=uei)) + for baseline in baselines: + baseline.is_active = False + baseline.save() + CognizantBaseline(ein=ein, uei=uei, cognizant_agency=cognizant_agency).save() + + try: + gen = General.objects.get(report_id=sac.report_id) + gen.cognizant_agency = cognizant_agency + gen.save() + except General.DoesNotExist: + pass # etl may not have been run yet diff --git a/backend/audit/test_cog_over.py b/backend/support/test_cog_over.py similarity index 51% rename from backend/audit/test_cog_over.py rename to backend/support/test_cog_over.py index ffef9d073e..8d9c614c0b 100644 --- a/backend/audit/test_cog_over.py +++ b/backend/support/test_cog_over.py @@ -1,28 +1,100 @@ from django.test import TestCase +from dissemination.hist_models.census_2019 import CensusGen19, CensusCfda19 +from dissemination.hist_models.census_2022 import CensusGen22 from model_bakery import baker from faker import Faker +from django.db import connection -from .models import SingleAuditChecklist, CognizantBaseline, User +from audit.models import SingleAuditChecklist +from .models import CognizantBaseline, CognizantAssignment -from audit.cog_over import cog_over +from .cog_over import compute_cog_over # Note: Fake data is generated for SingleAuditChecklist, CognizantBaseline. # Using only the data fields that apply to cog / over assignment. +UNIQUE_EIN_WITHOUT_DBKEY = "UEWOD1234" +DUP_EIN_WITHOUT_RESOLVER = "DEWOR1234" +EIN_2023_ONLY = "EIN202312" +RESOLVABLE_EIN_WITHOUT_BASELINE = "REWOB1234" +RESOLVABLE_UEI_WITHOUT_BASELINE = "RUWOB1234" +RESOLVABLE_DBKEY_WITHOUT_BASELINE = "20220" +UEI_WITH_BASELINE = "UB0011223" + class CogOverTests(TestCase): def __init__(self, method_name: str = "runTest") -> None: super().__init__(method_name) def setUp(self): - self.user = baker.make(User) + with connection.schema_editor() as schema_editor: + schema_editor.create_model(CensusGen19) + schema_editor.create_model(CensusGen22) + schema_editor.create_model(CensusCfda19) + + gen = baker.make( + CensusGen19, + index=1, + ein=UNIQUE_EIN_WITHOUT_DBKEY, + dbkey=None, + totfedexpend="210000000", + ) + gen.save() + for i in range(6): + cfda = baker.make( + CensusCfda19, + index=i, + ein=gen.ein, + dbkey=gen.dbkey, + cfda="84.032", + amount=10_000_000 * i, + direct="Y", + ) + cfda.save() + for i in range(2, 5): + gen = baker.make( + CensusGen19, + index=i, + ein=DUP_EIN_WITHOUT_RESOLVER, + dbkey=str(10_000 + i), + totfedexpend="10000000", + ) + gen.save() + gen = baker.make( + CensusGen22, + index=11, + ein=RESOLVABLE_EIN_WITHOUT_BASELINE, + uei=RESOLVABLE_UEI_WITHOUT_BASELINE, + dbkey=RESOLVABLE_DBKEY_WITHOUT_BASELINE, + totfedexpend="210000000", + ) + gen.save() + gen = baker.make( + CensusGen19, + index=11, + ein=RESOLVABLE_EIN_WITHOUT_BASELINE, + dbkey=RESOLVABLE_DBKEY_WITHOUT_BASELINE, + totfedexpend="210000000", + ) + gen.save() + for i in range(6): + cfda = baker.make( + CensusCfda19, + index=i + 10, + ein=gen.ein, + dbkey=gen.dbkey, + cfda="22.032", + amount=10_000_000 * i, + direct="Y", + ) + cfda.save() @staticmethod def _fake_general(): fake = Faker() return { - "ein": fake.ssn().replace("-", ""), + "ein": "ABC123DEF456", "audit_type": "single-audit", "auditee_uei": "ZQGGHJH74DW7", "auditee_zip": fake.zipcode(), @@ -67,8 +139,7 @@ def _fake_cognizantbaseline(): "cognizant_agency": "20", } - @staticmethod - def _fake_federal_awards(): + def _fake_federal_awards(self): return { "FederalAwards": { "auditee_uei": "ABC123DEF456", @@ -124,12 +195,11 @@ def _fake_federal_awards(): "direct_or_indirect_award": {"is_direct": "Y"}, }, ], - "total_amount_expended": 51_000_000, + "total_amount_expended": 52_000_200, } } - @staticmethod - def _fake_federal_awards_lt_cog_limit(): + def _fake_federal_awards_lt_cog_limit(self): return { "FederalAwards": { "auditee_uei": "ABC123DEF456", @@ -138,7 +208,7 @@ def _fake_federal_awards_lt_cog_limit(): "award_reference": "ABC125", "program": { "program_name": "SENIOR VOLUNTEER PROGRAM", - "amount_expended": 11_000_000, + "amount_expended": 11_200_300, "federal_agency_prefix": "15", "federal_program_total": 12_000_000, "three_digit_extension": "600", @@ -146,12 +216,11 @@ def _fake_federal_awards_lt_cog_limit(): "direct_or_indirect_award": {"is_direct": "Y"}, }, ], - "total_amount_expended": 11_000_000, + "total_amount_expended": 11_200_300, } } - @staticmethod - def _fake_federal_awards_lt_da_threshold(): + def _fake_federal_awards_lt_da_threshold(self): return { "FederalAwards": { "auditee_uei": "ABC123DEF456", @@ -172,90 +241,124 @@ def _fake_federal_awards_lt_da_threshold(): } } - def test_cog_over_for_gt_cog_limit_gt_da_threshold_factor_cog_2019(self): - sac = SingleAuditChecklist.objects.create( - submitted_by=self.user, + def _fake_sac(self): + sac = baker.make( + SingleAuditChecklist, general_information=self._fake_general(), federal_awards=self._fake_federal_awards(), ) - sac.save() - self.sac = sac + return sac - fake_cog_baseline = self._fake_cognizantbaseline() - self.cognizantbaseline = CognizantBaseline( - dbkey=fake_cog_baseline["dbkey"], - audit_year=fake_cog_baseline["audit_year"], - ein=self.sac.general_information["ein"], - cognizant_agency=fake_cog_baseline["cognizant_agency"], - ).save() - cog_agency = None - over_agency = None - cog_agency, over_agency = cog_over(self.sac) - self.assertEqual(cog_agency, "20") + def test_cog_assignment_from_hist(self): + """ + When we have a matching row in 2019 and nothing in the + baseline table, we should use the cog computed from 2019 data + """ + sac = self._fake_sac() + sac.general_information["ein"] = UNIQUE_EIN_WITHOUT_DBKEY + cog_agency, over_agency = compute_cog_over( + sac.federal_awards, sac.submission_status, sac.ein, sac.auditee_uei + ) + self.assertEqual(cog_agency, "84") + self.assertEqual(over_agency, None) + + def test_cog_assignment_with_no_hist(self): + """ + We have no match in the base sheet and we + have no match in 2019. So, assign from 2023" + """ + sac = self._fake_sac() + sac.general_information["ein"] = EIN_2023_ONLY + cog_agency, over_agency = compute_cog_over( + sac.federal_awards, sac.submission_status, sac.ein, sac.auditee_uei + ) + self.assertEqual(cog_agency, "10") self.assertEqual(over_agency, None) - def test_cog_over_for_lt_cog_lit_gt_da_threshold_factor_oversight(self): - sac = SingleAuditChecklist.objects.create( - submitted_by=self.user, + def test_cog_assignment_with_multiple_hist(self): + """ + We have no match in the base sheet and we + have duplicates in 2019. So, assign from 2023 + """ + + sac = self._fake_sac() + sac.general_information["ein"] = DUP_EIN_WITHOUT_RESOLVER + cog_agency, over_agency = compute_cog_over( + sac.federal_awards, sac.submission_status, sac.ein, sac.auditee_uei + ) + self.assertEqual(cog_agency, "10") + self.assertEqual(over_agency, None) + + def test_cog_assignment_with_hist_resolution(self): + """ + We have a unique dbkey for the given uei/eint in 2022, + and we have a match in 2019, but nothing in the baseline. + So, assign from 2019 + """ + sac = self._fake_sac() + + sac.general_information["ein"] = RESOLVABLE_EIN_WITHOUT_BASELINE + sac.general_information["auditee_uei"] = RESOLVABLE_UEI_WITHOUT_BASELINE + cog_agency, over_agency = compute_cog_over( + sac.federal_awards, sac.submission_status, sac.ein, sac.auditee_uei + ) + self.assertEqual(cog_agency, "22") + self.assertEqual(over_agency, None) + + def test_over_assignment(self): + """ + Awards with totals less than the threshold should result in an oversight agency being assigned. + """ + sac = baker.make( + SingleAuditChecklist, general_information=self._fake_general(), federal_awards=self._fake_federal_awards_lt_cog_limit(), ) - sac.save() - self.sac = sac - - fake_cog_baseline = self._fake_cognizantbaseline() - self.cognizantbaseline = CognizantBaseline( - dbkey=fake_cog_baseline["dbkey"], - audit_year=fake_cog_baseline["audit_year"], - ein=self.sac.general_information["ein"], - cognizant_agency=fake_cog_baseline["cognizant_agency"], - ).save() - cog_agency = None - over_agency = None - cog_agency, over_agency = cog_over(self.sac) + cog_agency, over_agency = compute_cog_over( + sac.federal_awards, sac.submission_status, sac.ein, sac.auditee_uei + ) self.assertEqual(cog_agency, None) self.assertEqual(over_agency, "15") - def test_cog_over_for_lt_cog_limit_lt_da_threshold_oversight(self): - sac = SingleAuditChecklist.objects.create( - submitted_by=self.user, + def test_over_assignment_with_hist(self): + """ + Awards with totals less than the threshold should result in an oversight agency being assigned. + And history data should not be used. + """ + sac = baker.make( + SingleAuditChecklist, general_information=self._fake_general(), - federal_awards=self._fake_federal_awards_lt_da_threshold(), + federal_awards=self._fake_federal_awards_lt_cog_limit(), + ) + sac.general_information["ein"] = UNIQUE_EIN_WITHOUT_DBKEY + cog_agency, over_agency = compute_cog_over( + sac.federal_awards, sac.submission_status, sac.ein, sac.auditee_uei ) - sac.save() - self.sac = sac - - fake_cog_baseline = self._fake_cognizantbaseline() - self.cognizantbaseline = CognizantBaseline( - dbkey=fake_cog_baseline["dbkey"], - audit_year=fake_cog_baseline["audit_year"], - ein=self.sac.general_information["ein"], - cognizant_agency=fake_cog_baseline["cognizant_agency"], - ).save() - cog_agency = None - over_agency = None - cog_agency, over_agency = cog_over(self.sac) self.assertEqual(cog_agency, None) - self.assertEqual(over_agency, "25") + self.assertEqual(over_agency, "15") - def test_cog_over_gt_cog_limit_no_2019(self): - sac = SingleAuditChecklist.objects.create( - submitted_by=self.user, - general_information=self._fake_general(), - federal_awards=self._fake_federal_awards(), + def test_cog_assignment_with_uei_in_baseline(self): + sac = self._fake_sac() + sac.general_information["auditee_uei"] = UEI_WITH_BASELINE + baker.make( + CognizantBaseline, + uei=UEI_WITH_BASELINE, + cognizant_agency="17", ) - sac.save() - self.sac = sac - - fake_cog_baseline = self._fake_cognizantbaseline() - self.cognizantbaseline = CognizantBaseline( - dbkey=fake_cog_baseline["dbkey"], - audit_year=fake_cog_baseline["audit_year"], - ein=fake_cog_baseline["ein"], - cognizant_agency=fake_cog_baseline["cognizant_agency"], - ).save() - cog_agency = None - over_agency = None - cog_agency, over_agency = cog_over(self.sac) - self.assertEqual(cog_agency, "10") + cog_agency, over_agency = compute_cog_over( + sac.federal_awards, sac.submission_status, sac.ein, sac.auditee_uei + ) + self.assertEqual(cog_agency, "17") self.assertEqual(over_agency, None) + + def test_cog_assignment_with_uei_in_baseline_and_overris(self): + sac = self._fake_sac() + sac.general_information["auditee_uei"] = UEI_WITH_BASELINE + baker.make( + CognizantBaseline, + uei=UEI_WITH_BASELINE, + cognizant_agency="17", + ) + sac.assign_cog_over() + cas = CognizantAssignment.objects.all() + self.assertEquals(1, len(cas)) diff --git a/backend/templates/includes/header.html b/backend/templates/includes/header.html index f3bf560ab1..4e7a2a320b 100644 --- a/backend/templates/includes/header.html +++ b/backend/templates/includes/header.html @@ -76,17 +76,23 @@
    -
    -
    This application is currently under active development. Thank you for helping to improve the FAC by participating in user testing.
    -
    {% include "includes/nav_primary.html" %}
    +{% comment %} "Breadcrumbs" for all pages, excepting the homepage. {% endcomment %} {% if request.path != '/' %}
    - {% comment %} Placeholder. Replace with required OMB control value when designed. {% endcomment %} +
    + {% comment %} + OMB_NUMBER and OMB_EXP_DATE are global. + Added in /config/settings, passed in /config/context_processors.py + {% endcomment %} +

    + OMB# {{ OMB_NUMBER }} EXP: {{ OMB_EXP_DATE }} +

    +
    -{% endif %} \ No newline at end of file +{% endif %} diff --git a/backend/templates/includes/nav_primary.html b/backend/templates/includes/nav_primary.html index ff4ec5eab9..1f3497502e 100644 --- a/backend/templates/includes/nav_primary.html +++ b/backend/templates/includes/nav_primary.html @@ -32,11 +32,11 @@ @@ -49,36 +49,47 @@
  • - - FAQs - -
  • -
  • - - Contact Us - + +
  • {% if request.user.is_authenticated %} diff --git a/backend/users/migrations/0001_create_user_profile.py b/backend/users/migrations/0001_initial.py similarity index 93% rename from backend/users/migrations/0001_create_user_profile.py rename to backend/users/migrations/0001_initial.py index 8087919081..bb6138d5f6 100644 --- a/backend/users/migrations/0001_create_user_profile.py +++ b/backend/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.4 on 2022-04-22 19:09 +# Generated by Django 4.2.3 on 2023-09-20 14:33 from django.conf import settings from django.db import migrations, models @@ -29,6 +29,7 @@ class Migration(migrations.Migration): "entry_form_data", models.JSONField( blank=True, + default=dict, help_text="Store of form data for Eligiblity, Info, and access steps prior to creation of an SF-SAC", null=True, ), diff --git a/backend/users/migrations/0002_add_default_for_jsonfield.py b/backend/users/migrations/0002_add_default_for_jsonfield.py deleted file mode 100644 index 5763020222..0000000000 --- a/backend/users/migrations/0002_add_default_for_jsonfield.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.0.4 on 2022-04-25 14:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("users", "0001_create_user_profile"), - ] - - operations = [ - migrations.AlterField( - model_name="userprofile", - name="entry_form_data", - field=models.JSONField( - blank=True, - default=dict, - help_text="Store of form data for Eligiblity, Info, and access steps prior to creation of an SF-SAC", - null=True, - ), - ), - ] diff --git a/backend/users/migrations/0003_logingovuser.py b/backend/users/migrations/0003_logingovuser.py deleted file mode 100644 index 9093865da0..0000000000 --- a/backend/users/migrations/0003_logingovuser.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 4.0.4 on 2022-06-27 15:38 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("users", "0002_add_default_for_jsonfield"), - ] - - operations = [ - migrations.CreateModel( - name="LoginGovUser", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("login_id", models.CharField(max_length=100, unique=True)), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/users/migrations/0004_alter_logingovuser_login_id.py b/backend/users/migrations/0004_alter_logingovuser_login_id.py deleted file mode 100644 index 1d4e227b7f..0000000000 --- a/backend/users/migrations/0004_alter_logingovuser_login_id.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.0.4 on 2022-07-06 19:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("users", "0003_logingovuser"), - ] - - operations = [ - migrations.AlterField( - model_name="logingovuser", - name="login_id", - field=models.CharField(max_length=50, unique=True), - ), - ] diff --git a/backend/users/migrations/0005_delete_logingovuser.py b/backend/users/migrations/0005_delete_logingovuser.py deleted file mode 100644 index 245602bb5b..0000000000 --- a/backend/users/migrations/0005_delete_logingovuser.py +++ /dev/null @@ -1,15 +0,0 @@ -# Generated by Django 4.1.3 on 2022-11-23 16:25 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("users", "0004_alter_logingovuser_login_id"), - ] - - operations = [ - migrations.DeleteModel( - name="LoginGovUser", - ), - ] diff --git a/docs/dbbackups.md b/docs/dbbackups.md new file mode 100644 index 0000000000..055f5d7446 --- /dev/null +++ b/docs/dbbackups.md @@ -0,0 +1,27 @@ +# Database Backups + +Information regarding the django utility can be found [on the documentation page](https://django-dbbackup.readthedocs.io/en/master/commands.html) +Database backups occur in the following ways: +1. Django backups +```bash +python manage.py dbbbackup +python manage.py mediabackup +``` +2. Django restores +```bash +python manage.py dbrestore +python manage.py mediarestore +``` +3. Backups in the prod environment occur every deployment, [before the most recent code is applied](https://github.com/GSA-TTS/FAC/blob/fd3a59287d58aec06a78d6da3b42a5def8fc9c98/.github/workflows/deploy-application.yml#L72-L100) +4. Manual steps are listed in the following document for where to catalog backups + * [Deploying](./deploying.md) + * Login via CF and tail the logs during a deployment (before it gets to deploy application stage) + * Post the most recent dbbackup and mediabackup file names in https://github.com/GSA-TTS/FAC/issues/2221 +```bash +cf login -a api.fr.cloud.gov --sso +Select an org: +1. gsa-tts-oros-fac +Select a space: +5. production +cf logs gsa-fac +``` diff --git a/docs/deploying.md b/docs/deploying.md index b870c19895..d0be3e0f56 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -92,9 +92,28 @@ cf push -f manifests/manifest-dev.yml 5. Verify that the deploy steps all passed. 6. After deployment, the changes should be on https://app.fac.gov/. 7. If anything was merged directly into the `prod` branch, such as a hotfix, merge `prod` back into `main`. +8. Login via CF and tail the logs during a deployment (before it gets to deploy application stage) +```bash +cf login -a api.fr.cloud.gov --sso +Select an org: +1. gsa-tts-oros-fac +Select a space: +5. production +cf logs gsa-fac +``` +9. Post the most recent dbbackup and mediabackup file names in https://github.com/GSA-TTS/FAC/issues/2221 To see more about branching and the deployment steps, see the [Branching](branching.md) page. +#### Deploying to `preview` +1. Navigate to the [Deploy Preview Workflow](https://github.com/GSA-TTS/FAC/actions/workflows/deploy-preview.yml) + * Select a ref to target + * This ref can be `main`, but it was designed to allow deploys from any branch. ex `user/my-branch` +2. Wait for deployment to pass or fail +3. If checks or deployment fails, something has gone awry. Investigation will be needed. +4. Verify that the deploy steps all passed. +5. After deployment, the changes should be on https://fac-preview.app.cloud.gov/. + ## Running a Django admin command You can SSH into a running instance of the app. Running Django apps is a little more complicated on Cloud Foundry than running locally. @@ -112,7 +131,7 @@ export LD_LIBRARY_PATH=~/deps/0/python/lib ### Problem: A GitHub Action run says that Terraform cannot be applied because the plan differs from what was approved. #### Solution: Click through from the error message to the referenced PR, click the `Checks` tab, and rerun the Terraform plan action. Then rerun the action that failed originally. -More info: When there are changes to the `terraform/` directory in a PR, [a GitHub Action](https://github.com/GSA-TTS/FAC/blob/main/.github/workflows/terraform-plan.yml) posts comments to the PR which include the "plan" indicating what will change in each environment when the PR is deployed there. ([example](https://github.com/GSA-TTS/FAC/pull/875)) Those comments help those reviewing and approving the PR to understand the implication of the change. +More info: When there are changes to the `terraform/` directory in a PR, [a GitHub Action](https://github.com/GSA-TTS/FAC/blob/main/.github/workflows/terraform-plan.yml) posts comments to the PR which include the "plan" indicating what will change in each environment when the PR is deployed there. ([example](https://github.com/GSA-TTS/FAC/pull/875)) Those comments help those reviewing and approving the PR to understand the implication of the change. However, we don't deploy immediately to the `staging` and `production` environments, and it's possible that other PRs will get merged, or that someone will make manual changes in those environments in the meantime. As a sanity check the plan is regenerated right before applying a change in those environments. If there's any difference from what was approved in the PR (captured in those comments), it's an indicator that the approved plan is **not** what is about to happen, and the action aborts so that humans can inspect and intervene if necessary. The simplest way to say "yeah, I want the new plan to happen, that's still OK" is to go regenerate the plans being compared against in the PR, using the steps indicated. diff --git a/terraform/meta/README.md b/terraform/meta/README.md index 623955148d..bc9a7e0131 100644 --- a/terraform/meta/README.md +++ b/terraform/meta/README.md @@ -23,7 +23,7 @@ NOTE: The deploying account must have the OrgManager role in the target organization. -## TODO: +## TODO: * Make bootstrap.sh script * Checks that the user is logged into GitHub as repo admin and Cloud Foundry as OrgAdmin @@ -36,3 +36,5 @@ organization. * Move the services currently in "management" into the "meta" space; we're not really using that space anyway * Double-check that the "management" space can be blown away (first confirming that the *actual* Terraform state is in the S3 instance in the "production" space) * Update/simplify ../terraform/README.md! +* Meta module handing sharing the spaces + diff --git a/terraform/meta/config.tf b/terraform/meta/config.tf index 741300b93b..59d1b27d9e 100644 --- a/terraform/meta/config.tf +++ b/terraform/meta/config.tf @@ -1,10 +1,16 @@ locals { org_name = "gsa-tts-oros-fac" spaces = { - "dev" = {}, - "staging" = {}, - "preview" = {}, - "production" = { allow_ssh = false }, + "dev" = {}, + "preview" = {}, + "staging" = { + uses_backups = true + }, + "production" = { + allow_ssh = false, + uses_backups = true, + is_production = true + }, } # All spaces have the same SpaceDevelopers for now diff --git a/terraform/meta/meta.tf b/terraform/meta/meta.tf index 05446c8c96..28fc325d62 100644 --- a/terraform/meta/meta.tf +++ b/terraform/meta/meta.tf @@ -34,3 +34,34 @@ module "environments" { # bootstrap-env module manage both spaces in a future PR! asgs = lookup(each.value, "allow_egress", false) ? tolist(local.egress_asgs) : tolist(local.internal_asgs) } + +locals { + # Filters the list of spaces with a value of true for "uses_backups". We only want to share the bucket to those spaces. + spaces_that_use_backups = join(" ", [for key, config in local.spaces : lookup(config, "uses_backups", false) ? key : ""]) +} + +module "s3-backups" { + source = "github.com/18f/terraform-cloudgov//s3?ref=v0.5.1" + + cf_org_name = local.org_name + # TODO: This should be the key for the first space that says "is_production = + # true" rather than being hardcoded + cf_space_name = "production" + name = "backups" + s3_plan_name = "basic" +} + +# TODO: We should have a corresponding "unshar-backup-from-spaces" resource, in +# case a space is removed from the list + +# resource "null_resource" "share-backup-to-spaces" { +# provisioner "local-exec" { +# environment = { +# SPACES = "${local.spaces_that_use_backups}" +# } +# command = "for space in $SPACES ; do cf share-service backups -s $space; done" +# } +# depends_on = [ +# module.s3-backups +# ] +# }