{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":244515109,"sourceType":"kernelVersion"},{"sourceId":244830110,"sourceType":"kernelVersion"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport polars as pl\nimport pandas as pd\nimport numpy as np\nimport catboost as cb\nfrom sklearn.preprocessing import LabelEncoder\nimport joblib\nimport kaggle_evaluation.cmi_inference_server\nimport warnings\n\nwarnings.filterwarnings(\"ignore\")\n\n# --- 1. Load All Models and Artifacts ---\nprint(\"Loading all specialist models and assets...\")\n\n# Load IMU-Only Model and its feature list\nimu_only_model = joblib.load('/kaggle/input/cmi2025-single-catboost-train/catboost_advanced_model.pkl')\nwith open('/kaggle/input/cmi2025-single-catboost-train/catboost_advanced_feature_list.txt', 'r') as f:\n    imu_only_feature_list = [line.strip() for line in f.readlines()]\n\n# Load All-Sensors Model and its feature list\nall_sensors_model = joblib.load('/kaggle/input/cmi2025-catboost-all-censors/catboost_all_sensors_model.pkl')\n\n# Load the shared Label Encoder\nle = joblib.load('/kaggle/input/cmi2025-catboost-all-censors/catboost_label_encoder.pkl')\n\nprint(\"All models loaded successfully.\")\n\n# Pipeline 1: For IMU-Only data\ndef create_imu_only_features(df: pl.DataFrame) -> pl.DataFrame:\n    imu_cols = [col for col in df.columns if 'acc_' in col or 'rot_' in col]\n    aggs = []\n    for col in imu_cols:\n        aggs.extend([\n            pl.mean(col).alias(f'{col}_mean'), pl.std(col).alias(f'{col}_std'),\n            pl.max(col).alias(f'{col}_max'), pl.min(col).alias(f'{col}_min'),\n            pl.quantile(col, 0.25).alias(f'{col}_q25'), pl.quantile(col, 0.75).alias(f'{col}_q75'),\n        ])\n    for col in imu_cols:\n        aggs.extend([\n            (pl.col(col).diff().fill_null(0)).mean().alias(f'{col}_diff_mean'),\n            (pl.col(col).diff().fill_null(0)).std().alias(f'{col}_diff_std'),\n        ])\n    for part_name, part_expr in [\n        ('first_30pct', pl.col('sequence_counter') < pl.max('sequence_counter') * 0.3),\n        ('middle_40pct', (pl.col('sequence_counter') >= pl.max('sequence_counter') * 0.3) & (pl.col('sequence_counter') < pl.max('sequence_counter') * 0.7)),\n        ('last_30pct', pl.col('sequence_counter') >= pl.max('sequence_counter') * 0.7),\n    ]:\n        for col in imu_cols:\n            aggs.extend([\n                (pl.when(part_expr).then(pl.col(col))).mean().alias(f'{col}_mean_{part_name}'),\n                (pl.when(part_expr).then(pl.col(col))).std().alias(f'{col}_std_{part_name}'),\n            ])\n    return df.group_by('sequence_id').agg(aggs).fill_null(0)\n\n# Pipeline 2: For All-Sensors data\ndef create_all_sensors_features(df: pl.DataFrame) -> pl.DataFrame:\n    # Step 1: Hierarchical feature engineering for ToF sensors\n    for i in range(1, 6):\n        tof_cols = [f'tof_{i}_v{j}' for j in range(64)]\n        df = df.with_columns([\n            pl.concat_list([pl.col(c).fill_null(0) for c in tof_cols]).list.mean().alias(f'tof_{i}_pixels_mean'),\n            pl.concat_list([pl.col(c).fill_null(0) for c in tof_cols]).list.std().alias(f'tof_{i}_pixels_std'),\n            pl.concat_list([pl.col(c).fill_null(0) for c in tof_cols]).list.max().alias(f'tof_{i}_pixels_max'),\n            pl.concat_list([pl.col(c).fill_null(0) for c in tof_cols]).list.min().alias(f'tof_{i}_pixels_min'),\n        ])\n    \n    original_tof_cols = [c for c in df.columns if 'tof_' in c and '_pixels_' not in c]\n    df = df.drop(original_tof_cols)\n\n    # Step 2: Generate final sequence-level features from pre-aggregated data\n    feature_cols = [col for col in df.columns if any(p in col for p in ['acc_', 'rot_', 'thm_', 'tof_'])]\n    aggs = []\n    for col in feature_cols:\n        aggs.extend([\n            pl.mean(col).alias(f'{col}_mean'),\n            pl.std(col).alias(f'{col}_std'),\n        ])\n    for col in feature_cols:\n        aggs.extend([\n            (pl.col(col).diff().fill_null(0)).mean().alias(f'{col}_diff_mean'),\n            (pl.col(col).diff().fill_null(0)).std().alias(f'{col}_diff_std'),\n        ])\n    for part_name, part_expr in [\n        ('first_30pct', pl.col('sequence_counter') < pl.max('sequence_counter') * 0.3),\n        ('middle_40pct', (pl.col('sequence_counter') >= pl.max('sequence_counter') * 0.3) & (pl.col('sequence_counter') < pl.max('sequence_counter') * 0.7)),\n        ('last_30pct', pl.col('sequence_counter') >= pl.max('sequence_counter') * 0.7),\n    ]:\n        for col in feature_cols:\n            aggs.extend([\n                (pl.when(part_expr).then(pl.col(col))).mean().alias(f'{col}_mean_{part_name}'),\n                (pl.when(part_expr).then(pl.col(col))).std().alias(f'{col}_std_{part_name}'),\n            ])\n    \n    return df.group_by('sequence_id').agg(aggs).fill_null(0)\n\n# --- 3. Define Predict Function with Smart Switch ---\ndef predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    \"\"\"\n    Checks the input sequence and routes it to the appropriate specialist model.\n    \"\"\"\n    # Check if this sequence is IMU-Only by looking for nulls in a thm column\n    is_imu_only = sequence['thm_1'].is_null().all()\n    \n    if is_imu_only:\n        # --- IMU-Only Model Path ---\n        features_df = create_imu_only_features(sequence)\n        features_pd = features_df.drop('sequence_id').to_pandas()\n        X_test = features_pd[imu_only_feature_list] # Use the exact feature list from training\n        \n        # Predict using the IMU-Only model\n        pred_idx = imu_only_model.predict(X_test)\n    else:\n        # --- All-Sensors Model Path ---\n        features_df = create_all_sensors_features(sequence)\n        features_pd = features_df.drop('sequence_id').to_pandas()\n        \n        # Ensure the column order matches the training order for the all_sensors_model\n        # We can get the feature list from the model object itself\n        all_sensors_feature_list = all_sensors_model.feature_names_\n        X_test = features_pd[all_sensors_feature_list]\n        \n        # Predict using the All-Sensors model\n        pred_idx = all_sensors_model.predict(X_test)\n        \n    # Common post-processing\n    pred_label = le.inverse_transform(pred_idx.flatten())[0]\n    \n    return pred_label\n\n# --- 4. Start the Inference Server ---\nprint(\"Starting inference server with dual-model strategy...\")\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-12T00:21:21.821344Z","iopub.execute_input":"2025-06-12T00:21:21.821702Z","iopub.status.idle":"2025-06-12T00:21:24.792246Z","shell.execute_reply.started":"2025-06-12T00:21:21.821673Z","shell.execute_reply":"2025-06-12T00:21:24.791314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}