65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
from datetime import datetime, timedelta
|
|
|
|
import pandas as pd
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from sqlalchemy import text
|
|
|
|
from ..db import get_engine
|
|
from ..services.forecast import forecast_next_n
|
|
from ..services.schema_discovery import discover_schema
|
|
from ..services.trend_engine import compute_trend_scores
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/potential-winners")
|
|
def potential_winners(days: int = Query(14, ge=3, le=60), limit: int = Query(50, ge=1, le=200)):
|
|
engine = get_engine()
|
|
schema = discover_schema(engine)
|
|
if not schema.sales_table:
|
|
raise HTTPException(status_code=422, detail="未发现可用销量/订单明细表")
|
|
|
|
since = datetime.utcnow() - timedelta(days=days)
|
|
q = text(schema.trend_candidates_sql)
|
|
with engine.connect() as conn:
|
|
df = pd.read_sql(q, conn, params={"since": since, "limit": limit * 5})
|
|
if df.empty:
|
|
return {"items": []}
|
|
|
|
scored = compute_trend_scores(df)
|
|
scored = scored.sort_values("potential_score", ascending=False).head(limit)
|
|
return {"items": scored.to_dict(orient="records")}
|
|
|
|
|
|
@router.get("/forecast")
|
|
def forecast(
|
|
product_id: str = Query(..., min_length=1),
|
|
days: int = Query(30, ge=7, le=180),
|
|
horizon: int = Query(14, ge=1, le=60),
|
|
):
|
|
engine = get_engine()
|
|
schema = discover_schema(engine)
|
|
if not schema.sales_table:
|
|
raise HTTPException(status_code=422, detail="未发现可用销量/订单明细表")
|
|
|
|
since = datetime.utcnow() - timedelta(days=days)
|
|
q = text(schema.timeseries_sql)
|
|
with engine.connect() as conn:
|
|
df = pd.read_sql(q, conn, params={"product_id": product_id, "since": since})
|
|
if df.empty:
|
|
return {"product_id": product_id, "forecast": []}
|
|
|
|
df = df.sort_values("ds")
|
|
y = df["units"].astype(float).fillna(0.0).values
|
|
yhat = forecast_next_n(y, n=horizon)
|
|
start = pd.to_datetime(df["ds"]).max()
|
|
out = []
|
|
for i, v in enumerate(yhat, start=1):
|
|
out.append({"ds": (start + pd.Timedelta(days=i)).to_pydatetime().isoformat(), "units_hat": float(max(0.0, v))})
|
|
return {"product_id": product_id, "forecast": out}
|
|
|