Skip to content

Commit

Permalink
feat(#684): change map of gene names to lists (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
tamslo committed Jan 9, 2024
1 parent 1cae81d commit e57255e
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 93 deletions.
12 changes: 6 additions & 6 deletions app/integration_test/drugs_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,20 @@ void main() {
implication: 'nothing',
warningLevel: WarningLevel.green))
]);
UserData.instance.lookups = {
'CYP2C9': CpicLookup(
UserData.instance.lookups = [
CpicLookup(
gene: 'CYP2C9',
phenotype: 'Normal Metabolizer',
variant: '*1/*1',
lookupkey: '2')
};
UserData.instance.geneResults = {
'CYP2C9': GeneResult(
];
UserData.instance.geneResults = [
GeneResult(
gene: 'CYP2C9',
phenotype: 'Normal Metabolizer',
variant: '*1/*1',
allelesTested: '1')
};
];
final testDrugWithoutGuidelines = Drug(
id: '2',
version: 1,
Expand Down
2 changes: 1 addition & 1 deletion app/integration_test/main_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ void main() {

binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps;

UserData.instance.lookups = {};
UserData.instance.lookups = [];

CachedDrugs.instance.version = 1;
CachedDrugs.instance.drugs = List.empty();
Expand Down
6 changes: 3 additions & 3 deletions app/lib/common/models/drug/drug.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ extension DrugExtension on Drug {

Guideline? get userGuideline => guidelines.firstOrNullWhere(
(guideline) => guideline.lookupkey.all(
(gene, geneResults) =>
geneResults.contains(UserData.lookupFor(gene, drug: name))
),
(gene, lookupkeys) =>
lookupkeys.any((lookupkey) => UserData.lookupkeysFor(gene)!.contains(lookupkey))
)
);

Guideline? get userOrFirstGuideline => userGuideline ??
Expand Down
16 changes: 13 additions & 3 deletions app/lib/common/models/drug/drug_inhibitors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,19 @@ bool isModerateInhibitor(String drugName) {
bool isInhibitor(String drugName) {
var drugIsInhibitor = false;
for (final gene in _drugInhibitorsPerGene.keys) {
final influencingDrugs = _drugInhibitorsPerGene[gene];
final originalLookup = UserData.lookupFor(gene, drug: drugName, useOverwrite: false);
if (influencingDrugs!.contains(drugName) && originalLookup != '0.0') {
final influencingDrugs = _drugInhibitorsPerGene[gene]!;
// If the drug is not in the current gene list, test the next gene
if (!influencingDrugs.contains(drugName)) continue;
// If the drug is listed as inhibitor, test if the user is already a
// poor metabolizer for the gene; if yes, the drug cannot inhibit the
// activity further and is not regarded as an inhibitor
final originalLookups = UserData.lookupkeysFor(gene, useOverwrite: false)!;
if (originalLookups.length != 1) {
debugPrint('[WARNING] Inhibited genes are (currently) only expected to '
'have one matching user lookup but found ${originalLookups.length} for '
'$drugName; ignoring all but the first one');
}
if (originalLookups.first != '0.0') {
drugIsInhibitor = true;
break;
}
Expand Down
2 changes: 1 addition & 1 deletion app/lib/common/models/userdata/genotype.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
abstract class Genotype{
class Genotype{
Genotype({
required this.gene,
required this.variant,
Expand Down
209 changes: 146 additions & 63 deletions app/lib/common/models/userdata/userdata.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,30 +46,165 @@ class UserData {
}

@HiveField(0)
Map<String, GeneResult>? geneResults;
List<GeneResult>? geneResults;

static PhenotypeInformation phenotypeInformationFor(
@HiveField(1)
List<CpicLookup>? lookups;

// hive can't deal with sets so we have to use a list :(
@HiveField(2)
List<String>? activeDrugNames;

static List<Genotype>? _genotypesFrom(
List<Genotype>? genotypes,
String gene,
[String? variant]
) {
if (genotypes == null) return null;
final matchingGenotypes = genotypes.where(
(geneResult) => geneResult.gene == gene &&
(variant == null || geneResult.variant == variant)
).toList();
if (matchingGenotypes.isEmpty) {
throw Exception(
'Could not find Genotype for $gene, $variant'
);
}
return matchingGenotypes;
}

static Genotype? _genotypeFrom(
List<Genotype>? genotypes,
Genotype genotype
) {
final matchingGenotypes =
_genotypesFrom(genotypes, genotype.gene, genotype.variant);
if (matchingGenotypes != null && matchingGenotypes.length != 1) {
throw Exception(
'Found more than one Genotype for ${genotype.toString()} but should '
'only find one'
);
}
return matchingGenotypes?.first;
}

static GeneResult? _geneResultFor(Genotype genotype) =>
_genotypeFrom(UserData.instance.geneResults, genotype) as GeneResult?;

static List<GeneResult>? _geneResultsFor(String gene) =>
_genotypesFrom(UserData.instance.geneResults, gene) as List<GeneResult>?;

static List<CpicLookup>? _lookupkeysFor(String gene) =>
_genotypesFrom(UserData.instance.lookups, gene) as List<CpicLookup>?;

static String? variantFor(Genotype genotype) =>
_geneResultFor(genotype)?.variant;

static String? allelesTestedFor(Genotype genotype) =>
_geneResultFor(genotype)?.allelesTested;

static Genotype? genotypeFor(
String gene,
Drug drug,
{ required bool useOverwrite }
) {
final overwrite = useOverwrite
? UserData.overwrittenLookup(gene, drug: drug.name)
: null;
if (overwrite != null) {
return Genotype(gene: gene, variant: overwrite.value);
}
final matchingGeneResults = _geneResultsFor(gene);
if (matchingGeneResults == null) return null;
if (matchingGeneResults.length == 1) {
return Genotype(
gene: gene,
variant: matchingGeneResults.first.variant,
);
}
// When multiple lookups were found it means that the gene has positive/
// negative results for multiple alleles; return the lookup that matches
// the allele
final guidelineAlleles = drug.userGuideline?.lookupkey[gene]?.map(
(lookupkey) => lookupkey.split(' ').first
).toSet();
if (guidelineAlleles == null || guidelineAlleles.length != 1) return null;
final variant = matchingGeneResults.firstWhere(
(geneResult) => geneResult.variant.startsWith(guidelineAlleles.first)
).variant;
return Genotype(gene: gene, variant: variant);
}

static List<String>? lookupkeysFor(
String gene,
{
String? drug,
bool useOverwrite = true,
}
) {
final overwrittenLookup = UserData.overwrittenLookup(gene, drug: drug);
final matchingLookups = _lookupkeysFor(gene);
return matchingLookups?.map(
(lookup) => (useOverwrite && overwrittenLookup != null)
? overwrittenLookup.value
: lookup.lookupkey
).toList();
}

static MapEntry<String, String>? overwrittenLookup(
String gene,
{ String? drug }
) {
final inhibitors = strongDrugInhibitors[gene];
if (inhibitors == null) return null;
final lookup = inhibitors.entries.firstWhereOrNull((entry) {
final isActiveInhitor =
UserData.instance.activeDrugNames?.contains(entry.key) ?? false;
final wouldInhibitItself = drug == entry.key;
return isActiveInhitor && !wouldInhibitItself;
});
if (lookup == null) return null;
return lookup;
}

static List<String> activeInhibitorsFor(String gene, { String? drug }) {
return UserData.instance.activeDrugNames == null
? <String>[]
: UserData.instance.activeDrugNames!.filter(
(activeDrug) =>
inhibitorsFor(gene).contains(activeDrug) &&
activeDrug != drug
).toList();
}

// TODO: revisit all the data types and their usage again; can we make this
// less redundant? Can we safely assume that binary gene results cannot be
// inhibited and what would change then?
// TODO(me): should probably receive geneResult already (otherwise not clear)
// if should use overwrite
static PhenotypeInformation phenotypeInformationFor(
Genotype? genotype,
BuildContext context,
{
String? drug,
bool thirdPerson = false,
bool useLongPrefix = false,
}
) {
final missingResult = PhenotypeInformation(
phenotype: context.l10n.general_not_tested,
);
if (genotype == null) return missingResult;
final originalPhenotype = _geneResultFor(genotype)?.phenotype;
if (originalPhenotype == null) return missingResult;
final userSalutation = thirdPerson
? context.l10n.drugs_page_inhibitor_third_person_salutation
: context.l10n.drugs_page_inhibitor_direct_salutation;
final strongInhibitorTextPrefix = useLongPrefix
? context.l10n.strong_inhibitor_long_prefix
: context.l10n.gene_page_phenotype.toLowerCase();
final originalPhenotype = UserData.instance.geneResults?[gene]?.phenotype;
if (originalPhenotype == null) {
return PhenotypeInformation(
phenotype: context.l10n.general_not_tested,
);
}
final activeInhibitors = UserData.activeInhibitorsFor(gene, drug: drug);
final activeInhibitors =
UserData.activeInhibitorsFor(genotype.gene, drug: drug);
if (activeInhibitors.isEmpty) {
return PhenotypeInformation(phenotype: originalPhenotype);
}
Expand All @@ -81,7 +216,8 @@ class UserData {
phenotype: originalPhenotype,
);
}
final overwrittenLookup = UserData.overwrittenLookup(gene, drug: drug);
final overwrittenLookup =
UserData.overwrittenLookup(genotype.gene, drug: drug);
if (overwrittenLookup == null) {
return PhenotypeInformation(
phenotype: originalPhenotype,
Expand Down Expand Up @@ -110,59 +246,6 @@ class UserData {
overwrittenPhenotypeText: originalPhenotypeText,
);
}

static String? variantFor(String gene) =>
UserData.instance.geneResults?[gene]?.variant;

static String? allelesTestedFor(String gene) =>
UserData.instance.geneResults?[gene]?.allelesTested;

@HiveField(1)
Map<String, CpicLookup>? lookups;

static MapEntry<String, String>? overwrittenLookup(
String gene,
{ String? drug }
) {
final inhibitors = strongDrugInhibitors[gene];
if (inhibitors == null) return null;
final lookup = inhibitors.entries.firstWhereOrNull((entry) {
final isActiveInhitor =
UserData.instance.activeDrugNames?.contains(entry.key) ?? false;
final wouldInhibitItself = drug == entry.key;
return isActiveInhitor && !wouldInhibitItself;
});
if (lookup == null) return null;
return lookup;
}

static String? lookupFor(
String gene,
{
String? drug,
bool useOverwrite = true,
}
) {
final overwrittenLookup = UserData.overwrittenLookup(gene, drug: drug);
if (useOverwrite && overwrittenLookup != null) {
return overwrittenLookup.value;
}
return UserData.instance.lookups?[gene]?.lookupkey;
}

// hive can't deal with sets so we have to use a list :(
@HiveField(2)
List<String>? activeDrugNames;

static List<String> activeInhibitorsFor(String gene, { String? drug }) {
return UserData.instance.activeDrugNames == null
? <String>[]
: UserData.instance.activeDrugNames!.filter(
(activeDrug) =>
inhibitorsFor(gene).contains(activeDrug) &&
activeDrug != drug
).toList();
}
}

// Wrapper of UserData.instance.activeDrugNames that manages changes; used to
Expand Down
17 changes: 6 additions & 11 deletions app/lib/common/utilities/genome_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,10 @@ Future<void> _saveDiplotypeAndActiveDrugsResponse(
Response response,
ActiveDrugs activeDrugs,
) async {
// parse response to list of user's diplotypes
final diplotypes =
geneResultsFromHTTPResponse(response);
final activeDrugList = activeDrugsFromHTTPResponse(response);

UserData.instance.geneResults = {
for (final diplotype in diplotypes) diplotype.gene: diplotype
};
// parse response to list of user's diplotypes and active drugs
UserData.instance.geneResults = geneResultsFromHTTPResponse(response);
await UserData.save();
final activeDrugList = activeDrugsFromHTTPResponse(response);
await activeDrugs.setList(activeDrugList);
// invalidate cached drugs because lookups may have changed and we need to
// refilter the matching guidelines
Expand All @@ -60,9 +55,9 @@ Future<void> fetchAndSaveLookups() async {
value: (lookup) => lookup,
);
// ignore: omit_local_variable_types
final Map<String, CpicLookup> matchingLookups = {};
final List<CpicLookup> matchingLookups = [];
// extract the matching lookups
for (final diplotype in usersDiplotypes.values) {
for (final diplotype in usersDiplotypes) {
// the gene and the genotype build the key for the hashmap
final key = '${diplotype.gene}__${diplotype.variant}';
final lookup = lookupsHashMap[key];
Expand All @@ -72,7 +67,7 @@ Future<void> fetchAndSaveLookups() async {
// print(
// 'Lab phenotype ${diplotype.phenotype} for ${diplotype.gene} (${diplotype.genotype}) is "${lookup.phenotype}" for CPIC');
// }
matchingLookups[diplotype.gene] = lookup;
matchingLookups.add(lookup);
}

// uncomment to make user have CYP2D6 lookupkey 0.0
Expand Down
4 changes: 2 additions & 2 deletions app/lib/common/utilities/pdf_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ String? _getPhenotypeInfo(String gene, Drug drug, BuildContext context) {

String? _getActivityScoreInfo(String gene, Drug drug, BuildContext context) {
final originalLookup = UserData.lookupFor(
gene,
UserData.genotypeFor(gene, drug),
drug: drug.name,
useOverwrite: false,
);
final overwrittenLookup = UserData.lookupFor(
gene,
UserData.genotypeFor(gene, drug),
drug: drug.name,
useOverwrite: true,
);
Expand Down
2 changes: 1 addition & 1 deletion app/lib/drug/widgets/annotation_cards/guideline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class GuidelineAnnotationCard extends StatelessWidget {
} else {
final geneDescriptions = drug.guidelineGenes.map((gene) {
final phenotypeInformation = UserData.phenotypeInformationFor(
gene,
UserData.genotypeFor(gene, drug, useOverwrite: false),
context,
drug: drug.name,
);
Expand Down
Loading

0 comments on commit e57255e

Please sign in to comment.