new changes in the api

This commit is contained in:
2026-07-06 15:15:51 +05:30
parent c742ef0e53
commit 871981035a
43 changed files with 414 additions and 1975 deletions

View File

@@ -3,8 +3,8 @@ Analytics & ML Admin API
=========================
Exposes historical assignment quality data, the empirical-ETA pipeline, and the
autonomous road-sequencing / rider-affinity agents. (The XGBoost/Optuna hypertuner,
ID3 risk tree, and profit predictor have all been retired — they never affected
assignment.)
ID3 risk tree, profit predictor, and Thompson-Sampling strategy bandit have all
been retired — none of them ever affected assignment or routing behavior.)
Endpoints:
GET /api/v1/ml/status quality trend, analytics DB + history stats
@@ -12,7 +12,6 @@ Endpoints:
GET /api/v1/ml/config active config values
PATCH /api/v1/ml/config manual config override
POST /api/v1/ml/reset reset config to defaults
POST /api/v1/ml/strategy change optimization strategy
POST /api/v1/ml/refresh-eta sync nearledb + rebuild empirical ETA stats
GET /api/v1/ml/eta-accuracy formula vs empirical ETA backtest
GET/POST /api/v1/ml/road-eval autonomous road-sequencing decision
@@ -103,7 +102,6 @@ def ml_analytics():
"hourly_stats": collector.get_hourly_stats(),
"zone_stats": collector.get_zone_stats(),
"quality_histogram": collector.get_quality_histogram(),
"strategy_comparison": collector.get_strategy_comparison(),
}
except Exception as e:
logger.error(f"[ML API] analytics: {e}", exc_info=True)
@@ -163,142 +161,6 @@ def ml_reset():
raise HTTPException(status_code=500, detail=str(e))
# ---------------------------------------------------------------------------
# POST /strategy
# ---------------------------------------------------------------------------
@router.post("/strategy", summary="Change the optimization strategy")
def ml_strategy(strategy: str = Body(default="balanced", embed=True)):
"""
Choices: balanced | fuel_saver | aggressive_speed | zone_strict
Affects how the quality score is computed in analytics only.
"""
valid = ["balanced", "fuel_saver", "aggressive_speed", "zone_strict"]
if strategy not in valid:
raise HTTPException(400, f"Invalid strategy. Choose from {valid}")
from app.config.dynamic_config import get_config
try:
get_config().set("ml_strategy", strategy)
return {"status": "ok", "strategy": strategy}
except Exception as e:
logger.error(f"[ML API] strategy: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
# ---------------------------------------------------------------------------
# POST /auto-tune
# ---------------------------------------------------------------------------
@router.post("/auto-tune", summary="Run SQL-based strategy auto-tuner")
def ml_auto_tune():
"""
Analyses the assignment log and picks the best-performing ml_strategy
based on average quality score across all recorded calls.
Rules:
- A strategy needs ≥ 10 calls to be considered.
- At least 2 strategies must have enough data to compare.
- If a better strategy is found it is written to DynamicConfig
immediately and takes effect on the next /riderassign call.
- Also returns per-hour breakdown so you can see peak-hour patterns.
Safe to call any time. Runs in < 100ms.
"""
from app.services.ml.ml_data_collector import get_collector
from app.config.dynamic_config import get_config, DEFAULTS
try:
collector = get_collector()
cfg = get_config()
comparison = collector.get_strategy_comparison()
hourly = collector.get_hourly_stats()
total_records = collector.count_records()
if total_records == 0:
return {
"status": "no_data",
"message": "No assignment events logged yet. "
"Call /riderassign a few times first.",
"total_records": 0,
}
qualified = [s for s in comparison if s["call_count"] >= 10]
current_strategy = cfg.get("ml_strategy", "balanced")
action = "no_change"
recommendation = None
if len(qualified) >= 2:
best = max(qualified, key=lambda x: x["avg_quality"])
recommendation = best["strategy"]
if best["strategy"] != current_strategy:
cfg.set("ml_strategy", best["strategy"], source="auto_tuner")
action = "updated"
logger.info(
f"[AutoTune API] Strategy: '{current_strategy}'"
f"'{best['strategy']}' (quality={best['avg_quality']:.1f})"
)
elif len(comparison) > 0:
action = "insufficient_data"
recommendation = comparison[0]["strategy"] # best so far even if < 10 calls
# Per-hour best strategy (informational — not auto-applied)
# Shows which strategy logged the highest quality at each hour
hour_best: list = []
if hourly:
for h in hourly:
# Find which strategy performed best in this hour block
# (simple: use the dominant strategy for that hour from comparison)
hour_best.append({
"hour": h["hour"],
"avg_quality": h["avg_quality"],
"call_count": h["call_count"],
"sla_breaches": h["sla_breaches"],
})
return {
"status": "ok",
"action": action,
"current_strategy": cfg.get("ml_strategy", "balanced"),
"recommendation": recommendation,
"total_records": total_records,
"strategy_comparison": comparison,
"hourly_quality": hour_best,
"message": (
f"Strategy updated to '{recommendation}'."
if action == "updated"
else "Current strategy is already optimal."
if action == "no_change"
else "More data needed (≥ 10 calls per strategy to compare)."
),
}
except Exception as e:
logger.error(f"[ML API] auto-tune: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
# ---------------------------------------------------------------------------
# GET /bandit
# ---------------------------------------------------------------------------
@router.get("/bandit", summary="Thompson Sampling bandit — posterior stats per context")
def ml_bandit():
"""
Shows the current state of the RL strategy bandit.
Each context (time_band|load_band) has 4 arms (strategies).
For each arm:
mean_reward — expected quality / 100 based on posterior mean
observations — number of observed calls (excluding prior)
alpha / beta — Beta distribution parameters
The bandit uses Thompson Sampling to select strategies automatically
on every /riderassign call, balancing exploration vs exploitation.
"""
try:
from app.services.ml.strategy_bandit import get_bandit
return {"status": "ok", **get_bandit().get_stats()}
except Exception as e:
logger.error(f"[ML API] bandit: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
# ---------------------------------------------------------------------------
@@ -346,21 +208,24 @@ def ml_history(patterns: bool = False):
(all clear dominant-rider zones, sorted by pattern score).
"""
from app.services.vector.delivery_history_store import (
get_delivery_history_store, _INDEX_PATH, _META_PATH, CSV_PATH
get_delivery_history_store, _paths_for, _pattern_source, CSV_PATH
)
try:
store = get_delivery_history_store()
meta = {}
if os.path.isfile(_META_PATH):
with open(_META_PATH, "r", encoding="utf-8") as f:
store = get_delivery_history_store()
source = _pattern_source()
vectors_path, _records_path, meta_path = _paths_for(source)
meta = {}
if os.path.isfile(meta_path):
with open(meta_path, "r", encoding="utf-8") as f:
meta = json.load(f)
resp = {
"status": "ok",
"pattern_source": source,
"record_count": store.record_count(),
"pattern_count": store.pattern_count(),
"index_ready": store.record_count() > 0,
"disk_index": os.path.isfile(_INDEX_PATH),
"disk_index": os.path.isfile(vectors_path),
"csv_path": CSV_PATH,
"csv_exists": os.path.isfile(CSV_PATH),
"saved_meta": meta,