From 252877d6f7a229114bcb31fa9a5d6f2ee14713f1 Mon Sep 17 00:00:00 2001 From: Jakub Monhart Date: Tue, 4 Jul 2023 09:54:53 +0200 Subject: [PATCH] Update README and rename 'issues' in summary to 'details_and_issues'. --- documentation/README.md | 17 +++++ tests/web/test_do_crossmatch_api.py | 54 +++++++-------- .../base_patient_swagger.py | 12 ++-- .../crossmatch/crossmatch_in_swagger.py | 6 +- txmatching/utils/hla_system/hla_crossmatch.py | 69 +++++++++---------- .../model/crossmatchSummaryGenerated.ts | 8 ++- txmatching/web/swagger/swagger.json | 14 ++-- txmatching/web/swagger/swagger.yaml | 26 ++++--- 8 files changed, 114 insertions(+), 92 deletions(-) diff --git a/documentation/README.md b/documentation/README.md index 3afac01f4..a94dac5fb 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -644,6 +644,23 @@ Priority is arranged in the following way: For example, if some antibodies crossmatched at the `HIGH RES` level, others at the `SPLIT` level, then the summary match type would be `HIGH RES` as the most important of these. +As the eight possible match types above do not always describe the situation exactly, we do not display it in the summary. +Instead, we send a message describing the crossmatch type together with some possible issues in the `details_and_issues` +property of the summary. +We do this mainly to distinguish two types of HIGH_RES match that can occur. + +If the HIGH_RES match occurs due to a single HIGH RES antibody matching to single HIGH RES antigen, we send a message: +`There is a single positively crossmatched HIGH RES HLA type - HIGH RES antibody pair.` +, if there are multiple antibody - antigen pairs, we send a different message informing about this: `SPLIT HLA code +displayed in summary, but there are multiple positive crossmatches of HIGH RES HLA type - HIGH RES antibody pairs.` + +HIGH_RES match can also occur if all positive HIGH RES antibodies correspond to an antigen on SPLIT level (satisfying +some more conditions, described as case 2. and 3. in the `HIGH_RES` match description above in this documentation). +In this case we send a message saying: `There is no exact match, but some of the HIGH RES antibodies corresponding to +the summary HLA code on SPLIT or BROAD level are positive.` + +The rest of match types are described with corresponding messages in a straightforward manner. + #### How to choose summary HLA code? For summary, we would like to take into account just frequent codes among all assumed HLA types. When this code is the only one, then everything is quite simple, we consider this code as a summary, diff --git a/tests/web/test_do_crossmatch_api.py b/tests/web/test_do_crossmatch_api.py index f9253ad27..98574dc0b 100644 --- a/tests/web/test_do_crossmatch_api.py +++ b/tests/web/test_do_crossmatch_api.py @@ -7,7 +7,7 @@ from txmatching.patients.hla_code import HLACode from txmatching.patients.hla_model import HLATypeWithFrequencyRaw from txmatching.utils.hla_system.hla_crossmatch import CrossmatchSummary, \ - CadaverousCrossmatchIssueDetail + CadaverousCrossmatchDetailsIssues from txmatching.utils.hla_system.hla_preparation_utils import create_hla_type_with_frequency from txmatching.utils.hla_system.hla_transformations.parsing_issue_detail import \ ParsingIssueDetail @@ -664,12 +664,12 @@ def test_do_crossmatch_for_assumed_hla_types(self): res_assumed_hla_typing = [antibody_match['assumed_hla_types'] for antibody_match in res.json['hla_to_antibody']] res_crossmatch_issues = [issue for antibody_match in res.json['hla_to_antibody'] - for issue in antibody_match['summary']['issues']] + for issue in antibody_match['summary']['details_and_issues']] expected_assumed_hla_typing = [ [asdict(create_hla_type_with_frequency(HLATypeWithFrequencyRaw('DPA1*01:03', True))), asdict(create_hla_type_with_frequency(HLATypeWithFrequencyRaw('DPA1*01:06', True)))] ] - self.assertFalse(CadaverousCrossmatchIssueDetail.RARE_ALLELE_POSITIVE_CROSSMATCH + self.assertFalse(CadaverousCrossmatchDetailsIssues.RARE_ALLELE_POSITIVE_CROSSMATCH in res_crossmatch_issues) self.assertTrue(len(res_assumed_hla_typing) == len(json['potential_donor_hla_typing'])) self.assertCountEqual(expected_assumed_hla_typing, @@ -696,13 +696,13 @@ def test_do_crossmatch_for_assumed_hla_types(self): res_assumed_hla_types = res.json['hla_to_antibody'][0]['assumed_hla_types'] res_crossmatch_issues = [issue for antibody_match in res.json['hla_to_antibody'] - for issue in antibody_match['summary']['issues']] + for issue in antibody_match['summary']['details_and_issues']] # Here, the code is evaluated as infrequent because the potential typing comprises a mix of # frequent and infrequent codes, and a crossmatch occurred only with the infrequent one. expected_assumed_hla_types = \ [asdict(create_hla_type_with_frequency(HLATypeWithFrequencyRaw('DPA1*01:04', False))), asdict(create_hla_type_with_frequency(HLATypeWithFrequencyRaw('DPA1*01:03', True)))] - self.assertTrue(CadaverousCrossmatchIssueDetail.RARE_ALLELE_POSITIVE_CROSSMATCH + self.assertTrue(CadaverousCrossmatchDetailsIssues.RARE_ALLELE_POSITIVE_CROSSMATCH in res_crossmatch_issues) self.assertTrue(len(res_assumed_hla_typing) == len(json['potential_donor_hla_typing'])) self.assertCountEqual(expected_assumed_hla_types, @@ -762,8 +762,8 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res=None, split='DPA1'), mfi=2075, - issues=[CadaverousCrossmatchIssueDetail.MULTIPLE_HIGH_RES_MATCH, - CadaverousCrossmatchIssueDetail.ANTIBODIES_MIGHT_NOT_BE_DSA] + details_and_issues=[CadaverousCrossmatchDetailsIssues.MULTIPLE_HIGH_RES_MATCH, + CadaverousCrossmatchDetailsIssues.ANTIBODIES_MIGHT_NOT_BE_DSA] ) self.assertEqual(asdict(expected_summary), res.json['hla_to_antibody'][0]['summary']) @@ -778,7 +778,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=antibody_match_without_crossmatched_antibodies['assumed_hla_types'][0], mfi=None, - issues=[CadaverousCrossmatchIssueDetail.NO_MATCHING_ANTIBODY] + details_and_issues=[CadaverousCrossmatchDetailsIssues.NO_MATCHING_ANTIBODY] ) self.assertCountEqual( asdict(expected_summary), @@ -809,8 +809,8 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res=None, split='DPA1'), mfi=2150, - issues=[CadaverousCrossmatchIssueDetail.MULTIPLE_HIGH_RES_MATCH, - CadaverousCrossmatchIssueDetail.ANTIBODIES_MIGHT_NOT_BE_DSA] + details_and_issues=[CadaverousCrossmatchDetailsIssues.MULTIPLE_HIGH_RES_MATCH, + CadaverousCrossmatchDetailsIssues.ANTIBODIES_MIGHT_NOT_BE_DSA] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -839,8 +839,8 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res='DPA1*01:03', split='DPA1'), mfi=2200, - issues=[CadaverousCrossmatchIssueDetail.HIGH_RES_MATCH, - CadaverousCrossmatchIssueDetail.ANTIBODIES_MIGHT_NOT_BE_DSA] + details_and_issues=[CadaverousCrossmatchDetailsIssues.HIGH_RES_MATCH, + CadaverousCrossmatchDetailsIssues.ANTIBODIES_MIGHT_NOT_BE_DSA] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -868,7 +868,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res=None, split='DPA1'), mfi=2100, - issues=[CadaverousCrossmatchIssueDetail.RARE_ALLELE_POSITIVE_CROSSMATCH] + details_and_issues=[CadaverousCrossmatchDetailsIssues.RARE_ALLELE_POSITIVE_CROSSMATCH] ) self.assertEqual(res_summaries, [asdict(expected_summary)]) @@ -899,7 +899,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res=None, split='DPA1'), mfi=3333, - issues=[CadaverousCrossmatchIssueDetail.SPLIT_BROAD_MATCH] + details_and_issues=[CadaverousCrossmatchDetailsIssues.SPLIT_BROAD_MATCH] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -930,7 +930,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res=None, split='DPA1'), mfi=3500, - issues=[CadaverousCrossmatchIssueDetail.HIGH_RES_MATCH_ON_SPLIT_LEVEL] + details_and_issues=[CadaverousCrossmatchDetailsIssues.HIGH_RES_MATCH_ON_SPLIT_LEVEL] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -960,7 +960,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA2', high_res=None, split='DPA2'), mfi=5000, - issues=[CadaverousCrossmatchIssueDetail.HIGH_RES_MATCH_ON_SPLIT_LEVEL] + details_and_issues=[CadaverousCrossmatchDetailsIssues.HIGH_RES_MATCH_ON_SPLIT_LEVEL] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -991,8 +991,8 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='B7', high_res=None, split='B7'), mfi=3000, - issues=[CadaverousCrossmatchIssueDetail.MULTIPLE_HIGH_RES_MATCH, - CadaverousCrossmatchIssueDetail.ANTIBODIES_MIGHT_NOT_BE_DSA] + details_and_issues=[CadaverousCrossmatchDetailsIssues.MULTIPLE_HIGH_RES_MATCH, + CadaverousCrossmatchDetailsIssues.ANTIBODIES_MIGHT_NOT_BE_DSA] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -1023,8 +1023,8 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='B7', high_res=None, split='B7'), mfi=3000, - issues=[CadaverousCrossmatchIssueDetail.MULTIPLE_HIGH_RES_MATCH, - CadaverousCrossmatchIssueDetail.AMBIGUITY_IN_HLA_TYPIZATION] + details_and_issues=[CadaverousCrossmatchDetailsIssues.MULTIPLE_HIGH_RES_MATCH, + CadaverousCrossmatchDetailsIssues.AMBIGUITY_IN_HLA_TYPIZATION] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -1046,7 +1046,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res='DPA1*01:03', split='DPA1'), mfi=2000, - issues=[CadaverousCrossmatchIssueDetail.HIGH_RES_MATCH] + details_and_issues=[CadaverousCrossmatchDetailsIssues.HIGH_RES_MATCH] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -1068,7 +1068,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='DPA1', high_res=None, split='DPA1'), mfi=1000, - issues=[CadaverousCrossmatchIssueDetail.NEGATIVE_ANTIBODY_IN_SUMMARY] + details_and_issues=[CadaverousCrossmatchDetailsIssues.NEGATIVE_ANTIBODY_IN_SUMMARY] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -1093,7 +1093,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='B7', high_res=None, split='B7'), mfi=1500, - issues=[CadaverousCrossmatchIssueDetail.NEGATIVE_ANTIBODY_IN_SUMMARY] + details_and_issues=[CadaverousCrossmatchDetailsIssues.NEGATIVE_ANTIBODY_IN_SUMMARY] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -1117,7 +1117,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='B7', high_res=None, split='B7'), mfi=1500, - issues=[CadaverousCrossmatchIssueDetail.NEGATIVE_ANTIBODY_IN_SUMMARY] + details_and_issues=[CadaverousCrossmatchDetailsIssues.NEGATIVE_ANTIBODY_IN_SUMMARY] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -1138,7 +1138,7 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad=None, high_res='A*01:01N', split=None), mfi=None, - issues=[CadaverousCrossmatchIssueDetail.NO_MATCHING_ANTIBODY] + details_and_issues=[CadaverousCrossmatchDetailsIssues.NO_MATCHING_ANTIBODY] ) self.assertEqual(res_summary, [asdict(expected_summary)]) @@ -1166,8 +1166,8 @@ def test_summary(self): expected_summary = CrossmatchSummary( hla_code=HLACode(broad='A2', high_res=None, split='A2'), mfi=3000, - issues=[CadaverousCrossmatchIssueDetail.SPLIT_BROAD_MATCH, - CadaverousCrossmatchIssueDetail.ANTIBODIES_MIGHT_NOT_BE_DSA] + details_and_issues=[CadaverousCrossmatchDetailsIssues.SPLIT_BROAD_MATCH, + CadaverousCrossmatchDetailsIssues.ANTIBODIES_MIGHT_NOT_BE_DSA] ) self.assertEqual(res_summary, [asdict(expected_summary)]) diff --git a/txmatching/data_transfer_objects/base_patient_swagger.py b/txmatching/data_transfer_objects/base_patient_swagger.py index ea32b043f..fcb34b277 100644 --- a/txmatching/data_transfer_objects/base_patient_swagger.py +++ b/txmatching/data_transfer_objects/base_patient_swagger.py @@ -185,8 +185,8 @@ 'split': 'A1' }, 'mfi': 3000, - 'issues': ['Antibodies against this HLA Type might not be DSA, for more ' - 'see detailed section.'] + 'details_and_issues': ['Antibodies against this HLA Type might not be DSA, for more ' + 'see detailed section.'] } }, { @@ -247,7 +247,7 @@ 'split': 'B7' }, 'mfi': 2000, - 'issues': [] + 'details_and_issues': [] } }, { @@ -332,8 +332,8 @@ 'split': 'DR15' }, 'mfi': 3000, - 'issues': ['Antibodies against this HLA Type might not be DSA, for more ' - 'see detailed section.'] + 'details_and_issues': ['Antibodies against this HLA Type might not be DSA, for more ' + 'see detailed section.'] } }, { @@ -418,7 +418,7 @@ 'split': 'DPA2' }, 'mfi': 3000, - 'issues': [] + 'details_and_issues': [] } }, { diff --git a/txmatching/data_transfer_objects/crossmatch/crossmatch_in_swagger.py b/txmatching/data_transfer_objects/crossmatch/crossmatch_in_swagger.py index 16b32d2c1..f503636d9 100644 --- a/txmatching/data_transfer_objects/crossmatch/crossmatch_in_swagger.py +++ b/txmatching/data_transfer_objects/crossmatch/crossmatch_in_swagger.py @@ -10,7 +10,7 @@ ParsingIssueBaseJson from txmatching.data_transfer_objects.matchings.matching_swagger import \ AntibodyMatchJson -from txmatching.utils.hla_system.hla_crossmatch import CadaverousCrossmatchIssueDetail +from txmatching.utils.hla_system.hla_crossmatch import CadaverousCrossmatchDetailsIssues from txmatching.web.web_utils.namespaces import crossmatch_api HLACode = crossmatch_api.clone('HlaCode', HLACode) @@ -34,8 +34,8 @@ { 'hla_code': fields.Nested(HLACode, required=True), 'mfi': fields.Integer(reqired=False), - 'issues': fields.List(required=False, cls_or_instance=fields.String( - required=False, enum=[issue.value for issue in CadaverousCrossmatchIssueDetail])) + 'details_and_issues': fields.List(required=False, cls_or_instance=fields.String( + required=False, enum=[issue.value for issue in CadaverousCrossmatchDetailsIssues])) } ) diff --git a/txmatching/utils/hla_system/hla_crossmatch.py b/txmatching/utils/hla_system/hla_crossmatch.py index dea5d1966..e504e92ab 100644 --- a/txmatching/utils/hla_system/hla_crossmatch.py +++ b/txmatching/utils/hla_system/hla_crossmatch.py @@ -35,7 +35,7 @@ class AntibodyMatchForHLAGroup: antibody_matches: List[AntibodyMatch] -class CadaverousCrossmatchIssueDetail(str, Enum): +class CadaverousCrossmatchDetailsIssues(str, Enum): RARE_ALLELE_POSITIVE_CROSSMATCH = 'There is most likely no crossmatch, but there is a small chance that a ' \ 'crossmatch could occur. Therefore, this case requires further investigation.' \ 'In summary we send SPLIT resolution of an infrequent code with the highest ' \ @@ -67,7 +67,7 @@ class CadaverousCrossmatchIssueDetail(str, Enum): class CrossmatchSummary: hla_code: HLACode mfi: Optional[int] - issues: Optional[List[CadaverousCrossmatchIssueDetail]] + details_and_issues: Optional[List[CadaverousCrossmatchDetailsIssues]] @dataclass @@ -110,6 +110,7 @@ def from_crossmatched_antibodies(cls, assumed_hla_types: List[HLATypeWithFrequen antibody_matches = cls._find_common_matches(assumed_hla_types, crossmatched_antibodies) return cls(assumed_hla_types, antibody_matches, all_antibodies) + # pylint: disable=too-many-locals def _calculate_crossmatch_summary(self, all_antibodies: List[AntibodiesPerGroup]): frequent_codes = [ hla_type.hla_type.code @@ -140,7 +141,7 @@ def _calculate_crossmatch_summary(self, all_antibodies: List[AntibodiesPerGroup] return CrossmatchSummary( hla_code=summary_match.hla_antibody.code.to_low_res_hla_code(), mfi=summary_match.hla_antibody.mfi, - issues=[CadaverousCrossmatchIssueDetail.NEGATIVE_ANTIBODY_IN_SUMMARY] + details_and_issues=[CadaverousCrossmatchDetailsIssues.NEGATIVE_ANTIBODY_IN_SUMMARY] ) else: # This is a special case that can occur with codes such as `A*01:01N`. Such codes picked to be in @@ -151,7 +152,7 @@ def _calculate_crossmatch_summary(self, all_antibodies: List[AntibodiesPerGroup] return CrossmatchSummary( hla_code=self.assumed_hla_types[0].hla_type.code, mfi=None, - issues=[CadaverousCrossmatchIssueDetail.NO_MATCHING_ANTIBODY] + details_and_issues=[CadaverousCrossmatchDetailsIssues.NO_MATCHING_ANTIBODY] ) matches_with_frequent_codes = list(filter( @@ -167,7 +168,7 @@ def _calculate_crossmatch_summary(self, all_antibodies: List[AntibodiesPerGroup] return CrossmatchSummary( hla_code=summary_match.hla_antibody.code.to_low_res_hla_code(), mfi=summary_match.hla_antibody.mfi, - issues=[CadaverousCrossmatchIssueDetail.RARE_ALLELE_POSITIVE_CROSSMATCH] + details_and_issues=[CadaverousCrossmatchDetailsIssues.RARE_ALLELE_POSITIVE_CROSSMATCH] ) # get summary match type @@ -183,8 +184,8 @@ def _calculate_crossmatch_summary(self, all_antibodies: List[AntibodiesPerGroup] "For now, we assume that these lists have at least one element. " \ "If not, then this is a logic error in the code." - # get crossmatch issues - crossmatch_issues = self._calculate_crossmatch_issues( + # get crossmatch type details and issues + crossmatch_details_and_issues = self._calculate_crossmatch_details_and_issues( frequent_codes, matches_with_frequent_codes_and_summary_type) # get summary HLA code @@ -210,61 +211,57 @@ def _calculate_crossmatch_summary(self, all_antibodies: List[AntibodiesPerGroup] return CrossmatchSummary( hla_code=summary_hla_code, mfi=summary_mfi, - issues=crossmatch_issues + details_and_issues=crossmatch_details_and_issues ) + # pylint: disable=too-many-branches @staticmethod - def _calculate_crossmatch_issues(frequent_codes: List[HLACode], - matches_with_frequent_codes_and_summary_type: List[AntibodyMatch]) \ - -> List[CadaverousCrossmatchIssueDetail]: - crossmatch_issues: List[CadaverousCrossmatchIssueDetail] = [] - + def _calculate_crossmatch_details_and_issues(frequent_codes: List[HLACode], + matches_with_frequent_codes_and_summary_type: List[AntibodyMatch]) \ + -> List[CadaverousCrossmatchDetailsIssues]: # Match type info (all matches are already filtered to have the same match type, we can use single match # to infer it) + match_type = matches_with_frequent_codes_and_summary_type[0].match_type if match_type == AntibodyMatchTypes.NONE: - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.NONE_MATCH) + crossmatch_detail = CadaverousCrossmatchDetailsIssues.NONE_MATCH elif match_type == AntibodyMatchTypes.THEORETICAL: - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.THEORETICAL_MATCH) - elif match_type == AntibodyMatchTypes.BROAD \ - or match_type == AntibodyMatchTypes.SPLIT: - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.SPLIT_BROAD_MATCH) - elif match_type == AntibodyMatchTypes.HIGH_RES_WITH_BROAD \ - or match_type == AntibodyMatchTypes.HIGH_RES_WITH_SPLIT: - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.HIGH_RES_WITH_SPLIT_BROAD_MATCH) + crossmatch_detail = CadaverousCrossmatchDetailsIssues.THEORETICAL_MATCH + elif match_type in (AntibodyMatchTypes.BROAD, AntibodyMatchTypes.SPLIT): + crossmatch_detail = CadaverousCrossmatchDetailsIssues.SPLIT_BROAD_MATCH + elif match_type in (AntibodyMatchTypes.HIGH_RES_WITH_BROAD, AntibodyMatchTypes.HIGH_RES_WITH_SPLIT): + crossmatch_detail = CadaverousCrossmatchDetailsIssues.HIGH_RES_WITH_SPLIT_BROAD_MATCH elif match_type == AntibodyMatchTypes.HIGH_RES: if HLACode.are_codes_in_high_res(frequent_codes): if len(matches_with_frequent_codes_and_summary_type) > 1: - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.MULTIPLE_HIGH_RES_MATCH) + crossmatch_detail = CadaverousCrossmatchDetailsIssues.MULTIPLE_HIGH_RES_MATCH else: # len(matches_with_frequent_codes_and_summary_type) == 1 -> # -> true high res (single antibody-hla code pair) - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.HIGH_RES_MATCH) + crossmatch_detail = CadaverousCrossmatchDetailsIssues.HIGH_RES_MATCH else: # Everything is in split, HIGH_RES match could not have been achieved by high_res-high_res # corresspondence. - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.HIGH_RES_MATCH_ON_SPLIT_LEVEL) + crossmatch_detail = CadaverousCrossmatchDetailsIssues.HIGH_RES_MATCH_ON_SPLIT_LEVEL elif match_type == AntibodyMatchTypes.UNDECIDABLE: - raise AssertionError(f"Unexpected match type: {match_type}. Such type should not occur at this point.") - - else: - raise AssertionError("Unknown antibody match type. This should never happen.") + raise AssertionError(f"Unexpected match type: {match_type}. Such type is unknown or " + f"should not occur at this point.") # Issues + crossmatch_issues: List[CadaverousCrossmatchDetailsIssues] = [] if not len(frequent_codes) > 1 or not HLACode.are_codes_in_high_res(frequent_codes): - return crossmatch_issues - if not HLACode.do_codes_have_different_low_res(frequent_codes): - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.ANTIBODIES_MIGHT_NOT_BE_DSA) - return crossmatch_issues + pass + elif not HLACode.do_codes_have_different_low_res(frequent_codes): + crossmatch_issues.append(CadaverousCrossmatchDetailsIssues.ANTIBODIES_MIGHT_NOT_BE_DSA) else: if len(frequent_codes) == len(matches_with_frequent_codes_and_summary_type): # all codes above cutoff - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.AMBIGUITY_IN_HLA_TYPIZATION) - return crossmatch_issues + crossmatch_issues.append(CadaverousCrossmatchDetailsIssues.AMBIGUITY_IN_HLA_TYPIZATION) else: # some codes below cutoff - crossmatch_issues.append(CadaverousCrossmatchIssueDetail.ANTIBODIES_MIGHT_NOT_BE_DSA) - return crossmatch_issues + crossmatch_issues.append(CadaverousCrossmatchDetailsIssues.ANTIBODIES_MIGHT_NOT_BE_DSA) + + return [crossmatch_detail] + crossmatch_issues @classmethod def _find_common_matches(cls, assumed_hla_types: List[HLATypeWithFrequency], diff --git a/txmatching/web/frontend/src/app/generated/model/crossmatchSummaryGenerated.ts b/txmatching/web/frontend/src/app/generated/model/crossmatchSummaryGenerated.ts index b7b9ab33e..c43ac280b 100644 --- a/txmatching/web/frontend/src/app/generated/model/crossmatchSummaryGenerated.ts +++ b/txmatching/web/frontend/src/app/generated/model/crossmatchSummaryGenerated.ts @@ -13,11 +13,11 @@ import { HlaCodeGenerated } from './hlaCodeGenerated'; export interface CrossmatchSummaryGenerated { + details_and_issues?: Array; hla_code: HlaCodeGenerated; - issues?: Array; mfi?: number; } -export enum CrossmatchSummaryGeneratedIssuesEnum { +export enum CrossmatchSummaryGeneratedDetailsAndIssuesEnum { ThereIsMostLikelyNoCrossmatchButThereIsASmallChanceThatACrossmatchCouldOccurThereforeThisCaseRequiresFurtherInvestigationInSummaryWeSendSplitResolutionOfAnInfrequentCodeWithTheHighestMfiValueAmongAntibodiesThatHaveRarePositiveCrossmatch = 'There is most likely no crossmatch, but there is a small chance that a crossmatch could occur. Therefore, this case requires further investigation.In summary we send SPLIT resolution of an infrequent code with the highest MFI value among antibodies that have rare positive crossmatch.', AntibodiesAgainstThisHlaTypeMightNotBeDsaForMoreSeeDetailedSection = 'Antibodies against this HLA Type might not be DSA, for more see detailed section.', AmbiguousHlaTypizationMultipleCrossmatchedFrequentHlaCodesWithDifferentSplitLevel = 'Ambiguous HLA typization, multiple crossmatched frequent HLA codes with different SPLIT level.', @@ -27,7 +27,9 @@ export enum CrossmatchSummaryGeneratedIssuesEnum { RecipientWasNotTestedForDonorsHlaTypeButAllHighResAntibodiesCorrespondingToTheSummaryHlaCodeOnSplitLevelArePositivelyCrossmatched = 'Recipient was not tested for donor\'s HLA type, but all HIGH RES antibodies corresponding to the summary HLA code on SPLIT level are positively crossmatched.', SplitHlaCodeDisplayedInSummaryButThereAreMultiplePositiveCrossmatchesOfHighResHlaTypeHighResAntibodyPairs = 'SPLIT HLA code displayed in summary, but there are multiple positive crossmatches of HIGH RES HLA type - HIGH RES antibody pairs.', ThereIsNoExactMatchButSomeOfTheHighResAntibodiesCorrespondingTotheSummaryHlaCodeOnSplitOrBroadLevelArePositive = 'There is no exact match, but some of the HIGH RES antibodies corresponding tothe summary HLA code on SPLIT or BROAD level are positive.', - ThereIsAMatchInSplitOrBroadResolution = 'There is a match in SPLIT or BROAD resolution.' + ThereIsAMatchInSplitOrBroadResolution = 'There is a match in SPLIT or BROAD resolution.', + ThereIsAMatchWithTheoreticalAntibody = 'There is a match with theoretical antibody.', + ThereIsAMatchOfTypeNoneThisIsMostProbablyCausedByOnlyOneOfTheAntibodiesFromDoubleAntibodyFindingAMatch = 'There is a match of type NONE, this is most probably caused by only one of the antibodies from double antibody finding a match.' }; diff --git a/txmatching/web/swagger/swagger.json b/txmatching/web/swagger/swagger.json index d1a1e4e90..8260fad00 100644 --- a/txmatching/web/swagger/swagger.json +++ b/txmatching/web/swagger/swagger.json @@ -3195,7 +3195,7 @@ "split": "A1" }, "mfi": 3000, - "issues": [ + "details_and_issues": [ "Antibodies against this HLA Type might not be DSA, for more see detailed section." ] } @@ -3258,7 +3258,7 @@ "split": "B7" }, "mfi": 2000, - "issues": [] + "details_and_issues": [] } }, { @@ -3343,7 +3343,7 @@ "split": "DR15" }, "mfi": 3000, - "issues": [ + "details_and_issues": [ "Antibodies against this HLA Type might not be DSA, for more see detailed section." ] } @@ -3430,7 +3430,7 @@ "split": "DPA2" }, "mfi": 3000, - "issues": [] + "details_and_issues": [] } }, { @@ -3519,7 +3519,7 @@ "mfi": { "type": "integer" }, - "issues": { + "details_and_issues": { "type": "array", "items": { "type": "string", @@ -3534,7 +3534,9 @@ "Recipient was not tested for donor's HLA type, but all HIGH RES antibodies corresponding to the summary HLA code on SPLIT level are positively crossmatched.", "SPLIT HLA code displayed in summary, but there are multiple positive crossmatches of HIGH RES HLA type - HIGH RES antibody pairs.", "There is no exact match, but some of the HIGH RES antibodies corresponding tothe summary HLA code on SPLIT or BROAD level are positive.", - "There is a match in SPLIT or BROAD resolution." + "There is a match in SPLIT or BROAD resolution.", + "There is a match with theoretical antibody.", + "There is a match of type NONE, this is most probably caused by only one of the antibodies from double antibody finding a match." ] } } diff --git a/txmatching/web/swagger/swagger.yaml b/txmatching/web/swagger/swagger.yaml index 741dc5578..769d2e7f6 100644 --- a/txmatching/web/swagger/swagger.yaml +++ b/txmatching/web/swagger/swagger.yaml @@ -712,13 +712,13 @@ definitions: raw_code: A*01:19 is_frequent: true summary: + details_and_issues: + - Antibodies against this HLA Type might not be DSA, for more + see detailed section. hla_code: broad: A1 high_res: null split: A1 - issues: - - Antibodies against this HLA Type might not be DSA, for more - see detailed section. mfi: 3000 - antibody_matches: [] assumed_hla_types: @@ -754,11 +754,11 @@ definitions: raw_code: B7 is_frequent: true summary: + details_and_issues: [] hla_code: broad: B7 high_res: null split: B7 - issues: [] mfi: 2000 - antibody_matches: [] assumed_hla_types: @@ -810,13 +810,13 @@ definitions: raw_code: DRB1*15:07 is_frequent: true summary: + details_and_issues: + - Antibodies against this HLA Type might not be DSA, for more + see detailed section. hla_code: broad: DR2 high_res: null split: DR15 - issues: - - Antibodies against this HLA Type might not be DSA, for more - see detailed section. mfi: 3000 - antibody_matches: [] assumed_hla_types: @@ -868,11 +868,11 @@ definitions: raw_code: DPA2 is_frequent: true summary: + details_and_issues: [] hla_code: broad: DPA2 high_res: null split: DPA2 - issues: [] mfi: 3000 - antibody_matches: [] assumed_hla_types: @@ -930,9 +930,7 @@ definitions: type: object CrossmatchSummary: properties: - hla_code: - $ref: '#/definitions/HlaCode' - issues: + details_and_issues: items: enum: - There is most likely no crossmatch, but there is a small chance @@ -962,6 +960,10 @@ definitions: corresponding tothe summary HLA code on SPLIT or BROAD level are positive. - There is a match in SPLIT or BROAD resolution. + - There is a match with theoretical antibody. + - There is a match of type NONE, this is most probably caused + by only one of the antibodies from double antibody finding + a match. example: There is most likely no crossmatch, but there is a small chance that a crossmatch could occur. Therefore, this case requires further investigation.In summary we send SPLIT resolution @@ -969,6 +971,8 @@ definitions: that have rare positive crossmatch. type: string type: array + hla_code: + $ref: '#/definitions/HlaCode' mfi: type: integer required: