diff --git a/README.md b/README.md index e97214602..5e6bf4fca 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ Eligibility made easy Eligibility Made Easy (Emmy) using Consent-Based Verification (CBV) is a prototype that allows benefit applicants to verify their income directly using payroll providers. It is currently being piloted for testing and validation purposes. - ## Project Vision + ## Project Vision Eligibility Made Easy (Emmy) is a project to allow applicants to verify their income and community engagement directly using payroll providers and educational records. Emmy was developed and is supported by CMS in order to offer states a drop-in component for their application process to allow applicants to apply for state benefits more easily. This is part of a more comprehensive process to [improve data services for benefits delivery](https://assets.performance.gov/cx/files/OMB-CX-LifeExperience-FFS-ImprovingData.pdf). - ## Project Mission -Emmy uses consent-based verification (CBV) with multiple data sources, making the process much faster and more efficient than a simple document-upload service. CBV enables additional cost avoidance by optimizing manual document review processes. Rather than having to process incorrect or blurry documents, consent-based verification produces an easily consumed report with essential information. Verification information is returned in a standardized format easy for other systems to process (JSON), allowing integration with existing state systems. + ## Project Mission +Emmy uses consent-based verification (CBV) with multiple data sources, making the process much faster and more efficient than a simple document-upload service. CBV enables additional cost avoidance by optimizing manual document review processes. Rather than having to process incorrect or blurry documents, consent-based verification produces an easily consumed report with essential information. Verification information is returned in a standardized format easy for other systems to process (JSON), allowing integration with existing state systems. Emmy is under active development by CMS, with new updates released on a 2-week cadence. # Core Team @@ -110,15 +110,28 @@ To run database migrations on the test environment that is used by rpec tests, r ### JSON API Testing +To acceptance test the JSON API, you can run the independent **reference server implementation**. + 1. **Create an API key for the agency you want to test:** ```bash cd app - rails 'users:create_api_token[agency_name]' + bin/rails 'users:create_api_token[agency_id]' ``` 2. **Run the standalone test receiver:** ```bash - JSON_API_KEY=$(rails runner "puts User.api_key_for_agency('agency_name')") ruby lib/json_api_receiver.rb + JSON_API_KEY=$(bin/rails runner "puts User.api_key_for_agency('agency_id')") ruby lib/json_api_receiver.rb + ``` + +3. **Configure Emmy App to POST to the reference server.** Add this to your `.env.local`: + ```bash + # For testing LA SFTP against sinatra reference implementation + LA_LDH_TRANSMISSION_METHOD=json_and_pdf + LA_LDH_INCOME_REPORT_URL=http://localhost:4567 + LA_LDH_PDF_API_URL=http://localhost:4567/pdf + LA_LDH_INCOME_REPORT_APIKEY=foo + LA_LDH_INCLUDE_REPORT_PDF=false + LA_LDH_INCOME_REPORT_ACCOUNTCODE=foobar ``` This starts a standalone test server on port 4567 that logs incoming JSON data and verifies HMAC signatures. The receiver is completely independent and can be used as a reference implementation for agencies building their own JSON API endpoints. @@ -453,9 +466,6 @@ See [GOVERNANCE.md](./GOVERNANCE.md) If you have ideas for how we can improve or add to our capacity building efforts and methods for welcoming people into our community, please let us know by sending an email to: ffs at nava pbc dot com. If you would like to comment on the tool itself, please let us know by filing an **issue on our GitHub repository.** -## Glossary -Information about terminology and acronyms used in this documentation may be found in [GLOSSARY.md](GLOSSARY.md). - ## Policies ### Open Source Policy @@ -491,4 +501,4 @@ This project is in the public domain within the United States, and copyright and All contributions to this project will be released under the CC0 dedication. By submitting a pull request or issue, you are agreeing to comply with this waiver of copyright interest. ## Core team -See [COMMUNITY.md](./COMMUNITY.md). \ No newline at end of file +See [COMMUNITY.md](./COMMUNITY.md). diff --git a/app/.env b/app/.env index 292f5798e..914df30b7 100644 --- a/app/.env +++ b/app/.env @@ -11,6 +11,7 @@ # ############################################################################## LA_LDH_PINWHEEL_ENVIRONMENT=sandbox SANDBOX_PINWHEEL_ENVIRONMENT=sandbox +RESEARCH_PINWHEEL_ENVIRONMENT=sandbox MAINTENANCE_MODE=false LA_LDH_WEEKLY_REPORT_RECIPIENTS=test@email.com @@ -22,6 +23,7 @@ SUPPORTED_PROVIDERS=pinwheel,argyle DOMAIN_NAME=localhost LA_LDH_DOMAIN_NAME=la.localhost SANDBOX_DOMAIN_NAME=localhost +RESEARCH_DOMAIN_NAME=research.localhost PINWHEEL_API_TOKEN_SANDBOX=API secret ARGYLE_SANDBOX_WEBHOOK_SECRET=Webhook Secret diff --git a/app/AGENTS.md b/app/AGENTS.md index 7759279f2..eb4034e03 100644 --- a/app/AGENTS.md +++ b/app/AGENTS.md @@ -26,7 +26,7 @@ When developing on the Rails app, ensure you are always in the `app` subdirector - JS/TS: Prettier (`tabWidth: 2`, double quotes, no semicolons, `printWidth: 100`) via `npm run format` or `npm run format:precommit`. - Tests follow `_spec.rb` / `.test.ts`; favor descriptive, imperative example names. Use snake_case for Ruby, camelCase for JS, kebab-case for Stimulus files. Prefer `let` for object setup, `before` blocks for shared session/context setup, and `Timecop` for time freezing in controller specs (using `around` blocks). - ERB/HTML: Put each HTML tag on its own line (opening tag, contents, closing tag) for readability and avoid `usa-prose` classes unless required by design. -- Do not use margin or padding utility helpers (e.g., `margin-bottom-*`, `padding-*`) unless explicitly requested. +- Layout and spacing: Prefer USWDS / project utility classes in ERB for one-off layout and spacing (e.g., `display-flex`, `flex-justify-center`, `margin-top-*`, `padding-*`). Add SCSS when the same rules repeat across elements, when a named class carries semantic meaning (states, variants), or when styling is too complex or token-heavy to express cleanly as utilities. ## Testing Guidelines - Add coverage for new endpoints, logic, and service objects; exercise eligibility and payroll edge cases. diff --git a/app/Gemfile.lock b/app/Gemfile.lock index 7304ae8e0..8860c533c 100644 --- a/app/Gemfile.lock +++ b/app/Gemfile.lock @@ -119,12 +119,12 @@ GEM aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) - axe-core-api (4.11.1) + axe-core-api (4.11.2) dumb_delegator ostruct virtus - axe-core-rspec (4.11.1) - axe-core-api (= 4.11.1) + axe-core-rspec (4.11.2) + axe-core-api (= 4.11.2) dumb_delegator ostruct virtus @@ -215,7 +215,7 @@ GEM tzinfo factory_bot (6.5.6) activesupport (>= 6.1.0) - faker (3.6.1) + faker (3.8.0) i18n (>= 1.8.11, < 2) faraday (2.14.1) faraday-net_http (>= 2.0, < 3.5) @@ -267,7 +267,7 @@ GEM jmespath (1.6.2) jsbundling-rails (1.3.1) railties (>= 6.0.0) - json (2.19.3) + json (2.19.4) json-logic-rb (0.1.5) language_server-protocol (3.17.0.5) lint_roller (1.1.0) @@ -298,7 +298,7 @@ GEM method_source (1.1.0) mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (6.0.3) + minitest (6.0.5) drb (~> 2.0) prism (~> 1.5) mission_control-jobs (1.1.0) @@ -333,7 +333,7 @@ GEM net-smtp (0.5.1) net-protocol net-ssh (7.3.2) - newrelic_rpm (10.2.0) + newrelic_rpm (10.4.0) logger nio4r (2.7.5) nokogiri (1.19.2-aarch64-linux-gnu) @@ -346,8 +346,8 @@ GEM racc (~> 1.4) orm_adapter (0.5.0) ostruct (0.6.3) - parallel (1.27.0) - parser (3.3.10.2) + parallel (2.0.1) + parser (3.3.11.1) ast (~> 2.4.1) racc pdf-reader (2.15.1) @@ -461,7 +461,7 @@ GEM redis-client (>= 0.22.0) redis-client (0.26.4) connection_pool - regexp_parser (2.11.3) + regexp_parser (2.12.0) reline (0.6.3) io-console (~> 0.5) responders (3.2.0) @@ -486,11 +486,11 @@ GEM rspec-mocks (>= 3.13.0, < 5.0.0) rspec-support (>= 3.13.0, < 5.0.0) rspec-support (3.13.7) - rubocop (1.86.0) + rubocop (1.86.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) - parallel (~> 1.10) + parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) @@ -579,7 +579,7 @@ GEM uri (1.1.1) useragent (0.16.11) vcr (6.4.0) - view_component (4.6.0) + view_component (4.7.0) actionview (>= 7.1.0) activesupport (>= 7.1.0) concurrent-ruby (~> 1) @@ -608,7 +608,7 @@ GEM wkhtmltopdf-binary (0.12.6.10) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.38) + yard (0.9.42) zeitwerk (2.7.5) PLATFORMS @@ -646,7 +646,7 @@ DEPENDENCIES ed25519 erb_lint factory_bot (~> 6.5) - faker (~> 3.6) + faker (~> 3.8) faraday (~> 2.14.1) gpgme (~> 2.0) i18n-tasks (~> 1.1) diff --git a/app/app/assets/stylesheets/application.postcss.css b/app/app/assets/stylesheets/application.postcss.css index a2594d88b..6c6a39ef7 100644 --- a/app/app/assets/stylesheets/application.postcss.css +++ b/app/app/assets/stylesheets/application.postcss.css @@ -5,6 +5,7 @@ @forward "activity_hub.scss"; @forward "cbv.scss"; @forward "demo-launcher.scss"; +@forward "document_uploads.scss"; /* Import styling from ViewComponents */ @forward "../../components/activity_flow_progress_indicator/activity_flow_progress_indicator.scss"; diff --git a/app/app/assets/stylesheets/cbv.scss b/app/app/assets/stylesheets/cbv.scss index 272cf50ef..cb1898389 100644 --- a/app/app/assets/stylesheets/cbv.scss +++ b/app/app/assets/stylesheets/cbv.scss @@ -532,24 +532,3 @@ input#invitation_link { font-weight: bold; color: #005EA2; } - -/* - * Document upload page - */ -.document-uploads__preview { - display: flex; - align-items: center; -} -.document-uploads__preview-icon { - box-sizing: content-box; - color: color("gray-cool-40"); - flex-shrink: 0; - height: units(4); - padding: units(1); - width: units(4); -} -.document-uploads__preview-filename { - @include u-font("sans", 2); - overflow: hidden; - word-break: break-word; -} diff --git a/app/app/assets/stylesheets/demo-launcher.scss b/app/app/assets/stylesheets/demo-launcher.scss index d6a14060c..1066f686c 100644 --- a/app/app/assets/stylesheets/demo-launcher.scss +++ b/app/app/assets/stylesheets/demo-launcher.scss @@ -106,9 +106,42 @@ margin: 0; } + .demo-launcher__launch-section { + padding-top: units(2); + border-top: 1px solid color("base-lighter"); + } + + .demo-launcher__share-widget { + margin-top: units(2); + padding: units(2); + border: 1px solid color("base-lighter"); + border-radius: radius("md"); + background-color: color("base-lightest"); + } + + .demo-launcher__share-row { + display: grid; + gap: units(1); + align-items: end; + + @include at-media("tablet") { + grid-template-columns: minmax(0, 1fr) auto auto; + } + + .usa-input, + .usa-button { + margin: 0; + } + } + + .demo-launcher__share-status { + margin-top: units(1); + margin-bottom: 0; + } + // -- Left column: test scenarios -- .demo-launcher__test-scenarios { - margin-top: units(8); + margin-top: 0; } .demo-launcher__scenario-radios { @@ -186,4 +219,10 @@ flex-shrink: 0; } } + + .demo-launcher__renewal-required { + &.demo-launcher__renewal-required--hidden { + display: none; + } + } } diff --git a/app/app/assets/stylesheets/document_uploads.scss b/app/app/assets/stylesheets/document_uploads.scss new file mode 100644 index 000000000..54444b884 --- /dev/null +++ b/app/app/assets/stylesheets/document_uploads.scss @@ -0,0 +1,57 @@ +@forward "uswds"; +@use "uswds" as *; + +/* + * Document uploads + */ +.document-uploads { + margin-bottom: units(4); + margin-top: units(4); +} +.document-uploads__heading { + margin-bottom: units(2); + margin-top: 0; +} +.document-uploads__list { + list-style: none; + margin: 0; + padding: 0; +} +.document-uploads__item { + align-items: center; + border-bottom: 1px solid color("gray-cool-20"); + display: flex; + gap: units(2); + justify-content: space-between; + padding-bottom: units(1); + padding-top: units(1); +} +.document-uploads__file { + align-items: center; + display: flex; + gap: units(2); + min-width: 0; +} +.document-uploads__icon { + background-color: color("blue-warm-5"); + border: 1px solid color("blue-warm-20v"); + border-radius: radius("md"); + box-sizing: content-box; + color: color("primary"); + flex-shrink: 0; + height: 1.5rem; + padding: units(1); + width: 1.5rem; +} +.document-uploads__filename { + @include u-font("sans", 4); + line-height: line-height("sans", 4); + overflow: hidden; + word-break: break-word; +} +.document-uploads__remove-link { + color: color("secondary-dark"); + flex-shrink: 0; + @include u-font("sans", 4); + line-height: line-height("sans", 4); +} diff --git a/app/app/components/activity_flow_header_component.rb b/app/app/components/activity_flow_header_component.rb index b94082441..a1d87e88e 100644 --- a/app/app/components/activity_flow_header_component.rb +++ b/app/app/components/activity_flow_header_component.rb @@ -8,4 +8,8 @@ def initialize(title:, exit_url:, back_url: nil) @exit_url = exit_url @back_url = back_url end + + def confirm_on_exit? + helpers.params[:from_edit].blank? + end end diff --git a/app/app/components/activity_flow_header_component/activity_flow_header_component.html.erb b/app/app/components/activity_flow_header_component/activity_flow_header_component.html.erb index d8f347bf8..609835428 100644 --- a/app/app/components/activity_flow_header_component/activity_flow_header_component.html.erb +++ b/app/app/components/activity_flow_header_component/activity_flow_header_component.html.erb @@ -1,6 +1,9 @@
- <% if reporting_window_start_month && reporting_window_end_month %> - <%= t( - ".renewal_subtitle", - required: required_month_count, - start_month: l(reporting_window_start_month, format: :month), - end_month: l(reporting_window_end_month, format: :month) - ) %> +<% if collapsed? %> + <% success_icon_href = helpers.uswds_sprite_icon_href("check_circle") %> +
+ <% if reporting_window_start_month && reporting_window_end_month %> + <%= t( + ".renewal_subtitle", + required: required_month_count, + start_month: l(reporting_window_start_month, format: :month), + end_month: l(reporting_window_end_month, format: :month) + ) %> + <% else %> + <%= t(".renewal_subtitle_no_range", required: required_month_count) %> + <% end %> +
+ <% end %> -