From c89f418ab365427ee99277363bc16a0782dc368c Mon Sep 17 00:00:00 2001 From: tamslo Date: Thu, 5 Dec 2024 18:59:41 +0100 Subject: [PATCH] feat(app): improve inhibitor information (WIP) --- .../common/models/drug/drug_inhibitors.dart | 176 +----------------- app/lib/common/utilities/pdf_utils.dart | 12 +- .../common/widgets/gene_modulator_list.dart | 18 +- app/lib/common/widgets/module.dart | 1 + .../widgets/phenoconversion_explanation.dart | 109 +++++++++++ .../widgets/annotation_cards/guideline.dart | 17 +- app/lib/l10n/app_en.arb | 9 - app/lib/report/pages/gene.dart | 7 +- 8 files changed, 146 insertions(+), 203 deletions(-) create mode 100644 app/lib/common/widgets/phenoconversion_explanation.dart diff --git a/app/lib/common/models/drug/drug_inhibitors.dart b/app/lib/common/models/drug/drug_inhibitors.dart index 32613d25..11796f49 100644 --- a/app/lib/common/models/drug/drug_inhibitors.dart +++ b/app/lib/common/models/drug/drug_inhibitors.dart @@ -55,132 +55,13 @@ bool _isInhibitorOfType( return influencingDrugs.contains(drugName); } -bool _isModerateInhibitor(String drugName) { - return _isInhibitorOfType(drugName, moderateDrugInhibitors); -} - -class _DisplayConfig { - _DisplayConfig({ - required this.partSeparator, - required this.userSalutation, - required this.userGenitive, - required this.useConsult, - }); - - final String partSeparator; - final String userSalutation; - final String userGenitive; - final bool useConsult; -} - -_DisplayConfig _getDisplayConfig( - BuildContext context, - { required bool userFacing } -) { - final displayConfigs = { - true: _DisplayConfig( - partSeparator: '\n\n', - userSalutation: context.l10n.inhibitor_direct_salutation, - userGenitive: context.l10n.inhibitor_direct_salutation_genitive, - useConsult: true, - ), - false: _DisplayConfig( - partSeparator: ' ', - userSalutation: context.l10n.inhibitor_third_person_salutation, - userGenitive: context.l10n.inhibitor_third_person_salutation_genitive, - useConsult: false, - ), - }; - return displayConfigs[userFacing]!; -} - -String _getPhenoconversionConsequence( - BuildContext context, - GenotypeResult genotypeResult, - { - required String? drug, - required _DisplayConfig displayConfig, - } -) { - final activeInhibitors = _activeInhibitorsFor( - genotypeResult.gene, - drug: drug, - ); - return activeInhibitors.all(_isModerateInhibitor) - ? context.l10n.inhibitors_consequence_not_adapted( - genotypeResult.geneDisplayString, - displayConfig.userGenitive, - ).capitalize() - : context.l10n.inhibitors_consequence_adapted( - genotypeResult.geneDisplayString, - genotypeResult.phenotypeDisplayString(context), - displayConfig.userGenitive, - ).capitalize(); -} - -String _getInhibitorsString( - BuildContext context, - GenotypeResult genotypeResult, - { required String? drug } -) { - return context.l10n.inhibitors_tooltip(enumerationWithAnd( - getDrugsWithBrandNames(_activeInhibitorsFor( - genotypeResult.gene, - drug: drug, - )), - context, - )); -} - -String _inhibitionTooltipText( - BuildContext context, - GenotypeResult genotypeResult, - { - required String? drug, - required _DisplayConfig displayConfig, - } -) { - final inhibitorsString = _getInhibitorsString( - context, - genotypeResult, - drug: drug, - ); - final consequence = _getPhenoconversionConsequence( - context, - genotypeResult, - drug: drug, - displayConfig: displayConfig, - ); - return '$consequence${ - displayConfig.useConsult ? ' ${context.l10n.consult_text}' : '' - }${displayConfig.partSeparator}$inhibitorsString'; -} +// Public helper functions -Widget _drugInteractionTemplate( - BuildContext context, - String tooltipText, - _DisplayConfig displayConfig, -) { - return PrettyExpansionTile( - title: buildTable([ - TableRowDefinition( - drugInteractionIndicator, - context.l10n.inhibitor_message( - displayConfig.userSalutation, - displayConfig.userGenitive, - ), - )], - boldHeader: false, - ), - titlePadding: EdgeInsets.zero, - childrenPadding: EdgeInsets.all(PharMeTheme.smallSpace), - children: [ - Text(tooltipText), - ], - ); +bool isModerateInhibitor(String drugName) { + return _isInhibitorOfType(drugName, moderateDrugInhibitors); } -List _activeInhibitorsFor(String gene, { required String? drug }) { +List activeInhibitorsFor(String gene, { required String? drug }) { return UserData.instance.activeDrugNames == null ? [] : UserData.instance.activeDrugNames!.filter( @@ -190,8 +71,6 @@ List _activeInhibitorsFor(String gene, { required String? drug }) { ).toList(); } -// Public helper functions - final inhibitableGenes = List.from({ ...strongDrugInhibitors.keys, ...moderateDrugInhibitors.keys, @@ -219,7 +98,7 @@ bool isInhibited( GenotypeResult genotypeResult, { required String? drug } ) { - final activeInhibitors = _activeInhibitorsFor( + final activeInhibitors = activeInhibitorsFor( genotypeResult.gene, drug: drug, ); @@ -269,48 +148,3 @@ String possiblyAdaptedPhenotype( } return '$overwritePhenotype$drugInteractionIndicator'; } - -String inhibitionTooltipText( - BuildContext context, - List genotypeResults, - { - required String? drug, - required bool userFacing, - } -) { - final displayConfig = _getDisplayConfig(context, userFacing: userFacing); - final inhibitedGenotypeResults = genotypeResults.filter( - (genotypeResult) => isInhibited(genotypeResult, drug: drug) - ).toList(); - var tooltipText = ''; - for (final (index, genotypeResult) in inhibitedGenotypeResults.indexed) { - final separator = index == 0 ? '' : displayConfig.partSeparator; - // ignore: use_string_buffers - tooltipText = '$tooltipText$separator${ - _inhibitionTooltipText( - context, - genotypeResult, - drug: drug, - displayConfig: displayConfig, - ) - }'; - } - return tooltipText; -} - -Widget buildDrugInteractionInfo( - BuildContext context, - List genotypeResults, - { required String? drug } -) { - return _drugInteractionTemplate( - context, - inhibitionTooltipText( - context, - genotypeResults, - drug: drug, - userFacing: true, - ), - _getDisplayConfig(context, userFacing: true), - ); -} diff --git a/app/lib/common/utilities/pdf_utils.dart b/app/lib/common/utilities/pdf_utils.dart index b9a27b26..39daf7cc 100644 --- a/app/lib/common/utilities/pdf_utils.dart +++ b/app/lib/common/utilities/pdf_utils.dart @@ -142,14 +142,16 @@ String? _getPhenotypeInfo(String genotypeKey, Drug drug, BuildContext context) { String? _getPhenoconversionInfo(Drug drug, BuildContext context) { if (drug.guidelines.isEmpty) return null; - final genotypeResults = drug.guidelineGenotypes.map((genotypeKey) => + final inhibitedGenotypes = drug.guidelineGenotypes.map((genotypeKey) => UserData.instance.genotypeResults![genotypeKey]! + ).filter( + (genotypeResult) => isInhibited(genotypeResult, drug: drug.name) ).toList(); - final tooltipText = inhibitionTooltipText( + if (inhibitedGenotypes.isEmpty) return null; + final tooltipText = getExpertPhenoconversionExplanation( context, - genotypeResults, - drug: drug.name, - userFacing: false, + inhibitedGenotypes, + drug.name, ); if (tooltipText.isBlank) return null; return '$drugInteractionIndicator $tooltipText'; diff --git a/app/lib/common/widgets/gene_modulator_list.dart b/app/lib/common/widgets/gene_modulator_list.dart index ec91096b..b5844f9c 100644 --- a/app/lib/common/widgets/gene_modulator_list.dart +++ b/app/lib/common/widgets/gene_modulator_list.dart @@ -1,18 +1,28 @@ import '../module.dart'; class GeneModulatorList extends StatelessWidget { - const GeneModulatorList({super.key, required this.geneName, this.drugNames}); + const GeneModulatorList({ + super.key, + required this.geneName, + this.onlyActiveDrugs = false, + this.displayedDrug, + }); final String geneName; - final List? drugNames; + final bool onlyActiveDrugs; + final String? displayedDrug; List _getModulatorDrugNames( Map>modulatorDefinition, String geneName, ) { final allModulatorDrugs = modulatorDefinition[geneName]!.keys.toList(); - if (drugNames != null) { - return allModulatorDrugs.filter(drugNames!.contains).toList(); + if (onlyActiveDrugs) { + final activeModulators = activeInhibitorsFor( + geneName, + drug: displayedDrug, + ); + return allModulatorDrugs.filter(activeModulators.contains).toList(); } return allModulatorDrugs; } diff --git a/app/lib/common/widgets/module.dart b/app/lib/common/widgets/module.dart index e0b25bdd..3ab80e31 100644 --- a/app/lib/common/widgets/module.dart +++ b/app/lib/common/widgets/module.dart @@ -23,6 +23,7 @@ export 'page_description.dart'; export 'page_indicator_explanation.dart'; export 'page_scaffold.dart'; export 'pharme_logo_page.dart'; +export 'phenoconversion_explanation.dart'; export 'pretty_expansion_tile.dart'; export 'radiant_gradient_mask.dart'; export 'resized_icon_button.dart'; diff --git a/app/lib/common/widgets/phenoconversion_explanation.dart b/app/lib/common/widgets/phenoconversion_explanation.dart new file mode 100644 index 00000000..67ae5956 --- /dev/null +++ b/app/lib/common/widgets/phenoconversion_explanation.dart @@ -0,0 +1,109 @@ +import '../module.dart'; + +class _PhenoconversionDisplayConfig { + _PhenoconversionDisplayConfig({ + required this.partSeparator, + required this.userSalutation, + required this.userGenitive, + required this.useConsult, + }); + + final String partSeparator; + final String userSalutation; + final String userGenitive; + final bool useConsult; +} + +String getExpertPhenoconversionExplanation( + BuildContext context, + List inhibitedGenotypes, + String drugName, +) { + final displayConfig = _PhenoconversionDisplayConfig( + partSeparator: ' ', + userSalutation: context.l10n.inhibitor_third_person_salutation, + userGenitive: context.l10n.inhibitor_third_person_salutation_genitive, + useConsult: false, + ); + return inhibitedGenotypes.flatMap((genotypeResult) => [ + _getPhenoconversionDetailText(context, genotypeResult, drug: drugName, displayConfig: displayConfig), + // TODO: get list + ]).join(displayConfig.partSeparator); +} + +class PhenoconversionExplanation extends StatelessWidget { + const PhenoconversionExplanation({ + super.key, + required this.inhibitedGenotypes, + required this.drugName, + }); + + final List inhibitedGenotypes; + final String? drugName; + + @override + Widget build(BuildContext context) { + final displayConfig = _PhenoconversionDisplayConfig( + partSeparator: '\n\n', + userSalutation: context.l10n.inhibitor_direct_salutation, + userGenitive: context.l10n.inhibitor_direct_salutation_genitive, + useConsult: true, + ); + return PrettyExpansionTile( + title: buildTable([ + TableRowDefinition( + drugInteractionIndicator, + context.l10n.inhibitor_message( + displayConfig.userSalutation, + displayConfig.userGenitive, + ), + )], + boldHeader: false, + ), + titlePadding: EdgeInsets.zero, + childrenPadding: EdgeInsets.all(PharMeTheme.mediumSpace), + children: inhibitedGenotypes.flatMap( + (genotypeResult) => [ + Text(_getPhenoconversionDetailText( + context, + genotypeResult, + drug: drugName, + displayConfig: displayConfig, + )), + GeneModulatorList( + geneName: genotypeResult.gene, + onlyActiveDrugs: true, + displayedDrug: drugName, + ), + ] + ).toList(), + ); + } +} + +String _getPhenoconversionDetailText( + BuildContext context, + GenotypeResult genotypeResult, + { + required String? drug, + required _PhenoconversionDisplayConfig displayConfig, + }) +{ + final activeInhibitors = activeInhibitorsFor( + genotypeResult.gene, + drug: drug, + ); + final consequence = activeInhibitors.all(isModerateInhibitor) + ? context.l10n.inhibitors_consequence_not_adapted( + genotypeResult.geneDisplayString, + displayConfig.userGenitive, + ).capitalize() + : context.l10n.inhibitors_consequence_adapted( + genotypeResult.geneDisplayString, + genotypeResult.phenotypeDisplayString(context), + displayConfig.userGenitive, + ).capitalize(); + return '$consequence${ + displayConfig.useConsult ? ' ${context.l10n.consult_text}' : '' + }'; +} diff --git a/app/lib/drug/widgets/annotation_cards/guideline.dart b/app/lib/drug/widgets/annotation_cards/guideline.dart index dc24938b..ee1f9195 100644 --- a/app/lib/drug/widgets/annotation_cards/guideline.dart +++ b/app/lib/drug/widgets/annotation_cards/guideline.dart @@ -163,18 +163,15 @@ class GuidelineAnnotationCard extends StatelessWidget { Widget? _maybeBuildPhenoconversionInformation(BuildContext context) { final genotypeResults = _getGenotypeResults(); - if ( - genotypeResults != null && - genotypeResults.any( - (genotypeResult) => isInhibited(genotypeResult, drug: drug.name) - ) - ) { + final inhibitedGenotypes = genotypeResults?.filter( + (genotypeResult) => isInhibited(genotypeResult, drug: drug.name) + ).toList() ?? []; + if (inhibitedGenotypes.isNotEmpty) { return Padding( padding: EdgeInsets.only(top: PharMeTheme.smallSpace), - child: buildDrugInteractionInfo( - context, - genotypeResults, - drug: drug.name, + child: PhenoconversionExplanation( + inhibitedGenotypes: inhibitedGenotypes, + drugName: drug.name, ), ); } diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 467251d3..8ba400f4 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -198,15 +198,6 @@ } } }, - "inhibitors_tooltip": "Current interacting medications: {inhibitors}.", - "@inhibitors_tooltip": { - "placeholders": { - "inhibitors": { - "type": "String", - "example": "bupropion" - } - } - }, "consult_text": "Consult your pharmacist or doctor for more information.", "@consult_text": {}, diff --git a/app/lib/report/pages/gene.dart b/app/lib/report/pages/gene.dart index f252cb89..454e4033 100644 --- a/app/lib/report/pages/gene.dart +++ b/app/lib/report/pages/gene.dart @@ -74,10 +74,9 @@ class GenePage extends HookWidget { ), if (isInhibited(genotypeResult, drug: null)) ...[ SizedBox(height: PharMeTheme.smallSpace), - buildDrugInteractionInfo( - context, - [genotypeResult], - drug: null, + PhenoconversionExplanation( + inhibitedGenotypes: [genotypeResult], + drugName: null, ), ] ],