Column transformers and pipelines
Numeric and categorical inputs need different preparation. ColumnTransformer applies a chosen transformation to each named group of columns and combines the results. A Pipeline then passes that prepared matrix to a classifier. Calling fit on the complete pipeline learns each step from training rows in the correct order.
The numeric branch in these tasks imputes medians and standardizes values. The categorical branch creates training-defined one-hot columns and ignores unknown categories at prediction time. Keep both branches inside the fitted model so a future validation split can repeat the same process without leakage. You will predict probabilities for new rows that contain missing values and a previously unseen plan. The fitted pipeline accepts raw rows through predict_proba. You can hand it to another analyst without asking them to repeat each manual transformation.
numeric = Pipeline([('impute', SimpleImputer(strategy='median')), ('scale', StandardScaler())])
categorical = OneHotEncoder(handle_unknown='ignore', sparse_output=False)
preprocessor = ColumnTransformer([('num', numeric, num_cols), ('cat', categorical, cat_cols)])
model = Pipeline([('prepare', preprocessor), ('classify', LogisticRegression(max_iter=300))]).fit(X_train, y_train)
scores = model.predict_proba(X_test)[:, 1]
The pipeline learns imputation, scaling, categories and coefficients together. Unknown plans get zeros in the learned category columns instead of causing an error or learning test categories.