Skip to content

Commit ab10d21

Browse files
committed
Support allele specific antigen MACs
1 parent b9e8220 commit ab10d21

File tree

3 files changed

+61
-5
lines changed

3 files changed

+61
-5
lines changed

pyard/ard.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import re
2626
import sqlite3
2727
import sys
28+
from collections import Counter
2829
from typing import Iterable, List
2930

3031
from . import broad_splits, smart_sort
@@ -484,12 +485,38 @@ def is_mac(self, allele: str) -> bool:
484485
:return: True if MAC
485486
"""
486487
if ":" in allele:
487-
code = allele.split(":")[1]
488-
try:
488+
allele_split = allele.split(":")
489+
if len(allele_split) == 2: # MACs have only single :
490+
locus_antigen, code = allele.split(":")
489491
if code.isalpha():
490-
return db.is_valid_mac_code(self.db_connection, code)
491-
except sqlite3.OperationalError as e:
492-
print("Error: ", e)
492+
try:
493+
alleles = db.mac_code_to_alleles(self.db_connection, code)
494+
if alleles:
495+
if any(map(lambda a: ":" in a, alleles)):
496+
# allele specific antigen codes has ':' in the MAC mapping
497+
# e.g. CFWRN -> 15:01/15:98/15:157/15:202/
498+
# 15:239/15:280/15:340/35:43/35:67/35:79/35:102/35:118/35:185/51:220
499+
antigen_groups = map(lambda a: a.split(":")[0], alleles)
500+
# Rule 1: The 1st field with the most allele designations in the request is
501+
# the 1st field of the allele code designation
502+
# Rule 2: If there is a tie in the number of alleles designations sharing the 1st field,
503+
# the 1st field with the lowest numeric value is selected.
504+
antigen_counts = Counter(antigen_groups)
505+
# Create a table of antigen to it's counts
506+
# '15': 7
507+
# '35': 6
508+
# '51': 1
509+
# Valid antigen is the first most common one.
510+
# As it's presorted in db, also satisfies Rule 2.
511+
valid_antigen = antigen_counts.most_common(1).pop()[0]
512+
# Get antigen value 15 from 'DRB1*15'
513+
provided_antigen = locus_antigen.split("*").pop()
514+
# The MAC is only valid if the given antigen satisfies the antigen matching Rule 1 and 2
515+
return provided_antigen == valid_antigen
516+
# Valid when antigen group codes
517+
return True
518+
except sqlite3.OperationalError as e:
519+
print("Error: ", e)
493520
return False
494521

495522
def is_v2(self, allele: str) -> bool:

tests/features/mac.feature

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,16 @@ Feature: MAC (Multiple Allele Code)
4848
| A*01:01/A*01:02 | A*01:AB |
4949
| HLA-A*25:01/HLA-A*26:01 | HLA-A*25:BYHR |
5050
| HLA-A*02:01/HLA-A*02:09/HLA-A*02:43N | HLA-A*02:GNF |
51+
52+
53+
Scenario Outline: Invalid MACs
54+
55+
Given the MAC code is <MAC>
56+
When checking for validity of the MAC
57+
Then the validness is <Validity>
58+
59+
Examples:
60+
| MAC | Validity |
61+
| DRB1*07:DFJR | Invalid |
62+
| DPB1*08:BHHE | Invalid |
63+
| A*31:CMZEY | Invalid |

tests/steps/mac.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from behave import *
22
from hamcrest import assert_that, is_
33

4+
from pyard.exceptions import InvalidTypingError
5+
46

57
@given("the MAC code is {mac_code}")
68
def step_impl(context, mac_code):
@@ -30,3 +32,17 @@ def step_impl(context):
3032
@then("the decoded MAC is {mac_code}")
3133
def step_impl(context, mac_code):
3234
assert_that(context.mac_code, is_(mac_code))
35+
36+
37+
@when("checking for validity of the MAC")
38+
def step_impl(context):
39+
try:
40+
context.is_valid = context.ard.validate(context.mac_code)
41+
except InvalidTypingError:
42+
context.is_valid = False
43+
44+
45+
@then("the validness is {validity}")
46+
def step_impl(context, validity):
47+
valid = validity == "Valid"
48+
assert_that(context.is_valid, is_(valid))

0 commit comments

Comments
 (0)