#!/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}")