"""Feature engineering: the only copy. Training imports it, and train.log_and_register ships this package inside the logged pyfunc (code_paths), so serving runs exactly this code on the raw columns below. **Allele frequency is deliberately not a feature.** It used to be, and it dominated everything: the same missense variant scored 0.887 at AF 0 and 0.0003 at AF 0.01, so the model was largely a frequency lookup. That caused two problems. It double-counted, because the ranking already scores frequency explicitly and auditably in `triage.rarity_score`, putting ~45% of the rank on one measurement. And it was circular, because ClinVar's labels are assigned with ACMG criteria that call a variant benign *on frequency* (BA1/BS1), so the model was rediscovering the rule used to label its own training data — which is most of why the headline AUROC looked so good. What is left is the variant's predicted effect: what it does to the protein, and how damaging two independent predictors think that is. That is evidence the rest of the ranking does not already have, which is the only reason to give the model a weight at all. """ import pandas as pd # What serving must send: raw values as stored in the variants table / its annotations. RAW_COLUMNS = ["impact", "consequence", "cadd_phred", "am_pathogenicity"] IMPACT_ORDER = {"MODIFIER": 0, "LOW": 1, "MODERATE": 2, "HIGH": 3} def build(df: pd.DataFrame) -> pd.DataFrame: out = pd.DataFrame(index=df.index) out["impact_rank"] = df["impact"].map(IMPACT_ORDER).fillna(0).astype(int) # Left as NaN on purpose: LightGBM handles missing natively, and imputing a number here would # assert a score nobody computed. out["cadd_phred"] = pd.to_numeric(df["cadd_phred"], errors="coerce") out["am_pathogenicity"] = pd.to_numeric(df["am_pathogenicity"], errors="coerce") out["consequence"] = df["consequence"].astype("category") return out