"""Offline orchestration of the full feature engineering pipeline. Replicates the merge_files() function from notebooks/EDA.ipynb so that the parquet feature store can be built once with the same logic that trained the model. """ from __future__ import annotations import gc import logging from pathlib import Path import numpy as np import pandas as pd from feature_engineering.aggregations import ( bureau_and_balance, credit_card_balance, installments_payments, one_hot_encoder, pos_cash, previous_applications, ) logger = logging.getLogger(__name__) def app_train_clean( data_dir: Path, num_rows: int | None = None, nan_as_category: bool = False ) -> pd.DataFrame: """Read application_train.csv and apply the same transformations as the training notebook (binary factorize, one-hot, ratio features).""" df = pd.read_csv(data_dir / "application_train.csv", nrows=num_rows) df = df[df["CODE_GENDER"] != "XNA"] for bin_feature in ["CODE_GENDER", "FLAG_OWN_CAR", "FLAG_OWN_REALTY"]: df[bin_feature], _ = pd.factorize(df[bin_feature]) df, _ = one_hot_encoder(df, nan_as_category) df["DAYS_EMPLOYED"] = df["DAYS_EMPLOYED"].replace(365243, np.nan) df["DAYS_EMPLOYED_PERC"] = df["DAYS_EMPLOYED"] / df["DAYS_BIRTH"] df["INCOME_CREDIT_PERC"] = df["AMT_INCOME_TOTAL"] / df["AMT_CREDIT"] df["INCOME_PER_PERSON"] = df["AMT_INCOME_TOTAL"] / df["CNT_FAM_MEMBERS"] df["ANNUITY_INCOME_PERC"] = df["AMT_ANNUITY"] / df["AMT_INCOME_TOTAL"] df["PAYMENT_RATE"] = df["AMT_ANNUITY"] / df["AMT_CREDIT"] return df def merge_files(data_dir: Path, debug: bool = False) -> pd.DataFrame: """Build the full 770-column training dataframe by joining application_train with all auxiliary aggregates. Returns a DataFrame with SK_ID_CURR + TARGET + 768 engineered features. """ num_rows = 30000 if debug else None df = app_train_clean(data_dir, num_rows) bureau = bureau_and_balance(data_dir, num_rows) logger.info("Bureau df shape: %s", bureau.shape) df = df.join(bureau, how="left", on="SK_ID_CURR") del bureau gc.collect() prev = previous_applications(data_dir, num_rows) logger.info("Previous applications df shape: %s", prev.shape) df = df.join(prev, how="left", on="SK_ID_CURR") del prev gc.collect() pos = pos_cash(data_dir, num_rows) logger.info("Pos-cash balance df shape: %s", pos.shape) df = df.join(pos, how="left", on="SK_ID_CURR") del pos gc.collect() ins = installments_payments(data_dir, num_rows) logger.info("Installments payments df shape: %s", ins.shape) df = df.join(ins, how="left", on="SK_ID_CURR") del ins gc.collect() cc = credit_card_balance(data_dir, num_rows) logger.info("Credit card balance df shape: %s", cc.shape) df = df.join(cc, how="left", on="SK_ID_CURR") del cc gc.collect() return df