1 lines (1 with data), 22.4 kB
{"cells":[{"cell_type":"code","source":["import mlflow\n","import mlflow.sklearn\n","from sklearn.ensemble import RandomForestClassifier\n","from sklearn.model_selection import train_test_split\n","from sklearn.preprocessing import LabelEncoder, StandardScaler\n","from sklearn.impute import SimpleImputer\n","from sklearn.pipeline import make_pipeline\n","import pandas as pd\n","import numpy as np\n","from notebookutils import mssparkutils\n","\n","def train_and_deploy_model():\n"," \"\"\"\n"," Train and deploy a model with comprehensive NaN handling in Microsoft Fabric\n"," \"\"\"\n"," try:\n"," # 1. Load data from Lakehouse with validation\n"," print(\"๐ Loading data from Lakehouse...\")\n"," df = pd.read_csv(\"/lakehouse/default/Files/PDC_biospecimen_manifest_03272025_214257.csv\")\n"," print(f\"โ
Data loaded successfully. Shape: {df.shape}\")\n"," \n"," # 2. Data validation\n"," print(\"\\n๐ Validating data...\")\n"," feature_cols = [\n"," 'Aliquot Quantity', \n"," 'Aliquot Volume',\n"," 'Concentration',\n"," 'Days To Collection',\n"," 'Days To Sample Procurement',\n"," 'Current Weight',\n"," 'Initial Weight'\n"," ]\n"," feature_cols = [col for col in feature_cols if col in df.columns]\n"," \n"," if not feature_cols:\n"," raise ValueError(\"โ No valid feature columns found\")\n"," \n"," target_col = 'Case Status'\n"," if target_col not in df.columns:\n"," raise ValueError(f\"โ Target column '{target_col}' not found\")\n"," \n"," # 3. NaN handling and preprocessing\n"," print(\"\\n๐งน Handling missing values...\")\n"," \n"," # Show missing values before imputation\n"," print(\"\\nMissing values per feature column:\")\n"," print(df[feature_cols].isnull().sum())\n"," \n"," # Encode target (no NaN handling needed as it's categorical)\n"," le = LabelEncoder()\n"," y = le.fit_transform(df[target_col].astype(str))\n"," \n"," # Create preprocessing pipeline\n"," preprocessor = make_pipeline(\n"," SimpleImputer(strategy='median'), # Handles NaN values\n"," StandardScaler() # Scales features\n"," )\n"," \n"," # Apply preprocessing\n"," X = preprocessor.fit_transform(df[feature_cols])\n"," \n"," # Verify no NaN values remain\n"," if np.isnan(X).any():\n"," raise ValueError(\"โ NaN values still present after preprocessing\")\n"," \n"," # 4. Train model with MLflow tracking\n"," print(\"\\n๐ค Training model...\")\n"," with mlflow.start_run():\n"," # Create and train model\n"," model = RandomForestClassifier(\n"," n_estimators=100,\n"," random_state=42,\n"," n_jobs=-1,\n"," class_weight='balanced'\n"," )\n"," \n"," # Split data\n"," X_train, X_test, y_train, y_test = train_test_split(\n"," X, y, test_size=0.2, random_state=42)\n"," \n"," model.fit(X_train, y_train)\n"," \n"," # Log important information\n"," mlflow.log_param(\"n_estimators\", 100)\n"," mlflow.log_metric(\"train_accuracy\", model.score(X_train, y_train))\n"," mlflow.log_metric(\"test_accuracy\", model.score(X_test, y_test))\n"," \n"," # Log the preprocessing pipeline\n"," mlflow.sklearn.log_model(preprocessor, \"preprocessor\")\n"," \n"," # Log the trained model\n"," mlflow.sklearn.log_model(model, \"model\")\n"," \n"," # Save feature columns as artifact\n"," with open(\"feature_columns.txt\", \"w\") as f:\n"," f.write(\"\\n\".join(feature_cols))\n"," mlflow.log_artifact(\"feature_columns.txt\")\n"," \n"," # Register model\n"," mlflow.register_model(\n"," \"runs:/{}/model\".format(mlflow.active_run().info.run_id),\n"," \"BiospecimenClassifier\"\n"," )\n"," \n"," print(\"โ
Model trained and registered successfully!\")\n"," \n"," # Return run ID for reference\n"," return mlflow.active_run().info.run_id\n"," \n"," except Exception as e:\n"," print(f\"\\nโ Error in model deployment: {str(e)}\")\n"," print(\"\\n๐ ๏ธ Troubleshooting steps:\")\n"," print(\"1. Check for missing values in your data\")\n"," print(\"2. Verify all feature columns exist\")\n"," print(\"3. Ensure target column has valid values\")\n"," raise\n","\n","if __name__ == \"__main__\":\n"," train_and_deploy_model()"],"outputs":[{"output_type":"display_data","data":{"application/vnd.livy.statement-meta+json":{"spark_pool":null,"statement_id":15,"statement_ids":[15],"state":"finished","livy_statement_state":"available","session_id":"e50020a4-5e38-4ad5-9b81-a3040d5ab3df","normalized_state":"finished","queued_time":"2025-04-01T11:47:43.2194044Z","session_start_time":null,"execution_start_time":"2025-04-01T11:47:43.2208452Z","execution_finish_time":"2025-04-01T11:48:10.4631319Z","parent_msg_id":"a6da7c3f-34d4-45ab-9bd1-4b04cb4ec9c7"},"text/plain":"StatementMeta(, e50020a4-5e38-4ad5-9b81-a3040d5ab3df, 15, Finished, Available, Finished)"},"metadata":{}},{"output_type":"stream","name":"stdout","text":["๐ Loading data from Lakehouse...\nโ
Data loaded successfully. Shape: (452, 45)\n\n๐ Validating data...\n\n๐งน Handling missing values...\n\nMissing values per feature column:\nAliquot Quantity 452\nAliquot Volume 452\nConcentration 452\nDays To Collection 347\nDays To Sample Procurement 452\nCurrent Weight 452\nInitial Weight 347\ndtype: int64\n"]},{"output_type":"display_data","data":{"application/vnd.mlflow.run-widget+json":{"info":{"artifact_uri":"sds://onelakesouthafricanorth.pbidedicated.windows.net/cde60769-1208-4712-9d88-602cb5dae476/05fdd04f-1a5d-4785-be80-12ef22e29bba/10d2e4fc-48f9-4f8d-9bd6-7463ed30fdfb/artifacts","end_time":1743508072,"experiment_id":"1bf6b0a4-b874-47b8-a79f-292c615471ec","lifecycle_stage":"active","run_id":"10d2e4fc-48f9-4f8d-9bd6-7463ed30fdfb","run_name":"quiet_box_69mtdgp6","run_uuid":"10d2e4fc-48f9-4f8d-9bd6-7463ed30fdfb","start_time":1743508065,"status":"FINISHED","user_id":"561e81b0-4da1-42c1-b290-381c5782272c"},"data":{"metrics":{},"params":{"memory":"None","steps":"[('simpleimputer', SimpleImputer(strategy='median')), ('standardscaler', StandardScaler())]","verbose":"False","simpleimputer":"SimpleImputer(strategy='median')","standardscaler":"StandardScaler()","simpleimputer__add_indicator":"False","simpleimputer__copy":"True","simpleimputer__fill_value":"None","simpleimputer__keep_empty_features":"False","simpleimputer__missing_values":"nan","simpleimputer__strategy":"median","simpleimputer__verbose":"deprecated","standardscaler__copy":"True","standardscaler__with_mean":"True","standardscaler__with_std":"True"},"tags":{"mlflow.user":"dcaf7b03-32e1-45bc-b03b-2da2786f6cfd","synapseml.notebook.artifactId":"541c9fd0-f54a-44d9-bdb9-a8db2d979286","synapseml.user.name":"danny","synapseml.user.id":"7c38aabf-71be-44a2-8bd1-240d7015d15e","synapseml.livy.id":"e50020a4-5e38-4ad5-9b81-a3040d5ab3df","mlflow.autologging":"sklearn","estimator_name":"Pipeline","estimator_class":"sklearn.pipeline.Pipeline","mlflow.rootRunId":"10d2e4fc-48f9-4f8d-9bd6-7463ed30fdfb","mlflow.runName":"quiet_box_69mtdgp6","synapseml.experimentName":"model_deployment","synapseml.experiment.artifactId":"05fdd04f-1a5d-4785-be80-12ef22e29bba"}},"inputs":{"dataset_inputs":[]}}},"metadata":{}},{"output_type":"stream","name":"stdout","text":["\n๐ค Training model...\nโ
Model trained and registered successfully!\n"]},{"output_type":"stream","name":"stderr","text":["2025/04/01 11:47:56 WARNING mlflow.sklearn: Model was missing function: predict. Not logging python_function flavor!\n2025-04-01:11:48:01,946 ERROR [shared_platform_utils.py:82] Create MLModel failed, status_code: 400, b'{\"requestId\":\"6f84f5ef-22d8-443f-b2b2-f98075bc112b\",\"errorCode\":\"ItemDisplayNameAlreadyInUse\",\"message\":\"Requested \\'BiospecimenClassifier\\' is already in use\"}'\nRegistered model 'BiospecimenClassifier' already exists. Creating a new version of this model...\n"]},{"output_type":"display_data","data":{"application/vnd.mlflow.run-widget+json":{"info":{"artifact_uri":"sds://onelakesouthafricanorth.pbidedicated.windows.net/cde60769-1208-4712-9d88-602cb5dae476/05fdd04f-1a5d-4785-be80-12ef22e29bba/4f7ccc8a-f09a-4a51-a9ee-5a8da1f4b95a/artifacts","end_time":1743508087,"experiment_id":"1bf6b0a4-b874-47b8-a79f-292c615471ec","lifecycle_stage":"active","run_id":"4f7ccc8a-f09a-4a51-a9ee-5a8da1f4b95a","run_name":"mighty_carpet_y037hw3z","run_uuid":"4f7ccc8a-f09a-4a51-a9ee-5a8da1f4b95a","start_time":1743508074,"status":"FINISHED","user_id":"561e81b0-4da1-42c1-b290-381c5782272c"},"data":{"metrics":{"train_accuracy":0.997229916897507,"test_accuracy":1},"params":{"n_estimators":"100"},"tags":{"mlflow.user":"dcaf7b03-32e1-45bc-b03b-2da2786f6cfd","synapseml.notebook.artifactId":"541c9fd0-f54a-44d9-bdb9-a8db2d979286","synapseml.user.name":"danny","synapseml.user.id":"7c38aabf-71be-44a2-8bd1-240d7015d15e","synapseml.livy.id":"e50020a4-5e38-4ad5-9b81-a3040d5ab3df","mlflow.rootRunId":"4f7ccc8a-f09a-4a51-a9ee-5a8da1f4b95a","mlflow.runName":"mighty_carpet_y037hw3z","synapseml.experimentName":"model_deployment","synapseml.experiment.artifactId":"05fdd04f-1a5d-4785-be80-12ef22e29bba"}},"inputs":{"dataset_inputs":[]}}},"metadata":{}}],"execution_count":12,"metadata":{"microsoft":{"language":"python","language_group":"synapse_pyspark"}},"id":"25725e07-fa66-49ef-b30c-1ad0fc305722"},{"cell_type":"code","source":["import mlflow\n","import pandas as pd\n","import numpy as np\n","import json\n","from notebookutils import mssparkutils\n","\n","def load_model_artifacts():\n"," \"\"\"\n"," Load model and preprocessing pipeline from MLflow\n"," \"\"\"\n"," try:\n"," # Load the entire run artifacts\n"," client = mlflow.tracking.MlflowClient()\n"," latest_version = client.get_latest_versions(\"BiospecimenClassifier\")[0]\n"," run_id = latest_version.run_id\n"," \n"," # Load model and preprocessor separately\n"," model_uri = f\"runs:/{run_id}/model\"\n"," preprocessor_uri = f\"runs:/{run_id}/preprocessor\"\n"," \n"," model = mlflow.sklearn.load_model(model_uri)\n"," preprocessor = mlflow.sklearn.load_model(preprocessor_uri)\n"," \n"," return model, preprocessor, latest_version.version\n"," \n"," except Exception as e:\n"," print(f\"Error loading model artifacts: {str(e)}\")\n"," raise\n","\n","# Load model components\n","model, preprocessor, model_version = load_model_artifacts()\n","\n","def validate_input(data):\n"," \"\"\"Validate input data structure and values\"\"\"\n"," required_columns = [\n"," 'Aliquot Quantity', \n"," 'Aliquot Volume',\n"," 'Concentration',\n"," 'Days To Collection',\n"," 'Days To Sample Procurement',\n"," 'Current Weight',\n"," 'Initial Weight'\n"," ]\n"," \n"," # Check all required columns are present\n"," missing_cols = [col for col in required_columns if col not in data]\n"," if missing_cols:\n"," raise ValueError(f\"Missing required columns: {missing_cols}\")\n"," \n"," # Convert to DataFrame for processing\n"," input_df = pd.DataFrame([data])\n"," \n"," # Check for non-numeric values\n"," for col in required_columns:\n"," if not pd.api.types.is_numeric_dtype(input_df[col]):\n"," try:\n"," input_df[col] = pd.to_numeric(input_df[col])\n"," except:\n"," raise ValueError(f\"Column {col} contains non-numeric value: {input_df[col].values[0]}\")\n"," \n"," return input_df[required_columns]\n","\n","def score(data):\n"," \"\"\"\n"," Score new data using the deployed model\n"," Args:\n"," data: Dictionary with feature values\n"," Returns:\n"," Dictionary with predictions and metadata\n"," \"\"\"\n"," try:\n"," # Validate and prepare input\n"," input_data = validate_input(data)\n"," \n"," # Preprocess (handles NaN if any remain)\n"," processed_data = preprocessor.transform(input_data)\n"," \n"," # Verify no NaN values\n"," if np.isnan(processed_data).any():\n"," raise ValueError(\"NaN values present after preprocessing\")\n"," \n"," # Get predictions\n"," prediction = int(model.predict(processed_data)[0])\n"," probabilities = model.predict_proba(processed_data)[0].tolist()\n"," \n"," return {\n"," \"prediction\": prediction,\n"," \"probabilities\": probabilities,\n"," \"status\": \"success\",\n"," \"model_version\": model_version\n"," }\n"," \n"," except Exception as e:\n"," return {\n"," \"error\": str(e),\n"," \"status\": \"error\",\n"," \"input_received\": data\n"," }\n","\n","# Example test\n","if __name__ == \"__main__\":\n"," test_data = {\n"," \"Aliquot Quantity\": 5.2,\n"," \"Aliquot Volume\": 1.0,\n"," \"Concentration\": 50.0,\n"," \"Days To Collection\": 10,\n"," \"Days To Sample Procurement\": 12,\n"," \"Current Weight\": 0.5,\n"," \"Initial Weight\": 0.6\n"," }\n"," \n"," result = score(test_data)\n"," print(\"Test scoring result:\")\n"," print(json.dumps(result, indent=2))\n"," "],"outputs":[{"output_type":"display_data","data":{"application/vnd.livy.statement-meta+json":{"spark_pool":null,"statement_id":16,"statement_ids":[16],"state":"finished","livy_statement_state":"available","session_id":"e50020a4-5e38-4ad5-9b81-a3040d5ab3df","normalized_state":"finished","queued_time":"2025-04-01T11:48:46.3928457Z","session_start_time":null,"execution_start_time":"2025-04-01T11:48:46.3943776Z","execution_finish_time":"2025-04-01T11:48:49.0170382Z","parent_msg_id":"f220882e-e408-4be1-9b86-e576f49c9351"},"text/plain":"StatementMeta(, e50020a4-5e38-4ad5-9b81-a3040d5ab3df, 16, Finished, Available, Finished)"},"metadata":{}},{"output_type":"stream","name":"stderr","text":["/tmp/ipykernel_6012/159969211.py:14: FutureWarning: ``mlflow.tracking.client.MlflowClient.get_latest_versions`` is deprecated since 2.9.0. Model registry stages will be removed in a future major release. To learn more about the deprecation of model registry stages, see our migration guide here: https://mlflow.org/docs/2.12.2/model-registry.html#migrating-from-stages\n latest_version = client.get_latest_versions(\"BiospecimenClassifier\")[0]\n"]},{"output_type":"display_data","data":{"text/plain":"Downloading artifacts: 0%| | 0/9 [00:00<?, ?it/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"65cf402a9e374808a15884343d8496b9"}},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"Downloading artifacts: 0%| | 0/9 [00:00<?, ?it/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"12e47caad77147be841ee49eac18f0e6"}},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Test scoring result:\n{\n \"prediction\": 1,\n \"probabilities\": [\n 0.0,\n 1.0\n ],\n \"status\": \"success\",\n \"model_version\": \"2\"\n}\n"]},{"output_type":"display_data","data":{"application/vnd.livy.statement-meta+json":{"spark_pool":null,"statement_id":17,"statement_ids":[17],"state":"finished","livy_statement_state":"available","session_id":"e50020a4-5e38-4ad5-9b81-a3040d5ab3df","normalized_state":"finished","queued_time":"2025-04-01T11:48:51.0462336Z","session_start_time":null,"execution_start_time":"2025-04-01T11:48:51.047614Z","execution_finish_time":"2025-04-01T11:48:51.3668893Z","parent_msg_id":"4b240cca-914b-437c-9505-57476da08759"},"text/plain":"StatementMeta(, e50020a4-5e38-4ad5-9b81-a3040d5ab3df, 17, Finished, Available, Finished)"},"metadata":{}}],"execution_count":13,"metadata":{"microsoft":{"language":"python","language_group":"synapse_pyspark"}},"id":"4f04db78-7834-45b8-9b00-db371ff2e1fd"}],"metadata":{"kernel_info":{"name":"synapse_pyspark"},"kernelspec":{"name":"synapse_pyspark","language":"Python","display_name":"Synapse PySpark"},"language_info":{"name":"python"},"microsoft":{"language":"python","language_group":"synapse_pyspark","ms_spell_check":{"ms_spell_check_language":"en"}},"widgets":{"application/vnd.jupyter.widget-state+json":{"version_major":2,"version_minor":0,"state":{"667d426548d34986af007b5aecfd4617":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"18180fd849a64045baf4d0a689e02073":{"model_name":"HTMLStyleModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"description_width":"","font_size":null,"text_color":null}},"2846c3ddfb274223b784e94deda55801":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"7ac65861fd8d4147b9075f8c7d701287":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"65c75526d0b34a949fdd16a7d2b786cc":{"model_name":"HTMLStyleModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"description_width":"","font_size":null,"text_color":null}},"c576ab227e674478893824085df73fbc":{"model_name":"ProgressStyleModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"description_width":""}},"6a8769511a3b47a6b47032bc64b5ba5a":{"model_name":"HTMLModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"value":" 9/9 [00:00<00:00, 22.76it/s]","layout":"IPY_MODEL_2846c3ddfb274223b784e94deda55801","style":"IPY_MODEL_081c003870584b3c9cf0b27f4143435e"}},"efc7f2647a72486891b6fe26fc4b9bcb":{"model_name":"HTMLModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"value":"Downloading artifacts: 100%","layout":"IPY_MODEL_caffed907567450193252a922d529cde","style":"IPY_MODEL_aee981ac0d744191a8b587fe6f1c77e4"}},"610615ce8b224fa98c3f5006606e0ff9":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"499c44658e664bfaa8db65c013970799":{"model_name":"ProgressStyleModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"description_width":""}},"325a61cfb6974777904a5a1cd736094d":{"model_name":"FloatProgressModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"value":9,"max":9,"bar_style":"success","style":"IPY_MODEL_c576ab227e674478893824085df73fbc","layout":"IPY_MODEL_667d426548d34986af007b5aecfd4617"}},"06fd0ac661214a8d93cabc415ad627ea":{"model_name":"HTMLModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"value":" 9/9 [00:00<00:00, 11.64it/s]","layout":"IPY_MODEL_610615ce8b224fa98c3f5006606e0ff9","style":"IPY_MODEL_65c75526d0b34a949fdd16a7d2b786cc"}},"ccf26434afa44af98563bf310fba83ac":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"0d5898265127416a9b9b788794ff2014":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"aee981ac0d744191a8b587fe6f1c77e4":{"model_name":"HTMLStyleModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"description_width":"","font_size":null,"text_color":null}},"cf88ac05097c4fdf9fe1d65cd0f32a98":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"caffed907567450193252a922d529cde":{"model_name":"LayoutModel","model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","state":{}},"081c003870584b3c9cf0b27f4143435e":{"model_name":"HTMLStyleModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"description_width":"","font_size":null,"text_color":null}},"db83b088a54e45458d6e8a0619fee27e":{"model_name":"HTMLModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"value":"Downloading artifacts: 100%","layout":"IPY_MODEL_cf88ac05097c4fdf9fe1d65cd0f32a98","style":"IPY_MODEL_18180fd849a64045baf4d0a689e02073"}},"65cf402a9e374808a15884343d8496b9":{"model_name":"HBoxModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"children":["IPY_MODEL_db83b088a54e45458d6e8a0619fee27e","IPY_MODEL_325a61cfb6974777904a5a1cd736094d","IPY_MODEL_06fd0ac661214a8d93cabc415ad627ea"],"layout":"IPY_MODEL_ccf26434afa44af98563bf310fba83ac"}},"12e47caad77147be841ee49eac18f0e6":{"model_name":"HBoxModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"children":["IPY_MODEL_efc7f2647a72486891b6fe26fc4b9bcb","IPY_MODEL_8f6e1166e13e42c69484caee5ccbc2df","IPY_MODEL_6a8769511a3b47a6b47032bc64b5ba5a"],"layout":"IPY_MODEL_0d5898265127416a9b9b788794ff2014"}},"8f6e1166e13e42c69484caee5ccbc2df":{"model_name":"FloatProgressModel","model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","state":{"value":9,"max":9,"bar_style":"success","style":"IPY_MODEL_499c44658e664bfaa8db65c013970799","layout":"IPY_MODEL_7ac65861fd8d4147b9075f8c7d701287"}}}}},"nteract":{"version":"nteract-front-end@1.0.0"},"spark_compute":{"compute_id":"/trident/default","session_options":{"conf":{"spark.synapse.nbs.session.timeout":"1200000"}}},"dependencies":{"lakehouse":{"known_lakehouses":[{"id":"53477481-ba13-4a4f-a8ea-d1f736d0f87e"}],"default_lakehouse":"53477481-ba13-4a4f-a8ea-d1f736d0f87e","default_lakehouse_name":"GenomeLH","default_lakehouse_workspace_id":"cde60769-1208-4712-9d88-602cb5dae476"}}},"nbformat":4,"nbformat_minor":5}