File size: 2,597 Bytes
8ef0a28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/env python3
"""
kappa_lambda_meta.py  (v3 - rule-based)
========================================
Derives kappa/lambda clonality via deterministic clinical rules.
No classifier needed: the logic is unambiguous.

Rules:
  Kappa+ and Lambda-  -> kappa_dominant
  Lambda+ and Kappa-  -> lambda_dominant
  Both positive       -> polyclonal
  Both negative       -> polyclonal (non-specific, no light chain restriction)

Clinical note: kappa/lambda ratio indicates CLONALITY, not IS need directly.
Monoclonal patterns narrow the differential toward plasma cell dyscrasias
(LCDD, amyloid, myeloma cast nephropathy) which are mostly IS=No or
require disease-specific regimens. The ratio feeds into the IS classifier
as one feature among many, not as a standalone IS decision.

Used by: infer_case_v2.py (inline rule, no pkl needed)
         train_is_classifier.py (encodes ratio as ordinal feature)
"""

# Threshold: grade > 0 = positive (i.e. 1+, 2+, 3+, 4+)
# grade = 0 means negative or trace

def derive_kappa_lambda_ratio(kappa_grade, lambda_grade):
    """
    Args:
        kappa_grade:  float, predicted or true ordinal grade (0-4)
        lambda_grade: float, predicted or true ordinal grade (0-4)
    Returns:
        (label, confidence)
        label: 'kappa_dominant' | 'lambda_dominant' | 'polyclonal'
        confidence: float (1.0 for pure rule, lower if grades are borderline)
    """
    k_pos = kappa_grade > 0
    l_pos = lambda_grade > 0

    if k_pos and not l_pos:
        label = "kappa_dominant"
    elif l_pos and not k_pos:
        label = "lambda_dominant"
    else:
        label = "polyclonal"

    # Confidence reflects how clear-cut the contrast is
    # Strong contrast (e.g. k=3, l=0) -> high confidence
    # Borderline (e.g. k=1, l=1) -> lower
    contrast = abs(kappa_grade - lambda_grade)
    if contrast >= 2:
        conf = 0.90
    elif contrast == 1:
        conf = 0.70
    else:
        conf = 0.55  # both 0 or both equal positive -> ambiguous

    return label, conf


if __name__ == "__main__":
    # Sanity check
    tests = [
        (3, 0, "kappa_dominant"),
        (0, 2, "lambda_dominant"),
        (2, 2, "polyclonal"),
        (0, 0, "polyclonal"),
        (1, 0, "kappa_dominant"),
    ]
    print("Rule check:")
    all_pass = True
    for k, l, expected in tests:
        label, conf = derive_kappa_lambda_ratio(k, l)
        status = "OK" if label == expected else "FAIL"
        if status == "FAIL": all_pass = False
        print(f"  k={k} l={l} -> {label} (conf={conf:.2f})  [{status}]")
    print(f"\nAll tests passed: {all_pass}")