feat: persist user comments per video with author and source filters
Browse filesExtends /predict to accept video_id, author and a persist flag, so a manual post lands in Supabase under source=user_comment with the team-member name attached. /predictions now wraps the rows under a predictions key and supports filtering by source. The list_predictions helper and SQL schema gain the author column and a matching source filter.
- src/api/routes/predict.py +13 -9
- src/api/schemas.py +3 -0
- src/db/supabase_client.py +5 -0
- supabase/predictions_setup.sql +5 -1
src/api/routes/predict.py
CHANGED
|
@@ -21,13 +21,16 @@ router = APIRouter(tags=["Prediction"])
|
|
| 21 |
@router.post("/predict", response_model=PredictResponse)
|
| 22 |
async def predict(request: PredictRequest):
|
| 23 |
response = predict_single(request.text, request.threshold)
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
| 31 |
return response
|
| 32 |
|
| 33 |
|
|
@@ -110,7 +113,8 @@ async def predict_video(request: VideoRequest):
|
|
| 110 |
@router.get("/predictions")
|
| 111 |
async def get_predictions(
|
| 112 |
video_id: str | None = Query(default=None),
|
|
|
|
| 113 |
limit: int = Query(default=50, ge=1, le=200),
|
| 114 |
):
|
| 115 |
-
rows = list_predictions(video_id=video_id, limit=limit)
|
| 116 |
-
return rows
|
|
|
|
| 21 |
@router.post("/predict", response_model=PredictResponse)
|
| 22 |
async def predict(request: PredictRequest):
|
| 23 |
response = predict_single(request.text, request.threshold)
|
| 24 |
+
if request.persist:
|
| 25 |
+
save_prediction(
|
| 26 |
+
text=request.text,
|
| 27 |
+
result=response,
|
| 28 |
+
source="user_comment" if request.author else "api_direct",
|
| 29 |
+
video_id=request.video_id,
|
| 30 |
+
author=request.author,
|
| 31 |
+
threshold=request.threshold,
|
| 32 |
+
latency_ms=response.latency_ms,
|
| 33 |
+
)
|
| 34 |
return response
|
| 35 |
|
| 36 |
|
|
|
|
| 113 |
@router.get("/predictions")
|
| 114 |
async def get_predictions(
|
| 115 |
video_id: str | None = Query(default=None),
|
| 116 |
+
source: str | None = Query(default=None),
|
| 117 |
limit: int = Query(default=50, ge=1, le=200),
|
| 118 |
):
|
| 119 |
+
rows = list_predictions(video_id=video_id, source=source, limit=limit)
|
| 120 |
+
return {"predictions": rows, "total": len(rows)}
|
src/api/schemas.py
CHANGED
|
@@ -8,6 +8,9 @@ from pydantic import BaseModel, Field, field_validator
|
|
| 8 |
class PredictRequest(BaseModel):
|
| 9 |
text: str = Field(..., min_length=1, max_length=5000)
|
| 10 |
threshold: float = Field(0.5, ge=0.0, le=1.0)
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
@field_validator("text")
|
| 13 |
@classmethod
|
|
|
|
| 8 |
class PredictRequest(BaseModel):
|
| 9 |
text: str = Field(..., min_length=1, max_length=5000)
|
| 10 |
threshold: float = Field(0.5, ge=0.0, le=1.0)
|
| 11 |
+
video_id: Optional[str] = None
|
| 12 |
+
author: Optional[str] = None
|
| 13 |
+
persist: bool = True
|
| 14 |
|
| 15 |
@field_validator("text")
|
| 16 |
@classmethod
|
src/db/supabase_client.py
CHANGED
|
@@ -51,6 +51,7 @@ def save_prediction(
|
|
| 51 |
video_url: str | None = None,
|
| 52 |
threshold: float | None = None,
|
| 53 |
latency_ms: float | None = None,
|
|
|
|
| 54 |
) -> None:
|
| 55 |
"""Persist a single prediction, silently no-op when DB is not configured.
|
| 56 |
|
|
@@ -87,6 +88,7 @@ def save_prediction(
|
|
| 87 |
"threshold": threshold,
|
| 88 |
"latency_ms": latency_ms if latency_ms is not None else data.get("latency_ms"),
|
| 89 |
"source": source,
|
|
|
|
| 90 |
}
|
| 91 |
client.table(_TABLE).insert(row).execute()
|
| 92 |
except Exception as exc:
|
|
@@ -96,6 +98,7 @@ def save_prediction(
|
|
| 96 |
def list_predictions(
|
| 97 |
video_id: str | None = None,
|
| 98 |
limit: int = 50,
|
|
|
|
| 99 |
) -> list[dict]:
|
| 100 |
"""Return latest predictions ordered by ``created_at`` desc.
|
| 101 |
|
|
@@ -109,6 +112,8 @@ def list_predictions(
|
|
| 109 |
query = client.table(_TABLE).select("*").order("created_at", desc=True)
|
| 110 |
if video_id:
|
| 111 |
query = query.eq("video_id", video_id)
|
|
|
|
|
|
|
| 112 |
query = query.limit(max(1, min(limit, 200)))
|
| 113 |
response = query.execute()
|
| 114 |
return list(getattr(response, "data", []) or [])
|
|
|
|
| 51 |
video_url: str | None = None,
|
| 52 |
threshold: float | None = None,
|
| 53 |
latency_ms: float | None = None,
|
| 54 |
+
author: str | None = None,
|
| 55 |
) -> None:
|
| 56 |
"""Persist a single prediction, silently no-op when DB is not configured.
|
| 57 |
|
|
|
|
| 88 |
"threshold": threshold,
|
| 89 |
"latency_ms": latency_ms if latency_ms is not None else data.get("latency_ms"),
|
| 90 |
"source": source,
|
| 91 |
+
"author": author,
|
| 92 |
}
|
| 93 |
client.table(_TABLE).insert(row).execute()
|
| 94 |
except Exception as exc:
|
|
|
|
| 98 |
def list_predictions(
|
| 99 |
video_id: str | None = None,
|
| 100 |
limit: int = 50,
|
| 101 |
+
source: str | None = None,
|
| 102 |
) -> list[dict]:
|
| 103 |
"""Return latest predictions ordered by ``created_at`` desc.
|
| 104 |
|
|
|
|
| 112 |
query = client.table(_TABLE).select("*").order("created_at", desc=True)
|
| 113 |
if video_id:
|
| 114 |
query = query.eq("video_id", video_id)
|
| 115 |
+
if source:
|
| 116 |
+
query = query.eq("source", source)
|
| 117 |
query = query.limit(max(1, min(limit, 200)))
|
| 118 |
response = query.execute()
|
| 119 |
return list(getattr(response, "data", []) or [])
|
supabase/predictions_setup.sql
CHANGED
|
@@ -17,9 +17,13 @@ create table if not exists public.predictions (
|
|
| 17 |
model_used text,
|
| 18 |
threshold double precision,
|
| 19 |
latency_ms double precision,
|
| 20 |
-
source text
|
|
|
|
| 21 |
);
|
| 22 |
|
|
|
|
|
|
|
|
|
|
| 23 |
-- 2. Indexes for the queries the API will run
|
| 24 |
create index if not exists predictions_created_at_idx
|
| 25 |
on public.predictions (created_at desc);
|
|
|
|
| 17 |
model_used text,
|
| 18 |
threshold double precision,
|
| 19 |
latency_ms double precision,
|
| 20 |
+
source text, -- "api_direct" | "video_fetch" | "user_comment"
|
| 21 |
+
author text
|
| 22 |
);
|
| 23 |
|
| 24 |
+
-- Migration: add author column on existing installs
|
| 25 |
+
alter table public.predictions add column if not exists author text;
|
| 26 |
+
|
| 27 |
-- 2. Indexes for the queries the API will run
|
| 28 |
create index if not exists predictions_created_at_idx
|
| 29 |
on public.predictions (created_at desc);
|