Skip to content

Commit

Permalink
feat(app): improve missing guideline handling
Browse files Browse the repository at this point in the history
  • Loading branch information
tamslo committed Sep 23, 2024
1 parent 42073f5 commit c98d97f
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 98 deletions.
14 changes: 0 additions & 14 deletions app/lib/common/models/drug/cached_drugs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,3 @@ Future<void> initCachedDrugs() async {
final cachedDrugs = Hive.box<CachedDrugs>(_boxName);
CachedDrugs._instance = cachedDrugs.get('data') ?? CachedDrugs();
}

extension CachedDrugsMethods on CachedDrugs {
Set<String> get allGuidelineGenes {
final guidelineGenes = <String>{};
if (CachedDrugs.instance.drugs != null) {
for (final drug in CachedDrugs.instance.drugs!) {
for (final guideline in drug.guidelines) {
guideline.lookupkey.keys.forEach(guidelineGenes.add);
}
}
}
return guidelineGenes;
}
}
9 changes: 5 additions & 4 deletions app/lib/common/models/drug/drug_inhibitors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ String _getPhenoconversionConsequence(
).capitalize()
: context.l10n.inhibitors_consequence_adapted(
genotypeResult.geneDisplayString,
genotypeResult.phenotype,
genotypeResult.phenotypeDisplayString(context),
displayConfig.userGenitive,
).capitalize();
}
Expand Down Expand Up @@ -217,9 +217,9 @@ bool isInhibited(
genotypeResult.gene,
drug: drug,
);
final originalPhenotype = genotypeResult.phenotypeDisplayString;
final originalPhenotype = genotypeResult.phenotype;
final phenotypeCanBeInhibited =
originalPhenotype.toLowerCase() != overwritePhenotype.toLowerCase();
originalPhenotype?.toLowerCase() != overwritePhenotype.toLowerCase();
return activeInhibitors.isNotEmpty && phenotypeCanBeInhibited;
}

Expand All @@ -246,10 +246,11 @@ MapEntry<String, String>? getOverwrittenLookup (
}

String possiblyAdaptedPhenotype(
BuildContext context,
GenotypeResult genotypeResult,
{ required String? drug }
) {
final originalPhenotype = genotypeResult.phenotypeDisplayString;
final originalPhenotype = genotypeResult.phenotypeDisplayString(context);
if (!isInhibited(genotypeResult, drug: drug)) {
return originalPhenotype;
}
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
Expand Up @@ -5,5 +5,5 @@ abstract class Genotype{
});

