Spaces:
Running
Running
| import os | |
| import json | |
| import joblib | |
| import pandas as pd | |
| from fastapi import FastAPI | |
| from fastapi.responses import HTMLResponse | |
| from pydantic import BaseModel, ConfigDict | |
| # ========================================================= | |
| # Paths | |
| # ========================================================= | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| PIPELINE_PATH = os.path.join(BASE_DIR, "pipeline.pkl") | |
| FEATURES_PATH = os.path.join(BASE_DIR, "feature_names.json") | |
| METRICS_PATH = os.path.join(BASE_DIR, "model_metrics.json") | |
| # ========================================================= | |
| # Load artefacts | |
| # ========================================================= | |
| pipeline = joblib.load(PIPELINE_PATH) | |
| with open(FEATURES_PATH, "r", encoding="utf-8") as f: | |
| feature_names = json.load(f) | |
| if os.path.exists(METRICS_PATH): | |
| with open(METRICS_PATH, "r", encoding="utf-8") as f: | |
| model_metrics = json.load(f) | |
| else: | |
| model_metrics = {} | |
| # ========================================================= | |
| # FastAPI app | |
| # ========================================================= | |
| app = FastAPI( | |
| title="GetAround Pricing API", | |
| description="Predicts the optimal rental price per day for a car", | |
| version="1.0.0", | |
| docs_url=None, | |
| redoc_url=None | |
| ) | |
| # ========================================================= | |
| # Input schema | |
| # ========================================================= | |
| class PredictInput(BaseModel): | |
| input: list[list] | |
| model_config = ConfigDict( | |
| json_schema_extra={ | |
| "example": { | |
| "input": [[ | |
| "Citroën", | |
| 50000, | |
| 120, | |
| "diesel", | |
| "black", | |
| "sedan", | |
| 1, | |
| 1, | |
| 1, | |
| 0, | |
| 1, | |
| 1, | |
| 0 | |
| ]] | |
| } | |
| } | |
| ) | |
| # ========================================================= | |
| # Helpers | |
| # ========================================================= | |
| def metric_value(name: str, digits: int = 2) -> str: | |
| value = model_metrics.get(name) | |
| if value is None: | |
| return "N/A" | |
| try: | |
| return f"{float(value):.{digits}f}" | |
| except Exception: | |
| return "N/A" | |
| def get_model_name() -> str: | |
| try: | |
| return pipeline.named_steps["model"].__class__.__name__ | |
| except Exception: | |
| return "Unknown" | |
| # ========================================================= | |
| # Routes | |
| # ========================================================= | |
| def root(): | |
| return """ | |
| <html> | |
| <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;"> | |
| <h1>🚗 GetAround Pricing API</h1> | |
| <p>API is running.</p> | |
| <a href="/docs">📄 Go to Documentation</a> | |
| </body> | |
| </html> | |
| """ | |
| def predict(data: PredictInput): | |
| X = pd.DataFrame(data.input, columns=feature_names) | |
| predictions = pipeline.predict(X) | |
| return {"prediction": [round(float(p), 2) for p in predictions]} | |
| def documentation(): | |
| algo_name = get_model_name() | |
| rmse = metric_value("RMSE") | |
| mae = metric_value("MAE") | |
| r2 = metric_value("R²", 3) | |
| cv_rmse = metric_value("CV_RMSE_mean") | |
| cv_std = metric_value("CV_RMSE_std") | |
| feature_rows = "".join([ | |
| f"<tr><td>{i+1}</td><td>{feature}</td></tr>" | |
| for i, feature in enumerate(feature_names) | |
| ]) | |
| return f""" | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>GetAround Pricing API Documentation</title> | |
| <style> | |
| * {{ margin: 0; padding: 0; box-sizing: border-box; }} | |
| body {{ font-family: 'Segoe UI', Arial, sans-serif; background: #f5f7fa; color: #333; }} | |
| header {{ background: #1a1a2e; color: white; padding: 40px; text-align: center; }} | |
| header h1 {{ font-size: 2.5em; margin-bottom: 10px; }} | |
| header p {{ color: #ccc; font-size: 1.05em; }} | |
| .container {{ max-width: 900px; margin: 40px auto; padding: 0 20px; }} | |
| .endpoint {{ background: white; border-radius: 12px; padding: 30px; margin-bottom: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }} | |
| .endpoint h2 {{ font-size: 1.4em; margin-bottom: 15px; display: flex; align-items: center; gap: 12px; }} | |
| .badge {{ padding: 5px 14px; border-radius: 20px; font-size: 0.85em; font-weight: bold; }} | |
| .post {{ background: #d4edda; color: #155724; }} | |
| .get {{ background: #cce5ff; color: #004085; }} | |
| .url {{ background: #1a1a2e; color: #00d4aa; padding: 12px 18px; border-radius: 8px; font-family: monospace; margin: 15px 0; }} | |
| .section-title {{ font-weight: bold; margin: 20px 0 8px; color: #555; text-transform: uppercase; font-size: 0.85em; letter-spacing: 1px; }} | |
| pre {{ background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; font-family: monospace; font-size: 0.9em; overflow-x: auto; }} | |
| .param-table {{ width: 100%; border-collapse: collapse; margin-top: 10px; }} | |
| .param-table th {{ background: #f1f3f5; padding: 10px; text-align: left; font-size: 0.85em; color: #555; }} | |
| .param-table td {{ padding: 10px; border-bottom: 1px solid #f1f3f5; font-size: 0.9em; }} | |
| footer {{ text-align: center; padding: 30px; color: #888; font-size: 0.9em; }} | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>🚗 GetAround Pricing API</h1> | |
| <p>Predict the optimal rental price per day for a car</p> | |
| </header> | |
| <div class="container"> | |
| <div class="endpoint"> | |
| <h2><span class="badge post">POST</span>/predict</h2> | |
| <p>Returns a predicted rental price per day based on the car's characteristics.</p> | |
| <div class="url">/predict</div> | |
| <div class="section-title">Expected input features</div> | |
| <table class="param-table"> | |
| <tr><th>#</th><th>Feature</th></tr> | |
| {feature_rows} | |
| </table> | |
| <div class="section-title">Request example</div> | |
| <pre>{{ | |
| "input": [[ | |
| "Citroën", | |
| 50000, | |
| 120, | |
| "diesel", | |
| "black", | |
| "sedan", | |
| 1, | |
| 1, | |
| 1, | |
| 0, | |
| 1, | |
| 1, | |
| 0 | |
| ]] | |
| }}</pre> | |
| <div class="section-title">Response example</div> | |
| <pre>{{ | |
| "prediction": [124.52] | |
| }}</pre> | |
| </div> | |
| <div class="endpoint"> | |
| <h2><span class="badge get">GET</span>/</h2> | |
| <p>Health check endpoint.</p> | |
| <div class="url">/</div> | |
| </div> | |
| <div class="endpoint"> | |
| <h2><span class="badge get">GET</span>/docs</h2> | |
| <p>Custom API documentation page.</p> | |
| <div class="url">/docs</div> | |
| </div> | |
| <div class="endpoint"> | |
| <h2>🤖 Model Information</h2> | |
| <table class="param-table"> | |
| <tr><th>Property</th><th>Value</th></tr> | |
| <tr><td>Algorithm</td><td>{algo_name}</td></tr> | |
| <tr><td>Target</td><td>rental_price_per_day (€)</td></tr> | |
| <tr><td>RMSE</td><td>{rmse}</td></tr> | |
| <tr><td>MAE</td><td>{mae}</td></tr> | |
| <tr><td>R²</td><td>{r2}</td></tr> | |
| <tr><td>CV RMSE</td><td>{cv_rmse}</td></tr> | |
| <tr><td>CV RMSE std</td><td>{cv_std}</td></tr> | |
| <tr><td>Number of features</td><td>{len(feature_names)}</td></tr> | |
| </table> | |
| </div> | |
| </div> | |
| <footer>GetAround Pricing API — Built with FastAPI</footer> | |
| </body> | |
| </html> | |
| """ |