From d709260be870f87d34d6d535e2d4b1b50ed72f12 Mon Sep 17 00:00:00 2001 From: Tamara Slosarek Date: Mon, 12 Feb 2024 17:19:12 +0100 Subject: [PATCH] feat(#684): introduce GenotypeResult --- app/generate_screenshots/app_test.dart | 2 +- app/integration_test/drugs_test.dart | 81 ++++++++++--------- app/integration_test/main_page_test.dart | 2 +- app/lib/common/models/drug/drug.dart | 8 +- .../common/models/drug/drug_inhibitors.dart | 6 +- app/lib/common/models/module.dart | 3 + app/lib/common/models/userdata/genotype.dart | 6 +- .../common/models/userdata/genotype_key.dart | 27 +++++++ .../models/userdata/genotype_result.dart | 59 ++++++++++++++ .../models/userdata/lookup_information.dart | 29 ++----- .../userdata/phenotype_information.dart | 11 +++ app/lib/common/models/userdata/userdata.dart | 62 ++++++-------- app/lib/common/utilities/drug_utils.dart | 4 - app/lib/common/utilities/genome_data.dart | 60 +++++--------- app/lib/common/utilities/pdf_utils.dart | 24 +++--- app/lib/common/widgets/drug_list/cubit.dart | 16 ++-- .../widgets/annotation_cards/guideline.dart | 9 ++- app/lib/login/cubit.dart | 2 +- app/lib/main.dart | 2 +- app/lib/report/pages/gene.dart | 35 ++++---- app/lib/report/pages/report.dart | 46 +++++------ 21 files changed, 280 insertions(+), 214 deletions(-) create mode 100644 app/lib/common/models/userdata/genotype_key.dart create mode 100644 app/lib/common/models/userdata/genotype_result.dart create mode 100644 app/lib/common/models/userdata/phenotype_information.dart diff --git a/app/generate_screenshots/app_test.dart b/app/generate_screenshots/app_test.dart index cf1cddd5..82ee8fb6 100644 --- a/app/generate_screenshots/app_test.dart +++ b/app/generate_screenshots/app_test.dart @@ -34,7 +34,7 @@ void main() { // Part before runApp in lib/main.dart await initServices(); - await fetchAndSaveLookups(); + await updateGenotypeResults(); // Load the app await tester.pumpWidget( diff --git a/app/integration_test/drugs_test.dart b/app/integration_test/drugs_test.dart index bfb0b225..9298de9d 100644 --- a/app/integration_test/drugs_test.dart +++ b/app/integration_test/drugs_test.dart @@ -19,49 +19,52 @@ void main() { binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps; final testDrug = Drug( - id: '1', - version: 1, - name: 'Ibuprofen', - rxNorm: 'rxnorm', - annotations: DrugAnnotations( - drugclass: 'NSAID', - indication: 'indication', - brandNames: ['brand name', 'another brand name']), - guidelines: [ - Guideline( - id: '1', - version: 1, - lookupkey: { - 'CYP2C9': ['2'] - }, - externalData: [ - GuidelineExtData( - source: 'CPIC', - guidelineName: 'cpic name', - guidelineUrl: 'cpic url', - implications: {'CYP2C9': 'normal metabolization'}, - recommendation: 'default dose', - comments: 'comments') - ], - annotations: GuidelineAnnotations( + id: '1', + version: 1, + name: 'Ibuprofen', + rxNorm: 'rxnorm', + annotations: DrugAnnotations( + drugclass: 'NSAID', + indication: 'indication', + brandNames: ['brand name', 'another brand name']), + guidelines: [ + Guideline( + id: '1', + version: 1, + lookupkey: { + 'CYP2C9': ['2'] + }, + externalData: [ + GuidelineExtData( + source: 'CPIC', + guidelineName: 'cpic name', + guidelineUrl: 'cpic url', + implications: {'CYP2C9': 'normal metabolization'}, recommendation: 'default dose', - implication: 'nothing', - warningLevel: WarningLevel.green)) - ]); - UserData.instance.lookups = { - 'CYP2C9': LookupInformation( - gene: 'CYP2C9', - phenotype: 'Normal Metabolizer', - variant: '*1/*1', - lookupkey: '2') - }; - UserData.instance.geneResults = { - 'CYP2C9': GeneResult( + comments: 'comments') + ], + annotations: GuidelineAnnotations( + recommendation: 'default dose', + implication: 'nothing', + warningLevel: WarningLevel.green)) + ]); + UserData.instance.geneResults = [ + GeneResult( gene: 'CYP2C9', phenotype: 'Normal Metabolizer', variant: '*1/*1', - allelesTested: '1') - }; + allelesTested: '1', + ), + ]; + UserData.instance.genotypeResults = { + 'CYP2C9': GenotypeResult( + gene: UserData.instance.geneResults![0].gene, + phenotype: UserData.instance.geneResults![0].phenotype, + variant: UserData.instance.geneResults![0].variant, + allelesTested: UserData.instance.geneResults![0].variant, + lookupkey: '2', + ), + }; final testDrugWithoutGuidelines = Drug( id: '2', version: 1, diff --git a/app/integration_test/main_page_test.dart b/app/integration_test/main_page_test.dart index be977bd1..f3393382 100644 --- a/app/integration_test/main_page_test.dart +++ b/app/integration_test/main_page_test.dart @@ -9,7 +9,7 @@ void main() { binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps; - UserData.instance.lookups = {}; + UserData.instance.genotypeResults = {}; CachedDrugs.instance.version = 1; CachedDrugs.instance.drugs = List.empty(); diff --git a/app/lib/common/models/drug/drug.dart b/app/lib/common/models/drug/drug.dart index b5a4cb7e..e2668130 100644 --- a/app/lib/common/models/drug/drug.dart +++ b/app/lib/common/models/drug/drug.dart @@ -88,8 +88,12 @@ extension DrugExtension on Drug { Guideline? get userOrFirstGuideline => userGuideline ?? (guidelines.isNotEmpty ? guidelines.first : null); - List get guidelineGenes => guidelines.isNotEmpty - ? guidelines.first.lookupkey.keys.toList() + List get guidelineGenotypes => guidelines.isNotEmpty + ? guidelines.first.lookupkey.keys.flatMap( + (gene) => guidelines.first.lookupkey[gene]!.map((variant) => + GenotypeKey(gene, variant).value + ).toList() + ).toList() : []; WarningLevel get warningLevel => diff --git a/app/lib/common/models/drug/drug_inhibitors.dart b/app/lib/common/models/drug/drug_inhibitors.dart index a5e00579..cfd0b47e 100644 --- a/app/lib/common/models/drug/drug_inhibitors.dart +++ b/app/lib/common/models/drug/drug_inhibitors.dart @@ -83,7 +83,7 @@ List inhibitorsFor(String gene) { return _drugInhibitorsPerGene[gene] ?? []; } -bool canBeInhibited(LookupInformation lookup) { - return inhibitableGenes.contains(lookup.gene) && - lookup.lookupkey != '0.0'; +bool canBeInhibited(GenotypeResult genotypeResult) { + return inhibitableGenes.contains(genotypeResult.gene) && + genotypeResult.lookupkey != '0.0'; } diff --git a/app/lib/common/models/module.dart b/app/lib/common/models/module.dart index b7af9bf7..29b53cd6 100644 --- a/app/lib/common/models/module.dart +++ b/app/lib/common/models/module.dart @@ -7,5 +7,8 @@ export 'drug/warning_level.dart'; export 'metadata.dart'; export 'userdata/gene_result.dart'; export 'userdata/genotype.dart'; +export 'userdata/genotype_key.dart'; +export 'userdata/genotype_result.dart'; export 'userdata/lookup_information.dart'; +export 'userdata/phenotype_information.dart'; export 'userdata/userdata.dart'; diff --git a/app/lib/common/models/userdata/genotype.dart b/app/lib/common/models/userdata/genotype.dart index da4226d7..2f9cce28 100644 --- a/app/lib/common/models/userdata/genotype.dart +++ b/app/lib/common/models/userdata/genotype.dart @@ -1,9 +1,9 @@ abstract class Genotype{ - Genotype({ + const Genotype({ required this.gene, required this.variant, }); - String gene; - String variant; + final String gene; + final String variant; } \ No newline at end of file diff --git a/app/lib/common/models/userdata/genotype_key.dart b/app/lib/common/models/userdata/genotype_key.dart new file mode 100644 index 00000000..39957703 --- /dev/null +++ b/app/lib/common/models/userdata/genotype_key.dart @@ -0,0 +1,27 @@ +import '../../module.dart'; + +class GenotypeKey implements Genotype { + GenotypeKey(this.gene, this.variant); + + factory GenotypeKey.fromGenotype(Genotype genotype) => + GenotypeKey(genotype.gene, genotype.variant); + + @override + String gene; + + @override + String variant; + + String get value { + final geneResults = UserData.instance.geneResults!.where( + (geneResult) => geneResult.gene == gene + ); + if (geneResults.length > 1) { + return '$gene ${variant.split(' ').first}'; + } + return gene; + } + + static String extractGene(String genotypeKey) => + genotypeKey.split(' ').first; +} \ No newline at end of file diff --git a/app/lib/common/models/userdata/genotype_result.dart b/app/lib/common/models/userdata/genotype_result.dart new file mode 100644 index 00000000..4dbd1862 --- /dev/null +++ b/app/lib/common/models/userdata/genotype_result.dart @@ -0,0 +1,59 @@ +import 'package:hive/hive.dart'; + +import '../../module.dart'; + +part 'genotype_result.g.dart'; + +@HiveType(typeId: 2) +class GenotypeResult implements Genotype { + GenotypeResult({ + required this.gene, + required this.variant, + required this.phenotype, + required this.lookupkey, + required this.allelesTested, + }); + + factory GenotypeResult.fromGenotypeData( + GeneResult labResult, + LookupInformation lookup, + ) => GenotypeResult( + gene: labResult.gene, + variant: labResult.variant, + phenotype: labResult.phenotype, + lookupkey: lookup.lookupkey, + allelesTested: labResult.allelesTested, + ); + + factory GenotypeResult.missingResult(String gene, BuildContext context) => + GenotypeResult( + gene: gene, + variant: context.l10n.general_not_tested, + phenotype: context.l10n.general_not_tested, + lookupkey: context.l10n.general_not_tested, + allelesTested: context.l10n.general_not_tested, + ); + + @override + @HiveField(0) + String gene; + @override + @HiveField(1) + String variant; + @HiveField(2) + String phenotype; + @HiveField(3) + String lookupkey; + @HiveField(4) + String allelesTested; + + String get key => GenotypeKey.fromGenotype(this).value; +} + +extension FindGenotypeResultByKey on Map { + GenotypeResult findOrMissing(String genotypeKey, BuildContext context) => + this[genotypeKey] ?? GenotypeResult.missingResult( + GenotypeKey.extractGene(genotypeKey), + context, + ); +} \ No newline at end of file diff --git a/app/lib/common/models/userdata/lookup_information.dart b/app/lib/common/models/userdata/lookup_information.dart index 3585181c..da48ceba 100644 --- a/app/lib/common/models/userdata/lookup_information.dart +++ b/app/lib/common/models/userdata/lookup_information.dart @@ -1,12 +1,5 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hive/hive.dart'; - import 'genotype.dart'; -part 'lookup_information.g.dart'; - -@HiveType(typeId: 2) -@JsonSerializable() class LookupInformation implements Genotype { LookupInformation({ required this.gene, @@ -16,28 +9,18 @@ class LookupInformation implements Genotype { }); factory LookupInformation.fromJson(dynamic json) { - return _$LookupInformationFromJson({ - ...json, - // transform lookupkey map to string - 'lookupkey': json['lookupkey'][json['genesymbol']], - }); + return LookupInformation( + gene: json['genesymbol'] as String, + variant: json['diplotype'] as String, + phenotype: json['generesult'] as String, + lookupkey: json['lookupkey'][json['genesymbol']] as String, + ); } @override - @HiveField(0) - @JsonKey(name: 'genesymbol') String gene; - @override - @HiveField(1) - @JsonKey(name: 'diplotype') String variant; - - @HiveField(2) - @JsonKey(name: 'generesult') String phenotype; - - @HiveField(3) - @JsonKey(name: 'lookupkey') String lookupkey; } diff --git a/app/lib/common/models/userdata/phenotype_information.dart b/app/lib/common/models/userdata/phenotype_information.dart new file mode 100644 index 00000000..6343cbc0 --- /dev/null +++ b/app/lib/common/models/userdata/phenotype_information.dart @@ -0,0 +1,11 @@ +class PhenotypeInformation { + PhenotypeInformation({ + required this.phenotype, + this.adaptionText, + this.overwrittenPhenotypeText, + }); + + String phenotype; + String? adaptionText; + String? overwrittenPhenotypeText; +} \ No newline at end of file diff --git a/app/lib/common/models/userdata/userdata.dart b/app/lib/common/models/userdata/userdata.dart index 309be5ce..d6bca927 100644 --- a/app/lib/common/models/userdata/userdata.dart +++ b/app/lib/common/models/userdata/userdata.dart @@ -11,18 +11,6 @@ part 'userdata.g.dart'; const _boxName = 'userdata'; -class PhenotypeInformation { - PhenotypeInformation({ - required this.phenotype, - this.adaptionText, - this.overwrittenPhenotypeText, - }); - - String phenotype; - String? adaptionText; - String? overwrittenPhenotypeText; -} - /// UserData is a singleton data-class which contains various user-specific /// data It is intended to be loaded from a Hive box once at app launch, from /// where it's contents can be modified by accessing it's properties. @@ -46,10 +34,15 @@ class UserData { } @HiveField(0) - Map? geneResults; + List? geneResults; + @HiveField(1) + // hive can't deal with sets so we have to use a list :( + List? activeDrugNames; + @HiveField(2) + Map? genotypeResults; static PhenotypeInformation phenotypeInformationFor( - String gene, + GenotypeResult genotypeResult, BuildContext context, { String? drug, @@ -63,13 +56,11 @@ class UserData { 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 originalPhenotype = genotypeResult.phenotype; + final activeInhibitors = UserData.activeInhibitorsFor( + genotypeResult.gene, + drug: drug, + ); if (activeInhibitors.isEmpty) { return PhenotypeInformation(phenotype: originalPhenotype); } @@ -81,7 +72,10 @@ class UserData { phenotype: originalPhenotype, ); } - final overwrittenLookup = UserData.overwrittenLookup(gene, drug: drug); + final overwrittenLookup = UserData.overwrittenLookup( + genotypeResult.gene, + drug: drug, + ); if (overwrittenLookup == null) { return PhenotypeInformation( phenotype: originalPhenotype, @@ -111,14 +105,11 @@ class UserData { ); } - static String? variantFor(String gene) => - UserData.instance.geneResults?[gene]?.variant; + static String? variantFor(String genotypeKey) => + UserData.instance.genotypeResults?[genotypeKey]?.variant; - static String? allelesTestedFor(String gene) => - UserData.instance.geneResults?[gene]?.allelesTested; - - @HiveField(1) - Map? lookups; + static String? allelesTestedFor(String genotypeKey) => + UserData.instance.genotypeResults?[genotypeKey]?.allelesTested; static MapEntry? overwrittenLookup( String gene, @@ -137,23 +128,20 @@ class UserData { } static String? lookupFor( - String gene, + String genotypeKey, { String? drug, bool useOverwrite = true, } ) { - final overwrittenLookup = UserData.overwrittenLookup(gene, drug: drug); + final overwrittenLookup = + UserData.overwrittenLookup(genotypeKey, drug: drug); if (useOverwrite && overwrittenLookup != null) { return overwrittenLookup.value; } - return UserData.instance.lookups?[gene]?.lookupkey; + return UserData.instance.genotypeResults?[genotypeKey]?.lookupkey; } - // hive can't deal with sets so we have to use a list :( - @HiveField(2) - List? activeDrugNames; - static List activeInhibitorsFor(String gene, { String? drug }) { return UserData.instance.activeDrugNames == null ? [] @@ -223,7 +211,7 @@ Future initUserData() async { try { Hive.registerAdapter(UserDataAdapter()); Hive.registerAdapter(GeneResultAdapter()); - Hive.registerAdapter(LookupInformationAdapter()); + Hive.registerAdapter(GenotypeResultAdapter()); } catch (e) { return; } diff --git a/app/lib/common/utilities/drug_utils.dart b/app/lib/common/utilities/drug_utils.dart index 6620203e..b181f0ac 100644 --- a/app/lib/common/utilities/drug_utils.dart +++ b/app/lib/common/utilities/drug_utils.dart @@ -5,19 +5,16 @@ import '../../app.dart'; import '../module.dart'; Future updateCachedDrugs() async { - if (UserData.instance.lookups == null) throw Exception(); final isOnline = await hasConnectionTo(anniUrl().host); if (!isOnline && CachedDrugs.instance.version == null) { throw Exception(); } - final versionResponse = await get(anniUrl('version')); if (versionResponse.statusCode != 200) throw Exception(); final version = AnniVersionResponse.fromJson(jsonDecode(versionResponse.body)) .data .version; if (version == CachedDrugs.instance.version) return; - final dataResponse = await get(anniUrl('data')); if (dataResponse.statusCode != 200) throw Exception(); final data = AnniDataResponse.fromJson(jsonDecode(dataResponse.body)).data; @@ -25,7 +22,6 @@ Future updateCachedDrugs() async { CachedDrugs.instance.drugs = data.drugs; CachedDrugs.instance.version = data.version; await CachedDrugs.save(); - if (previousVersion != null) { final context = PharMeApp.navigatorKey.currentContext; if (context != null) { diff --git a/app/lib/common/utilities/genome_data.dart b/app/lib/common/utilities/genome_data.dart index d3a7b3a6..10342703 100644 --- a/app/lib/common/utilities/genome_data.dart +++ b/app/lib/common/utilities/genome_data.dart @@ -3,8 +3,7 @@ import 'dart:convert'; import 'package:http/http.dart'; -import '../constants.dart'; -import '../models/module.dart'; +import '../module.dart'; Future fetchAndSaveDiplotypesAndActiveDrugs( String token, String url, ActiveDrugs activeDrugs) async { @@ -21,24 +20,16 @@ Future getDiplotypes(String? token, String url) async { return get(Uri.parse(url), headers: {'Authorization': 'Bearer $token'}); } -String getGenotypeKey(Genotype genotype) { - // If gene is unique return gene; else return gene plus first part of variant - // (before space) - return genotype.gene; -} - Future _saveDiplotypeAndActiveDrugsResponse( Response response, ActiveDrugs activeDrugs, ) async { - // parse response to list of user's diplotypes - final diplotypes = + // parse response to list of user's geneResults + final geneResults = geneResultsFromHTTPResponse(response); final activeDrugList = activeDrugsFromHTTPResponse(response); - UserData.instance.geneResults = { - for (final diplotype in diplotypes) diplotype.gene: diplotype - }; + UserData.instance.geneResults = geneResults; await UserData.save(); await activeDrugs.setList(activeDrugList); // invalidate cached drugs because lookups may have changed and we need to @@ -46,17 +37,13 @@ Future _saveDiplotypeAndActiveDrugsResponse( await CachedDrugs.erase(); } -Future fetchAndSaveLookups() async { - if (!shouldFetchLookups()) return; +Future updateGenotypeResults() async { + if (!shouldUpdateGenotypeResults()) return; + // fetch lookups final response = await get(Uri.parse(cpicLookupUrl)); if (response.statusCode != 200) throw Exception(); - - // the returned json is a list of lookups which we wish to individually map - // to a concrete LookupInformation instance, hence the cast to a List final json = jsonDecode(response.body) as List; final lookups = json.map(LookupInformation.fromJson); - final geneResults = UserData.instance.geneResults; - if (geneResults == null) throw Exception(); // use a HashMap for better time complexity final lookupsHashMap = HashMap.fromIterable( @@ -64,26 +51,18 @@ Future fetchAndSaveLookups() async { key: (lookup) => '${lookup.gene}__${lookup.variant}', value: (lookup) => lookup, ); - // ignore: omit_local_variable_types - final Map matchingLookups = {}; - // extract the matching lookups - for (final geneResult in geneResults.values) { - // the gene and the genotype build the key for the hashmap - final key = '${geneResult.gene}__${geneResult.variant}'; + final genotypeResults = {}; + // we know that labData is present because we check this in + // shouldUpdateGenotypeResults + for (final labResult in UserData.instance.geneResults!) { + final key = '${labResult.gene}__${labResult.variant}'; final lookup = lookupsHashMap[key]; if (lookup == null) continue; - // uncomment to print literal mismatches between lab/CPIC phenotypes - // if (geneResult.phenotype != lookup.phenotype) { - // print( - // 'Lab phenotype ${geneResult.phenotype} for ${geneResult.gene} (${geneResult.genotype}) is "${lookup.phenotype}" for CPIC'); - // } - matchingLookups[geneResult.gene] = lookup; + final genotypeResult = GenotypeResult.fromGenotypeData(labResult, lookup); + genotypeResults[genotypeResult.key] = genotypeResult; } - // uncomment to make user have CYP2D6 lookupkey 0.0 - // matchingLookups['CYP2D6'] = lookupsHashMap['CYP2D6__*100/*100']!; - - UserData.instance.lookups = matchingLookups; + UserData.instance.genotypeResults = genotypeResults; await UserData.save(); // Save datetime at which lookups were fetched @@ -91,10 +70,11 @@ Future fetchAndSaveLookups() async { await MetaData.save(); } -bool shouldFetchLookups() { - final lookupsPresent = UserData.instance.lookups?.isNotEmpty ?? false; - final diplotypesPresent = UserData.instance.geneResults?.isNotEmpty ?? false; - return (_isOutDated() || !lookupsPresent) && diplotypesPresent; +bool shouldUpdateGenotypeResults() { + final genotypeResultsPresent = + UserData.instance.geneResults?.isNotEmpty ?? false; + final labDataPresent = UserData.instance.geneResults?.isNotEmpty ?? false; + return (!genotypeResultsPresent || _isOutDated()) && labDataPresent; } bool shouldFetchDiplotypes() { diff --git a/app/lib/common/utilities/pdf_utils.dart b/app/lib/common/utilities/pdf_utils.dart index 38e7c3a2..c2dc7e01 100644 --- a/app/lib/common/utilities/pdf_utils.dart +++ b/app/lib/common/utilities/pdf_utils.dart @@ -124,9 +124,9 @@ List _buildDrugPart(Drug drug, BuildContext buildContext) { ]; } -String? _getPhenotypeInfo(String gene, Drug drug, BuildContext context) { +String? _getPhenotypeInfo(String genotypeKey, Drug drug, BuildContext context) { final phenotypeInformation = UserData.phenotypeInformationFor( - gene, + UserData.instance.genotypeResults!.findOrMissing(genotypeKey, context), context, drug: drug.name, thirdPerson: true, @@ -144,14 +144,18 @@ String? _getPhenotypeInfo(String gene, Drug drug, BuildContext context) { return '$phenotypeInformationText)'; } -String? _getActivityScoreInfo(String gene, Drug drug, BuildContext context) { +String? _getActivityScoreInfo( + String genotypeKey, + Drug drug, + BuildContext context, +) { final originalLookup = UserData.lookupFor( - gene, + genotypeKey, drug: drug.name, useOverwrite: false, ); final overwrittenLookup = UserData.lookupFor( - gene, + genotypeKey, drug: drug.name, useOverwrite: true, ); @@ -168,9 +172,9 @@ String _userInfoPerGene( BuildContext buildContext, ) { if (drug.guidelines.isEmpty) return buildContext.l10n.pdf_no_value; - return drug.guidelineGenes.map((gene) => - '$gene: ${ - getInfo(gene, drug, buildContext) ?? buildContext.l10n.pdf_no_value + return drug.guidelineGenotypes.map((genotypeKey) => + '$genotypeKey: ${ + getInfo(genotypeKey, drug, buildContext) ?? buildContext.l10n.pdf_no_value }' ).join(', '); } @@ -183,7 +187,7 @@ List _buildUserPart( ) { final patientGenotype = _userInfoPerGene( drug, - (gene, drug, context) => UserData.variantFor(gene), + (genotypeKey, drug, context) => UserData.variantFor(genotypeKey), buildContext, ); final patientPhenotype = _userInfoPerGene( @@ -198,7 +202,7 @@ List _buildUserPart( ); final allelesTested = _userInfoPerGene( drug, - (gene, drug, context) => UserData.allelesTestedFor(gene), + (genotypeKey, drug, context) => UserData.allelesTestedFor(genotypeKey), buildContext, ); final warningLevelIcons = { diff --git a/app/lib/common/widgets/drug_list/cubit.dart b/app/lib/common/widgets/drug_list/cubit.dart index 6d20f631..53c4a086 100644 --- a/app/lib/common/widgets/drug_list/cubit.dart +++ b/app/lib/common/widgets/drug_list/cubit.dart @@ -77,7 +77,7 @@ class FilterState { required this.query, required this.showInactive, required this.showWarningLevel, - required this.gene, + required this.genotypeKey, }); FilterState.initial() @@ -87,7 +87,7 @@ class FilterState { showWarningLevel: { for (var level in WarningLevel.values) level: true }, - gene: '', + genotypeKey: '', ); FilterState.from( @@ -95,7 +95,7 @@ class FilterState { String? query, bool? showInactive, Map? showWarningLevel, - String? gene, + String? genotypeKey, }) : this( query: query ?? other.query, showInactive: showInactive ?? other.showInactive, @@ -103,23 +103,23 @@ class FilterState { for (var level in WarningLevel.values) level: showWarningLevel?[level] ?? other.showWarningLevel[level]! }, - gene: gene ?? other.gene, + genotypeKey: genotypeKey ?? other.genotypeKey, ); - FilterState.forGene(String gene) + FilterState.forGenotypeKey(String genotypeKey) : this( query: '', showInactive: true, showWarningLevel: { for (var level in WarningLevel.values) level: true }, - gene: gene, + genotypeKey: genotypeKey, ); final String query; final bool showInactive; final Map showWarningLevel; - final String gene; + final String genotypeKey; bool isAccepted(Drug drug, ActiveDrugs activeDrugs, { required bool useDrugClass, @@ -135,7 +135,7 @@ class FilterState { drug.matches(query: query, useClass: useDrugClass) && (activeDrugs.contains(drug.name) || showInactive) && warningLevelMatches && - (gene.isBlank || (drug.guidelineGenes.contains(gene))); + (genotypeKey.isBlank || (drug.guidelineGenotypes.contains(genotypeKey))); return isDrugAccepted; } diff --git a/app/lib/drug/widgets/annotation_cards/guideline.dart b/app/lib/drug/widgets/annotation_cards/guideline.dart index f3ff93e1..305bb0d9 100644 --- a/app/lib/drug/widgets/annotation_cards/guideline.dart +++ b/app/lib/drug/widgets/annotation_cards/guideline.dart @@ -162,9 +162,12 @@ class GuidelineAnnotationCard extends StatelessWidget { style: TextStyle(fontStyle: FontStyle.italic), ); } else { - final geneDescriptions = drug.guidelineGenes.map((gene) { + final geneDescriptions = drug.guidelineGenotypes.map((genotypeKey) { final phenotypeInformation = UserData.phenotypeInformationFor( - gene, + UserData.instance.genotypeResults!.findOrMissing( + genotypeKey, + context, + ), context, drug: drug.name, ); @@ -172,7 +175,7 @@ class GuidelineAnnotationCard extends StatelessWidget { if (phenotypeInformation.adaptionText.isNotNullOrBlank) { description = '$description (${phenotypeInformation.adaptionText})'; } - return TableRowDefinition(gene, description); + return TableRowDefinition(genotypeKey, description); }); return buildTable(geneDescriptions.toList()); } diff --git a/app/lib/login/cubit.dart b/app/lib/login/cubit.dart index b7f806c8..d8979083 100644 --- a/app/lib/login/cubit.dart +++ b/app/lib/login/cubit.dart @@ -49,7 +49,7 @@ class LoginCubit extends Cubit { // get data await fetchAndSaveDiplotypesAndActiveDrugs( token, lab.starAllelesUrl.toString(), activeDrugs); - await fetchAndSaveLookups(); + await updateGenotypeResults(); await updateCachedDrugs(); diff --git a/app/lib/main.dart b/app/lib/main.dart index d868f73c..aa70d2bb 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -6,7 +6,7 @@ import 'common/module.dart'; Future main() async { await initServices(); // Maybe refresh lookups on app start - await fetchAndSaveLookups(); + await updateGenotypeResults(); runApp( ChangeNotifierProvider( create: (context) => ActiveDrugs(), diff --git a/app/lib/report/pages/gene.dart b/app/lib/report/pages/gene.dart index 0e50c15a..2a7b25c1 100644 --- a/app/lib/report/pages/gene.dart +++ b/app/lib/report/pages/gene.dart @@ -5,12 +5,13 @@ import '../../drug/widgets/module.dart'; @RoutePage() class GenePage extends HookWidget { - GenePage(this.lookup) + GenePage(this.genotypeResult) : cubit = DrugListCubit( - initialFilter: FilterState.forGene(lookup.gene), + initialFilter: + FilterState.forGenotypeKey(genotypeResult.key), ); - final LookupInformation lookup; + final GenotypeResult genotypeResult; final DrugListCubit cubit; @override @@ -20,7 +21,7 @@ class GenePage extends HookWidget { create: (context) => cubit, child: BlocBuilder( builder: (context, state) => pageScaffold( - title: context.l10n.gene_page_headline(lookup.gene), + title: context.l10n.gene_page_headline(genotypeResult.gene), body: [ Padding( padding: EdgeInsets.symmetric( @@ -31,9 +32,9 @@ class GenePage extends HookWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SubHeader( - context.l10n.gene_page_your_variant(lookup.gene), + context.l10n.gene_page_your_variant(genotypeResult.gene), tooltip: context.l10n - .gene_page_name_tooltip(lookup.gene), + .gene_page_name_tooltip(genotypeResult.gene), ), SizedBox(height: PharMeTheme.smallToMediumSpace), RoundedCard( @@ -49,16 +50,16 @@ class GenePage extends HookWidget { children: [ _buildRow( context.l10n.gene_page_genotype, - lookup.variant, + genotypeResult.variant, tooltip: context.l10n.gene_page_genotype_tooltip ), _buildPhenotypeRow(context), ], ), - if (canBeInhibited(lookup)) + if (canBeInhibited(genotypeResult)) ...buildDrugInteractionInfo( context, - lookup.gene, + genotypeResult, ), ], )), @@ -80,7 +81,7 @@ class GenePage extends HookWidget { TableRow _buildPhenotypeRow(BuildContext context) { final phenotypeInformation = UserData.phenotypeInformationFor( - lookup.gene, + genotypeResult, context, ); final phenotypeText = phenotypeInformation.adaptionText.isNotNullOrBlank @@ -115,15 +116,19 @@ class GenePage extends HookWidget { Padding(padding: EdgeInsets.fromLTRB(0, 4, 0, 4), child: Text(value)), ]); - List buildDrugInteractionInfo(BuildContext context, String gene) { + List buildDrugInteractionInfo( + BuildContext context, + GenotypeResult genotypeResult, + ) { final phenotypeInformation = UserData.phenotypeInformationFor( - gene, + genotypeResult, context, useLongPrefix: true, ); if (phenotypeInformation.adaptionText.isNotNullOrBlank) { - final furtherInhibitors = inhibitorsFor(gene).filter((drugName) => - !UserData.activeInhibitorsFor(gene).contains(drugName) + final furtherInhibitors = inhibitorsFor(genotypeResult.gene).filter( + (drugName) => + !UserData.activeInhibitorsFor(genotypeResult.gene).contains(drugName) ); var phenotypeInformationText = ''; if (phenotypeInformation.overwrittenPhenotypeText.isNotNullOrBlank) { @@ -156,7 +161,7 @@ class GenePage extends HookWidget { Text(context.l10n.gene_page_inhibitor_drugs), SizedBox(height: PharMeTheme.smallSpace), Text( - inhibitorsFor(gene).join(', ') + inhibitorsFor(genotypeResult.gene).join(', ') ), ]; } diff --git a/app/lib/report/pages/report.dart b/app/lib/report/pages/report.dart index bc878563..7bd34585 100644 --- a/app/lib/report/pages/report.dart +++ b/app/lib/report/pages/report.dart @@ -13,21 +13,19 @@ class ReportPage extends StatelessWidget { } Widget _buildReportPage(BuildContext context, ActiveDrugs activeDrugs) { + final presentGenes = Set.from(UserData.instance.genotypeResults!.values.map( + (genotypeResult) => genotypeResult.gene + )); + final missingGenes = Set.from(CachedDrugs.instance.allGuidelineGenes.filter( + (gene) => !presentGenes.contains(gene) + )); + final userGenotypes = [ + ...UserData.instance.genotypeResults!.values, + ...missingGenes.map( + (gene) => GenotypeResult.missingResult(gene, context), + ), + ].sortedBy((genotypeResult) => genotypeResult.gene); final hasActiveInhibitors = activeDrugs.names.any(isInhibitor); - final notTestedString = context.l10n.general_not_tested; - final userLookus = CachedDrugs.instance.allGuidelineGenes.map( - (gene) => UserData.instance.lookups![gene] ?? - // Add LookupInformation for unmatched lookup - LookupInformation( - gene: gene, - // phenotype will be overwritten with phenotype from lab or inhibited - // phenotype using PhenotypeInformation in GeneCard and GenePage - phenotype: notTestedString, - variant: UserData.instance.geneResults?[gene]?.variant ?? - notTestedString, - lookupkey: notTestedString - ) - ).sortedBy((lookup) => lookup.gene); return PopScope( canPop: false, child: unscrollablePageScaffold( @@ -36,9 +34,9 @@ class ReportPage extends StatelessWidget { children: [ PageDescription(context.l10n.report_content_explanation), scrollList( - userLookus.map((lookup) => GeneCard( - lookup, - key: Key('gene-card-${lookup.gene}') + userGenotypes.map((genotypeResult) => GeneCard( + genotypeResult, + key: Key('gene-card-${genotypeResult.key}') )).toList(), ), if (hasActiveInhibitors) PageIndicatorExplanation( @@ -55,21 +53,21 @@ class ReportPage extends StatelessWidget { } class GeneCard extends StatelessWidget { - const GeneCard(this.lookup, { super.key }); + const GeneCard(this.genotypeResult, { super.key }); - final LookupInformation lookup; + final GenotypeResult genotypeResult; @override Widget build(BuildContext context) { final phenotypeInformation = UserData.phenotypeInformationFor( - lookup.gene, + genotypeResult, context, ); final phenotypeText = phenotypeInformation.adaptionText.isNullOrBlank ? phenotypeInformation.phenotype : '${phenotypeInformation.phenotype}$drugInteractionIndicator'; final affectedDrugs = CachedDrugs.instance.drugs?.filter( - (drug) => drug.guidelineGenes.contains(lookup.gene) + (drug) => drug.guidelineGenotypes.contains(genotypeResult.key) ) ?? []; final warningLevelIndicators = WarningLevel.values.map( (warningLevel) { @@ -98,7 +96,9 @@ class GeneCard extends StatelessWidget { } ).toList(); return RoundedCard( - onTap: () => context.router.push(GeneRoute(lookup: lookup)), + onTap: () => context.router.push( + GeneRoute(genotypeResult: genotypeResult) + ), radius: 16, child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( @@ -106,7 +106,7 @@ class GeneCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - lookup.gene, + genotypeResult.gene, style: PharMeTheme.textTheme.titleMedium ), SizedBox(height: 8),