final String gene;
final String variant;
final String? variant;
}
13 changes: 11 additions & 2 deletions app/lib/common/models/userdata/genotype_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class GenotypeKey implements Genotype {
String gene;

@override
String variant;
String? variant;

bool get isGeneUnique {
final isDefinedAsNonUnique = definedNonUniqueGenes.contains(gene);
Expand All @@ -26,12 +26,21 @@ class GenotypeKey implements Genotype {
// heavily relies on "non-unique" gene HLA-B, for which the variant is
// in the format "[allele] [positive/negative]" (which currently is the only)
// relevant case for "non-unique" genes)
String get allele => isGeneUnique ? variant : variant.split(' ').first;
String? get allele => variant != null
? isGeneUnique ? variant : variant!.split(' ').first
: null;

String get value => isGeneUnique
? gene
: '$gene $allele';

static String extractGene(String genotypeKey) =>
genotypeKey.split(' ').first;

static String? maybeExtractVariant(String genotypeKey) {
final relevantGenotypeParts = genotypeKey.split(' ');
return relevantGenotypeParts.length > 1
? genotypeKey.removePrefix(relevantGenotypeParts.first)
: null;
}
}
66 changes: 27 additions & 39 deletions app/lib/common/models/userdata/genotype_result.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:collection/collection.dart';
import 'package:hive/hive.dart';

import '../../module.dart';
Expand All @@ -17,29 +16,22 @@ class GenotypeResult implements Genotype {

factory GenotypeResult.fromGenotypeData(
LabResult labResult,
LookupInformation lookup,
LookupInformation? lookup,
) => GenotypeResult(
gene: labResult.gene,
variant: labResult.variant,
phenotype: labResult.phenotype,
lookupkey: lookup.lookupkey,
lookupkey: lookup?.lookupkey,
allelesTested: labResult.allelesTested,
);

factory GenotypeResult.missingResult(String gene, BuildContext context) {
final potentialLabResultWithoutLookups =
UserData.instance.labData?.firstWhereOrNull(
(labResult) => labResult.gene == gene
);
factory GenotypeResult.missingResult(String gene, {String? variant}) {
return GenotypeResult(
gene: gene,
variant: potentialLabResultWithoutLookups?.variant
?? context.l10n.general_not_tested,
phenotype: potentialLabResultWithoutLookups?.phenotype
?? context.l10n.general_not_tested,
lookupkey: context.l10n.general_not_tested,
allelesTested: potentialLabResultWithoutLookups?.allelesTested
?? context.l10n.general_not_tested,
variant: variant,
phenotype: null,
lookupkey: null,
allelesTested: null,
);
}

Expand All @@ -48,39 +40,35 @@ class GenotypeResult implements Genotype {
String gene;
@override
@HiveField(1)
String variant;
String? variant;
@HiveField(2)
String phenotype;
String? phenotype;
@HiveField(3)
String lookupkey;
String? lookupkey;
@HiveField(4)
String allelesTested;
String? allelesTested;

GenotypeKey get key => GenotypeKey.fromGenotype(this);

String get geneDisplayString => key.value;

String get variantDisplayString => key.allele;
String? get variantDisplayString => key.allele;

String _removeAllele(String textWithAllele) =>
textWithAllele.removePrefix(key.allele).trim().capitalize();

String get phenotypeDisplayString => key.isGeneUnique
? phenotype
: _removeAllele(phenotype);
String get genotypeDisplayString => key.isGeneUnique
? variant
: _removeAllele(variant);
}
String _removeAlleleOrNull(String textWithAllele) =>
key.allele != null
? textWithAllele.removePrefix(key.allele!).trim().capitalize()
: textWithAllele;

String _getDisplayString(BuildContext context, String? text) {
final displayString = text ?? context.l10n.general_not_tested;
return key.isGeneUnique
? displayString
: _removeAlleleOrNull(displayString);
}

extension FindGenotypeResultByKey on Map<String, GenotypeResult> {
bool isMissing(String genotypeKey) => this[genotypeKey] == null;
String phenotypeDisplayString(BuildContext context) =>
_getDisplayString(context, phenotype);

GenotypeResult findOrMissing(String genotypeKey, BuildContext context) =>
isMissing(genotypeKey)
? GenotypeResult.missingResult(
GenotypeKey.extractGene(genotypeKey),
context,
)
: this[genotypeKey]!;
String genotypeDisplayString(BuildContext context) =>
_getDisplayString(context, variant);
}
1 change: 1 addition & 0 deletions app/lib/common/utilities/drug_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Future<void> updateCachedDrugs() async {
CachedDrugs.instance.drugs = data.drugs;
CachedDrugs.instance.version = data.version;
await CachedDrugs.save();
await updateGenotypeResults();
if (previousVersion != null) {
final context = PharMeApp.navigatorKey.currentContext;
if (context != null) {
Expand Down
44 changes: 35 additions & 9 deletions app/lib/common/utilities/genome_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,35 @@ String formatLookupMapKey(String gene, String variant) {
return '${gene}__$variant';
}

Map<String, GenotypeResult> _initializeGenotypeResultKeys() {
final emptyGenotypeResults = <String, GenotypeResult>{};
for (final drug in CachedDrugs.instance.drugs ?? <Drug>[]) {
for (final guideline in drug.guidelines) {
for (final gene in guideline.lookupkey.keys) {
for (final variant in guideline.lookupkey[gene]!) {
final skipLookup = variant == SpecialLookup.anyNotHandled.value ||
variant == SpecialLookup.noResult.value;
if (skipLookup) continue;
final currentGenotypeKey = GenotypeKey(gene, variant);
final variantIsRelevant = definedNonUniqueGenes.contains(gene);
emptyGenotypeResults[currentGenotypeKey.value] =
GenotypeResult.missingResult(
gene,
variant: variantIsRelevant ? currentGenotypeKey.allele : null,
);
}
}
}
}
return emptyGenotypeResults;
}

Future<void> updateGenotypeResults() async {
final skipUpdate = !shouldUpdateGenotypeResults();
if (skipUpdate) return;

final genotypeResults = _initializeGenotypeResultKeys();

// fetch lookups
final response = await get(Uri.parse(cpicLookupUrl));
if (response.statusCode != 200) throw Exception();
Expand All @@ -60,17 +86,14 @@ Future<void> updateGenotypeResults() async {
lookupsHashMap[variantKey] = lookup;
if (variantKey != lookupKey) lookupsHashMap[lookupKey] = lookup;
}
final genotypeResults = <String, GenotypeResult>{};
// we know that labData is present because we check this in
// shouldUpdateGenotypeResults
for (final labResult in UserData.instance.labData!) {

for (final labResult in UserData.instance.labData ?? []) {
final lookupMapKey = formatLookupMapKey(labResult.gene, labResult.variant);
final lookup = lookupsHashMap[lookupMapKey];
if (lookup == null) continue;
final genotypeResult = GenotypeResult.fromGenotypeData(labResult, lookup);
if (!genotypeResults.keys.contains(genotypeResult.key.value)) continue;
genotypeResults[genotypeResult.key.value] = genotypeResult;
}

UserData.instance.genotypeResults = genotypeResults;
await UserData.save();

Expand All @@ -80,13 +103,16 @@ Future<void> updateGenotypeResults() async {
}

bool shouldUpdateGenotypeResults() {
final genotypeResultsPresent =
UserData.instance.labData?.isNotEmpty ?? false;
final genotypeResultsMissing =
UserData.instance.genotypeResults?.isEmpty ?? true;
final lookupsAreOutdated = MetaData.instance.lookupsLastFetchDate == null ||
DateTime.now().difference(MetaData.instance.lookupsLastFetchDate!) >
cpicMaxCacheTime;
final labDataPresent = UserData.instance.labData?.isNotEmpty ?? false;
return labDataPresent && (!genotypeResultsPresent || lookupsAreOutdated);
final cachedDrugsPresent = CachedDrugs.instance.drugs?.isNotEmpty ?? false;
return labDataPresent &&
cachedDrugsPresent &&
(genotypeResultsMissing || lookupsAreOutdated);
}

bool shouldFetchDiplotypes() {
Expand Down
14 changes: 4 additions & 10 deletions app/lib/common/utilities/pdf_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,23 +133,17 @@ List<pw.Widget> _buildDrugPart(Drug drug, BuildContext buildContext) {
}

String? _getPhenotypeInfo(String genotypeKey, Drug drug, BuildContext context) {
final genotypeResult = UserData.instance.genotypeResults!.findOrMissing(
genotypeKey,
context,
);
final genotypeResult = UserData.instance.genotypeResults![genotypeKey]!;
if (!isInhibited(genotypeResult, drug: drug.name)) {
return genotypeResult.phenotypeDisplayString;
return genotypeResult.phenotypeDisplayString(context);
}
return possiblyAdaptedPhenotype(genotypeResult, drug: drug.name);
return possiblyAdaptedPhenotype(context, genotypeResult, drug: drug.name);
}

String? _getPhenoconversionInfo(Drug drug, BuildContext context) {
if (drug.guidelines.isEmpty) return null;
final genotypeResults = drug.guidelineGenotypes.map((genotypeKey) =>
UserData.instance.genotypeResults!.findOrMissing(
genotypeKey,
context,
)
UserData.instance.genotypeResults![genotypeKey]!
).toList();
return '$drugInteractionIndicator ${inhibitionTooltipText(
context,
Expand Down
6 changes: 2 additions & 4 deletions app/lib/drug/widgets/annotation_cards/guideline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,13 @@ class GuidelineAnnotationCard extends StatelessWidget {
];
} else {
final genotypeResults = drug.guidelineGenotypes.map((genotypeKey) =>
UserData.instance.genotypeResults!.findOrMissing(
genotypeKey,
context,
)
UserData.instance.genotypeResults![genotypeKey]!
).toList();
final geneDescriptions = genotypeResults.map((genotypeResult) =>
TableRowDefinition(
genotypeResult.geneDisplayString,
possiblyAdaptedPhenotype(
context,
genotypeResult,
drug: drug.name,
),
Expand Down
5 changes: 3 additions & 2 deletions app/lib/report/pages/gene.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class GenePage extends HookWidget {
children: [
_buildRow(
context.l10n.gene_page_genotype,
genotypeResult.variantDisplayString,
genotypeResult.variantDisplayString ??
context.l10n.general_not_tested,
tooltip: context.l10n.gene_page_genotype_tooltip
),
_buildPhenotypeRow(context),
Expand Down Expand Up @@ -97,7 +98,7 @@ class GenePage extends HookWidget {
TableRow _buildPhenotypeRow(BuildContext context) {
return _buildRow(
context.l10n.gene_page_phenotype,
possiblyAdaptedPhenotype(genotypeResult, drug: null),
possiblyAdaptedPhenotype(context, genotypeResult, drug: null),
tooltip:
context.l10n.gene_page_phenotype_tooltip,
);
Expand Down
15 changes: 2 additions & 13 deletions app/lib/report/pages/report.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,7 @@ 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),
),
];
final userGenotypes = UserData.instance.genotypeResults!.values;
final warningLevelCounts = <String, WarningLevelCounts>{};
for (final genotypeResult in userGenotypes) {
warningLevelCounts[genotypeResult.key.value] = {};
Expand Down Expand Up @@ -141,7 +130,7 @@ class GeneCard extends StatelessWidget {
),
SizedBox(height: 8),
Text(
possiblyAdaptedPhenotype(genotypeResult, drug: null),
possiblyAdaptedPhenotype(context, genotypeResult, drug: null),
style: PharMeTheme.textTheme.titleSmall,
),
],
Expand Down

0 comments on commit c98d97f

Please sign in to comment.