| """ |
| Exports the champion credit scoring model from OC_P6 to a local joblib file |
| for embedding in the OC_P8 deployment image. |
| |
| Approach: bypass MLflow Server entirely. Load the model directly from the |
| filesystem using its physical path. Use MlflowClient only to fetch run-level |
| metadata (metrics, params) for traceability. |
| """ |
|
|
| import json |
| from datetime import datetime |
| from pathlib import Path |
|
|
| import joblib |
| import mlflow |
| from mlflow.tracking import MlflowClient |
|
|
| |
| |
| |
| P6_MLFLOW_TRACKING_URI = "http://127.0.0.1:5000" |
| MODEL_NAME = "lgbm_credit_scoring" |
| MODEL_VERSION = "2" |
|
|
| |
| |
| P6_MODEL_PATH = Path( |
| "C:/Users/Kevin/projects/OC_P6/mlartifacts/3/models/" |
| "m-dbe6fc95d74c49a8bb4775a6b7516fcd/artifacts" |
| ) |
|
|
| OUTPUT_DIR = Path("models") |
| OUTPUT_MODEL = OUTPUT_DIR / "model.joblib" |
| OUTPUT_INFO = OUTPUT_DIR / "model_info.json" |
|
|
| |
| |
| |
| mlflow.set_tracking_uri(P6_MLFLOW_TRACKING_URI) |
| client = MlflowClient() |
| OUTPUT_DIR.mkdir(parents=True, exist_ok=True) |
|
|
| |
| |
| |
| mv = client.get_model_version(MODEL_NAME, MODEL_VERSION) |
|
|
| print("=" * 60) |
| print("Model identified for export:") |
| print(f" Name : {mv.name}") |
| print(f" Version : {mv.version}") |
| print(f" Run ID : {mv.run_id}") |
| print(f" Source : {mv.source}") |
|
|
| if mv.run_id is None: |
| raise RuntimeError(f"Model version {MODEL_NAME}/{MODEL_VERSION} has no associated run_id") |
|
|
| run = client.get_run(mv.run_id) |
| print("\n Run metrics:") |
| for k, v in run.data.metrics.items(): |
| print(f" {k}: {v:.4f}") |
|
|
| |
| |
| |
| if not P6_MODEL_PATH.exists(): |
| raise FileNotFoundError( |
| f"Model artifacts not found at: {P6_MODEL_PATH}\n" |
| f"Verify the path matches the 'Source' shown above." |
| ) |
|
|
| print(f"\n Loading from filesystem: {P6_MODEL_PATH}") |
| model = mlflow.pyfunc.load_model(str(P6_MODEL_PATH)) |
|
|
| print("\n Model signature:") |
| sig = model.metadata.signature |
| if sig is None: |
| print(" [WARN] No signature logged - inputs will not be validated at inference!") |
| n_features = None |
| feature_names = None |
| else: |
| inputs = sig.inputs.to_dict() |
| n_features = len(inputs) |
| feature_names = [f["name"] for f in inputs] |
| print(f" Expected input features: {n_features}") |
| print(f" First 5 feature names: {feature_names[:5]}") |
|
|
| |
| |
| |
| joblib.dump(model, OUTPUT_MODEL) |
| size_mb = OUTPUT_MODEL.stat().st_size / 1e6 |
| print(f"\n[OK] Model saved to: {OUTPUT_MODEL} ({size_mb:.2f} MB)") |
|
|
| |
| |
| |
| info = { |
| "model_name": mv.name, |
| "version": mv.version, |
| "model_source": mv.source, |
| "run_id": mv.run_id, |
| "physical_path": str(P6_MODEL_PATH), |
| "exported_at": datetime.now().isoformat(), |
| "metrics": run.data.metrics, |
| "params": run.data.params, |
| "n_features_expected": n_features, |
| "feature_names": feature_names, |
| } |
| with open(OUTPUT_INFO, "w") as f: |
| json.dump(info, f, indent=2, default=str) |
| print(f"[OK] Metadata saved to: {OUTPUT_INFO}") |
|
|
| print("=" * 60) |