| """Unit tests for the runtime app_train transformation. |
| |
| The critical property under test is the one-hot pitfall fix: a single-row |
| input must produce one column for EVERY known training category, not just |
| the one currently present. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
|
|
| from api.inputs_transform import transform_app_train_inputs |
|
|
| CATS = { |
| "NAME_CONTRACT_TYPE": ["Cash loans", "Revolving loans"], |
| "NAME_INCOME_TYPE": ["Pensioner", "Working", "State servant"], |
| "ORGANIZATION_TYPE": ["Bank", "School", "XNA"], |
| } |
| BINARY = { |
| "CODE_GENDER": {"M": 0, "F": 1}, |
| "FLAG_OWN_CAR": {"N": 0, "Y": 1}, |
| "FLAG_OWN_REALTY": {"Y": 0, "N": 1}, |
| } |
|
|
|
|
| def test_binary_columns_factorized_with_training_codes(): |
| raw = {"CODE_GENDER": "F", "FLAG_OWN_CAR": "Y", "FLAG_OWN_REALTY": "N"} |
| out = transform_app_train_inputs(raw, CATS, BINARY) |
| assert int(out["CODE_GENDER"].iloc[0]) == 1 |
| assert int(out["FLAG_OWN_CAR"].iloc[0]) == 1 |
| assert int(out["FLAG_OWN_REALTY"].iloc[0]) == 1 |
|
|
|
|
| def test_one_hot_emits_all_known_categories_even_with_one_row(): |
| """The whole point of pd.Categorical(categories=KNOWN): every training |
| category becomes a column regardless of what the row contains.""" |
| raw = {"NAME_CONTRACT_TYPE": "Cash loans"} |
| out = transform_app_train_inputs(raw, CATS, BINARY) |
| assert "NAME_CONTRACT_TYPE_Cash loans" in out.columns |
| assert "NAME_CONTRACT_TYPE_Revolving loans" in out.columns |
| assert int(out["NAME_CONTRACT_TYPE_Cash loans"].iloc[0]) == 1 |
| assert int(out["NAME_CONTRACT_TYPE_Revolving loans"].iloc[0]) == 0 |
|
|
|
|
| def test_unknown_category_value_becomes_zero_everywhere(): |
| """If the JSON sends a value never seen in training, it's coerced to NaN |
| by pd.Categorical, then get_dummies emits all-zero columns.""" |
| raw = {"ORGANIZATION_TYPE": "Mars Colony"} |
| out = transform_app_train_inputs(raw, CATS, BINARY) |
| assert int(out["ORGANIZATION_TYPE_Bank"].iloc[0]) == 0 |
| assert int(out["ORGANIZATION_TYPE_School"].iloc[0]) == 0 |
| assert int(out["ORGANIZATION_TYPE_XNA"].iloc[0]) == 0 |
|
|
|
|
| def test_days_employed_sentinel_replaced_with_nan(): |
| raw = {"DAYS_EMPLOYED": 365243} |
| out = transform_app_train_inputs(raw, CATS, BINARY) |
| assert np.isnan(out["DAYS_EMPLOYED"].iloc[0]) |
|
|
|
|
| def test_days_employed_normal_value_preserved(): |
| raw = {"DAYS_EMPLOYED": -1500} |
| out = transform_app_train_inputs(raw, CATS, BINARY) |
| assert int(out["DAYS_EMPLOYED"].iloc[0]) == -1500 |
|
|
|
|
| def test_output_is_single_row(): |
| raw = {"NAME_CONTRACT_TYPE": "Cash loans"} |
| out = transform_app_train_inputs(raw, CATS, BINARY) |
| assert len(out) == 1 |
|
|