-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add flash picker for ALS with feature flag
- Loading branch information
Showing
6 changed files
with
244 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# frozen_string_literal: true | ||
|
||
module ClaimFastTracking | ||
class FlashPicker | ||
DEFAULT_FUZZY_TOLERANCE = 0.2 | ||
MIN_FUZZY_MATCH_LENGTH = 6 | ||
MIN_LENGTH_RATIO = 0.9 | ||
|
||
ALS_DC = 8017 | ||
ALS_PARTIAL_MATCH_TERMS = [ | ||
'amyotrophic lateral sclerosis', | ||
'(als)' | ||
].freeze | ||
ALS_MATCH_TERMS = (ALS_PARTIAL_MATCH_TERMS + [ | ||
'als', | ||
'lou gehrig disease', | ||
'lou gehrigs disease', | ||
'lou gehrig\'s disease', | ||
'lou gehrig', | ||
'lou gehrigs', | ||
'lou gehrig\'s' | ||
]).freeze | ||
|
||
def self.als?(claimed_disabilities) | ||
return if claimed_disabilities.pluck('diagnosticCode').include?(ALS_DC) | ||
|
||
claimed_disabilities.map { |disability| disability['name']&.downcase }.compact.any? do |name| | ||
partial_matches?(name, ALS_PARTIAL_MATCH_TERMS) || matches?(name, ALS_MATCH_TERMS) | ||
end | ||
end | ||
|
||
def self.partial_matches?(name, match_terms) | ||
match_terms = [match_terms] unless match_terms.is_a?(Array) | ||
|
||
match_terms.any? { |term| name.include?(term) } | ||
end | ||
|
||
def self.matches?(name, | ||
match_terms, | ||
tolerance = DEFAULT_FUZZY_TOLERANCE, | ||
min_length_ratio = MIN_LENGTH_RATIO, | ||
min_length_limit = MIN_FUZZY_MATCH_LENGTH) | ||
match_terms = [match_terms] unless match_terms.is_a?(Array) | ||
|
||
match_terms.any? do |term| | ||
# Early exact match check (case insensitive) | ||
return true if name.casecmp?(term) | ||
|
||
# Prevent fuzzy matching for very short terms (e.g., less than min_length_limit) | ||
next false if name.length < min_length_limit || term.length < min_length_limit | ||
|
||
# Calculate the length ratio based on the shorter and longer lengths | ||
shorter_length = [name.length, term.length].min | ||
longer_length = [name.length, term.length].max | ||
|
||
# Skip comparison if the length ratio is below minimum length ratio, indicating a significant length difference | ||
next false if shorter_length.to_f / longer_length < min_length_ratio | ||
|
||
# Calculate the Levenshtein threshold based on tolerance and maximum length | ||
return true if fuzzy_match?(name, term, longer_length, tolerance) | ||
end | ||
end | ||
|
||
def self.fuzzy_match?(name, term, longer_length, tolerance = DEFAULT_FUZZY_TOLERANCE) | ||
threshold = (longer_length * tolerance).ceil | ||
distance = StringHelpers.levenshtein_distance(name, term) | ||
|
||
if distance - 1 == threshold | ||
Rails.logger.info( | ||
'FlashPicker close fuzzy match for condition', | ||
{ name: name, match_term: term, distance: distance, threshold: threshold } | ||
) | ||
end | ||
distance <= threshold | ||
end | ||
|
||
private_class_method :partial_matches?, :matches?, :fuzzy_match? | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
|
||
RSpec.describe ClaimFastTracking::FlashPicker do | ||
subject { described_class } | ||
|
||
describe '#als?' do | ||
context 'when testing for ALS' do | ||
context 'when the disabilities is empty' do | ||
let(:disabilities) { [] } | ||
|
||
it 'returns an empty array' do | ||
expect(subject.als?(disabilities)).to eq(false) | ||
end | ||
end | ||
|
||
context 'when the disabilities does not contain ALS' do | ||
let(:disabilities) { [{ 'name' => 'Tinnitus', 'diagnosticCode' => 6260 }] } | ||
|
||
it 'returns false' do | ||
expect(subject.als?(disabilities)).to eq(false) | ||
end | ||
end | ||
|
||
context 'when the disability name exactly matches any of the ALS_TERMS' do | ||
described_class::ALS_MATCH_TERMS.each do |term| | ||
it "returns true for term #{term}" do | ||
expect(subject.als?([{ 'name' => term }])).to eq(true) | ||
end | ||
end | ||
end | ||
|
||
context 'when the disability name has partial match' do | ||
[ | ||
{ condition: 'amyotrophic lateral sclerosis', reason: 'full name' }, | ||
{ condition: '(als)', reason: 'acronym only in parentheses' }, | ||
{ | ||
condition: 'amyotrophic lateral sclerosis with lower extremity weakness, abnormal speech and abnormal gait', | ||
reason: 'full name with symptoms' | ||
}, | ||
{ condition: 'als amyotrophic lateral sclerosis', reason: 'full name with acronym on left' }, | ||
{ condition: 'als (amyotrophic lateral sclerosis)', reason: 'full name in parentheses with acronym on left' }, | ||
{ condition: 'als amyotrophic lateral sclerosis', reason: 'full name with acronym on left' }, | ||
{ condition: '(als) amyotrophic lateral sclerosis', reason: 'full name with acronym on left in parentheses' }, | ||
{ condition: 'amyotrophic lateral sclerosis als', reason: 'full name with acronym on right' }, | ||
{ condition: '(amyotrophic lateral sclerosis) als', | ||
reason: 'full name in parentheses with acronym on right' }, | ||
{ condition: 'amyotrophic lateral sclerosis (als)', | ||
reason: 'full name with acronym on right in parentheses' }, | ||
{ condition: 'amyotropic lateril scerolses (als)', | ||
reason: 'full name with several letter typo and acronym in parentheses' } | ||
].each do |test_case| | ||
it "returns true for term #{test_case[:reason]}" do | ||
disabilities = [{ 'name' => test_case[:condition] }] | ||
expect(subject.als?(disabilities)).to eq(true) | ||
end | ||
end | ||
end | ||
|
||
context 'when the disabilities contains a fuzzy match' do | ||
[ | ||
{ condition: 'amyotrophic lateral scleroses', reason: 'Pluralization error' }, | ||
{ condition: 'Amyothrophic lateral sclerosis', reason: 'Minor typo' }, | ||
{ condition: 'amyotrophic lateral sclerosiss', reason: 'Minor double letter typo' }, | ||
{ condition: 'amyotropic lareral sclerosiss', reason: 'several letter typo' }, | ||
{ condition: 'amyotrophic lateral scelrosis', reason: 'Phonetic misspelling' }, | ||
{ condition: 'lou gherig disease', reason: 'Phonetic misspelling of Gehrig' }, | ||
{ condition: 'lou gehrigs desease', reason: 'Double typo' }, | ||
{ condition: 'lou gehrigs desase', reason: 'Phonetic error' }, | ||
{ condition: "lou gehrig's disease", reason: 'Included Apostrophe with disease' }, | ||
{ condition: "lou gehrig'", reason: 'Included Apostrophe without s' }, | ||
{ condition: 'lou gehrig', reason: 'Missing possessive "s"' } | ||
].each do |test_case| | ||
it "returns true for term with #{test_case[:reason]}" do | ||
disabilities = [{ 'name' => test_case[:condition] }] | ||
expect(subject.als?(disabilities)).to eq(true) | ||
end | ||
end | ||
end | ||
|
||
context 'when the disabilities does not contains any fuzzy match' do | ||
[ | ||
{ condition: 'ALT', reason: 'wrong acronym but too small to fuzzy match' }, | ||
{ condition: 'sclerosis disease', reason: 'Too vague' }, | ||
{ condition: 'Lou diseases', reason: 'Doesn’t specify' }, | ||
{ condition: 'lateral disease', reason: 'Partial match with missing context' }, | ||
{ condition: 'neuro disease', reason: 'Different condition entirely' } | ||
].each do |test_case| | ||
it "returns false for term with #{test_case[:reason]}" do | ||
disabilities = [{ 'name' => test_case[:condition] }] | ||
expect(subject.als?(disabilities)).to eq(false) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |