From da829fe8e606fd7ee4304b8d10e6a5c4241bea4a Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 10 Feb 2025 15:40:39 -0500 Subject: [PATCH 1/3] 2024 Transfer assessment (#848) * CAS version of the transfer assessment * Q5 for transfer assessment, rule and sync logic * Ignore erb files in rubocop check * Expose PSH required on client dashboard * Family pathways 2024 (#899) * WIP: stubs for family pathways * WIP; changes to family pathways * WIP; most questions for family pathways * WIP * Non-HMIS Pathways * Expose first date homeless on pathways client view * Update Pathways and Families Pathways assessments to collect first date homeless * Update tie breaker calculation for pathways assessments * WIP; Family PSH prioritization scheme * Tests for Family PSH prioritization * New prioritization schemes * Handle empty service needs * Q9 and Q9a updates; Bitly link updates * Pathways updates * Mark client unavailable if the availability question is skipped * Toggles for Q5A, Q5B, Q6 * Toggle visibility of Q5 dependents * Only calculate scores if you say yes or maybe to Q5 * Hide Family Pathways in production * Expose total days homeless on client dashboard, used for family pathways * Capture eviction history differently * Differentiate more family pathways from individual pathways * Missing file * Remove debugging * Review changes --- .github/workflows/rubocop.yml | 2 +- .../non_hmis_assessments_controller.rb | 10 - app/models/cas/update_clients.rb | 3 + app/models/client.rb | 24 + .../pathways_version_four_calculations.rb | 1027 +++++---- app/models/match_prioritization/base.rb | 5 +- app/models/match_prioritization/family_psh.rb | 67 + .../low_income_subsidies.rb | 67 + app/models/match_prioritization/rrh_and_th.rb | 67 + app/models/non_hmis_assessment.rb | 37 +- app/models/non_hmis_client.rb | 40 + app/models/rules/psh_required.rb | 18 + app/views/admin/match_routes/edit.haml | 2 +- app/views/clients/_domestic.haml | 8 + app/views/clients/_housing_requirements.haml | 4 + app/views/clients/_pathways_homelessness.haml | 7 + .../_barriers_preamble.haml | 1 + .../_ce_required_questions_preamble.haml | 2 + ...y_pathways_household_history_epilogue.haml | 8 + ...y_pathways_household_history_preamble.haml | 11 + .../_family_pathways_version_four_js.haml | 82 + ...family_pathways_version_four_preamble.haml | 43 + .../_household_size_preamble.haml | 1 + .../_pathways_household_history_preamble.haml | 12 +- .../_service_need_preamble.haml | 1 + .../_transfer_assessment_preamble.haml | 90 +- ...ommon_pathways_version_four_questions.haml | 3 +- .../_pathways_version_four_js.haml | 14 +- ...34210_add_federal_benefits_for_transfer.rb | 7 + ...827172144_add_contact_order_to_contacts.rb | 2 +- ...50102144339_add_psh_required_to_clients.rb | 7 + .../20250107142055_add_school_contacts.rb | 17 + ..._homeless_night_to_non_hmis_assessments.rb | 5 + db/migrate/20250113184426_add_household_dv.rb | 17 + ...42954_add_eviction_question_to_pathways.rb | 5 + .../20250206162624_add_eviction_history.rb | 5 + db/rules.csv | 1 + db/schema.rb | 1921 +++++++++++++++++ db/structure.sql | 35 +- docker/docker-compose.yml | 2 + spec/factories/match_priorities.rb | 9 + spec/factories/rules.rb | 4 + spec/models/client_spec.rb | 171 ++ spec/models/rules/psh_required_spec.rb | 43 + 44 files changed, 3419 insertions(+), 488 deletions(-) create mode 100644 app/models/match_prioritization/family_psh.rb create mode 100644 app/models/match_prioritization/low_income_subsidies.rb create mode 100644 app/models/match_prioritization/rrh_and_th.rb create mode 100644 app/models/rules/psh_required.rb create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_barriers_preamble.haml create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_ce_required_questions_preamble.haml create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_epilogue.haml create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_preamble.haml create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_js.haml create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_preamble.haml create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_household_size_preamble.haml create mode 100644 app/views/non_hmis_assessments/pathways_version_four/_service_need_preamble.haml create mode 100644 db/migrate/20240816134210_add_federal_benefits_for_transfer.rb create mode 100644 db/migrate/20250102144339_add_psh_required_to_clients.rb create mode 100644 db/migrate/20250107142055_add_school_contacts.rb create mode 100644 db/migrate/20250113131532_add_first_homeless_night_to_non_hmis_assessments.rb create mode 100644 db/migrate/20250113184426_add_household_dv.rb create mode 100644 db/migrate/20250118142954_add_eviction_question_to_pathways.rb create mode 100644 db/migrate/20250206162624_add_eviction_history.rb create mode 100644 db/schema.rb create mode 100644 spec/models/rules/psh_required_spec.rb diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 88acf5734..88d436fcb 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -61,7 +61,7 @@ jobs: run: | # Ignore files we don't control the format of ignore_patterns="db/schema.rb\|bin/rails\|bin/rake\|bin/bundle" - files=`echo "${ALL_CHANGED_FILES}" | tr " " "\n" | grep -v $ignore_patterns | grep "**/*.rb" | tr "\n" " ./"` + files=`echo "${ALL_CHANGED_FILES}" | tr " " "\n" | grep -v $ignore_patterns | grep "**/*\.rb" | tr "\n" " ./"` num=`echo $files | wc -w` echo $files if [ $num -gt 0 ]; then bundle exec rubocop --config ./.rubocop.yml $files; else echo "No changed ruby files"; fi diff --git a/app/controllers/non_hmis_assessments_controller.rb b/app/controllers/non_hmis_assessments_controller.rb index 4a3aff212..0b58e8a62 100644 --- a/app/controllers/non_hmis_assessments_controller.rb +++ b/app/controllers/non_hmis_assessments_controller.rb @@ -27,7 +27,6 @@ def create @assessment.update(opts) @non_hmis_client.update(assessed_at: @assessment.entry_date) if @assessment.entry_date if @assessment.save - enforce_no_transfer_2024! redirect_to @non_hmis_client else render :new @@ -44,21 +43,12 @@ def update opts = clean_assessment_params(@assessment.assessment_params(params)) if @assessment.update(opts) @non_hmis_client.update(assessed_at: @assessment.entry_date) if @assessment.entry_date - enforce_no_transfer_2024! redirect_to @non_hmis_client else render :edit end end - # TODO: Until we have a Transfer 2024 assessment, just convert all transfer assessments to 2021 assessments - private def enforce_no_transfer_2024! - NonHmisAssessment.where(assessment_name: :DeidentifiedPathwaysVersionFourTransfer). - update_all(assessment_name: :DeidentifiedPathwaysVersionThreeTransfer, type: :DeidentifiedPathwaysVersionThree) - NonHmisAssessment.where(assessment_name: :IdentifiedPathwaysVersionFourTransfer). - update_all(assessment_name: :IdentifiedPathwaysVersionThreeTransfer, type: :IdentifiedPathwaysVersionThree) - end - def destroy @assessment.destroy flash[:notice] = 'Assessment successfully deleted.' diff --git a/app/models/cas/update_clients.rb b/app/models/cas/update_clients.rb index 5cd8ba092..389ef9483 100644 --- a/app/models/cas/update_clients.rb +++ b/app/models/cas/update_clients.rb @@ -310,6 +310,9 @@ def project_client_attributes :ongoing_es_enrollments, :ongoing_so_enrollments, :last_seen_projects, + :household_dv_survivor, + :disqualified_for_state_assistance, + :psh_required, # Transfer V4 Q5 ] end diff --git a/app/models/client.rb b/app/models/client.rb index de65556e4..7392aa8fb 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -643,6 +643,15 @@ def need_daily_assistance_for_export bool_for_export(need_daily_assistance) end + def psh_required_for_export + case psh_required + when 'yes', 'no' + psh_required.capitalize + else + 'Unknown' + end + end + private def numeric_bool_for_export(value) return 'Yes' if value.to_s == '1' return 'No' if value.to_s == '0' @@ -1256,6 +1265,21 @@ def self.prioritized_columns_data description: nil, type: 'String', }, + household_dv_survivor: { + title: 'Household member experiencing domestic violence', + description: nil, + type: 'Boolean', + }, + disqualified_for_state_assistance: { + title: 'Ineligible for state emergency assistance', + description: nil, + type: 'Boolean', + }, + psh_required: { + title: 'In need of Permanent Supportive Housing', + description: nil, + type: 'String', + }, } end diff --git a/app/models/concerns/pathways_version_four_calculations.rb b/app/models/concerns/pathways_version_four_calculations.rb index 1b277fd08..7c58a56a6 100644 --- a/app/models/concerns/pathways_version_four_calculations.rb +++ b/app/models/concerns/pathways_version_four_calculations.rb @@ -16,11 +16,19 @@ module PathwaysVersionFourCalculations # The denial_required validation requires the .to_s due to the varchar data type validates_inclusion_of :denial_required, in: [[''].to_s], message: 'Cannot be checked unless Barriers to Housing is Yes', allow_blank: false, unless: :housing_barrier?, if: :pathways? validates_inclusion_of :service_need_indicators, in: [''], message: 'Cannot be checked unless Service Need Indicator is Yes', allow_blank: false, unless: :service_need?, if: :pathways? + validate :calculated_first_homeless_night, :historic_date, on: [:create, :update] + + def historic_date + errors.add(:calculated_first_homeless_night, 'First-Date Homeless cannot be in the future') if calculated_first_homeless_night.present? && calculated_first_homeless_night.to_date > Date.current + end def title return pathways_title if assessment_type.blank? - assessment_type_options[assessment_type.to_sym][:title] + title = assessment_type_options.dig(assessment_type.to_sym, :title) + return pathways_title if title.blank? + + title end def pathways? @@ -33,6 +41,10 @@ def for_matching { 'PathwaysVersionFourPathways' => pathways_title, } + when :family_pathways_2024 + { + 'PathwaysVersionFourFamilyPathways' => family_pathways_title, + } when :transfer_assessment { 'PathwaysVersionFourTransfer' => transfer_title, @@ -49,7 +61,7 @@ def for_matching def hud_assessment_level case assessment_type.to_sym - when :pathways_2024 + when :pathways_2024, :family_pathways_2024 2 # Housing Needs Assessment when :transfer_assessment 1 # Crisis Needs Assessment @@ -58,8 +70,8 @@ def hud_assessment_level def tie_breaker_date case assessment_type.to_sym - when :pathways_2024 - entry_date + when :pathways_2024, :family_pathways_2024 + calculated_first_homeless_night.presence || entry_date when :transfer_assessment financial_assistance_end_date end @@ -69,6 +81,10 @@ def family_member pregnant_or_parent end + def calculate_household_dv_survivor? + service_need_indicators&.include?('domestic violence') + end + def self.export_fields(assessment_name) shared = super.merge( { @@ -150,10 +166,18 @@ def self.export_fields(assessment_name) end end + def family_pathways? + assessment_type.to_sym == family_pathways_assessment_type + end + def pathways_assessment_type :pathways_2024 end + def family_pathways_assessment_type + :family_pathways_2024 + end + def transfer_assessment_type :transfer_assessment end @@ -162,6 +186,10 @@ def pathways_title 'Pathways 2024' end + def family_pathways_title + 'Family Pathways 2024' + end + def transfer_title 'Transfer Assessment 2024' end @@ -170,15 +198,21 @@ def pathways_description Translation.translate('We want to reach you when there is a housing program opening for you.') end + def family_pathways_description + Translation.translate('We want to reach you when there is a housing program opening for your family.') + end + def transfer_description Translation.translate('Gather information about a rapid re-housing (RRH) participant’s housing stability.') end def assessment_type_options - { - pathways_assessment_type => { title: pathways_title, description: pathways_description }, - transfer_assessment_type => { title: transfer_title, description: transfer_description }, - } + {}.tap do |options| + options[pathways_assessment_type] = { title: pathways_title, description: pathways_description } + # Family Pathways is disabled in production until further notice, remove trailing `unless Rails.env.production?` when ready to deploy + options[family_pathways_assessment_type] = { title: family_pathways_title, description: family_pathways_description } unless Rails.env.production? + options[transfer_assessment_type] = { title: transfer_title, description: transfer_description } + end end def locked? @@ -234,7 +268,15 @@ def requires_assessment_type_choice? def score_for(field) value = send(field) options = send("#{field}_options") - options[value].try(:[], :score) || 0 + score = 0 + if value.is_a?(Array) + value.each do |v| + score += options[v].try(:[], :score) || 0 + end + else + score += options[value].try(:[], :score) || 0 + end + score end def collection_for(field) @@ -246,17 +288,17 @@ def collection_for(field) private def times_moved_options { - 'never' => { - label: 'Client has not moved while enrolled', + 'none' => { + label: 'No Moves', score: 0, }, 'once' => { - label: 'Once', + label: 'One move', score: 2, }, 'two or more' => { - label: 'Two or more times', - score: 4, + label: 'Two or more', + score: 3, }, } end @@ -339,20 +381,80 @@ def collection_for(field) private def need_daily_assistance_options { 'no assistance' => { - label: 'Client requires little to no assistance with tasks of daily living', + label: 'Client requires little or no assistance with tasks of daily living', score: 0, }, 'minimal assistance' => { - label: 'Client requires minimal assistance with some tasks of daily living', - score: 1, + label: 'Client requires minimal assistance with daily life functions', + score: 5, }, 'requires assistance minor' => { - label: 'Client requires assistance with minor tasks of daily living (eg, brushing teeth, etc)', - score: 3, + label: 'Client requires some assistance with daily life functions', + score: 6, }, 'requires assistance all' => { - label: 'Client requires assistance with nearly all major tasks of daily living (eg, eating, bathing, etc)', - score: 6, + label: 'Client requires consistent assistance with daily life functions', + score: 7, + }, + } + end + + private def substance_use_options + { + false => { + label: 'No', + score: 0, + }, + true => { + label: 'Yes', + score: 5, + }, + } + end + + private def federal_benefits_options + { + false => { + label: 'No', + score: 3, + }, + true => { + label: 'Yes', + score: 0, + }, + } + end + + private def eviction_history_options + { + 'none' => { + label: 'No prior eviction, no long term homeless history', + score: 0, + }, + 'one' => { + label: 'Client has one eviction in the last 5 years', + score: 2, + }, + 'two or more' => { + label: 'Client has more than two evictions in the last 5 years', + score: 3, + }, + 'prevented documented history' => { + label: 'Client\'s length of time homeless prevents documented eviction history', + score: 2, + }, + } + end + + private def background_check_issues_disability_or_substance_use_options + { + true => { + label: 'Yes', + score: 2, + }, + false => { + label: 'No', + score: 0, }, } end @@ -447,21 +549,37 @@ def collection_for(field) } end + private def psh_requirements_options + { + 'no' => { + label: 'Client is in need of Homeless Set Aside or other subsidized housing resources. A homeless Set Aside is a subsidized unit that DOES NOT come with supportive services.', + score: 0, + }, + 'yes' => { + label: 'Client is in need of Permanent Supportive Housing. Permanent Supportive Housing is a voucher or project based housing resource that comes with comprehensive support services. Individuals must meet the Dedicated Plus length of time homeless definition and have an accompanying disability for eligibility.', + score: 0, + }, + 'maybe' => { + label: 'Client meets eligibility for Permanent Supportive Housing and is receiving services that could be used in a Homeless Set Aside Unit. This option is for clients who may benefit from either housing resource.', + score: 0, + }, + } + end + def calculated_score return total_days_homeless_in_the_last_three_years if assessment_type == pathways_assessment_type.to_s + return if assessment_type == family_pathways_assessment_type.to_s score = 0 - score += score_for(:times_moved) - score += score_for(:health_severity) - score += score_for(:ever_experienced_dv) - score += score_for(:eviction_risk) + # Answering No to Q5 invalidates further scoring + return score if psh_required == 'no' + score += score_for(:need_daily_assistance) - score += score_for(:any_income) - score += score_for(:income_source) - score += score_for(:positive_relationship) - score += score_for(:legal_concerns) - score += score_for(:healthcare_coverage) - score += score_for(:childcare) + score += score_for(:substance_use) + score += score_for(:federal_benefits) + score += score_for(:eviction_history) + score += score_for(:background_check_issues_disability_or_substance_use) + score += score_for(:times_moved) score end @@ -472,6 +590,10 @@ def form_fields pathways_fields = pathways_form_fields pathways_fields = deidentify_form_fields(pathways_fields) unless identified? pathways_fields + when family_pathways_title + pathways_fields = family_pathways_form_fields + pathways_fields = deidentify_form_fields(pathways_fields) unless identified? + pathways_fields when transfer_title transfer_form_fields else @@ -527,108 +649,9 @@ def transfer_form_fields as: :pretty_boolean_group, required: true, }, - _contact_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/contact_preamble', - }, - phone_number: { - label: 'Client phone number', - number: '2A', - }, - email_addresses: { - label: 'List any working email addresses you use', - number: '2B', - }, - shelter_name: { - label: 'What shelter(s) or street outreach programs do you currently stay with or work with?', - number: '2C', - as: :select_2, - collection: ShelterHistory.shelter_locations, - input_html: { data: { tags: true } }, - }, - case_manager_contact_info: { - label: 'Do you have any case managers or agencies we could contact to get a hold of you?', - number: '2D', - as: :text, - hint: 'Please provide email address and full name for each contact listed', - }, - mailing_address: { - label: 'Client\'s Mailing Address', - number: '2E', - as: :text, - }, - day_locations: { - label: 'Are there agencies, shelters or places you hang out in during the day where we could connect with you?', - number: '2F', - }, - night_locations: { - label: 'Are there agencies, shelters or places you hang out in during nights or weekends where we could connect with you?', - number: '2G', - }, - other_contact: { - label: 'Are there other ways we could contact you that we have not asked you or thought of yet?', - number: '2H', - }, - _household_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/household_preamble', - }, - household_section: { - label: 'Household Composition', - number: '3A', - questions: { - household_size: { - label: 'How many people are in the household? ', - input_html: { class: 'jHouseholdTrigger' }, - }, - hoh_age: { - label: 'How old is the head of household?', - collection: { - '18-24' => '18-24', - '25-49' => '25-49', - '50+' => '50+', - }, - as: :pretty_boolean_group, - }, - household_details: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/household_details', - }, - }, - }, - veteran_status: { - label: 'Did you serve in the military or do you have Veteran status?', - number: '3B', - as: :pretty_boolean_group, - collection: VeteranStatus.pluck(:text).map { |t| [t, t] }.to_h, - }, - _housing_preferences_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/housing_preferences_preamble', - }, - income_total_annual: { - label: 'What is your total household’s estimated gross (before taxes are taken out) annual income? We ask because some of these units have income requirements. You may figure out monthly and multiply it by 12.', - number: '4A', - }, - youth_rrh_aggregate: { - label: 'Youth Choice (for heads of household who are 24 yrs. or younger) Would you like to be considered for housing programs that are', - number: '5C', - collection: NonHmisClient.available_youth_choices, - as: :pretty_boolean_group, - }, - dv_rrh_aggregate: { - label: 'Survivor Choice (for those fleeing domestic violence): you indicated you are currently experiencing a form of violence. Would you like to be considered for housing programs that are', - number: '5D', - collection: NonHmisClient.available_dv_choices, - as: :pretty_boolean_group, - }, - _unit_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/unit_preamble', - }, sro_ok: { label: 'If you are a single adult, would you consider living in a single room occupancy (SRO)?', - number: '6A', + number: 'Q1', collection: { 'Yes' => true, 'No' => false, @@ -636,300 +659,158 @@ def transfer_form_fields }, as: :pretty_boolean_group, }, - required_number_of_bedrooms: { - label: 'If you need a bedroom size larger than an SRO, studio or 1 bedroom, select the size below you would move into.', - number: '6B', - collection: { - '2' => 2, - '3' => 3, - '4' => 4, - '5' => 5, - 'Not applicable' => nil, - }, - as: :pretty_boolean_group, - }, disability_section: { - label: 'Are you seeking any of the following due to a disability? If yes, you may have to provide documentation of disability - related need.', - number: '6C', + label: 'Are you seeking any of the following due to a disability? If yes, you may have to provide documentation of disability - related need?', + number: 'Q2', questions: { requires_wheelchair_accessibility: { label: 'Wheelchair accessible unit', - number: '6C', + number: 'Q2', as: :pretty_boolean, wrapper: :custom_boolean, }, requires_elevator_access: { label: 'First floor/elevator (little to no stairs to your unit)', - number: '6C', + number: 'Q2', as: :pretty_boolean, wrapper: :custom_boolean, }, accessibility_other: { label: 'Other accessibility', - number: '6C', + number: 'Q2', }, }, }, - disabled_housing: { - label: 'Are you interested in applying for housing units targeted for persons with disabilities? (You may have to provide documentation of a disability to qualify for these housing units.)', - number: '6D', - as: :pretty_boolean, - wrapper: :custom_boolean, - }, - hiv_housing: { - label: 'Are you interested in applying for housing units targeted for persons with an HIV+ diagnosis? (You may have to provide documentation of a HIV to qualify for these housing units.)', - number: '6E', - as: :pretty_boolean_group, - collection: { - 'Yes' => 'Yes', - 'No' => 'No', - }, - confidential: true, - }, - affordable_housing: { - label: 'While openings are not common, we do have different types of affordable housing. Check the types you would be willing to take if there was an opening', - number: '6F', - collection: { - Translation.translate('Voucher: An affordable housing "ticket" used to find a home with private landlords. It is mobile, so you can move units and still keep the affordability (about 30-40% of your income for rent)') => 'Voucher', - Translation.translate('Project-Based unit: The unit is affordable (about 30-40% of your income), but the affordability is attached to the unit. It is not mobile- if you leave, you will lose the affordability. You do not have to do a full housing search in the private market with landlords because the actual unit would be open and available.') => 'Project-Based unit', - }, - as: :pretty_checkboxes_group, - }, - _neighborhood_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/neighborhood_preamble', - }, - neighborhood_interests: { - label: 'Check off all the areas you are willing to live in. Another way to decide is to figure out which places you will not live in, and check off the rest. You are not penalized if you change your mind about where you would like to live.', - include_blank: 'Any Neighborhood / All Neighborhoods', - number: '7A', - collection: Neighborhood.for_select, - as: :pretty_checkboxes_group, - }, - _disability_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/disability_preamble', - }, - documented_disability: { - label: 'Disabling Condition: Have you ever been diagnosed by a licensed professional as having a disabling condition that is expected to be permanent and impede your ability to work? You do not need to disclose the condition.', - number: '8A', + required_number_of_bedrooms: { + label: 'If you need a bedroom size larger than an SRO, studio or 1 bedroom, select the size below.', + number: 'Q3', collection: { - 'Yes' => true, - 'No' => false, - 'Unknown' => nil, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + 'Not applicable' => nil, }, - hint: 'Note to assessor on generating an accurate response: if participant receives any type of disability benefits, you can automatically select "yes"; if you or the participant are unsure, ask them if a medical professional has ever written a letter on their behalf for disabled housing, EAEDC, or other benefits, or if they have ever tried to apply for a disability resource, even if they do not currently receive them- check yes. The assessor may also check yes if a permanent disability is observed.', as: :pretty_boolean_group, }, - background_check_issues: { - label: 'We are asking people what factors may be in their backgrounds so we can help people prepare supporting documentation, references and other positive information to the housing authority (check all that apply)? This is NOT to screen you out for a voucher, but rather to help overcome potential admission barriers.', - number: '8B', - collection: { - 'A housing authority or housing program terminated your subsidy (i.e. a housing voucher, a public housing unit, etc.)' => 'A housing authority or housing program terminated your subsidy (i.e. a housing voucher, a public housing unit, etc.)', - 'You have been evicted from a legal tenancy where you were the lease holder.' => 'You have been evicted from a legal tenancy where you were the lease holder.', - 'Prior to entering shelter or sleeping outside during this episode of homelessness, you came directly from jail, prison or a pre-release program.' => 'Prior to entering shelter or sleeping outside during this episode of homelessness, you came directly from jail, prison or a pre-release program.', - 'You have been convicted (found guilty of) a violent crime' => 'You have been convicted (found guilty of) a violent crime', - 'You have been convicted (found guilty of) a drug crime' => 'You have been convicted (found guilty of) a drug crime', - 'Any member of your household is subject to a lifetime registration requirement under a state sex offender registration program.' => 'Any member of your household is subject to a lifetime registration requirement under a state sex offender registration program.', - 'Any household member has been convicted of the manufacture or production of methamphetamine in federally assisted housing.' => 'Any household member has been convicted of the manufacture or production of methamphetamine in federally assisted housing.', - 'None of the above' => 'None of the above', + housing_barrier_section: { + label: 'Barriers to Housing:', + number: 'Q4', + questions: { + housing_barrier: { + label: 'Do you have any of the following histories and/or barriers?', + number: 'Q4', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + denial_required: { + label: 'If yes, which ones [OPTIONAL]', + number: 'Q4', + collection: { + Translation.translate('Have been convicted or found guilty of producing methamphetamine on subsidized properties OR') => 'manufacture or production of methamphetamine in household', + Translation.translate('Have been evicted from a BHA development or have had a BHA voucher terminated within the last three years OR') => 'evicted from or voucher terminated from a BHA', + Translation.translate('Registered sex offender (level 1,2,3) - lifetime registration (SORI)') => 'lifetime sex offender in household', + Translation.translate('Other (open cases, undocumented, etc.)') => 'other', + }, + as: :pretty_checkboxes_group, + }, }, - as: :pretty_checkboxes_group, - input_html: { multiple: true }, - }, - financial_assistance_end_date: { - label: 'Latest Date Eligible for Financial Assistance', - number: '8C', - as: :date_picker, - }, - _household_history_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/transfer_household_history_preamble', }, - homeless_nights_sheltered: { - label: 'How many sheltered Boston homeless nights does the participant\'s Window into the Warehouse record show?', - number: '9A', - }, - homeless_nights_unsheltered: { - label: 'How many unsheltered Boston homeless nights does the participant\'s Window into the Warehouse record show?', - number: '9B', + psh_required: { + label: 'Client is in need of below housing resource:', + number: 'Q5', + as: :pretty_boolean_group, + collection: collection_for(:psh_requirements), + include_blank: false, }, - _additional_homeless_nights_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/additional_homeless_nights_preamble', + need_daily_assistance: { + label: 'Does client have a physical or mental impairment that substantially limits one or more daily life functions? Daily life functions include: obtaining food/eating, sleeping, physical movement, caring for one’s personal hygiene, and communicating.', + number: 'Q6', + as: :select_2, + collection: collection_for(:need_daily_assistance), + include_blank: false, }, - additional_homeless_nights_sheltered: { - label: 'Sheltered Boston homeless nights you are adding to their length of time homeless in the warehouse.', - number: '9C', + substance_abuse_problem: { + label: 'Does the client have a history of substance use disorder that prevents them from living independently without support services?', + collection: collection_for(:substance_use), + as: :pretty_boolean_group, + number: 'Q7', }, - additional_homeless_nights_unsheltered: { - label: 'Unsheltered Boston homeless nights you are adding to their length of time homeless in the warehouse.', - number: '9D', + federal_benefits: { + label: 'Does the client qualify for federal benefits?', + collection: collection_for(:federal_benefits), + as: :pretty_boolean_group, + number: 'Q8', }, - total_days_homeless_in_the_last_three_years: { - label: 'Total # of Boston Homeless Nights: (9a+9b+9c+9d)', - hint: 'Auto calculated', - number: '9E', - disabled: true, + eviction_history: { + label: 'Does the client have one eviction within the last 5 years?', + number: 'Q9', + collection: collection_for(:eviction_history), + as: :pretty_boolean_group, }, - _housing_stability_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/housing_stability_preamble', + background_check_issues_disability_or_substance_use: { + label: 'Were any previous evictions due to a disability or substance use disorder?', + number: 'Q9a', + as: :pretty_boolean_group, + collection: collection_for(:background_check_issues_disability_or_substance_use), }, times_moved: { label: 'How many times have you moved while enrolled in rapid re-housing?', - number: '', - as: :select_2, + number: 'Q10', + as: :pretty_boolean_group, collection: collection_for(:times_moved), }, - health_severity: { - label: 'How serious are your health concerns right now (physical, mental health, substance use)?', - hint: 'Or how often have you been in the emergency room (ER) in the last 6 months?', - number: '', - as: :select_2, - collection: collection_for(:health_severity), - }, - ever_experienced_dv: { - label: 'Have you or are you currently experiencing domestic violence?', - number: '', - as: :select_2, - collection: collection_for(:ever_experienced_dv), + financial_assistance_end_date: { + label: 'Enter date for last day of financial assistance', + number: 'Q11', + as: :date_picker, }, - eviction_risk: { - label: 'Are you currently at risk of being evicted by your landlord?', - number: '', - as: :select_2, - collection: collection_for(:eviction_risk), + } + end + + def pathways_form_fields + { + _pathways_version_four_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/pathways_version_four_preamble', }, - need_daily_assistance: { - label: 'Do you ever need assistance with daily activities like eating, bathing/showering, dressing?', + entry_date: { + label: 'Date of Assessment', number: '', - as: :select_2, - collection: collection_for(:need_daily_assistance), + as: :date_picker, + required: true, }, - any_income: { - label: 'Do you have any income right now?', + hud_assessment_location: { + label: 'Assessment Location', number: '', as: :select_2, - collection: collection_for(:any_income), + collection: hud_assessment_locations, + required: true, }, - income_source: { - label: 'What is the source of the income?', + hud_assessment_type: { + label: 'Assessment Type', number: '', as: :select_2, - collection: collection_for(:income_source), + collection: hud_assessment_types, + required: true, }, - positive_relationship: { - label: 'Do you currently have positive family or friend relationships in your support network?', + setting: { + label: 'Current living situation - select one (required)?', number: '', - as: :select_2, - collection: collection_for(:positive_relationship), + collection: { + Translation.translate('Emergency Shelter (includes domestic violence shelters)') => 'Emergency Shelter', + Translation.translate('Unsheltered (outside, in a place not meant for human habitation, etc.)') => 'Unsheltered', + Translation.translate('Transitional Housing') => 'Transitional Housing', + Translation.translate('Actively fleeing domestic violence in your home or staying with someone else') => 'Actively fleeing DV', + }, + as: :pretty_boolean_group, }, - legal_concerns: { - label: 'Do you have any active legal concerns, open court cases, or convictions that may come up when we apply for other housing?', - number: '', - as: :select_2, - collection: collection_for(:legal_concerns), - }, - healthcare_coverage: { - label: 'Do you currently have healthcare coverage?', - number: '', - as: :select_2, - collection: collection_for(:healthcare_coverage), - }, - childcare: { - label: 'Do you currently have childcare?', - number: '', - as: :select_2, - collection: collection_for(:childcare), - }, - _next_step_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/next_steps_preamble', - }, - wait_times_ack: { - label: 'Wait Times', - number: '', - hint: 'Wait times can change from time to time based on how many people are interested, and the openings we have available. We also have a few priority populations we have to serve first if there are limited openings - these are young people, those who have been homeless the longest and people in an unsafe situation.', - as: :pretty_boolean, - wrapper: :custom_boolean, - required: true, - }, - not_matched_ack: { - label: 'What should I do to try to find housing if I am not matched with housing opening?', - number: '', - hint: 'We encourage you to think about ways we can help you move in with friends, family, return to safe living situations, or other options since these programs may not always have openings. We encourage you to keep thinking about other ways you may be able to move out of homelessness, like with roommates or people you know at the same time you are applying for affordable housing. If you think of an option, you can always be reassessed to see if we can help with the move in.', - as: :pretty_boolean, - wrapper: :custom_boolean, - required: true, - }, - matched_process_ack: { - label: 'Who Will I Hear From If I Am Matched to a housing opening?', - number: '', - hint: 'You may hear from me or any other case managers/contacts you listed here today; you may also hear directly from the housing program, so be sure to return calls or emails even if you do not know the agency. They are going to use all of the contact information you provided us to try to connect with you as quickly as possible. If any of your contact information changes, let me know and I can change it in the assessment.', - as: :pretty_boolean, - wrapper: :custom_boolean, - required: true, - }, - response_time_ack: { - label: 'How Long Will I Have to Respond to a housing opening I am matched with?', - number: '', - hint: 'In general, the housing programs will outreach to people who are matched with openings for about two weeks. They will move on to new people who may be interested after two weeks because they have to fill the openings. However, if you are interested after the two weeks, you should still return the call/email/message as you may be able to be matched to another opening at a later date.', - as: :pretty_boolean, - wrapper: :custom_boolean, - required: true, - }, - automatic_approval_ack: { - label: 'Am I automatically approved for the housing openings when I’m matched?', - number: '', - hint: 'No. Today we gathered information to help figure out if you’re eligible and match you to your preferences, but the housing programs will actually verify and document eligibility at the time you are referred. All of the programs have different eligibility criteria- our system will do its best to match you with those that you should be eligible for, but there may be times where you are matched, and are not eligible, and will be offered a new opening when one comes up.', - as: :pretty_boolean, - wrapper: :custom_boolean, - required: true, - }, - } - end - - def pathways_form_fields - { - _pathways_version_four_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/pathways_version_four_preamble', - }, - entry_date: { - label: 'Date of Assessment', - number: '', - as: :date_picker, - required: true, - }, - hud_assessment_location: { - label: 'Assessment Location', - number: '', - as: :select_2, - collection: hud_assessment_locations, - required: true, - }, - hud_assessment_type: { - label: 'Assessment Type', - number: '', - as: :select_2, - collection: hud_assessment_types, - required: true, - }, - setting: { - label: 'Current living situation - select one (required)?', - number: '', - collection: { - Translation.translate('Emergency Shelter (includes domestic violence shelters)') => 'Emergency Shelter', - Translation.translate('Unsheltered (outside, in a place not meant for human habitation, etc.)') => 'Unsheltered', - Translation.translate('Transitional Housing') => 'Transitional Housing', - Translation.translate('Actively fleeing domestic violence in your home or staying with someone else') => 'Actively fleeing DV', - }, - as: :pretty_boolean_group, - }, - _contact_preamble: { - as: :partial, - partial: 'non_hmis_assessments/pathways_version_four/pathways_contact_preamble', + _contact_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/pathways_contact_preamble', }, phone_number: { label: 'Client phone number:', @@ -1088,7 +969,7 @@ def pathways_form_fields collection: { Translation.translate('Have been convicted or found guilty of producing methamphetamine on subsidized properties OR') => 'manufacture or production of methamphetamine in household', Translation.translate('Have been evicted from a BHA development or have had a BHA voucher terminated within the last three years OR') => 'evicted from or voucher terminated from a BHA', - Translation.translate('Registered sex offender (level 1,2,3) - lifetime registration (SORI) OR') => 'lifetime sex offender in household', + Translation.translate('Registered sex offender (level 1,2,3) - lifetime registration (SORI)') => 'lifetime sex offender in household', Translation.translate('Other (open cases, undocumented, etc.)') => 'other', }, as: :pretty_checkboxes_group, @@ -1188,11 +1069,395 @@ def pathways_form_fields number: '10G', disabled: true, }, + calculated_first_homeless_night: { + label: 'Tiebreaker - First Date Homeless', + number: '10H', + as: :date_picker, + hint: 'Enter the date of the first time the client experienced homelessness in the city of Boston. If the exact date isn\'t known, enter the best estimate based on the information you have from the client.', + }, _household_history_epilogue: { as: :partial, partial: 'non_hmis_assessments/pathways_version_four/pathways_household_history_epilogue', }, } end + + def family_pathways_form_fields + { + _pathways_version_four_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/family_pathways_version_four_preamble', + }, + available: { + number: '1A', + label: 'If you are declining information sharing, are you still interested in being matched through an anonymous route.', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + _ce_required_questions_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/ce_required_questions_preamble', + }, + entry_date: { + label: 'Date of Assessment', + number: '', + as: :date_picker, + required: true, + }, + hud_assessment_location: { + label: 'Assessment Location', + number: '', + as: :select_2, + collection: hud_assessment_locations, + required: true, + }, + hud_assessment_type: { + label: 'Assessment Type', + number: '', + as: :select_2, + collection: hud_assessment_types, + required: true, + }, + setting: { + label: 'Current living situation - select one (required)?', + number: '', + collection: { + Translation.translate('Emergency Shelter (includes domestic violence shelters)') => 'Emergency Shelter', + Translation.translate('Unsheltered (outside, in a place not meant for human habitation, etc.)') => 'Unsheltered', + Translation.translate('Transitional Housing') => 'Transitional Housing', + Translation.translate('Actively fleeing domestic violence in your home or staying with someone else') => 'Actively fleeing DV', + }, + as: :pretty_boolean_group, + }, + _contact_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/pathways_contact_preamble', + }, + phone_number: { + label: 'Client phone number:', + number: '2C', + }, + email_addresses: { + label: 'Client email:', + number: '2D', + }, + shelter_section: { + label: 'What agencies may we contact to reach you (client)?', + number: '2E', + questions: { + agency_name: { + label: 'Agency:', + }, + case_manager_contact_info: { + label: 'Agency contact name/email/phone:', + as: :text, + hint: 'Please provide email address and full name for each contact listed', + }, + }, + }, + day_location_section: { + label: 'Are there agencies, shelters, or places you hang out in during the day where we could connect with you?', + number: '2F', + questions: { + day_locations: { + label: 'Agency:', + }, + agency_day_contact_info: { + label: 'Agency contact name/email/phone:', + as: :text, + hint: 'Please provide email address and full name for each contact listed', + }, + }, + }, + night_location_section: { + label: 'Are there agencies, shelters or places you hang out in during nights or weekends where we could connect with you?', + number: '2G', + questions: { + night_locations: { + label: 'Agency:', + }, + agency_night_contact_info: { + label: 'Agency contact name/email/phone:', + as: :text, + hint: 'Please provide email address and full name for each contact listed', + }, + }, + }, + child_contact_section: { + label: 'If you are comfortable with us reaching out to you through your child’s school(s), please list these here:', + number: '2H', + questions: { + schools: { + label: 'Schools:', + }, + schools_contact_info: { + label: 'Contact name/email/phone:', + as: :text, + hint: 'Please provide email address and full name for each contact listed', + }, + }, + }, + other_contact: { + label: 'Are there other ways we could contact you that we have not asked you or thought of yet?', + number: '2I', + }, + _household_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/pathways_household_preamble', + }, + household_size: { + label: 'What is the total number of people in your household?', + number: '3A', + input_html: { class: 'jHouseholdTrigger' }, + }, + pregnant_or_parent: { + label: 'Are you pregnant or parenting a child under 18?', + number: '3B', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + household_section: { + number: '3C', + label: 'If there is a second adult in your household, is this person also homeless in the City of Boston? (This is only for match coordination. This person can be assessed and matched separately)', + questions: { + partner_name: { # actually collecting relationship + label: 'Relationship of Adult:', + as: :text, + hint: 'Only collect if there is another adult in the household who is homeless in the City of Boston', + }, + }, + }, + _housing_size_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/household_size_preamble', + }, + required_number_of_bedrooms: { + label: 'Please list the minimum bedroom size needed for your family:', + number: '4A', + collection: { + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + 'Not applicable' => nil, + }, + as: :pretty_boolean_group, + }, + disability_section: { + label: 'Are you seeking any of the following due to a disability? If yes, you may have to provide documentation of disability - related need.', + number: '4B', + questions: { + requires_wheelchair_accessibility: { + label: 'Wheelchair accessible unit', + number: '6', + as: :pretty_boolean, + wrapper: :custom_boolean, + }, + requires_elevator_access: { + label: 'First floor/elevator (little to no stairs to your unit)', + number: '6', + as: :pretty_boolean, + wrapper: :custom_boolean, + }, + requires_vision_or_hearing_accessibility: { + label: 'Buildouts for vision/ hearing impairment', + number: '6', + as: :pretty_boolean, + wrapper: :custom_boolean, + }, + accessibility_other: { + label: 'Other accessibility', + number: '6', + }, + }, + }, + th_desired: { + label: 'Are you interested Transitional Housing?', + number: '4C', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + rrh_desired: { + label: 'Are you interested Rapid Re-Housing?', + number: '4D', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + housing_barrier_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/barriers_preamble', + }, + housing_disqualified_section: { + number: '5A', + questions: { + disqualified_for_state_assistance: { + label: 'Are you disqualified for the State Emergency Assistance for Families program?', + number: '5A', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + disqualified_for_state_assistance_reasons: { + label: 'If yes, which reasons apply [OPTIONAL]', + number: '5C', + collection: { + Translation.translate('At fault for a fire, flood or other reason building was condemned') => 'at fault for condemned building', + Translation.translate('At fault for foreclosure or eviction from previous housing') => 'at fault for foreclosure or eviction', + Translation.translate('Family is over income limit') => 'over income limit', + Translation.translate('Other reason') => 'other', + }, + as: :pretty_checkboxes_group, + }, + }, + }, + housing_barrier_section: { + number: '5B', + questions: { + housing_barrier: { + label: 'Do you have any of the following histories and/or barriers?', + number: '5B', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + denial_required: { + label: 'If yes, which reasons apply [OPTIONAL]', + number: '5C', + collection: { + Translation.translate('Have been convicted or found guilty of producing methamphetamine on subsidized properties') => 'manufacture or production of methamphetamine in household', + Translation.translate('Have been evicted from a BHA development or have had a BHA voucher terminated within the last three years') => 'evicted from or voucher terminated from a BHA', + Translation.translate('Registered sex offender (level 1,2,3) - lifetime registration (SORI)') => 'lifetime sex offender in household', + Translation.translate('My family has at least one person who is not a citizen of the United States') => 'non-us citizen in household', + Translation.translate('I have low English literacy (reading, writing or speaking)') => 'low english literacy', + Translation.translate('I have low educational attainment (less than high school diploma/ GED)') => 'low educational alignment', + Translation.translate('Other (large family size, etc.)') => 'other', + }, + as: :pretty_checkboxes_group, + }, + }, + }, + service_need_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/service_need_preamble', + }, + service_need_section: { + number: '6', + questions: { + service_need: { + label: 'Does any of the following apply to you?', + number: '6', + as: :pretty_boolean_group, + collection: { + 'Yes' => true, + 'No' => false, + }, + }, + service_need_indicators: { + label: 'If yes, which ones [OPTIONAL]', + number: '6', + collection: { + Translation.translate('Someone in my family (me/ my dependent child) is or has been at risk of harm from domestic violence, dating violence, sexual assault, stalking or human trafficking.') => 'domestic violence', + Translation.translate('Someone in my family requires full time assistance to meet daily living requirements.') => 'full-time assistance required', + Translation.translate('Someone in my family has used inpatient hospital or treatment facilities 2 or more times over the past year.') => 'frequent hospital use', + Translation.translate('An adult in my family has a felony criminal record (CORI).') => 'criminal record (CORI) or ongoing legal cases', + Translation.translate('An adult in my family has current legal issue or was incarcerated in the last 3-5 years.') => 'legal issues or incarceration', + Translation.translate('My family has experienced a legal eviction from subsidized housing in the past 10 years.') => 'eviction from subsidized housing', + Translation.translate('DCF is or has been involved with my family.') => 'DCF involvement', + }, + as: :pretty_checkboxes_group, + }, + }, + }, + _household_history_preamble: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/family_pathways_household_history_preamble', + }, + homeless_nights_sheltered_section: { + number: '7A', + label: 'Length of Time Homeless (Sheltered) - Warehouse:', + questions: { + homeless_nights_sheltered: { + label: 'Check the client’s record in the Warehouse; how many sheltered homeless nights in a shelter does the client have?', + }, + }, + }, + additional_homeless_nights_sheltered_section: { + number: '7B', + label: 'Length of Time Homeless (Sheltered) - Non-HMIS:', + questions: { + additional_homeless_nights_sheltered: { + label: 'Does the client have additional sheltered nights outside of HMIS/Warehouse? (At a shelter or hotel/motel paid by government or charity.)', + }, + }, + }, + homeless_nights_unsheltered_section: { + number: '7C', + label: 'Length of Time Homeless (Unsheltered) - Warehouse:', + questions: { + homeless_nights_unsheltered: { + label: 'Check the client’s record in the Warehouse; how many unsheltered homeless nights does the client have?', + }, + }, + }, + additional_homeless_nights_unsheltered_section: { + number: '7D', + label: 'Length of Time Homeless (Unsheltered) - Non-HMIS:', + questions: { + additional_homeless_nights_unsheltered: { + label: 'Does the client have additional unsheltered nights outside of HMIS/Warehouse? (Place not meant for human habitation/outside/car/station.)', + }, + }, + }, + self_reported_days_verified: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/self_reported_days_verified', + }, + total_homeless_nights_sheltered: { + label: 'Total # of Sheltered Nights:', + hint: 'Auto calculated', + number: '7E', + disabled: true, + }, + total_homeless_nights_unsheltered: { + label: 'Total # of Unsheltered Nights:', + hint: 'Auto calculated', + number: '7F', + disabled: true, + }, + days_homeless: { + label: 'Total # of Boston Homeless Nights:', + hint: 'Auto calculated', + number: '7G', + disabled: true, + }, + calculated_first_homeless_night: { + label: 'Tiebreaker - First Date Homeless', + number: '10H', + as: :date_picker, + hint: 'Enter the date of the first time the client experienced homelessness in the city of Boston. If the exact date isn\'t known, enter the best estimate based on the information you have from the client.', + }, + _household_history_epilogue: { + as: :partial, + partial: 'non_hmis_assessments/pathways_version_four/family_pathways_household_history_epilogue', + }, + } + end end end diff --git a/app/models/match_prioritization/base.rb b/app/models/match_prioritization/base.rb index 131c073eb..5e8303a4d 100644 --- a/app/models/match_prioritization/base.rb +++ b/app/models/match_prioritization/base.rb @@ -34,6 +34,7 @@ def self.prioritization_schemes MatchPrioritization::HoldsVoucherOn, MatchPrioritization::MatchGroup, MatchPrioritization::UnshelteredRandom, + MatchPrioritization::FamilyPsh, ] end @@ -71,7 +72,9 @@ def client_prioritization_summary_method self.class.client_prioritization_summary_method end - # NOTE match_route is only used in one prioritization scheme MatchPrioritization::Rank, which uses the tag associated with the route + # NOTE match_route is only used in a few prioritization schemes which uses the tag associated with the route + # MatchPrioritization::Rank + # MatchPrioritization::FamilyPsh def client_prioritization_summary(client, match_route) if self.class.client_prioritization_summary_method.present? fn = self.class.client_prioritization_summary_method diff --git a/app/models/match_prioritization/family_psh.rb b/app/models/match_prioritization/family_psh.rb new file mode 100644 index 000000000..83be9a620 --- /dev/null +++ b/app/models/match_prioritization/family_psh.rb @@ -0,0 +1,67 @@ +### +# Copyright 2016 - 2025 Green River Data Analysis, LLC +# +# License detail: https://github.com/greenriver/boston-cas/blob/production/LICENSE.md +### + +module MatchPrioritization + class FamilyPsh < Base + def self.title + 'Family PSH - Boston' + end + + # (1) Prioritization will FIRST look for households who HAVE at LEAST >0 day of unsheltered homelessness (HMIS or Family Pathways reported) OR on Housing Needs Enrollment indicating a family member is experiencing Domestic Violence (Yes on Entry) AND answer YES to service needs question on Family Pathways (Q6) + # OF this group, prioritize by total length of time homeless from Pathways + # (2) IF no clients meet the requirements in (1) THEN look for clients who are ENROLLED in Emergency Shelter OR Transitional Housing AND answer YES to service needs question on Family Pathways (Q6) + # OF this group, prioritize by total length of time homeless from Pathways + # (3) IF no clients meet requirements of (1) or (2), then prioritize by length of time homeless + # (4) Tie Breaker Date + def self.prioritization_for_clients(scope, match_route:) # rubocop:disable Lint/UnusedMethodArgument + # case statement + # if total_homeless_nights_unsheltered > 0 + # then 3 + # if service_need && household_dv_survivor + # then 3 + # if (enrolled_in_es OR enrolled_in_th) && service_need + # then 2 + # else + # 1 + unsheltered_nights = c_t[:total_homeless_nights_unsheltered].gt(0) + service_need = c_t[:service_need].eq(true).and(c_t[:household_dv_survivor].eq(true)) + enrolled = c_t[:service_need].eq(true).and(c_t[:enrolled_in_es].eq(true).or(c_t[:enrolled_in_th].eq(true))) + + # Primary sort desc + # Secondary sort days_homeless_in_last_three_years desc + # Tertiary sort tie_breaker_date + primary_order = Arel::Nodes::Case.new. + when(unsheltered_nights).then(3). # Prioritization will FIRST look for households who HAVE at LEAST >0 day of unsheltered homelessness + when(service_need).then(3). # on Housing Needs Enrollment indicating a family member is experiencing Domestic Violence (Yes on Entry) AND answer YES to service needs question on Family Pathways + when(enrolled).then(2). # THEN look for clients who are ENROLLED in Emergency Shelter OR Transitional Housing AND answer YES to service needs question on Family Pathways + else(1).desc + secondary_order = c_t[:days_homeless_in_last_three_years].desc.nulls_last + tertiary_order = c_t[:tie_breaker_date].asc.nulls_last + + scope.order(primary_order, secondary_order, tertiary_order) + end + + def self.supporting_column_names + [ + :total_homeless_nights_unsheltered, + :service_need, + :household_dv_survivor, + :days_homeless_in_last_three_years, + :tie_breaker_date, + ] + end + + def self.supporting_data_columns + { + 'Nights unsheltered' => lambda(&:total_homeless_nights_unsheltered), + 'Service need' => lambda(&:service_need), + 'Household DV survivor' => lambda(&:household_dv_survivor), + 'Days homeless in the last 3 years' => lambda(&:days_homeless_in_last_three_years), + 'Tie Breaker Date' => lambda(&:tie_breaker_date), + } + end + end +end diff --git a/app/models/match_prioritization/low_income_subsidies.rb b/app/models/match_prioritization/low_income_subsidies.rb new file mode 100644 index 000000000..5d2a876f8 --- /dev/null +++ b/app/models/match_prioritization/low_income_subsidies.rb @@ -0,0 +1,67 @@ +### +# Copyright 2016 - 2025 Green River Data Analysis, LLC +# +# License detail: https://github.com/greenriver/boston-cas/blob/production/LICENSE.md +### + +module MatchPrioritization + class LowIncomeSubsidies < Base + def self.title + 'Low Income Subsidies (Set Asides) - Boston' + end + + # (1) Prioritization will FIRST look for households who HAVE at LEAST >0 day of unsheltered homelessness (HMIS or Family Pathways reported) OR on Housing Needs Enrollment indicating a family member is experiencing Domestic Violence (Yes on Entry) AND answer YES to housing barriers question on Family Pathways (5b) + # OF this group, prioritize by total length of time homeless from Pathways + # (2) IF no clients meet the requirements in (1) THEN look for clients who are ENROLLED in Emergency Shelter OR Transitional Housing AND answer Yes to housing barriers question on Family Pathways (5b) + # OF this group, prioritize by total length of time homeless from Pathways + # (3) IF no clients meet requirements of (1) or (2), then prioritize by length of time homeless + # (4) Tie Breaker Date + def self.prioritization_for_clients(scope, match_route:) # rubocop:disable Lint/UnusedMethodArgument + # case statement + # if total_homeless_nights_unsheltered > 0 + # then 3 + # if housing_barrier && household_dv_survivor + # then 3 + # if (enrolled_in_es OR enrolled_in_th) && housing_barrier + # then 2 + # else + # 1 + unsheltered_nights = c_t[:total_homeless_nights_unsheltered].gt(0) + service_need = c_t[:housing_barrier].eq(true).and(c_t[:household_dv_survivor].eq(true)) + enrolled = c_t[:housing_barrier].eq(true).and(c_t[:enrolled_in_es].eq(true).or(c_t[:enrolled_in_th].eq(true))) + + # Primary sort desc + # Secondary sort days_homeless_in_last_three_years desc + # Tertiary sort tie_breaker_date + primary_order = Arel::Nodes::Case.new. + when(unsheltered_nights).then(3). # Prioritization will FIRST look for households who HAVE at LEAST >0 day of unsheltered homelessness + when(service_need).then(3). # on Housing Needs Enrollment indicating a family member is experiencing Domestic Violence (Yes on Entry) AND answer YES to housing barriers question on Family Pathways + when(enrolled).then(2). # IF no clients meet the requirements in (1) THEN look for clients who are ENROLLED in Emergency Shelter OR Transitional Housing AND answer Yes to housing barriers question on Family Pathways + else(1).desc + secondary_order = c_t[:days_homeless_in_last_three_years].desc.nulls_last + tertiary_order = c_t[:tie_breaker_date].asc.nulls_last + + scope.order(primary_order, secondary_order, tertiary_order) + end + + def self.supporting_column_names + [ + :total_homeless_nights_unsheltered, + :disqualified_for_state_assistance, + :household_dv_survivor, + :days_homeless_in_last_three_years, + :tie_breaker_date, + ] + end + + def self.supporting_data_columns + { + 'Nights unsheltered' => lambda(&:total_homeless_nights_unsheltered), + 'Ineligible for state emergency assistance' => lambda(&:disqualified_for_state_assistance), + 'Household DV survivor' => lambda(&:household_dv_survivor), + 'Days homeless in the last 3 years' => lambda(&:days_homeless_in_last_three_years), + 'Tie Breaker Date' => lambda(&:tie_breaker_date), + } + end + end +end diff --git a/app/models/match_prioritization/rrh_and_th.rb b/app/models/match_prioritization/rrh_and_th.rb new file mode 100644 index 000000000..3f5c4956c --- /dev/null +++ b/app/models/match_prioritization/rrh_and_th.rb @@ -0,0 +1,67 @@ +### +# Copyright 2016 - 2025 Green River Data Analysis, LLC +# +# License detail: https://github.com/greenriver/boston-cas/blob/production/LICENSE.md +### + +module MatchPrioritization + class RrhAndTh < Base + def self.title + 'RRH and TH - Boston' + end + + # (1) Prioritization will FIRST look for households who HAVE at LEAST >0 day of unsheltered homelessness (HMIS or Family Pathways reported) OR on Housing Needs Enrollment indicating a family member is experiencing Domestic Violence (Yes on Entry) AND answer YES to ineligible for State Emergency Assistance Question (5a) + # OF this group, prioritize by total length of time homeless from Pathways + # (2) IF no clients meet the requirements in (1) THEN look for clients who are ENROLLED in Emergency Shelter OR Transitional Housing AND answer YES to ineligible for State Emergency Assistance Question (5a) + # OF this group, prioritize by total length of time homeless from Pathways + # (3) IF no clients meet requirements of (1) or (2), then prioritize by length of time homeless + # (4) Tie Breaker Date + def self.prioritization_for_clients(scope, match_route:) # rubocop:disable Lint/UnusedMethodArgument + # case statement + # if total_homeless_nights_unsheltered > 0 + # then 3 + # if disqualified_for_state_assistance && household_dv_survivor + # then 3 + # if (enrolled_in_es OR enrolled_in_th) && disqualified_for_state_assistance + # then 2 + # else + # 1 + unsheltered_nights = c_t[:total_homeless_nights_unsheltered].gt(0) + service_need = c_t[:disqualified_for_state_assistance].eq(true).and(c_t[:household_dv_survivor].eq(true)) + enrolled = c_t[:disqualified_for_state_assistance].eq(true).and(c_t[:enrolled_in_es].eq(true).or(c_t[:enrolled_in_th].eq(true))) + + # Primary sort desc + # Secondary sort days_homeless_in_last_three_years desc + # Tertiary sort tie_breaker_date + primary_order = Arel::Nodes::Case.new. + when(unsheltered_nights).then(3). # Prioritization will FIRST look for households who HAVE at LEAST >0 day of unsheltered homelessness + when(service_need).then(3). # on Housing Needs Enrollment indicating a family member is experiencing Domestic Violence (Yes on Entry) AND answer YES to ineligible for State Emergency Assistance Question + when(enrolled).then(2). # THEN look for clients who are ENROLLED in Emergency Shelter OR Transitional Housing AND answer YES to ineligible for State Emergency Assistance Question + else(1).desc + secondary_order = c_t[:days_homeless_in_last_three_years].desc.nulls_last + tertiary_order = c_t[:tie_breaker_date].asc.nulls_last + + scope.order(primary_order, secondary_order, tertiary_order) + end + + def self.supporting_column_names + [ + :total_homeless_nights_unsheltered, + :disqualified_for_state_assistance, + :household_dv_survivor, + :days_homeless_in_last_three_years, + :tie_breaker_date, + ] + end + + def self.supporting_data_columns + { + 'Nights unsheltered' => lambda(&:total_homeless_nights_unsheltered), + 'Ineligible for state emergency assistance' => lambda(&:disqualified_for_state_assistance), + 'Household DV survivor' => lambda(&:household_dv_survivor), + 'Days homeless in the last 3 years' => lambda(&:days_homeless_in_last_three_years), + 'Tie Breaker Date' => lambda(&:tie_breaker_date), + } + end + end +end diff --git a/app/models/non_hmis_assessment.rb b/app/models/non_hmis_assessment.rb index b05f872af..0e84198ab 100644 --- a/app/models/non_hmis_assessment.rb +++ b/app/models/non_hmis_assessment.rb @@ -11,8 +11,9 @@ class NonHmisAssessment < ActiveRecord::Base has_paper_trail acts_as_paranoid - attr_accessor :youth_rrh_aggregate, :dv_rrh_aggregate, :date_of_birth + attr_accessor :youth_rrh_aggregate, :dv_rrh_aggregate, :date_of_birth, :available attr_writer :total_days_homeless_in_the_last_three_years + alias_attribute :substance_use, :substance_abuse_problem belongs_to :non_hmis_client belongs_to :user @@ -20,7 +21,9 @@ class NonHmisAssessment < ActiveRecord::Base after_find :populate_aggregates + after_initialize :set_non_hmis_assessment_availability before_save :update_assessment_score + before_save :set_non_hmis_client_availability scope :limitable_pathways, -> do where(type: limited_assessment_types) @@ -79,8 +82,10 @@ def self.known_assessments_for_matching merge(DeidentifiedPathwaysVersionThree.new(assessment_type: :pathways_2021).for_matching). merge(DeidentifiedPathwaysVersionThree.new(assessment_type: :transfer_assessment).for_matching). merge(IdentifiedPathwaysVersionFour.new(assessment_type: :pathways_2024).for_matching). + merge(IdentifiedPathwaysVersionFour.new(assessment_type: :family_pathways_2024).for_matching). merge(IdentifiedPathwaysVersionFour.new(assessment_type: :transfer_assessment).for_matching). merge(DeidentifiedPathwaysVersionFour.new(assessment_type: :pathways_2024).for_matching). + merge(DeidentifiedPathwaysVersionFour.new(assessment_type: :family_pathways_2024).for_matching). merge(DeidentifiedPathwaysVersionFour.new(assessment_type: :transfer_assessment).for_matching). merge(IdentifiedTcHat.new.for_matching). merge(DeidentifiedTcHat.new.for_matching). @@ -115,6 +120,25 @@ def update_assessment_score! non_hmis_client.save end + # The Family Pathways assessment collects availability, ensure we pull this from the + # associated client on load + def set_non_hmis_assessment_availability + return unless pathways_v4? + return unless title == family_pathways_title + return unless non_hmis_client + + self.available = non_hmis_client.available + end + + # The Family Pathways assessment collects availability, ensure we push this onto the + # associated client so that it is persisted + def set_non_hmis_client_availability + return unless pathways_v4? + return unless title == family_pathways_title + + non_hmis_client.update(available: available || false) + end + def pathways_v3? type.include?('PathwaysVersionThree') end @@ -407,6 +431,17 @@ def non_hmis_assessment_params :chronic_health_caused_episode, :acute_health_caused_episode, :idd_caused_episode, + :available, + :schools, + :schools_contact_info, + :requires_vision_or_hearing_accessibility, + :disqualified_for_state_assistance, + :calculated_first_homeless_night, + :federal_benefits, + :psh_required, + :background_check_issues_disability_or_substance_use, + :eviction_history, + disqualified_for_state_assistance_reasons: [], strengths: [], challenges: [], tc_hat_client_history: [], diff --git a/app/models/non_hmis_client.rb b/app/models/non_hmis_client.rb index e2387458e..485a07f28 100644 --- a/app/models/non_hmis_client.rb +++ b/app/models/non_hmis_client.rb @@ -210,6 +210,34 @@ def populate_project_client(project_client) project_client.enrolled_project_ids = enrolled_project_ids&.compact_blank&.map(&:to_i) # current_assessment fields + [ + :foster_care, + :drug_test, + :heavy_drug_use, + :sober, + :willing_case_management, + :employed_three_months, + :living_wage, + :need_daily_assistance, + :full_time_employed, + :can_work_full_time, + :willing_to_work_full_time, + :rrh_successful_exit, + :lifetime_sex_offender, + :th_desired, + :drug_test, + :employed_three_months, + :site_case_management_required, + :ongoing_case_management_required, + :currently_fleeing, + :dv_date, + :pregnancy_status, + :pregnant_under_28_weeks, + :child_in_household, + :psh_required, + ].each do |method| + project_client[method] = current_assessment&.send(method) + end project_client.assessment_name = current_assessment&.for_matching&.keys&.first project_client.assessment_score = current_assessment&.assessment_score || 0 project_client.days_homeless_in_last_three_years = current_assessment&.total_days_homeless_in_the_last_three_years || 0 @@ -302,6 +330,8 @@ def populate_project_client(project_client) project_client.challenges = current_assessment&.challenges&.reject(&:blank?) project_client.open_case = current_assessment&.tc_hat_client_history&.include?('open_case') project_client.housing_for_formerly_homeless = current_assessment&.housing_preferences&.include?('with_formerly_homeless') + project_client.household_dv_survivor = current_assessment&.calculate_household_dv_survivor? if current_assessment&.pathways_v4? + project_client.disqualified_for_state_assistance = current_assessment.disqualified_for_state_assistance if current_assessment.pathways_v4? [ :foster_care, @@ -328,9 +358,18 @@ def populate_project_client(project_client) :pregnancy_status, :pregnant_under_28_weeks, :child_in_household, + :requires_vision_or_hearing_accessibility, + :calculated_first_homeless_night, ].each do |method| project_client[method] = current_assessment&.send(method) end + + # Pathways transfer assessment 9/2024 changes + if current_assessment&.denial_required.present? + project_client.lifetime_sex_offender = current_assessment.denial_required.include?('lifetime sex offender in household') + project_client.meth_production_conviction = current_assessment.denial_required.include?('manufacture or production of methamphetamine in household') + end + project_client.needs_update = true project_client end @@ -362,6 +401,7 @@ def build_assessment_if_missing end def update_assessment_from_client(assessment = current_assessment) + assessment.available = available assessment.assessment_score = assessment_score assessment.actively_homeless = actively_homeless assessment.days_homeless_in_the_last_three_years = days_homeless_in_the_last_three_years diff --git a/app/models/rules/psh_required.rb b/app/models/rules/psh_required.rb new file mode 100644 index 000000000..14fda64eb --- /dev/null +++ b/app/models/rules/psh_required.rb @@ -0,0 +1,18 @@ +### +# Copyright 2016 - 2024 Green River Data Analysis, LLC +# +# License detail: https://github.com/greenriver/boston-cas/blob/production/LICENSE.md +### + +class Rules::PshRequired < Rule + def clients_that_fit(scope, requirement, _opportunity) + column = :psh_required + raise RuleDatabaseStructureMissing.new("clients.#{column} missing. Cannot check clients against #{self.class}.") unless Client.column_names.include?(column.to_s) + + if requirement.positive + scope.where(column => ['yes', 'maybe']) + else + scope.where(column => ['no', 'maybe']) + end + end +end diff --git a/app/views/admin/match_routes/edit.haml b/app/views/admin/match_routes/edit.haml index 9c98902d6..98ac53d16 100644 --- a/app/views/admin/match_routes/edit.haml +++ b/app/views/admin/match_routes/edit.haml @@ -68,6 +68,6 @@ = link_to(admin_match_route_weighting_rule_path(@route, rule), method: :delete, class: 'btn btn-sm btn-icon-only btn-danger', data: { confirm: 'Are you sure you want to remove this rule?' }) do %i.icon-cross - else - %p Weighting rules allow you to attach requiremnts to each voucher created on this route. If you have more than one weighting rule on the route, rules will be applied to balance out the use of each rule over time. + %p Weighting rules allow you to attach requirements to each voucher created on this route. If you have more than one weighting rule on the route, rules will be applied to balance out the use of each rule over time. = render 'init_select2' diff --git a/app/views/clients/_domestic.haml b/app/views/clients/_domestic.haml index d9cae3c7e..d58b51522 100644 --- a/app/views/clients/_domestic.haml +++ b/app/views/clients/_domestic.haml @@ -79,3 +79,11 @@ since #{@client.dv_date} - else #{boolean_icon false} + - if Config.using_pathways? + %li.c-detailed-list__item + %span.c-detailed-list__label= @column_data[:household_dv_survivor][:title] + %span.c-detailed-list__value= boolean_icon @client.household_dv_survivor + - if Config.using_pathways? + %li.c-detailed-list__item + %span.c-detailed-list__label= @column_data[:disqualified_for_state_assistance][:title] + %span.c-detailed-list__value= boolean_icon @client.disqualified_for_state_assistance diff --git a/app/views/clients/_housing_requirements.haml b/app/views/clients/_housing_requirements.haml index 28862cd44..4875510d3 100644 --- a/app/views/clients/_housing_requirements.haml +++ b/app/views/clients/_housing_requirements.haml @@ -75,3 +75,7 @@ %li.c-detailed-list__item %span.c-detailed-list__label @column_data[:last_ineligible_response] %span.c-detailed-list__value= response ? response : boolean_icon(false) + + %li.c-detailed-list__item + %span.c-detailed-list__label= @column_data[:psh_required][:title] + %span.c-detailed-list__value= @client.psh_required_for_export diff --git a/app/views/clients/_pathways_homelessness.haml b/app/views/clients/_pathways_homelessness.haml index 3f603e7d1..845e382f2 100644 --- a/app/views/clients/_pathways_homelessness.haml +++ b/app/views/clients/_pathways_homelessness.haml @@ -27,6 +27,13 @@ %span.c-detailed-list__label= @column_data[:days_homeless_in_last_three_years][:title] %span.icon-info{data: {toggle: :tooltip, title: @column_data[:days_homeless_in_last_three_years][:description]}} %span.c-detailed-list__value= number_with_delimiter(@client.days_homeless_in_last_three_years) + %li.c-detailed-list__item + %span.c-detailed-list__label= @column_data[:days_homeless][:title] + %span.icon-info{data: {toggle: :tooltip, title: @column_data[:days_homeless][:description]}} + %span.c-detailed-list__value= number_with_delimiter(@client.days_homeless) + %li.c-detailed-list__item + %span.c-detailed-list__label First Date Homeless + %span.c-detailed-list__value= date_format(@client.calculated_first_homeless_night) %li.c-detailed-list__item %span.c-detailed-list__label= @column_data[:calculated_last_homeless_night][:title] %span.c-detailed-list__value= @client.calculated_last_homeless_night.try(:strftime, I18n.t('date.formats.default')) diff --git a/app/views/non_hmis_assessments/pathways_version_four/_barriers_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_barriers_preamble.haml new file mode 100644 index 000000000..41d4e9067 --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_barriers_preamble.haml @@ -0,0 +1 @@ +%h2 Barriers to Housing: diff --git a/app/views/non_hmis_assessments/pathways_version_four/_ce_required_questions_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_ce_required_questions_preamble.haml new file mode 100644 index 000000000..34f8ade43 --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_ce_required_questions_preamble.haml @@ -0,0 +1,2 @@ +%h2.mt-8 Housing Needs Assessment Questions +%hr diff --git a/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_epilogue.haml b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_epilogue.haml new file mode 100644 index 000000000..e985a6333 --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_epilogue.haml @@ -0,0 +1,8 @@ +%p + * If you are adding more than 548 nights please complete the + %a{ href: 'https://bit.ly/pathways24addnights', target: :_blank } Certification of Non-HMIS Nights (bit.ly/pathways24addnights) + form. +%p + Please share this information: + %a{ href: 'https://bit.ly/fampath24faq', target: :blank } bit.ly/fampath24faq + with your client. diff --git a/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_preamble.haml new file mode 100644 index 000000000..f765fadc0 --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_household_history_preamble.haml @@ -0,0 +1,11 @@ + +%h3 Length of Time Homeless +%p + To prioritize housing assistance, we look at the total number of nights someone has experienced homelessness. +%p + If you are adding more than 1.5 years (548 nights) of non-HMIS nights to an individual's assessment you will need to complete the + %a{ href: 'https://bit.ly/pathways24addnights', target: :blank } Documenting Boston Homeless Nights form (bit.ly/pathways24addnights) + and send it to the CE Team at ceteam@homestart.org. +%p + %strong Note: + Boston families may be displaced when they receive a shelter placement outside of Boston through the State’s Emergency Assistance program. Please include all time sheltered outside of Boston when the family became homeless in Boston. diff --git a/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_js.haml b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_js.haml new file mode 100644 index 000000000..a41775750 --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_js.haml @@ -0,0 +1,82 @@ += content_for :page_js do + :javascript + // Q5A toggle + $('input[name*="[disqualified_for_state_assistance]"]').on('change', function(e) { + if($(e.currentTarget).val() == 'true') { + $('.deidentified_pathways_version_four_disqualified_for_state_assistance_reasons').removeClass('d-none'); + } else { + $('.deidentified_pathways_version_four_disqualified_for_state_assistance_reasons').addClass('d-none'); + } + }); + $('input[name*="[disqualified_for_state_assistance]"]').trigger('change'); + + // Q5B toggle + $('input[name*="[housing_barrier]"]').on('change', function(e) { + if($(e.currentTarget).val() == 'true') { + $('.deidentified_pathways_version_four_denial_required').removeClass('d-none'); + } else { + $('.deidentified_pathways_version_four_denial_required').addClass('d-none'); + } + }); + $('input[name*="[housing_barrier]"]').trigger('change'); + + // 6 toggle + $('input[name*="[service_need]"]').on('change', function(e) { + if($(e.currentTarget).val() == 'true') { + $('.deidentified_pathways_version_four_service_need_indicators').removeClass('d-none'); + } else { + $('.deidentified_pathways_version_four_service_need_indicators').addClass('d-none'); + } + }); + $('input[name*="[service_need]"]').trigger('change'); + + // Days Homeless + var warehouse_sheltered_selector = 'input[name*="[homeless_nights_sheltered]"]'; + var warehouse_unsheltered_selector = 'input[name*="[homeless_nights_unsheltered]"]'; + var extra_sheltered_selector = 'input[name*="[additional_homeless_nights_sheltered]"]'; + var extra_unsheltered_selector = 'input[name*="[additional_homeless_nights_unsheltered]"]'; + var total_sheltered_selector = 'input[name*="[total_homeless_nights_sheltered]"]'; + var total_unsheltered_selector = 'input[name*="[total_homeless_nights_unsheltered]"]'; + var total_selector = 'input[name*="[days_homeless]"]'; + var additional_days_verified_selector = 'input[name*="[self_reported_days_verified]"]' + var additional_days_verified_checkbox = $('input.pretty_boolean[name*="[self_reported_days_verified]"]')[0]; + + var calculate_total_days = function(e) { + var warehouse_sheltered = parseInt($(warehouse_sheltered_selector).val() || 0) + var warehouse_unsheltered = parseInt($(warehouse_unsheltered_selector).val() || 0); + var extra_sheltered_days = parseInt($(extra_sheltered_selector).val() || 0); + var extra_unsheltered_days = parseInt($(extra_unsheltered_selector).val() || 0); + + // If a client has more than 548 self-reported days (combination of sheltered and unsheltered) + // and does not have a verification uploaded, count unsheltered days first, then count sheltered days UP TO 548. + // If the additional days are verified, use the provided amounts. + if(!additional_days_verified_checkbox.checked) { + // 1. Cap the total unsheltered at 548 days if it is greater than this amount. + extra_unsheltered_days = extra_unsheltered_days > 548 ? 548 : extra_unsheltered_days + // 2. Find the maximum amount of sheltered days to count based on the total unsheltered days. + // The combination of the two cannot exeed 548. + max_sheltered = Math.max(548 - extra_unsheltered_days, 0) + // 3. Cap the sheltered days counted at the calculated max if it exceeds that amount. + extra_sheltered_days = extra_sheltered_days > max_sheltered ? max_sheltered : extra_sheltered_days + } + var warehouse_days = warehouse_sheltered + warehouse_unsheltered; + var extra_days = extra_sheltered_days + extra_unsheltered_days; + + // NOTE: this differs from the individual Pathways assessment as none of these are capped at 1,096 + var total_sheltered = warehouse_sheltered + extra_sheltered_days; + var total_unsheltered = warehouse_unsheltered + extra_unsheltered_days; + var total = warehouse_days + extra_days + + $(total_sheltered_selector).val(total_sheltered); + $(total_unsheltered_selector).val(total_unsheltered); + $(total_selector).val(total); + } + $( + warehouse_sheltered_selector + ',' + + warehouse_unsheltered_selector + ',' + + extra_sheltered_selector + ',' + + extra_unsheltered_selector + ',' + + additional_days_verified_selector + ).on('keyup, change', calculate_total_days); + + $(warehouse_sheltered_selector).trigger('change'); diff --git a/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_preamble.haml new file mode 100644 index 000000000..085075ab8 --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_family_pathways_version_four_preamble.haml @@ -0,0 +1,43 @@ += render 'non_hmis_assessments/pathways_version_four/family_pathways_version_four_js' +%p + Please read this + %a{ href: 'https://bit.ly/fambosceintro24', target: :_blank } bit.ly/fambosceintro24 + before completing the assessment + +.c-question{data: {number: '1'}} + .c-question__wrapper + .c-question__number 1 + .c-question__input + .form-group + .controls + .form--label-hint-wrapper + .form--lable-hint + %label.control-label Permission to Share Your Information with Partner Agencies + %p To sign you up for housing programs you may be interested in, we would like to ask for your permission to share information with participating agencies who manage these programs so they may contact you when there is an opening. All of the participating agencies have agreed to keep all data confidential and secure. Is this okay with you? + .c-question__input + %p + = form.input :share_information_permission, as: :pretty_boolean_group, collection: { 'Yes' => true, 'No' => false } + %br + %p + %strong If Yes + %p + Check to see if a Housing Assistance Network (HAN) Release of Information is uploaded into the client's + %a{ href: 'https://docs.google.com/document/d/16YiTz-g2J3xREtAsqS6NRRwckO0uWmdYApYg2NvOByI/edit?usp=sharing', target: :blank } warehouse + record. + %ul + %li If one IS uploaded, proceed to the next section + %li + If one IS NOT uploaded, complete a + %a{ href: 'https://docs.google.com/document/d/16YiTz-g2J3xREtAsqS6NRRwckO0uWmdYApYg2NvOByI/edit?usp=sharing', target: :blank } HAN release + and upload to the Warehouse. If it is not uploaded, the referral may get stuck due to lack of permission. If your agency is not on the warehouse, keep their release of information on file. If you are using CAS, also check the box on the Non-HMIS Client edit screen indicating that you have a HAN in your file. + %p + %strong If No + %ul + %li + If the client chooses not to share information with other provider agencies, staff may sign a + %a{ href: 'https://www.boston.gov/sites/default/files/file/2020/01/Limited-CAS-Release.pdf', target: :blank } Limited CAS release + so that the Coordinated Entry lead agency can sign them up for programs, but not share information with any other providers. Check to see if a Limited CAS release is uploaded into the client's + %a{ href: Config.get(:warehouse_url), target: :blank } warehouse + record. + %li If one IS uploaded, proceed to the next section. + %li If one IS NOT uploaded, staff may sign a Limited CAS release and upload to the Warehouse. If it is not uploaded, the referral may get stuck due to lack of permission. If your agency is not on the warehouse, keep their release of information on file. If you are using CAS, also check the box on the Non-HMIS Client edit screen indicating that you have a Limited CAS Release in your file. diff --git a/app/views/non_hmis_assessments/pathways_version_four/_household_size_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_household_size_preamble.haml new file mode 100644 index 000000000..5836841bd --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_household_size_preamble.haml @@ -0,0 +1 @@ +%h2 Housing size and location needs: diff --git a/app/views/non_hmis_assessments/pathways_version_four/_pathways_household_history_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_pathways_household_history_preamble.haml index e2c05f539..c44c45f86 100644 --- a/app/views/non_hmis_assessments/pathways_version_four/_pathways_household_history_preamble.haml +++ b/app/views/non_hmis_assessments/pathways_version_four/_pathways_household_history_preamble.haml @@ -1,7 +1,13 @@ %h3 Length of Time Homeless -%p To prioritize housing assistance, we look at the total number of nights someone has experienced homelessness in the last 3 years, up to a maximum of 1,096 nights. When doing the assessment, please don't enter more than 1,096 nights, or the person won't be matched with the right resources. -%p - If you are adding non-HMIS nights to an individual's assessment you will need to complete the +%p + To prioritize housing assistance, we look at the total number of nights someone has experienced homelessness + %strong in the last 3 years, + up to a maximum of 1,096 nights. When doing the assessment, please don't enter more than 1,096 nights, or the person won't be matched with the right resources. +%p + If you are adding more than 1.5 years (548 nights) of non-HMIS nights to an individual's assessment you will need to complete the %a{ href: 'https://bit.ly/pathways24addnights', target: :blank } Documenting Boston Homeless Nights form (bit.ly/pathways24addnights) and send it to the CE Team at ceteam@homestart.org. +%p + %strong Note: + Boston families may be displaced when they receive a shelter placement outside of Boston through the State's Emergency Assistance program. Please include all time sheltered outside of Boston when the family became homeless in Boston. diff --git a/app/views/non_hmis_assessments/pathways_version_four/_service_need_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_service_need_preamble.haml new file mode 100644 index 000000000..1b8edcb28 --- /dev/null +++ b/app/views/non_hmis_assessments/pathways_version_four/_service_need_preamble.haml @@ -0,0 +1 @@ +%h2 Service Need Indicator diff --git a/app/views/non_hmis_assessments/pathways_version_four/_transfer_assessment_preamble.haml b/app/views/non_hmis_assessments/pathways_version_four/_transfer_assessment_preamble.haml index 6f4b9c563..9fba1a6cf 100644 --- a/app/views/non_hmis_assessments/pathways_version_four/_transfer_assessment_preamble.haml +++ b/app/views/non_hmis_assessments/pathways_version_four/_transfer_assessment_preamble.haml @@ -1,86 +1,12 @@ %h1 Rapid Re-Housing ➡︎ Permanent Supportive Housing Transfer Assessment -%ol - %li - Purpose of This Assessment - %ul - %li The purpose of this assessment is to gather information about a rapid re-housing (RRH) participant’s housing stability. The information gathered will be used to add the participant to the pool of people who need a transfer to a permanent supportive housing resource. - %li Because we have so few permanent supportive housing resources that open up each year, the information will be used to prioritize people who are at most imminent risk of returning back to shelter or sleeping outside after their rapid re-housing ends. - %li - Which RRH Clients Can Complete the RRH Transfer Assessment? - %ul - %li - Participants must have signed a lease in the RRH program at least six - %strong (6) - months before completing this assessment - - %li - Who can administer the RRH Transfer Assessment? - %p Rapid Re-housing stabilization staff who: - %ul - %li - Attended a training on how to administer the - %strong RRH Transfer Assessment - in the last year - %br - and - %li Actively work with the client on housing stability - -%h2 Key Introduction Points to Share with the Participant - -%ul - %li - %strong Purpose of the RRH Transfer Assessment: - It is our role as a rapid re-housing program to explore ways you can achieve housing stability and avoid returning to homelessness after our program ends. One potential way for people struggling to maintain their current housing is to complete an assessment for permanent supportive housing resources. These are subsidized housing openings where your rent would be calculated at about 30-40% of your income, and most of them come with intensive supportive services to help people keep their housing. Sometimes they are vouchers to use with private landlords, and sometimes they are single rooms or units that open up. - %li - %strong Permanent Supportive Housing Openings Are Rare: - Openings depend on if programs are currently full or taking new clients, which changes all the time- this is why we don’t know exact timeframes. We have very few openings of this type per year, so we must still continue finding other solutions as well. - %li - %strong Right to Refuse Responses: - You may refuse to respond to questions on this assessment. You may stop this assessment at any time and pick it back up at a later meeting. Your responses will not harm any other services you receive from our agency. - - %li - %strong Filing a Non-Discrimination Complaint: - If at any time you would like information on filing a complaint because you believe you are being discriminated against, let me know and I can give you information on how to pursue this. (Note to assessor: - = succeed(')') do - = link_to 'Full Guidance on Changed Eligibility & Referrals to Boston CoC Housing Options', 'https://bostoncoc.mailchimpsites.com/', target: :_blank - - -%h2 Key Points to Share with the Participant Regarding Confidentiality -%p - %strong Right to Refuse Responses AND/OR Share Information: - We ask you to share information to connect you to housing programs you may be interested in. You may refuse to respond to certain questions, or refuse to have information stored and shared in the database. Declining to share information will not jeopardize any opportunities for you, but it may be harder to get in contact with you when an opening does come up. -%p Commitment to Confidentiality: If you decline to share your information, please know we will uphold your confidentiality to keep your information secure, private and safe. - - -.c-question{data: {number: '1A'}} - .c-question__wrapper - .c-question__number 1A - .c-question__input - .form-group - .controls - .form--label-hint-wrapper - .form--lable-hint - %label.control-label Permission to Share Your Information with Partner Agencies - %p To sign you up for housing programs you may be interested in, we would like to ask for your permission to share information with participating agencies who manage these programs so they may contact you when there is an opening. All of the participating agencies have agreed to keep all data confidential and secure. Is this okay with you? - %p - %strong Yes - %p - Check to see if a Housing Assistance Network (HAN) Release of Information is uploaded into the participant's warehouse record. - %ul - %li If one IS uploaded, proceed to the next section → Section 2, Language & Safety - %li - If one IS NOT uploaded, complete a COVID HAN release and upload to the Warehouse. COVID HAN release can be found - %a{ href: 'https://bostoncoc.mailchimpsites.com/', target: :_blank } here. - If it is not uploaded, the referral may get stuck due to lack of permission. If your agency is not on the warehouse, check the box in the box on the Non-HMIS Client edit screen indicating that you have a HAN in your file. - %p - %strong No - %ul - %li If the participant chooses not to share information with other provider agencies, staff may sign a Limited CAS release so that the Coordinated Entry lead agency can sign them up for programs, but not share information with any other providers. Check to see if a Limited CAS release is uploaded into the participant's warehouse record. - %li If one IS uploaded, proceed to the next section. - %li - If one IS NOT uploaded, staff may complete a Limited CAS Release and upload to the Warehouse. Limited CAS release can be found - %a{ href: 'https://bostoncoc.mailchimpsites.com/', target: :_blank } here. - If it is not uploaded, the referral may get stuck due to lack of permission. If your agency is not on the warehouse, check the box in the box on the Non-HMIS Client edit screen indicating that you have a Limited CAS release in your file. +.alert.alert-info + %p.mb-4.d-block + %i.icon-warning + = link_to 'Please read this (https://bit.ly/rrh2psh)', 'https://bit.ly/rrh2psh', target: :_blank + before completing the assessment + %h3 Eligibility: + %p + Clients are eligible for the transfer assessment after 3 months in RRH if they have had 1 year of homelessness in Boston in the last 3 years. %h1.mt-8 Assessment Questions %hr diff --git a/app/views/non_hmis_clients/assessments/_common_pathways_version_four_questions.haml b/app/views/non_hmis_clients/assessments/_common_pathways_version_four_questions.haml index 1a5853786..18217d99a 100644 --- a/app/views/non_hmis_clients/assessments/_common_pathways_version_four_questions.haml +++ b/app/views/non_hmis_clients/assessments/_common_pathways_version_four_questions.haml @@ -70,4 +70,5 @@ - locked_note = if field_locked then "Locked until #{@assessment.locked_until}" else '' end = form.input(field_name, { pre_label: options[:number], disabled: field_locked, hint: locked_note }.merge(options)) -= render 'non_hmis_clients/assessments/pathways_version_four_js' +- unless @assessment.family_pathways? + = render 'non_hmis_clients/assessments/pathways_version_four_js' diff --git a/app/views/non_hmis_clients/assessments/_pathways_version_four_js.haml b/app/views/non_hmis_clients/assessments/_pathways_version_four_js.haml index 7bad4436b..a81a321fc 100644 --- a/app/views/non_hmis_clients/assessments/_pathways_version_four_js.haml +++ b/app/views/non_hmis_clients/assessments/_pathways_version_four_js.haml @@ -22,6 +22,18 @@ }); $('input[name*="[setting]"]').trigger('change'); + // Set-asides + $('input[name*="[psh_required]"]').on('change', function(e) { + value = $('input[name*="[psh_required]"]:checked').val(); + var dependent = $('div[data-number="Q6"], div[data-number="Q7"], div[data-number="Q8"], div[data-number="Q9"], div[data-number="Q9a"], div[data-number="Q10"]') + if(value == 'no') { + dependent.addClass('d-none') + } else { + dependent.removeClass('d-none') + } + }); + $('input[name*="[psh_required]"]').trigger('change'); + // Days Homeless var warehouse_sheltered_selector = 'input[name*="[homeless_nights_sheltered]"]'; var warehouse_unsheltered_selector = 'input[name*="[homeless_nights_unsheltered]"]'; @@ -39,7 +51,7 @@ var extra_sheltered_days = parseInt($(extra_sheltered_selector).val() || 0); var extra_unsheltered_days = parseInt($(extra_unsheltered_selector).val() || 0); - // If a client has more than 548 self-reported days (combination of sheltered and unsheltered) + // If a client has more than 548 self-reported days (combination of sheltered and unsheltered) // and does not have a verification uploaded, count unsheltered days first, then count sheltered days UP TO 548. // If the additional days are verified, use the provided amounts. if(!additional_days_verified_checkbox.checked) { diff --git a/db/migrate/20240816134210_add_federal_benefits_for_transfer.rb b/db/migrate/20240816134210_add_federal_benefits_for_transfer.rb new file mode 100644 index 000000000..61ec03bb8 --- /dev/null +++ b/db/migrate/20240816134210_add_federal_benefits_for_transfer.rb @@ -0,0 +1,7 @@ +class AddFederalBenefitsForTransfer < ActiveRecord::Migration[7.0] + def change + [:non_hmis_assessments, :non_hmis_clients, :project_clients, :clients].each do |table| + add_column table, :federal_benefits, :boolean + end + end +end diff --git a/db/migrate/20240827172144_add_contact_order_to_contacts.rb b/db/migrate/20240827172144_add_contact_order_to_contacts.rb index 6715ffd27..75ad22d3a 100644 --- a/db/migrate/20240827172144_add_contact_order_to_contacts.rb +++ b/db/migrate/20240827172144_add_contact_order_to_contacts.rb @@ -1,7 +1,7 @@ class AddContactOrderToContacts < ActiveRecord::Migration[7.0] def change add_column :client_opportunity_match_contacts, :contact_order, :integer - + # Set contact_order to 1 for all single contact types on a match [ :dnd_staff, diff --git a/db/migrate/20250102144339_add_psh_required_to_clients.rb b/db/migrate/20250102144339_add_psh_required_to_clients.rb new file mode 100644 index 000000000..5955ebf0a --- /dev/null +++ b/db/migrate/20250102144339_add_psh_required_to_clients.rb @@ -0,0 +1,7 @@ +class AddPshRequiredToClients < ActiveRecord::Migration[7.0] + def change + [:non_hmis_assessments, :non_hmis_clients, :project_clients, :clients].each do |table| + add_column table, :psh_required, :string, default: 'maybe' + end + end +end diff --git a/db/migrate/20250107142055_add_school_contacts.rb b/db/migrate/20250107142055_add_school_contacts.rb new file mode 100644 index 000000000..f1ab05371 --- /dev/null +++ b/db/migrate/20250107142055_add_school_contacts.rb @@ -0,0 +1,17 @@ +class AddSchoolContacts < ActiveRecord::Migration[7.0] + def change + [ + :non_hmis_assessments, + :project_clients, + :clients, + ].each do |table| + add_column table, :requires_vision_or_hearing_accessibility, :boolean, default: false + end + + add_column :non_hmis_assessments, :schools, :string + add_column :non_hmis_assessments, :schools_contact_info, :text + add_column :non_hmis_assessments, :disqualified_for_state_assistance, :boolean, default: false + add_column :non_hmis_assessments, :disqualified_for_state_assistance_reasons, :string + + end +end diff --git a/db/migrate/20250113131532_add_first_homeless_night_to_non_hmis_assessments.rb b/db/migrate/20250113131532_add_first_homeless_night_to_non_hmis_assessments.rb new file mode 100644 index 000000000..8d0abc57f --- /dev/null +++ b/db/migrate/20250113131532_add_first_homeless_night_to_non_hmis_assessments.rb @@ -0,0 +1,5 @@ +class AddFirstHomelessNightToNonHmisAssessments < ActiveRecord::Migration[7.0] + def change + add_column :non_hmis_assessments, :calculated_first_homeless_night, :date + end +end diff --git a/db/migrate/20250113184426_add_household_dv.rb b/db/migrate/20250113184426_add_household_dv.rb new file mode 100644 index 000000000..067b6206d --- /dev/null +++ b/db/migrate/20250113184426_add_household_dv.rb @@ -0,0 +1,17 @@ +class AddHouseholdDv < ActiveRecord::Migration[7.0] + def change + [ + :non_hmis_assessments, + :project_clients, + :clients, + ].each do |table| + add_column table, :household_dv_survivor, :boolean + end + [ + :project_clients, + :clients, + ].each do |table| + add_column table, :disqualified_for_state_assistance, :boolean + end + end +end diff --git a/db/migrate/20250118142954_add_eviction_question_to_pathways.rb b/db/migrate/20250118142954_add_eviction_question_to_pathways.rb new file mode 100644 index 000000000..01c773b45 --- /dev/null +++ b/db/migrate/20250118142954_add_eviction_question_to_pathways.rb @@ -0,0 +1,5 @@ +class AddEvictionQuestionToPathways < ActiveRecord::Migration[7.0] + def change + add_column :non_hmis_assessments, :background_check_issues_disability_or_substance_use, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20250206162624_add_eviction_history.rb b/db/migrate/20250206162624_add_eviction_history.rb new file mode 100644 index 000000000..8fe2bb8c1 --- /dev/null +++ b/db/migrate/20250206162624_add_eviction_history.rb @@ -0,0 +1,5 @@ +class AddEvictionHistory < ActiveRecord::Migration[7.0] + def change + add_column :non_hmis_assessments, :eviction_history, :string + end +end diff --git a/db/rules.csv b/db/rules.csv index 471d8492d..3f860096e 100644 --- a/db/rules.csv +++ b/db/rules.csv @@ -129,3 +129,4 @@ EnrolledInRrhNoMoveIn,Enrolled in Rapid Re-Housing without move-in date,,be EnrolledInPshNoMoveIn,Enrolled in PSH (Permanent Supportive Housing) without move-in date,,be EnrolledInPhNoMoveIn,Enrolled in PH (PH – Housing Only or Housing with Services (no disability required for entry) without move-in date,,be EnrolledInHmisProjectTypeAnyPhNoMoveIn,Enrolled in one of the specified PH Project Types without a move in date,,be +PshRequired,in need of Permanent Supportive Housing,,be diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..5677e8bf6 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,1921 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.0].define(version: 2024_08_16_134210) do + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "activity_logs", force: :cascade do |t| + t.string "item_model" + t.integer "item_id" + t.string "title" + t.integer "user_id", null: false + t.string "controller_name", null: false + t.string "action_name", null: false + t.string "method" + t.string "path" + t.string "ip_address", null: false + t.string "session_hash" + t.text "referrer" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.index ["controller_name"], name: "index_activity_logs_on_controller_name" + t.index ["created_at", "item_model", "user_id"], name: "index_activity_logs_on_created_at_and_item_model_and_user_id" + t.index ["created_at"], name: "activity_logs_created_at_idx", using: :brin + t.index ["created_at"], name: "created_at_idx", using: :brin + t.index ["item_model", "user_id", "created_at"], name: "index_activity_logs_on_item_model_and_user_id_and_created_at" + t.index ["item_model", "user_id"], name: "index_activity_logs_on_item_model_and_user_id" + t.index ["item_model"], name: "index_activity_logs_on_item_model" + t.index ["user_id", "item_model", "created_at"], name: "index_activity_logs_on_user_id_and_item_model_and_created_at" + t.index ["user_id"], name: "index_activity_logs_on_user_id" + end + + create_table "agencies", id: :serial, force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "building_contacts", id: :serial, force: :cascade do |t| + t.integer "building_id", null: false + t.integer "contact_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["building_id"], name: "index_building_contacts_on_building_id" + t.index ["contact_id"], name: "index_building_contacts_on_contact_id" + t.index ["deleted_at"], name: "index_building_contacts_on_deleted_at" + end + + create_table "building_services", id: :serial, force: :cascade do |t| + t.integer "building_id" + t.integer "service_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["building_id"], name: "index_building_services_on_building_id" + t.index ["deleted_at"], name: "index_building_services_on_deleted_at" + t.index ["service_id"], name: "index_building_services_on_service_id" + end + + create_table "buildings", id: :serial, force: :cascade do |t| + t.string "name" + t.string "building_type" + t.integer "subgrantee_id" + t.integer "id_in_data_source" + t.integer "federal_program_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "data_source_id" + t.string "data_source_id_column_name" + t.string "address" + t.string "city" + t.string "state" + t.string "zip_code" + t.string "geo_code" + t.index ["id_in_data_source"], name: "index_buildings_on_id_in_data_source" + t.index ["subgrantee_id"], name: "index_buildings_on_subgrantee_id" + end + + create_table "client_contacts", id: :serial, force: :cascade do |t| + t.integer "client_id", null: false + t.integer "contact_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "shelter_agency", default: false, null: false + t.boolean "regular", default: false, null: false + t.boolean "dnd_staff", default: false, null: false + t.boolean "housing_subsidy_admin", default: false, null: false + t.boolean "ssp", default: false, null: false + t.boolean "hsp", default: false, null: false + t.boolean "do", default: false, null: false + t.index ["client_id"], name: "index_client_contacts_on_client_id" + t.index ["contact_id"], name: "index_client_contacts_on_contact_id" + t.index ["deleted_at"], name: "index_client_contacts_on_deleted_at" + end + + create_table "client_notes", id: :serial, force: :cascade do |t| + t.integer "user_id", null: false + t.integer "client_id", null: false + t.string "note" + t.datetime "deleted_at", precision: nil + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "client_opportunity_match_contacts", id: :serial, force: :cascade do |t| + t.integer "match_id", null: false + t.integer "contact_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "dnd_staff", default: false, null: false + t.boolean "housing_subsidy_admin", default: false, null: false + t.boolean "client", default: false, null: false + t.boolean "housing_search_worker", default: false, null: false + t.boolean "shelter_agency", default: false, null: false + t.boolean "ssp", default: false, null: false + t.boolean "hsp", default: false, null: false + t.boolean "do", default: false, null: false + t.index ["contact_id"], name: "index_client_opportunity_match_contacts_on_contact_id" + t.index ["deleted_at"], name: "index_client_opportunity_match_contacts_on_deleted_at" + t.index ["match_id"], name: "index_client_opportunity_match_contacts_on_match_id" + end + + create_table "client_opportunity_matches", id: :serial, force: :cascade do |t| + t.integer "score" + t.integer "client_id", null: false + t.integer "opportunity_id", null: false + t.integer "contact_id" + t.datetime "proposed_at", precision: nil + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "active", default: false, null: false + t.boolean "closed", default: false, null: false + t.string "closed_reason" + t.boolean "selected" + t.json "universe_state" + t.integer "custom_expiration_length" + t.date "shelter_expiration" + t.date "stall_date" + t.datetime "stall_contacts_notified", precision: nil + t.datetime "dnd_notified", precision: nil + t.integer "match_route_id" + t.index ["active"], name: "index_client_opportunity_matches_on_active" + t.index ["client_id"], name: "index_client_opportunity_matches_on_client_id" + t.index ["closed"], name: "index_client_opportunity_matches_on_closed" + t.index ["closed_reason"], name: "index_client_opportunity_matches_on_closed_reason" + t.index ["contact_id"], name: "index_client_opportunity_matches_on_contact_id" + t.index ["deleted_at"], name: "index_client_opportunity_matches_on_deleted_at", where: "(deleted_at IS NULL)" + t.index ["opportunity_id"], name: "index_client_opportunity_matches_on_opportunity_id" + end + + create_table "clients", id: :serial, force: :cascade do |t| + t.string "first_name" + t.string "middle_name" + t.string "last_name" + t.string "name_suffix" + t.string "name_quality", limit: 4 + t.string "ssn", limit: 9 + t.date "date_of_birth" + t.string "gender_other", limit: 50 + t.boolean "veteran", default: false + t.boolean "chronic_homeless", default: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.integer "merged_into" + t.integer "split_from" + t.integer "ssn_quality" + t.integer "date_of_birth_quality" + t.integer "race_id" + t.integer "ethnicity_id" + t.integer "gender_id" + t.integer "veteran_status_id" + t.integer "developmental_disability" + t.integer "domestic_violence" + t.date "calculated_first_homeless_night" + t.boolean "available", default: true, null: false + t.string "homephone" + t.string "cellphone" + t.string "workphone" + t.string "pager" + t.string "email" + t.boolean "hiv_aids", default: false + t.boolean "chronic_health_problem", default: false + t.boolean "mental_health_problem", default: false + t.boolean "substance_abuse_problem", default: false + t.boolean "physical_disability", default: false + t.boolean "disabling_condition", default: false + t.datetime "release_of_information", precision: nil + t.date "prevent_matching_until" + t.boolean "dmh_eligible", default: false, null: false + t.boolean "va_eligible", default: false, null: false + t.boolean "hues_eligible", default: false, null: false + t.datetime "disability_verified_on", precision: nil + t.datetime "housing_assistance_network_released_on", precision: nil + t.boolean "sync_with_cas", default: false, null: false + t.float "income_total_monthly" + t.datetime "income_total_monthly_last_collected", precision: nil + t.boolean "confidential", default: false, null: false + t.boolean "hiv_positive", default: false, null: false + t.string "housing_release_status" + t.integer "vispdat_score" + t.boolean "ineligible_immigrant", default: false, null: false + t.boolean "family_member", default: false, null: false + t.boolean "child_in_household", default: false, null: false + t.boolean "us_citizen", default: false, null: false + t.boolean "asylee", default: false, null: false + t.boolean "lifetime_sex_offender", default: false, null: false + t.boolean "meth_production_conviction", default: false, null: false + t.integer "days_homeless" + t.boolean "ha_eligible", default: false, null: false + t.integer "days_homeless_in_last_three_years" + t.integer "vispdat_priority_score", default: 0 + t.integer "vispdat_length_homeless_in_days", default: 0, null: false + t.boolean "cspech_eligible", default: false + t.string "alternate_names" + t.date "calculated_last_homeless_night" + t.boolean "congregate_housing", default: false + t.boolean "sober_housing", default: false + t.jsonb "enrolled_project_ids" + t.jsonb "active_cohort_ids" + t.string "client_identifier" + t.integer "assessment_score", default: 0, null: false + t.boolean "ssvf_eligible", default: false, null: false + t.boolean "rrh_desired", default: false, null: false + t.boolean "youth_rrh_desired", default: false, null: false + t.string "rrh_assessment_contact_info" + t.datetime "rrh_assessment_collected_at", precision: nil + t.boolean "enrolled_in_th", default: false, null: false + t.boolean "enrolled_in_es", default: false, null: false + t.boolean "enrolled_in_sh", default: false, null: false + t.boolean "enrolled_in_so", default: false, null: false + t.integer "days_literally_homeless_in_last_three_years", default: 0 + t.boolean "requires_wheelchair_accessibility", default: false + t.integer "required_number_of_bedrooms", default: 1 + t.integer "required_minimum_occupancy", default: 1 + t.boolean "requires_elevator_access", default: false + t.jsonb "neighborhood_interests", default: [], null: false + t.date "date_days_homeless_verified" + t.string "who_verified_days_homeless" + t.float "tie_breaker" + t.boolean "interested_in_set_asides", default: false + t.jsonb "tags" + t.string "case_manager_contact_info" + t.boolean "vash_eligible" + t.boolean "pregnancy_status", default: false + t.boolean "income_maximization_assistance_requested", default: false + t.boolean "pending_subsidized_housing_placement", default: false + t.boolean "rrh_th_desired", default: false + t.boolean "sro_ok", default: false + t.boolean "evicted", default: false + t.boolean "dv_rrh_desired", default: false + t.boolean "health_prioritized", default: false + t.boolean "is_currently_youth", default: false, null: false + t.boolean "older_than_65" + t.date "holds_voucher_on" + t.boolean "holds_internal_cas_voucher" + t.string "assessment_name" + t.date "entry_date" + t.date "financial_assistance_end_date" + t.boolean "enrolled_in_rrh", default: false + t.boolean "enrolled_in_psh", default: false + t.boolean "enrolled_in_ph", default: false + t.string "address" + t.boolean "majority_sheltered" + t.date "tie_breaker_date" + t.jsonb "strengths", default: [] + t.jsonb "challenges", default: [] + t.boolean "foster_care", default: false + t.boolean "open_case", default: false + t.boolean "housing_for_formerly_homeless", default: false + t.boolean "drug_test", default: false + t.boolean "heavy_drug_use", default: false + t.boolean "sober", default: false + t.boolean "willing_case_management", default: false + t.boolean "employed_three_months", default: false + t.boolean "living_wage", default: false + t.boolean "send_emails", default: false + t.boolean "need_daily_assistance", default: false + t.boolean "full_time_employed", default: false + t.boolean "can_work_full_time", default: false + t.boolean "willing_to_work_full_time", default: false + t.boolean "rrh_successful_exit", default: false + t.boolean "th_desired", default: false + t.boolean "site_case_management_required", default: false + t.boolean "currently_fleeing", default: false + t.date "dv_date" + t.string "assessor_first_name" + t.string "assessor_last_name" + t.string "assessor_email" + t.string "assessor_phone" + t.integer "hmis_days_homeless_all_time" + t.integer "hmis_days_homeless_last_three_years" + t.integer "match_group" + t.boolean "encampment_decomissioned", default: false + t.boolean "pregnant_under_28_weeks", default: false + t.boolean "am_ind_ak_native", default: false + t.boolean "asian", default: false + t.boolean "black_af_american", default: false + t.boolean "native_hi_pacific", default: false + t.boolean "white", default: false + t.boolean "female", default: false + t.boolean "male", default: false + t.boolean "no_single_gender", default: false + t.boolean "transgender", default: false + t.boolean "questioning", default: false + t.boolean "ongoing_case_management_required", default: false + t.jsonb "file_tags", default: {} + t.boolean "housing_barrier", default: false + t.boolean "service_need", default: false + t.integer "additional_homeless_nights_sheltered", default: 0 + t.integer "additional_homeless_nights_unsheltered", default: 0 + t.integer "total_homeless_nights_unsheltered", default: 0 + t.integer "calculated_homeless_nights_sheltered", default: 0 + t.integer "calculated_homeless_nights_unsheltered", default: 0 + t.integer "total_homeless_nights_sheltered", default: 0 + t.boolean "enrolled_in_ph_pre_move_in", default: false, null: false + t.boolean "enrolled_in_psh_pre_move_in", default: false, null: false + t.boolean "enrolled_in_rrh_pre_move_in", default: false, null: false + t.jsonb "ongoing_es_enrollments" + t.jsonb "ongoing_so_enrollments" + t.jsonb "last_seen_projects" + t.boolean "federal_benefits" + t.index ["active_cohort_ids"], name: "index_clients_on_active_cohort_ids" + t.index ["available"], name: "index_clients_on_available" + t.index ["calculated_last_homeless_night"], name: "index_clients_on_calculated_last_homeless_night" + t.index ["date_of_birth"], name: "index_clients_on_date_of_birth" + t.index ["days_homeless_in_last_three_years"], name: "index_clients_on_days_homeless_in_last_three_years" + t.index ["deleted_at"], name: "index_clients_on_deleted_at" + t.index ["disabling_condition"], name: "index_clients_on_disabling_condition" + t.index ["enrolled_project_ids"], name: "index_clients_on_enrolled_project_ids" + t.index ["family_member"], name: "index_clients_on_family_member" + t.index ["health_prioritized"], name: "index_clients_on_health_prioritized" + t.index ["vispdat_priority_score"], name: "index_clients_on_vispdat_priority_score" + t.index ["vispdat_score"], name: "index_clients_on_vispdat_score" + end + + create_table "configs", id: :serial, force: :cascade do |t| + t.integer "dnd_interval", null: false + t.string "warehouse_url", null: false + t.boolean "require_cori_release", default: true + t.integer "ami", default: 66600, null: false + t.string "vispdat_prioritization_scheme", default: "length_of_time" + t.text "non_hmis_fields" + t.integer "unavailable_for_length", default: 0 + t.string "deidentified_client_assessment", default: "DeidentifiedClientAssessment" + t.string "identified_client_assessment", default: "IdentifiedClientAssessment" + t.integer "lock_days", default: 0, null: false + t.integer "lock_grace_days", default: 0, null: false + t.boolean "limit_client_names_on_matches", default: true + t.boolean "include_note_in_email_default" + t.boolean "notify_all_on_progress_update", default: false + t.integer "send_match_summary_email_on" + end + + create_table "contacts", id: :serial, force: :cascade do |t| + t.string "email" + t.string "phone" + t.string "first_name" + t.string "last_name" + t.integer "user_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.string "role" + t.integer "id_in_data_source" + t.integer "data_source_id" + t.string "data_source_id_column_name" + t.integer "role_id" + t.string "role_in_organization" + t.string "cell_phone" + t.string "middle_name" + t.index ["deleted_at"], name: "index_contacts_on_deleted_at" + t.index ["user_id"], name: "index_contacts_on_user_id" + end + + create_table "data_sources", id: :serial, force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.string "db_identifier" + t.string "client_url" + end + + create_table "date_of_birth_quality_codes", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "deidentified_clients_xlsxes", id: :serial, force: :cascade do |t| + t.string "filename" + t.integer "user_id" + t.string "content_type" + t.binary "content" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.text "file" + end + + create_table "delayed_jobs", id: :serial, force: :cascade do |t| + t.integer "priority", default: 0, null: false + t.integer "attempts", default: 0, null: false + t.text "handler", null: false + t.text "last_error" + t.datetime "run_at", precision: nil + t.datetime "locked_at", precision: nil + t.datetime "failed_at", precision: nil + t.string "locked_by" + t.string "queue" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.index ["priority", "run_at"], name: "delayed_jobs_priority" + end + + create_table "disabling_conditions", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "discharge_statuses", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "domestic_violence_survivors", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "entity_view_permissions", id: :serial, force: :cascade do |t| + t.integer "user_id" + t.integer "entity_id", null: false + t.string "entity_type", null: false + t.boolean "editable" + t.datetime "deleted_at", precision: nil + t.bigint "agency_id" + t.index ["agency_id"], name: "index_entity_view_permissions_on_agency_id" + t.index ["entity_type", "entity_id"], name: "index_entity_view_permissions_on_entity_type_and_entity_id" + t.index ["user_id"], name: "index_entity_view_permissions_on_user_id" + end + + create_table "ethnicities", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "external_referrals", force: :cascade do |t| + t.bigint "client_id", null: false + t.bigint "user_id", null: false + t.date "referred_on", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["client_id"], name: "index_external_referrals_on_client_id" + t.index ["created_at"], name: "index_external_referrals_on_created_at" + t.index ["updated_at"], name: "index_external_referrals_on_updated_at" + t.index ["user_id"], name: "index_external_referrals_on_user_id" + end + + create_table "file_tags", id: :serial, force: :cascade do |t| + t.integer "sub_program_id", null: false + t.string "name" + t.integer "tag_id" + end + + create_table "funding_source_services", id: :serial, force: :cascade do |t| + t.integer "funding_source_id" + t.integer "service_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["deleted_at"], name: "index_funding_source_services_on_deleted_at" + t.index ["funding_source_id"], name: "index_funding_source_services_on_funding_source_id" + t.index ["service_id"], name: "index_funding_source_services_on_service_id" + end + + create_table "funding_sources", id: :serial, force: :cascade do |t| + t.string "name" + t.string "abbreviation" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "id_in_data_source" + t.integer "data_source_id" + t.string "data_source_id_column_name" + t.datetime "deleted_at", precision: nil + end + + create_table "has_developmental_disabilities", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "has_hivaids", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "has_mental_health_problems", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "helps", force: :cascade do |t| + t.string "controller_path", null: false + t.string "action_name", null: false + t.string "external_url" + t.string "title", null: false + t.text "content", null: false + t.string "location", default: "internal", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["controller_path", "action_name"], name: "index_helps_on_controller_path_and_action_name", unique: true + t.index ["created_at"], name: "index_helps_on_created_at" + t.index ["updated_at"], name: "index_helps_on_updated_at" + end + + create_table "housing_attributes", force: :cascade do |t| + t.string "housingable_type" + t.bigint "housingable_id" + t.string "name" + t.string "value" + t.datetime "deleted_at", precision: nil + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["housingable_type", "housingable_id"], name: "index_housing_attributes_on_housingable_type_and_housingable_id" + end + + create_table "housing_media_links", force: :cascade do |t| + t.string "housingable_type" + t.bigint "housingable_id" + t.string "label" + t.string "url" + t.datetime "deleted_at", precision: nil + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["housingable_type", "housingable_id"], name: "index_housing_media_links_on_housingable_type_and_id" + end + + create_table "imported_clients_csvs", id: :serial, force: :cascade do |t| + t.string "filename" + t.integer "user_id" + t.string "content_type" + t.string "content" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.text "file" + end + + create_table "letsencrypt_plugin_challenges", id: :serial, force: :cascade do |t| + t.text "response" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "letsencrypt_plugin_settings", id: :serial, force: :cascade do |t| + t.text "private_key" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "login_activities", id: :serial, force: :cascade do |t| + t.string "scope" + t.string "strategy" + t.string "identity" + t.boolean "success" + t.string "failure_reason" + t.string "user_type" + t.integer "user_id" + t.string "context" + t.string "ip" + t.text "user_agent" + t.text "referrer" + t.string "city" + t.string "region" + t.string "country" + t.datetime "created_at", precision: nil + t.index ["identity"], name: "index_login_activities_on_identity" + t.index ["ip"], name: "index_login_activities_on_ip" + end + + create_table "match_census", id: :serial, force: :cascade do |t| + t.date "date", null: false + t.integer "opportunity_id", null: false + t.integer "match_id" + t.string "program_name" + t.string "sub_program_name" + t.jsonb "prioritized_client_ids", default: [], null: false + t.integer "active_client_id" + t.jsonb "requirements", default: [], null: false + t.integer "match_prioritization_id" + t.integer "active_client_prioritization_value" + t.string "prioritization_method_used" + t.index ["date"], name: "index_match_census_on_date" + t.index ["match_id"], name: "index_match_census_on_match_id" + t.index ["match_prioritization_id"], name: "index_match_census_on_match_prioritization_id" + t.index ["opportunity_id"], name: "index_match_census_on_opportunity_id" + end + + create_table "match_decision_reasons", id: :serial, force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.boolean "active", default: true, null: false + t.boolean "ineligible_in_warehouse", default: false, null: false + t.integer "referral_result" + t.boolean "limited", default: false + t.datetime "deleted_at" + end + + create_table "match_decisions", id: :serial, force: :cascade do |t| + t.integer "match_id" + t.string "type" + t.string "status" + t.integer "contact_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.datetime "client_last_seen_date", precision: nil + t.datetime "criminal_hearing_date", precision: nil + t.datetime "client_move_in_date", precision: nil + t.integer "decline_reason_id" + t.text "decline_reason_other_explanation" + t.integer "not_working_with_client_reason_id" + t.text "not_working_with_client_reason_other_explanation" + t.boolean "client_spoken_with_services_agency", default: false + t.boolean "cori_release_form_submitted", default: false + t.datetime "deleted_at", precision: nil + t.integer "administrative_cancel_reason_id" + t.string "administrative_cancel_reason_other_explanation" + t.boolean "disable_opportunity", default: false + t.date "application_date" + t.boolean "external_software_used", default: false, null: false + t.string "address" + t.boolean "include_note_in_email" + t.datetime "date_voucher_issued", precision: nil + t.string "manager" + t.boolean "criminal_hearing_outcome_recorded" + t.index ["administrative_cancel_reason_id"], name: "index_match_decisions_on_administrative_cancel_reason_id" + t.index ["decline_reason_id"], name: "index_match_decisions_on_decline_reason_id" + t.index ["match_id"], name: "index_match_decisions_on_match_id" + t.index ["not_working_with_client_reason_id"], name: "index_match_decisions_on_not_working_with_client_reason_id" + end + + create_table "match_events", id: :serial, force: :cascade do |t| + t.string "type" + t.integer "match_id" + t.integer "notification_id" + t.integer "decision_id" + t.integer "contact_id" + t.string "action" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.text "note" + t.datetime "deleted_at", precision: nil + t.integer "not_working_with_client_reason_id" + t.date "client_last_seen_date" + t.boolean "admin_note", default: false, null: false + t.integer "client_id" + t.index ["decision_id"], name: "index_match_events_on_decision_id" + t.index ["match_id"], name: "index_match_events_on_match_id" + t.index ["not_working_with_client_reason_id"], name: "index_match_events_on_not_working_with_client_reason_id" + t.index ["notification_id"], name: "index_match_events_on_notification_id" + end + + create_table "match_mitigation_reasons", force: :cascade do |t| + t.bigint "client_opportunity_match_id" + t.bigint "mitigation_reason_id" + t.boolean "addressed", default: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["client_opportunity_match_id"], name: "index_match_mitigation_reasons_on_client_opportunity_match_id" + t.index ["mitigation_reason_id"], name: "index_match_mitigation_reasons_on_mitigation_reason_id" + end + + create_table "match_prioritizations", id: :serial, force: :cascade do |t| + t.string "type", null: false + t.boolean "active", default: true, null: false + t.integer "weight", default: 10, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "match_progress_updates", id: :serial, force: :cascade do |t| + t.string "type", null: false + t.integer "match_id", null: false + t.integer "notification_id" + t.integer "contact_id", null: false + t.integer "decision_id" + t.integer "notification_number" + t.datetime "requested_at", precision: nil + t.datetime "submitted_at", precision: nil + t.datetime "dnd_notified_at", precision: nil + t.string "response" + t.text "note" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.datetime "deleted_at", precision: nil + t.date "client_last_seen" + t.index ["contact_id"], name: "index_match_progress_updates_on_contact_id" + t.index ["decision_id"], name: "index_match_progress_updates_on_decision_id" + t.index ["match_id"], name: "index_match_progress_updates_on_match_id" + t.index ["notification_id"], name: "index_match_progress_updates_on_notification_id" + t.index ["type"], name: "index_match_progress_updates_on_type" + end + + create_table "match_routes", id: :serial, force: :cascade do |t| + t.string "type", null: false + t.boolean "active", default: true, null: false + t.integer "weight", default: 10, null: false + t.boolean "contacts_editable_by_hsa", default: false, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "stalled_interval", default: 7, null: false + t.integer "match_prioritization_id", default: 6, null: false + t.boolean "should_cancel_other_matches", default: true, null: false + t.boolean "should_activate_match", default: true, null: false + t.boolean "should_prevent_multiple_matches_per_client", default: true, null: false + t.boolean "allow_multiple_active_matches", default: false, null: false + t.boolean "default_shelter_agency_contacts_from_project_client", default: false, null: false + t.integer "tag_id" + t.boolean "show_default_contact_types", default: true + t.boolean "send_notifications", default: true + t.string "housing_type" + t.boolean "send_notes_by_default", default: false, null: false + t.boolean "expects_roi", default: true + t.text "prioritized_client_columns" + t.boolean "show_referral_source", default: false + t.boolean "show_move_in_date", default: false + t.boolean "show_address_field", default: false + t.text "routes_parked_on_active_match" + t.text "routes_parked_on_successful_match" + t.index ["tag_id"], name: "index_match_routes_on_tag_id" + end + + create_table "messages", id: :serial, force: :cascade do |t| + t.string "from", null: false + t.string "subject", null: false + t.text "body", null: false + t.boolean "html", default: false, null: false + t.datetime "seen_at", precision: nil + t.datetime "sent_at", precision: nil + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "contact_id", null: false + end + + create_table "mitigation_reasons", force: :cascade do |t| + t.string "name" + t.boolean "active", default: true + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "name_quality_codes", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "neighborhood_interests", id: :serial, force: :cascade do |t| + t.integer "client_id" + t.integer "neighborhood_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "neighborhoods", id: :serial, force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "non_hmis_assessments", id: :serial, force: :cascade do |t| + t.integer "non_hmis_client_id" + t.string "type" + t.integer "assessment_score" + t.integer "days_homeless_in_the_last_three_years" + t.boolean "veteran", default: false + t.boolean "rrh_desired", default: false + t.boolean "youth_rrh_desired", default: false, null: false + t.text "rrh_assessment_contact_info" + t.boolean "income_maximization_assistance_requested", default: false, null: false + t.boolean "pending_subsidized_housing_placement", default: false + t.boolean "requires_wheelchair_accessibility", default: false, null: false + t.integer "required_number_of_bedrooms" + t.integer "required_minimum_occupancy" + t.boolean "requires_elevator_access", default: false, null: false + t.boolean "family_member", default: false, null: false + t.integer "calculated_chronic_homelessness" + t.json "neighborhood_interests", default: [] + t.float "income_total_monthly" + t.boolean "disabling_condition", default: false, null: false + t.boolean "physical_disability", default: false, null: false + t.boolean "developmental_disability", default: false, null: false + t.date "date_days_homeless_verified" + t.string "who_verified_days_homeless" + t.boolean "domestic_violence", default: false + t.boolean "interested_in_set_asides", default: false, null: false + t.string "set_asides_housing_status" + t.boolean "set_asides_resident" + t.string "shelter_name" + t.date "entry_date" + t.string "case_manager_contact_info" + t.string "phone_number" + t.boolean "have_tenant_voucher" + t.string "children_info" + t.boolean "studio_ok" + t.boolean "one_br_ok" + t.boolean "sro_ok" + t.boolean "fifty_five_plus" + t.boolean "sixty_two_plus" + t.string "voucher_agency" + t.boolean "interested_in_disabled_housing" + t.boolean "chronic_health_condition" + t.boolean "mental_health_problem" + t.boolean "substance_abuse_problem" + t.integer "vispdat_score" + t.integer "vispdat_priority_score" + t.datetime "imported_timestamp", precision: nil + t.datetime "deleted_at", precision: nil + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.boolean "ssvf_eligible", default: false + t.boolean "veteran_rrh_desired", default: false + t.boolean "rrh_th_desired", default: false + t.boolean "dv_rrh_desired", default: false + t.integer "income_total_annual", default: 0 + t.boolean "other_accessibility", default: false + t.boolean "disabled_housing", default: false + t.boolean "actively_homeless", default: false, null: false + t.integer "user_id" + t.boolean "evicted", default: false + t.boolean "documented_disability", default: false + t.boolean "health_prioritized", default: false + t.boolean "hiv_aids", default: false + t.boolean "is_currently_youth", default: false, null: false + t.boolean "older_than_65" + t.string "email_addresses" + t.string "mailing_address" + t.text "day_locations" + t.text "night_locations" + t.text "other_contact" + t.integer "household_size" + t.string "hoh_age" + t.string "current_living_situation" + t.string "pending_housing_placement_type" + t.string "pending_housing_placement_type_other" + t.integer "maximum_possible_monthly_rent" + t.string "possible_housing_situation" + t.string "possible_housing_situation_other" + t.string "no_rrh_desired_reason" + t.string "no_rrh_desired_reason_other" + t.jsonb "provider_agency_preference" + t.string "accessibility_other" + t.string "hiv_housing" + t.jsonb "affordable_housing" + t.jsonb "high_covid_risk" + t.jsonb "service_need_indicators" + t.integer "medical_care_last_six_months" + t.jsonb "intensive_needs" + t.string "intensive_needs_other" + t.jsonb "background_check_issues" + t.integer "additional_homeless_nights" + t.string "homeless_night_range" + t.text "notes" + t.string "veteran_status" + t.bigint "agency_id" + t.string "assessment_type" + t.jsonb "household_members" + t.date "financial_assistance_end_date" + t.boolean "wait_times_ack", default: false, null: false + t.boolean "not_matched_ack", default: false, null: false + t.boolean "matched_process_ack", default: false, null: false + t.boolean "response_time_ack", default: false, null: false + t.boolean "automatic_approval_ack", default: false, null: false + t.string "times_moved" + t.string "health_severity" + t.string "ever_experienced_dv" + t.string "eviction_risk" + t.string "need_daily_assistance" + t.string "any_income" + t.string "income_source" + t.string "positive_relationship" + t.string "legal_concerns" + t.string "healthcare_coverage" + t.string "childcare" + t.string "setting" + t.string "outreach_name" + t.string "denial_required" + t.date "locked_until" + t.string "assessment_name" + t.integer "hud_assessment_location" + t.integer "hud_assessment_type" + t.string "staff_name" + t.string "staff_email" + t.boolean "enrolled_in_es", default: false, null: false + t.boolean "enrolled_in_so", default: false, null: false + t.integer "additional_homeless_nights_sheltered", default: 0 + t.integer "homeless_nights_sheltered", default: 0 + t.integer "additional_homeless_nights_unsheltered", default: 0 + t.integer "homeless_nights_unsheltered", default: 0 + t.integer "tc_hat_assessment_level" + t.string "tc_hat_household_type" + t.text "ongoing_support_reason" + t.string "ongoing_support_housing_type" + t.jsonb "strengths" + t.jsonb "challenges" + t.boolean "lifetime_sex_offender", default: false + t.boolean "state_id", default: false + t.boolean "birth_certificate", default: false + t.boolean "social_security_card", default: false + t.boolean "has_tax_id", default: false + t.string "tax_id" + t.boolean "roommate_ok", default: false + t.boolean "full_time_employed", default: false + t.boolean "can_work_full_time", default: false + t.boolean "willing_to_work_full_time", default: false + t.string "why_not_working" + t.boolean "rrh_successful_exit", default: false + t.boolean "th_desired", default: false + t.boolean "drug_test", default: false + t.boolean "heavy_drug_use", default: false + t.boolean "sober", default: false + t.boolean "willing_case_management", default: false + t.boolean "employed_three_months", default: false + t.boolean "living_wage", default: false + t.boolean "site_case_management_required", default: false + t.jsonb "tc_hat_client_history" + t.boolean "open_case", default: false + t.boolean "foster_care", default: false + t.boolean "currently_fleeing", default: false + t.date "dv_date" + t.boolean "tc_hat_ed_visits", default: false + t.boolean "tc_hat_hospitalizations", default: false + t.boolean "sixty_plus", default: false + t.boolean "cirrhosis", default: false + t.boolean "end_stage_renal_disease", default: false + t.boolean "heat_stroke", default: false + t.boolean "blind", default: false + t.boolean "tri_morbidity", default: false + t.boolean "high_potential_for_victimization", default: false + t.boolean "self_harm", default: false + t.boolean "medical_condition", default: false + t.boolean "psychiatric_condition", default: false + t.jsonb "housing_preferences" + t.string "housing_preferences_other" + t.jsonb "housing_rejected_preferences" + t.integer "tc_hat_apartment" + t.integer "tc_hat_tiny_home" + t.integer "tc_hat_rv" + t.integer "tc_hat_house" + t.integer "tc_hat_mobile_home" + t.integer "tc_hat_total_housing_rank" + t.integer "days_homeless" + t.boolean "pregnancy_status", default: false + t.boolean "jail_caused_episode", default: false + t.boolean "income_caused_episode", default: false + t.boolean "ipv_caused_episode", default: false + t.boolean "violence_caused_episode", default: false + t.boolean "chronic_health_caused_episode", default: false + t.boolean "acute_health_caused_episode", default: false + t.boolean "idd_caused_episode", default: false + t.boolean "pregnant", default: false + t.boolean "pregnant_under_28_weeks", default: false + t.boolean "ongoing_case_management_required", default: false + t.boolean "self_reported_days_verified", default: false + t.boolean "tc_hat_single_parent_child_over_ten", default: false + t.boolean "tc_hat_legal_custody" + t.boolean "tc_hat_will_gain_legal_custody" + t.boolean "housing_barrier", default: false + t.boolean "service_need", default: false + t.text "agency_name" + t.text "agency_day_contact_info" + t.text "agency_night_contact_info" + t.boolean "pregnant_or_parent" + t.text "partner_warehouse_id" + t.text "partner_name" + t.boolean "share_information_permission" + t.boolean "federal_benefits" + t.index ["agency_id"], name: "index_non_hmis_assessments_on_agency_id" + t.index ["user_id"], name: "index_non_hmis_assessments_on_user_id" + end + + create_table "non_hmis_clients", id: :serial, force: :cascade do |t| + t.string "client_identifier" + t.integer "assessment_score" + t.string "deprecated_agency" + t.string "first_name" + t.string "last_name" + t.jsonb "active_cohort_ids" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "identified" + t.date "date_of_birth" + t.string "ssn" + t.integer "days_homeless_in_the_last_three_years" + t.boolean "veteran", default: false, null: false + t.boolean "rrh_desired", default: false, null: false + t.boolean "youth_rrh_desired", default: false, null: false + t.text "rrh_assessment_contact_info" + t.boolean "income_maximization_assistance_requested", default: false, null: false + t.boolean "pending_subsidized_housing_placement", default: false, null: false + t.boolean "full_release_on_file", default: false, null: false + t.boolean "requires_wheelchair_accessibility", default: false, null: false + t.integer "required_number_of_bedrooms", default: 1 + t.integer "required_minimum_occupancy", default: 1 + t.boolean "requires_elevator_access", default: false, null: false + t.boolean "family_member", default: false, null: false + t.string "middle_name" + t.integer "calculated_chronic_homelessness" + t.string "type" + t.boolean "available", default: true, null: false + t.json "neighborhood_interests", default: [] + t.float "income_total_monthly" + t.boolean "disabling_condition", default: false + t.boolean "physical_disability", default: false + t.boolean "developmental_disability", default: false + t.date "date_days_homeless_verified" + t.string "who_verified_days_homeless" + t.boolean "domestic_violence", default: false, null: false + t.boolean "interested_in_set_asides", default: false + t.jsonb "tags" + t.datetime "imported_timestamp", precision: nil + t.string "set_asides_housing_status" + t.boolean "set_asides_resident" + t.string "shelter_name" + t.date "entry_date" + t.string "case_manager_contact_info" + t.string "phone_number" + t.string "email" + t.boolean "have_tenant_voucher" + t.string "children_info" + t.boolean "studio_ok" + t.boolean "one_br_ok" + t.boolean "sro_ok" + t.boolean "fifty_five_plus" + t.boolean "sixty_two_plus" + t.integer "warehouse_client_id" + t.string "voucher_agency" + t.boolean "interested_in_disabled_housing" + t.boolean "chronic_health_condition" + t.boolean "mental_health_problem" + t.boolean "substance_abuse_problem" + t.integer "agency_id" + t.integer "contact_id" + t.integer "vispdat_score", default: 0 + t.integer "vispdat_priority_score", default: 0 + t.boolean "actively_homeless", default: false, null: false + t.boolean "limited_release_on_file", default: false, null: false + t.boolean "active_client", default: true, null: false + t.boolean "eligible_for_matching", default: true, null: false + t.datetime "available_date", precision: nil + t.string "available_reason" + t.boolean "is_currently_youth", default: false, null: false + t.datetime "assessed_at", precision: nil + t.boolean "health_prioritized", default: false + t.boolean "hiv_aids", default: false + t.boolean "older_than_65" + t.boolean "ssn_refused", default: false + t.integer "ethnicity" + t.integer "days_homeless" + t.boolean "sixty_plus" + t.boolean "pregnancy_status", default: false + t.boolean "pregnant_under_28_weeks", default: false + t.boolean "am_ind_ak_native", default: false + t.boolean "asian", default: false + t.boolean "black_af_american", default: false + t.boolean "native_hi_pacific", default: false + t.boolean "white", default: false + t.boolean "female", default: false + t.boolean "male", default: false + t.boolean "no_single_gender", default: false + t.boolean "transgender", default: false + t.boolean "questioning", default: false + t.boolean "federal_benefits" + t.index ["deleted_at"], name: "index_non_hmis_clients_on_deleted_at" + end + + create_table "notifications", id: :serial, force: :cascade do |t| + t.string "type" + t.string "code" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "client_opportunity_match_id" + t.integer "recipient_id" + t.datetime "expires_at", precision: nil + t.boolean "include_content", default: true + end + + create_table "opportunities", id: :serial, force: :cascade do |t| + t.string "name" + t.boolean "available", default: false, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.integer "unit_id" + t.boolean "available_candidate", default: true + t.integer "voucher_id" + t.float "matchability" + t.boolean "success", default: false + t.index ["deleted_at"], name: "index_opportunities_on_deleted_at", where: "(deleted_at IS NULL)" + t.index ["unit_id"], name: "index_opportunities_on_unit_id" + t.index ["voucher_id"], name: "index_opportunities_on_voucher_id" + end + + create_table "opportunity_contacts", id: :serial, force: :cascade do |t| + t.integer "opportunity_id", null: false + t.integer "contact_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "housing_subsidy_admin", default: false, null: false + t.index ["contact_id"], name: "index_opportunity_contacts_on_contact_id" + t.index ["deleted_at"], name: "index_opportunity_contacts_on_deleted_at" + t.index ["opportunity_id"], name: "index_opportunity_contacts_on_opportunity_id" + end + + create_table "opportunity_properties", id: :serial, force: :cascade do |t| + t.integer "opportunity_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["opportunity_id"], name: "index_opportunity_properties_on_opportunity_id" + end + + create_table "outreach_histories", force: :cascade do |t| + t.bigint "non_hmis_client_id", null: false + t.bigint "user_id", null: false + t.string "outreach_name" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["non_hmis_client_id"], name: "index_outreach_histories_on_non_hmis_client_id" + t.index ["user_id"], name: "index_outreach_histories_on_user_id" + end + + create_table "physical_disabilities", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "program_contacts", id: :serial, force: :cascade do |t| + t.integer "program_id", null: false + t.integer "contact_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "dnd_staff", default: false, null: false + t.boolean "housing_subsidy_admin", default: false, null: false + t.boolean "client", default: false, null: false + t.boolean "housing_search_worker", default: false, null: false + t.boolean "shelter_agency", default: false, null: false + t.boolean "ssp", default: false, null: false + t.boolean "hsp", default: false, null: false + t.boolean "do", default: false, null: false + t.index ["contact_id"], name: "index_program_contacts_on_contact_id" + t.index ["program_id"], name: "index_program_contacts_on_program_id" + end + + create_table "program_services", id: :serial, force: :cascade do |t| + t.integer "program_id" + t.integer "service_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["deleted_at"], name: "index_program_services_on_deleted_at" + t.index ["program_id"], name: "index_program_services_on_program_id" + t.index ["service_id"], name: "index_program_services_on_service_id" + end + + create_table "programs", id: :serial, force: :cascade do |t| + t.string "name" + t.string "contract_start_date" + t.integer "funding_source_id" + t.integer "subgrantee_id" + t.integer "contact_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "confidential", default: false, null: false + t.integer "match_route_id", default: 1 + t.text "description" + t.index ["contact_id"], name: "index_programs_on_contact_id" + t.index ["deleted_at"], name: "index_programs_on_deleted_at" + t.index ["funding_source_id"], name: "index_programs_on_funding_source_id" + t.index ["subgrantee_id"], name: "index_programs_on_subgrantee_id" + end + + create_table "project_clients", id: :serial, force: :cascade do |t| + t.string "first_name" + t.string "last_name" + t.string "ssn" + t.date "date_of_birth" + t.string "veteran_status" + t.string "substance_abuse_problem" + t.date "entry_date" + t.date "exit_date" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.uuid "clientguid" + t.string "middle_name" + t.integer "ssn_quality_code" + t.integer "dob_quality_code" + t.string "primary_race" + t.string "secondary_race" + t.integer "gender" + t.integer "ethnicity" + t.integer "disabling_condition" + t.integer "hud_chronic_homelessness" + t.integer "calculated_chronic_homelessness" + t.integer "chronic_health_condition" + t.integer "physical_disability" + t.integer "hivaids_status" + t.integer "mental_health_problem" + t.integer "domestic_violence" + t.integer "discharge_type" + t.integer "developmental_disability" + t.boolean "us_citizen", default: false, null: false + t.boolean "asylee", default: false, null: false + t.boolean "lifetime_sex_offender", default: false, null: false + t.boolean "meth_production_conviction", default: false, null: false + t.integer "id_in_data_source" + t.date "calculated_first_homeless_night" + t.date "calculated_last_homeless_night" + t.datetime "source_last_changed", precision: nil + t.integer "data_source_id" + t.string "data_source_id_column_name" + t.integer "client_id" + t.string "homephone" + t.string "cellphone" + t.string "workphone" + t.string "pager" + t.string "email" + t.boolean "dmh_eligible", default: false, null: false + t.boolean "va_eligible", default: false, null: false + t.boolean "hues_eligible", default: false, null: false + t.datetime "disability_verified_on", precision: nil + t.datetime "housing_assistance_network_released_on", precision: nil + t.boolean "sync_with_cas", default: false, null: false + t.float "income_total_monthly" + t.datetime "income_total_monthly_last_collected", precision: nil + t.boolean "hiv_positive", default: false, null: false + t.string "housing_release_status" + t.boolean "needs_update", default: false, null: false + t.integer "vispdat_score" + t.boolean "ineligible_immigrant", default: false, null: false + t.boolean "family_member", default: false, null: false + t.boolean "child_in_household", default: false, null: false + t.integer "days_homeless" + t.boolean "ha_eligible", default: false, null: false + t.integer "days_homeless_in_last_three_years" + t.integer "vispdat_length_homeless_in_days", default: 0, null: false + t.boolean "cspech_eligible", default: false + t.string "alternate_names" + t.boolean "congregate_housing", default: false + t.boolean "sober_housing", default: false + t.jsonb "enrolled_project_ids" + t.jsonb "active_cohort_ids" + t.integer "vispdat_priority_score", default: 0 + t.string "client_identifier" + t.integer "assessment_score", default: 0, null: false + t.boolean "ssvf_eligible", default: false, null: false + t.boolean "rrh_desired", default: false, null: false + t.boolean "youth_rrh_desired", default: false, null: false + t.string "rrh_assessment_contact_info" + t.datetime "rrh_assessment_collected_at", precision: nil + t.boolean "enrolled_in_th", default: false, null: false + t.boolean "enrolled_in_es", default: false, null: false + t.boolean "enrolled_in_sh", default: false, null: false + t.boolean "enrolled_in_so", default: false, null: false + t.integer "days_literally_homeless_in_last_three_years", default: 0 + t.boolean "requires_wheelchair_accessibility", default: false + t.integer "required_number_of_bedrooms", default: 1 + t.integer "required_minimum_occupancy", default: 1 + t.boolean "requires_elevator_access", default: false + t.jsonb "neighborhood_interests", default: [], null: false + t.date "date_days_homeless_verified" + t.string "who_verified_days_homeless" + t.boolean "interested_in_set_asides", default: false + t.jsonb "default_shelter_agency_contacts" + t.jsonb "tags" + t.string "case_manager_contact_info" + t.string "non_hmis_client_identifier" + t.boolean "vash_eligible" + t.boolean "pregnancy_status", default: false + t.boolean "income_maximization_assistance_requested", default: false + t.boolean "pending_subsidized_housing_placement", default: false + t.boolean "rrh_th_desired", default: false + t.boolean "sro_ok", default: false + t.boolean "evicted", default: false + t.boolean "dv_rrh_desired", default: false + t.boolean "health_prioritized", default: false + t.boolean "is_currently_youth", default: false, null: false + t.boolean "older_than_65" + t.date "holds_voucher_on" + t.string "assessment_name" + t.date "financial_assistance_end_date" + t.boolean "enrolled_in_rrh", default: false + t.boolean "enrolled_in_psh", default: false + t.boolean "enrolled_in_ph", default: false + t.string "address" + t.boolean "majority_sheltered" + t.date "tie_breaker_date" + t.jsonb "strengths", default: [] + t.jsonb "challenges", default: [] + t.boolean "foster_care", default: false + t.boolean "open_case", default: false + t.boolean "housing_for_formerly_homeless", default: false + t.boolean "drug_test", default: false + t.boolean "heavy_drug_use", default: false + t.boolean "sober", default: false + t.boolean "willing_case_management", default: false + t.boolean "employed_three_months", default: false + t.boolean "living_wage", default: false + t.boolean "need_daily_assistance", default: false + t.boolean "full_time_employed", default: false + t.boolean "can_work_full_time", default: false + t.boolean "willing_to_work_full_time", default: false + t.boolean "rrh_successful_exit", default: false + t.boolean "th_desired", default: false + t.boolean "site_case_management_required", default: false + t.boolean "currently_fleeing", default: false + t.date "dv_date" + t.string "assessor_first_name" + t.string "assessor_last_name" + t.string "assessor_email" + t.string "assessor_phone" + t.integer "hmis_days_homeless_all_time" + t.integer "hmis_days_homeless_last_three_years" + t.integer "match_group" + t.boolean "force_remove_unavailable_fors", default: false + t.boolean "encampment_decomissioned", default: false + t.boolean "pregnant_under_28_weeks", default: false + t.boolean "am_ind_ak_native", default: false + t.boolean "asian", default: false + t.boolean "black_af_american", default: false + t.boolean "native_hi_pacific", default: false + t.boolean "white", default: false + t.boolean "female", default: false + t.boolean "male", default: false + t.boolean "no_single_gender", default: false + t.boolean "transgender", default: false + t.boolean "questioning", default: false + t.boolean "ongoing_case_management_required", default: false + t.jsonb "file_tags", default: {} + t.boolean "housing_barrier", default: false + t.boolean "service_need", default: false + t.integer "additional_homeless_nights_sheltered", default: 0 + t.integer "additional_homeless_nights_unsheltered", default: 0 + t.integer "total_homeless_nights_unsheltered", default: 0 + t.integer "calculated_homeless_nights_sheltered", default: 0 + t.integer "calculated_homeless_nights_unsheltered", default: 0 + t.integer "total_homeless_nights_sheltered", default: 0 + t.boolean "enrolled_in_ph_pre_move_in", default: false, null: false + t.boolean "enrolled_in_psh_pre_move_in", default: false, null: false + t.boolean "enrolled_in_rrh_pre_move_in", default: false, null: false + t.jsonb "ongoing_es_enrollments" + t.jsonb "ongoing_so_enrollments" + t.jsonb "last_seen_projects" + t.boolean "federal_benefits" + t.index ["calculated_chronic_homelessness"], name: "index_project_clients_on_calculated_chronic_homelessness" + t.index ["client_id"], name: "index_project_clients_on_client_id" + t.index ["date_of_birth"], name: "index_project_clients_on_date_of_birth" + t.index ["source_last_changed"], name: "index_project_clients_on_source_last_changed" + end + + create_table "project_programs", id: :serial, force: :cascade do |t| + t.string "id_in_data_source" + t.string "program_name" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "data_source_id" + t.string "data_source_id_column_name" + end + + create_table "reissue_requests", id: :serial, force: :cascade do |t| + t.integer "notification_id" + t.integer "reissued_by" + t.datetime "reissued_at", precision: nil + t.datetime "deleted_at", precision: nil + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "request_sent_at", precision: nil + t.index ["deleted_at"], name: "index_reissue_requests_on_deleted_at" + t.index ["notification_id"], name: "index_reissue_requests_on_notification_id" + t.index ["reissued_by"], name: "index_reissue_requests_on_reissued_by" + end + + create_table "rejected_matches", id: :serial, force: :cascade do |t| + t.integer "client_id", null: false + t.integer "opportunity_id", null: false + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.index ["client_id"], name: "index_rejected_matches_on_client_id" + t.index ["opportunity_id"], name: "index_rejected_matches_on_opportunity_id" + end + + create_table "report_definitions", force: :cascade do |t| + t.string "report_group" + t.string "url" + t.string "name", null: false + t.string "description" + t.integer "weight", default: 0, null: false + t.boolean "enabled", default: true, null: false + t.boolean "limitable", default: true + end + + create_table "reporting_decisions", force: :cascade do |t| + t.integer "client_id" + t.integer "match_id", null: false + t.integer "decision_id", null: false + t.integer "decision_order", null: false + t.string "match_step", null: false + t.string "decision_status", null: false + t.boolean "current_step", default: false, null: false + t.boolean "active_match", default: false, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "elapsed_days", null: false + t.datetime "client_last_seen_date", precision: nil + t.datetime "criminal_hearing_date", precision: nil + t.string "decline_reason" + t.string "not_working_with_client_reason" + t.string "administrative_cancel_reason" + t.boolean "client_spoken_with_services_agency" + t.boolean "cori_release_form_submitted" + t.datetime "match_started_at", precision: nil + t.string "program_type" + t.json "shelter_agency_contacts" + t.json "hsa_contacts" + t.json "ssp_contacts" + t.json "admin_contacts" + t.json "client_contacts" + t.json "hsp_contacts" + t.string "program_name", null: false + t.string "sub_program_name", null: false + t.string "terminal_status" + t.string "match_route", null: false + t.integer "cas_client_id", null: false + t.date "client_move_in_date" + t.string "source_data_source" + t.string "event_contact" + t.string "event_contact_agency" + t.integer "vacancy_id", null: false + t.string "housing_type" + t.boolean "ineligible_in_warehouse", default: false, null: false + t.string "actor_type" + t.boolean "confidential", default: false + t.string "current_status" + t.string "step_tag" + t.index ["client_id", "match_id", "decision_id"], name: "index_reporting_decisions_c_m_d", unique: true + end + + create_table "requirements", id: :serial, force: :cascade do |t| + t.integer "rule_id" + t.integer "requirer_id" + t.string "requirer_type" + t.boolean "positive" + t.datetime "deleted_at", precision: nil + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.string "variable" + t.index ["deleted_at"], name: "index_requirements_on_deleted_at" + t.index ["requirer_type", "requirer_id"], name: "index_requirements_on_requirer_type_and_requirer_id" + t.index ["rule_id"], name: "index_requirements_on_rule_id" + end + + create_table "roles", id: :serial, force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.boolean "can_view_all_clients", default: false + t.boolean "can_edit_all_clients", default: false + t.boolean "can_participate_in_matches", default: false + t.boolean "can_view_all_matches", default: false + t.boolean "can_see_alternate_matches", default: false + t.boolean "can_edit_match_contacts", default: false + t.boolean "can_approve_matches", default: false + t.boolean "can_reject_matches", default: false + t.boolean "can_act_on_behalf_of_match_contacts", default: false + t.boolean "can_view_reports", default: false + t.boolean "can_edit_roles", default: false + t.boolean "can_edit_users", default: false + t.boolean "can_view_full_ssn", default: false + t.boolean "can_view_full_dob", default: false + t.boolean "can_view_buildings", default: false + t.boolean "can_edit_buildings", default: false + t.boolean "can_view_funding_sources", default: false + t.boolean "can_edit_funding_sources", default: false + t.boolean "can_view_subgrantees", default: false + t.boolean "can_edit_subgrantees", default: false + t.boolean "can_view_vouchers", default: false + t.boolean "can_edit_vouchers", default: false + t.boolean "can_view_programs", default: false + t.boolean "can_edit_programs", default: false + t.boolean "can_view_opportunities", default: false + t.boolean "can_edit_opportunities", default: false + t.boolean "can_reissue_notifications", default: false + t.boolean "can_view_units", default: false + t.boolean "can_edit_units", default: false + t.boolean "can_add_vacancies", default: false + t.boolean "can_view_contacts", default: false + t.boolean "can_edit_contacts", default: false + t.boolean "can_view_rule_list", default: false + t.boolean "can_edit_rule_list", default: false + t.boolean "can_view_available_services", default: false + t.boolean "can_edit_available_services", default: false + t.boolean "can_assign_services", default: false + t.boolean "can_assign_requirements", default: false + t.boolean "can_view_dmh_eligibility", default: false + t.boolean "can_view_va_eligibility", default: false, null: false + t.boolean "can_view_hues_eligibility", default: false, null: false + t.boolean "can_become_other_users", default: false + t.boolean "can_view_client_confidentiality", default: false, null: false + t.boolean "can_view_hiv_positive_eligibility", default: false + t.boolean "can_view_own_closed_matches", default: false + t.boolean "can_edit_translations", default: false + t.boolean "can_view_vspdats", default: false + t.boolean "can_manage_config", default: false + t.boolean "can_create_overall_note", default: false + t.boolean "can_enter_deidentified_clients", default: false + t.boolean "can_manage_deidentified_clients", default: false + t.boolean "can_add_cohorts_to_deidentified_clients", default: false + t.boolean "can_delete_client_notes", default: false + t.boolean "can_enter_identified_clients", default: false + t.boolean "can_manage_identified_clients", default: false + t.boolean "can_add_cohorts_to_identified_clients", default: false + t.boolean "can_manage_neighborhoods", default: false + t.boolean "can_view_assigned_programs", default: false + t.boolean "can_edit_assigned_programs", default: false + t.boolean "can_export_deidentified_clients", default: false + t.boolean "can_export_identified_clients", default: false + t.boolean "can_manage_tags", default: false + t.boolean "can_manage_imported_clients", default: false + t.boolean "can_edit_clients_based_on_rules", default: false + t.boolean "can_send_notes_via_email", default: false + t.boolean "can_upload_deidentified_clients", default: false + t.boolean "can_delete_matches", default: false + t.boolean "can_reopen_matches", default: false + t.boolean "can_see_all_alternate_matches", default: false + t.boolean "can_edit_help", default: false + t.boolean "can_audit_users", default: false + t.boolean "can_view_all_covid_pathways", default: false + t.boolean "can_manage_sessions", default: false + t.boolean "can_edit_voucher_rules", default: false + t.boolean "can_manage_all_deidentified_clients", default: false + t.boolean "can_manage_all_identified_clients", default: false + t.index ["name"], name: "index_roles_on_name" + end + + create_table "rules", id: :serial, force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.string "type" + t.string "verb" + t.string "alternate_name" + t.index ["deleted_at"], name: "index_rules_on_deleted_at" + end + + create_table "service_rules", id: :serial, force: :cascade do |t| + t.integer "rule_id" + t.integer "service_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["deleted_at"], name: "index_service_rules_on_deleted_at" + t.index ["rule_id"], name: "index_service_rules_on_rule_id" + t.index ["service_id"], name: "index_service_rules_on_service_id" + end + + create_table "services", id: :serial, force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + end + + create_table "sessions", id: :serial, force: :cascade do |t| + t.string "session_id", null: false + t.text "data" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.index ["session_id"], name: "index_sessions_on_session_id", unique: true + t.index ["updated_at"], name: "index_sessions_on_updated_at" + end + + create_table "shelter_histories", force: :cascade do |t| + t.bigint "non_hmis_client_id", null: false + t.bigint "user_id", null: false + t.string "shelter_name" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["non_hmis_client_id"], name: "index_shelter_histories_on_non_hmis_client_id" + t.index ["user_id"], name: "index_shelter_histories_on_user_id" + end + + create_table "social_security_number_quality_codes", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "stalled_responses", id: :serial, force: :cascade do |t| + t.boolean "client_engaging", default: true, null: false + t.string "reason", null: false + t.string "decision_type", null: false + t.boolean "requires_note", default: false, null: false + t.boolean "active", default: true, null: false + t.integer "weight", default: 0, null: false + end + + create_table "sub_program_contacts", force: :cascade do |t| + t.bigint "sub_program_id", null: false + t.bigint "contact_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.boolean "dnd_staff", default: false, null: false + t.boolean "housing_subsidy_admin", default: false, null: false + t.boolean "client", default: false, null: false + t.boolean "housing_search_worker", default: false, null: false + t.boolean "shelter_agency", default: false, null: false + t.boolean "ssp", default: false, null: false + t.boolean "hsp", default: false, null: false + t.boolean "do", default: false, null: false + t.index ["contact_id"], name: "index_sub_program_contacts_on_contact_id" + t.index ["sub_program_id"], name: "index_sub_program_contacts_on_sub_program_id" + end + + create_table "sub_programs", id: :serial, force: :cascade do |t| + t.string "program_type" + t.integer "program_id" + t.integer "building_id" + t.integer "subgrantee_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.integer "matched", default: 0 + t.integer "in_progress", default: 0 + t.integer "vacancies", default: 0 + t.string "name" + t.integer "sub_contractor_id" + t.integer "hsa_id" + t.integer "voucher_count", default: 0 + t.boolean "confidential", default: false, null: false + t.text "eligibility_requirement_notes" + t.boolean "closed", default: false + t.integer "event" + t.boolean "weighting_rules_active", default: true + t.boolean "cori_hearing_required" + t.index ["building_id"], name: "index_sub_programs_on_building_id" + t.index ["deleted_at"], name: "index_sub_programs_on_deleted_at" + t.index ["program_id"], name: "index_sub_programs_on_program_id" + t.index ["subgrantee_id"], name: "index_sub_programs_on_subgrantee_id" + end + + create_table "subgrantee_contacts", id: :serial, force: :cascade do |t| + t.integer "subgrantee_id", null: false + t.integer "contact_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["contact_id"], name: "index_subgrantee_contacts_on_contact_id" + t.index ["deleted_at"], name: "index_subgrantee_contacts_on_deleted_at" + t.index ["subgrantee_id"], name: "index_subgrantee_contacts_on_subgrantee_id" + end + + create_table "subgrantee_services", id: :serial, force: :cascade do |t| + t.integer "subgrantee_id" + t.integer "service_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["deleted_at"], name: "index_subgrantee_services_on_deleted_at" + t.index ["service_id"], name: "index_subgrantee_services_on_service_id" + t.index ["subgrantee_id"], name: "index_subgrantee_services_on_subgrantee_id" + end + + create_table "subgrantees", id: :serial, force: :cascade do |t| + t.string "name" + t.string "abbreviation" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "id_in_data_source" + t.integer "disabled" + t.integer "data_source_id" + t.string "data_source_id_column_name" + t.datetime "deleted_at", precision: nil + end + + create_table "tags", id: :serial, force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.datetime "deleted_at", precision: nil + t.boolean "rrh_assessment_trigger", default: false, null: false + end + + create_table "translation_keys", id: :serial, force: :cascade do |t| + t.string "key", default: "", null: false + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.index ["key"], name: "index_translation_keys_on_key" + end + + create_table "translation_texts", id: :serial, force: :cascade do |t| + t.text "text" + t.string "locale" + t.integer "translation_key_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["translation_key_id"], name: "index_translation_texts_on_translation_key_id" + end + + create_table "translations", force: :cascade do |t| + t.string "key" + t.string "text" + t.boolean "common", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["key"], name: "index_translations_on_key" + end + + create_table "unavailable_as_candidate_fors", id: :serial, force: :cascade do |t| + t.integer "client_id", null: false + t.string "match_route_type", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "expires_at", precision: nil + t.bigint "user_id" + t.bigint "match_id" + t.string "reason" + t.index ["client_id"], name: "index_unavailable_as_candidate_fors_on_client_id" + t.index ["match_id"], name: "index_unavailable_as_candidate_fors_on_match_id" + t.index ["match_route_type"], name: "index_unavailable_as_candidate_fors_on_match_route_type" + t.index ["user_id"], name: "index_unavailable_as_candidate_fors_on_user_id" + end + + create_table "units", id: :serial, force: :cascade do |t| + t.integer "id_in_data_source" + t.string "name" + t.boolean "available" + t.string "target_population_a" + t.string "target_population_b" + t.boolean "mc_kinney_vento" + t.integer "chronic" + t.integer "veteran" + t.integer "adult_only" + t.integer "family" + t.integer "child_only" + t.integer "building_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.integer "data_source_id" + t.string "data_source_id_column_name" + t.boolean "elevator_accessible", default: false, null: false + t.boolean "active", default: true, null: false + t.index ["building_id"], name: "index_units_on_building_id" + t.index ["deleted_at"], name: "index_units_on_deleted_at", where: "(deleted_at IS NULL)" + t.index ["id_in_data_source"], name: "index_units_on_id_in_data_source" + end + + create_table "user_roles", id: :serial, force: :cascade do |t| + t.integer "role_id" + t.integer "user_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["role_id"], name: "index_user_roles_on_role_id" + t.index ["user_id"], name: "index_user_roles_on_user_id" + end + + create_table "users", id: :serial, force: :cascade do |t| + t.string "email", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at", precision: nil + t.datetime "remember_created_at", precision: nil + t.integer "sign_in_count", default: 0, null: false + t.datetime "current_sign_in_at", precision: nil + t.datetime "last_sign_in_at", precision: nil + t.inet "current_sign_in_ip" + t.inet "last_sign_in_ip" + t.string "confirmation_token" + t.datetime "confirmed_at", precision: nil + t.datetime "confirmation_sent_at", precision: nil + t.string "unconfirmed_email" + t.integer "failed_attempts", default: 0, null: false + t.string "unlock_token" + t.datetime "locked_at", precision: nil + t.string "invitation_token" + t.datetime "invitation_created_at", precision: nil + t.datetime "invitation_sent_at", precision: nil + t.datetime "invitation_accepted_at", precision: nil + t.integer "invitation_limit" + t.integer "invited_by_id" + t.string "invited_by_type" + t.integer "invitations_count", default: 0 + t.boolean "receive_initial_notification", default: false + t.string "first_name" + t.string "last_name" + t.string "email_schedule", default: "immediate", null: false + t.boolean "active", default: true, null: false + t.string "deprecated_agency" + t.integer "agency_id" + t.boolean "exclude_from_directory", default: false + t.boolean "exclude_phone_from_directory", default: false + t.string "unique_session_id" + t.boolean "receive_weekly_match_summary_email", default: true + t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true + t.index ["invitations_count"], name: "index_users_on_invitations_count" + t.index ["invited_by_id"], name: "index_users_on_invited_by_id" + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true + end + + create_table "versions", id: :serial, force: :cascade do |t| + t.string "item_type", null: false + t.integer "item_id", null: false + t.string "event", null: false + t.string "whodunnit" + t.text "object" + t.datetime "created_at", precision: nil + t.integer "user_id" + t.string "session_id" + t.string "request_id" + t.string "notification_code" + t.integer "referenced_user_id" + t.string "referenced_entity_name" + t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" + end + + create_table "veteran_statuses", id: :serial, force: :cascade do |t| + t.integer "numeric" + t.string "text" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "vouchers", id: :serial, force: :cascade do |t| + t.boolean "available" + t.date "date_available" + t.integer "sub_program_id" + t.integer "unit_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.integer "user_id" + t.datetime "made_available_at", precision: nil + t.datetime "archived_at", precision: nil + t.index ["deleted_at"], name: "index_vouchers_on_deleted_at" + t.index ["sub_program_id"], name: "index_vouchers_on_sub_program_id" + t.index ["unit_id"], name: "index_vouchers_on_unit_id" + end + + create_table "weighting_rules", force: :cascade do |t| + t.bigint "route_id" + t.bigint "requirement_id" + t.integer "applied_to", default: 0 + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.datetime "deleted_at", precision: nil + t.index ["requirement_id"], name: "index_weighting_rules_on_requirement_id" + t.index ["route_id"], name: "index_weighting_rules_on_route_id" + end + + add_foreign_key "non_hmis_assessments", "users", name: "non_hmis_assessments_user_id_fkey" + add_foreign_key "opportunities", "vouchers", name: "opportunities_voucher_id_fkey" + add_foreign_key "programs", "contacts", name: "programs_contact_id_fkey" + add_foreign_key "programs", "funding_sources", name: "programs_funding_source_id_fkey" + add_foreign_key "programs", "subgrantees", name: "programs_subgrantee_id_fkey" + add_foreign_key "reissue_requests", "notifications", name: "reissue_requests_notification_id_fkey" + add_foreign_key "reissue_requests", "users", column: "reissued_by", name: "reissue_requests_reissued_by_fkey" + add_foreign_key "sub_programs", "buildings", name: "sub_programs_building_id_fkey" + add_foreign_key "sub_programs", "programs", name: "sub_programs_program_id_fkey" + add_foreign_key "sub_programs", "subgrantees", column: "hsa_id", name: "sub_programs_hsa_id_fkey" + add_foreign_key "sub_programs", "subgrantees", column: "sub_contractor_id", name: "sub_programs_sub_contractor_id_fkey" + add_foreign_key "sub_programs", "subgrantees", name: "sub_programs_subgrantee_id_fkey" + add_foreign_key "user_roles", "roles", name: "user_roles_role_id_fkey", on_delete: :cascade + add_foreign_key "user_roles", "users", name: "user_roles_user_id_fkey", on_delete: :cascade + add_foreign_key "vouchers", "sub_programs", name: "vouchers_sub_program_id_fkey" + add_foreign_key "vouchers", "units", name: "vouchers_unit_id_fkey" + add_foreign_key "vouchers", "users", name: "vouchers_user_id_fkey" +end diff --git a/db/structure.sql b/db/structure.sql index 48512e0de..9f220114c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -552,7 +552,11 @@ CREATE TABLE public.clients ( ongoing_es_enrollments jsonb, ongoing_so_enrollments jsonb, last_seen_projects jsonb, - federal_benefits boolean + federal_benefits boolean, + psh_required character varying DEFAULT 'maybe'::character varying, + requires_vision_or_hearing_accessibility boolean DEFAULT false, + household_dv_survivor boolean, + disqualified_for_state_assistance boolean ); @@ -2180,7 +2184,17 @@ CREATE TABLE public.non_hmis_assessments ( partner_warehouse_id text, partner_name text, share_information_permission boolean, - federal_benefits boolean + federal_benefits boolean, + psh_required character varying DEFAULT 'maybe'::character varying, + requires_vision_or_hearing_accessibility boolean DEFAULT false, + schools character varying, + schools_contact_info text, + disqualified_for_state_assistance boolean DEFAULT false, + disqualified_for_state_assistance_reasons character varying, + calculated_first_homeless_night date, + household_dv_survivor boolean, + background_check_issues_disability_or_substance_use boolean DEFAULT false NOT NULL, + eviction_history character varying ); @@ -2302,7 +2316,8 @@ CREATE TABLE public.non_hmis_clients ( transgender boolean DEFAULT false, questioning boolean DEFAULT false, federal_benefits boolean, - enrolled_project_ids jsonb + enrolled_project_ids jsonb, + psh_required character varying DEFAULT 'maybe'::character varying ); @@ -2827,7 +2842,11 @@ CREATE TABLE public.project_clients ( ongoing_es_enrollments jsonb, ongoing_so_enrollments jsonb, last_seen_projects jsonb, - federal_benefits boolean + federal_benefits boolean, + psh_required character varying DEFAULT 'maybe'::character varying, + requires_vision_or_hearing_accessibility boolean DEFAULT false, + household_dv_survivor boolean, + disqualified_for_state_assistance boolean ); @@ -7279,6 +7298,12 @@ INSERT INTO "schema_migrations" (version) VALUES ('20241010194153'), ('20241105161611'), ('20241202135547'), -('20241202135711'); +('20241202135711'), +('20250102144339'), +('20250107142055'), +('20250113131532'), +('20250113184426'), +('20250118142954'), +('20250206162624'); diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 99411303e..f80869be9 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -89,6 +89,8 @@ services: dj-metrics: <<: *backend + profiles: + - manual entrypoint: /usr/bin/entrypoint.dj-metrics.sh ports: # visit localhost:34534/metrics diff --git a/spec/factories/match_priorities.rb b/spec/factories/match_priorities.rb index 0b4b2d5ed..00883e3c7 100644 --- a/spec/factories/match_priorities.rb +++ b/spec/factories/match_priorities.rb @@ -32,4 +32,13 @@ factory :priority_match_group_disability, class: 'MatchPrioritization::MatchGroup' do active { true } end + factory :priority_family_psh, class: 'MatchPrioritization::FamilyPsh' do + active { true } + end + factory :priority_rrh_and_th, class: 'MatchPrioritization::RrhAndTh' do + active { true } + end + factory :priority_low_income_subsidies, class: 'MatchPrioritization::LowIncomeSubsidies' do + active { true } + end end diff --git a/spec/factories/rules.rb b/spec/factories/rules.rb index dc62fe4e2..e46a14104 100644 --- a/spec/factories/rules.rb +++ b/spec/factories/rules.rb @@ -527,4 +527,8 @@ name { 'a housing barrier' } verb { 'have' } end + factory :psh_required, class: 'Rules::PshRequired' do + name { 'in need of Permanent Supportive Housing' } + verb { 'be' } + end end diff --git a/spec/models/client_spec.rb b/spec/models/client_spec.rb index 2a3d2e426..5d6f94746 100644 --- a/spec/models/client_spec.rb +++ b/spec/models/client_spec.rb @@ -167,6 +167,177 @@ expect(prioritized_clients).to eq(ordered_clients) end end + + context 'when prioritized by FamilyPsh' do + let(:priority) { create :priority_family_psh } + let(:route) { create :default_route, match_prioritization: priority } + let(:expected_order) do + tie_breaker_base = 1.years.ago.to_date + { + 2 => { + total_homeless_nights_unsheltered: 1, + days_homeless_in_last_three_years: 10, + tie_breaker_date: tie_breaker_base, + }, + 4 => { + service_need: true, + household_dv_survivor: true, + days_homeless_in_last_three_years: 9, + tie_breaker_date: tie_breaker_base - 1.days, + }, + 1 => { + total_homeless_nights_unsheltered: 1, + days_homeless_in_last_three_years: 9, + tie_breaker_date: tie_breaker_base, + }, + 3 => { + service_need: true, + enrolled_in_th: true, + days_homeless_in_last_three_years: 11, + tie_breaker_date: tie_breaker_base, + }, + 0 => { + days_homeless_in_last_three_years: 5, + tie_breaker_date: tie_breaker_base + 1.days, + }, + } + end + let(:columns) do + [ + :total_homeless_nights_unsheltered, + :service_need, + :household_dv_survivor, + :enrolled_in_th, + :days_homeless_in_last_three_years, + :tie_breaker_date, + ] + end + it 'is an ActiveRecord::Relation' do + expect(Client.prioritized(route.match_prioritization, Client.all)).to be_an ActiveRecord::Relation + end + it 'orders by match_group' do + expected_order.each do |i, values| + clients[i].update(**values) + end + ordered_clients = expected_order.keys.map { |i| clients[i] }.pluck(*columns) + prioritized_clients = Client.prioritized(route.match_prioritization, Client.all).pluck(*columns) + expect(prioritized_clients).to eq(ordered_clients) + end + end + + context 'when prioritized by RrhAndTh' do + let(:priority) { create :priority_rrh_and_th } + let(:route) { create :default_route, match_prioritization: priority } + let(:expected_order) do + tie_breaker_base = 1.years.ago.to_date + { + 2 => { + total_homeless_nights_unsheltered: 1, + days_homeless_in_last_three_years: 10, + tie_breaker_date: tie_breaker_base, + }, + 4 => { + disqualified_for_state_assistance: true, + household_dv_survivor: true, + days_homeless_in_last_three_years: 9, + tie_breaker_date: tie_breaker_base - 1.days, + }, + 1 => { + total_homeless_nights_unsheltered: 1, + days_homeless_in_last_three_years: 9, + tie_breaker_date: tie_breaker_base, + }, + 3 => { + disqualified_for_state_assistance: true, + enrolled_in_th: true, + days_homeless_in_last_three_years: 11, + tie_breaker_date: tie_breaker_base, + }, + 0 => { + days_homeless_in_last_three_years: 5, + tie_breaker_date: tie_breaker_base + 1.days, + }, + } + end + let(:columns) do + [ + :total_homeless_nights_unsheltered, + :disqualified_for_state_assistance, + :household_dv_survivor, + :enrolled_in_th, + :days_homeless_in_last_three_years, + :tie_breaker_date, + ] + end + it 'is an ActiveRecord::Relation' do + expect(Client.prioritized(route.match_prioritization, Client.all)).to be_an ActiveRecord::Relation + end + it 'orders by match_group' do + expected_order.each do |i, values| + clients[i].update(**values) + end + ordered_clients = expected_order.keys.map { |i| clients[i] }.pluck(*columns) + prioritized_clients = Client.prioritized(route.match_prioritization, Client.all).pluck(*columns) + expect(prioritized_clients).to eq(ordered_clients) + end + end + + context 'when prioritized by LowIncomeSubsidies' do + let(:priority) { create :priority_low_income_subsidies } + let(:route) { create :default_route, match_prioritization: priority } + let(:expected_order) do + tie_breaker_base = 1.years.ago.to_date + { + 2 => { + total_homeless_nights_unsheltered: 1, + days_homeless_in_last_three_years: 10, + tie_breaker_date: tie_breaker_base, + }, + 4 => { + housing_barrier: true, + household_dv_survivor: true, + days_homeless_in_last_three_years: 9, + tie_breaker_date: tie_breaker_base - 1.days, + }, + 1 => { + total_homeless_nights_unsheltered: 1, + days_homeless_in_last_three_years: 9, + tie_breaker_date: tie_breaker_base, + }, + 3 => { + housing_barrier: true, + enrolled_in_th: true, + days_homeless_in_last_three_years: 11, + tie_breaker_date: tie_breaker_base, + }, + 0 => { + days_homeless_in_last_three_years: 5, + tie_breaker_date: tie_breaker_base + 1.days, + }, + } + end + let(:columns) do + [ + :total_homeless_nights_unsheltered, + :housing_barrier, + :household_dv_survivor, + :enrolled_in_th, + :days_homeless_in_last_three_years, + :tie_breaker_date, + ] + end + it 'is an ActiveRecord::Relation' do + expect(Client.prioritized(route.match_prioritization, Client.all)).to be_an ActiveRecord::Relation + end + it 'orders by match_group' do + expected_order.each do |i, values| + clients[i].update(**values) + end + ordered_clients = expected_order.keys.map { |i| clients[i] }.pluck(*columns) + prioritized_clients = Client.prioritized(route.match_prioritization, Client.all).pluck(*columns) + expect(prioritized_clients).to eq(ordered_clients) + end + end end let(:bob_smith) { create :client, first_name: 'Bob', last_name: 'Smith' } diff --git a/spec/models/rules/psh_required_spec.rb b/spec/models/rules/psh_required_spec.rb new file mode 100644 index 000000000..6773b75bd --- /dev/null +++ b/spec/models/rules/psh_required_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +RSpec.describe Rules::PshRequired, type: :model do + describe 'clients_that_fit' do + let!(:rule) { create :psh_required } + + let!(:bob) { create :client, first_name: 'Bob', psh_required: 'yes' } + let!(:roy) { create :client, first_name: 'Roy', psh_required: 'no' } + let!(:job) { create :client, first_name: 'Roy', psh_required: 'maybe' } + + let!(:positive) { create :requirement, rule: rule, positive: true } + let!(:negative) { create :requirement, rule: rule, positive: false } + + let!(:clients_that_fit) { positive.clients_that_fit(Client.all) } + let!(:clients_that_dont_fit) { negative.clients_that_fit(Client.all) } + + context 'when positive' do + it 'matches 2' do + expect(clients_that_fit.count).to eq(2) + end + it 'contains Bob and Job' do + expect(clients_that_fit.ids).to include bob.id + expect(clients_that_fit.ids).to include job.id + end + it 'does not contain Roy' do + expect(clients_that_fit.ids).to_not include roy.id + end + end + + context 'when negative' do + it 'matches 2' do + expect(clients_that_dont_fit.count).to eq(2) + end + it 'does not contain Bob' do + expect(clients_that_dont_fit.ids).to_not include bob.id + end + it 'contains Roy and Job' do + expect(clients_that_dont_fit.ids).to include roy.id + expect(clients_that_dont_fit.ids).to include job.id + end + end + end +end From 83469d9d87cbee5a99e41e11c4f499dcdae42ed0 Mon Sep 17 00:00:00 2001 From: Elliot Anders Date: Mon, 10 Feb 2025 15:44:51 -0500 Subject: [PATCH 2/3] Rubocop cleanup --- db/migrate/20250107142055_add_school_contacts.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/migrate/20250107142055_add_school_contacts.rb b/db/migrate/20250107142055_add_school_contacts.rb index f1ab05371..3fd3a8b52 100644 --- a/db/migrate/20250107142055_add_school_contacts.rb +++ b/db/migrate/20250107142055_add_school_contacts.rb @@ -12,6 +12,5 @@ def change add_column :non_hmis_assessments, :schools_contact_info, :text add_column :non_hmis_assessments, :disqualified_for_state_assistance, :boolean, default: false add_column :non_hmis_assessments, :disqualified_for_state_assistance_reasons, :string - end end From ac425cd9b17c772bfa56a1371241945f56b4da19 Mon Sep 17 00:00:00 2001 From: Elliot Anders Date: Wed, 12 Feb 2025 12:55:13 -0500 Subject: [PATCH 3/3] Score family pathways --- app/models/concerns/pathways_version_four_calculations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/pathways_version_four_calculations.rb b/app/models/concerns/pathways_version_four_calculations.rb index 7c58a56a6..74702931a 100644 --- a/app/models/concerns/pathways_version_four_calculations.rb +++ b/app/models/concerns/pathways_version_four_calculations.rb @@ -568,7 +568,7 @@ def collection_for(field) def calculated_score return total_days_homeless_in_the_last_three_years if assessment_type == pathways_assessment_type.to_s - return if assessment_type == family_pathways_assessment_type.to_s + return days_homeless if assessment_type == family_pathways_assessment_type.to_s score = 0 # Answering No to Q5 invalidates further scoring