Spaces:
Sleeping
Sleeping
| # app.py | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| from typing import Dict, Any | |
| import joblib, os, time, math | |
| import numpy as np | |
| APP_PORT = int(os.environ.get("PORT", 7860)) # HF maps to 7860 | |
| app = FastAPI(title="Transit Tracker API") | |
| # In-memory store for vehicles | |
| VEHICLES: Dict[str, Dict[str, Any]] = {} | |
| MODEL = None | |
| MODEL_PATH = "models/model.joblib" | |
| # -- Utilities | |
| def haversine(lat1, lon1, lat2, lon2): | |
| # returns meters | |
| R = 6371000 | |
| phi1, phi2 = math.radians(lat1), math.radians(lat2) | |
| dphi = math.radians(lat2-lat1) | |
| dlambda = math.radians(lon2-lon1) | |
| a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2 | |
| return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) | |
| def load_model(): | |
| global MODEL | |
| if MODEL is None: | |
| if not os.path.exists(MODEL_PATH): | |
| raise RuntimeError("Model file not found: " + MODEL_PATH) | |
| MODEL = joblib.load(MODEL_PATH) | |
| return MODEL | |
| # -- Request models | |
| class VehicleUpdate(BaseModel): | |
| vehicle_id: str | |
| lat: float | |
| lon: float | |
| speed: float = 0.0 # meters/sec | |
| bearing: float = None | |
| timestamp: str = None # ISO | |
| class PredictRequest(BaseModel): | |
| distance_to_stop_m: float | |
| speed_mps: float = 3.0 | |
| timestamp_iso: str = None # optional | |
| # -- Endpoints | |
| def health(): | |
| ok = os.path.exists(MODEL_PATH) | |
| return {"status": "ok" if ok else "model-missing", "model_path": MODEL_PATH, "time": time.time()} | |
| def vehicle_update(v: VehicleUpdate): | |
| VEHICLES[v.vehicle_id] = { | |
| "lat": v.lat, | |
| "lon": v.lon, | |
| "speed": v.speed, | |
| "bearing": v.bearing, | |
| "timestamp": v.timestamp or time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) | |
| } | |
| return {"ok": True, "vehicle_id": v.vehicle_id} | |
| def get_vehicles(): | |
| return VEHICLES | |
| def predict(req: PredictRequest): | |
| # safety checks | |
| if req.distance_to_stop_m < 0: | |
| raise HTTPException(status_code=400, detail="distance must be >= 0") | |
| model = load_model() | |
| # compute TOD features (simple UTC seconds) | |
| if req.timestamp_iso: | |
| try: | |
| import datetime | |
| dt = datetime.datetime.fromisoformat(req.timestamp_iso.replace("Z","")) | |
| sec = dt.hour*3600 + dt.minute*60 + dt.second | |
| dow = dt.weekday() | |
| except: | |
| sec = time.gmtime().tm_hour*3600 | |
| dow = time.gmtime().tm_wday | |
| else: | |
| sec = time.gmtime().tm_hour*3600 + time.gmtime().tm_min*60 + time.gmtime().tm_sec | |
| dow = time.gmtime().tm_wday | |
| tod_sin = np.sin(2*np.pi*sec/86400) | |
| tod_cos = np.cos(2*np.pi*sec/86400) | |
| X = [[float(req.distance_to_stop_m), float(req.speed_mps), float(tod_sin), float(tod_cos), int(dow)]] | |
| eta_seconds = float(model.predict(X)[0]) | |
| return {"eta_seconds": eta_seconds, "eta_minutes": eta_seconds/60.0} | |