File size: 2,559 Bytes
bbe01fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# backend/app/api/feedback.py
# Feedback endpoint. Lets the widget surface thumbs-up / thumbs-down for any
# completed interaction. Ratings are stored against the SQLite interaction row.
#
# Self-improvement loop:
#   rating=1  → positive signal written to DB; used by data_prep.py to build
#               high-quality reranker triplets (in-scope chunks that helped).
#   rating=-1 → negative signal; used by data_prep.py to build hard-negative
#               triplets (chunks the model surfaced that didn't help the user).
#   The retrain_reranker.yml GitHub Actions workflow aggregates these signals
#   and auto-triggers reranker fine-tuning once enough triplets accumulate.

import sqlite3

from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, Field

from app.core.logging import get_logger
from app.security.jwt_auth import verify_jwt

router = APIRouter()
logger = get_logger(__name__)


class FeedbackRequest(BaseModel):
    interaction_id: int = Field(..., description="Row ID returned in the SSE done event.")
    rating: int = Field(..., description="1 for thumbs-up, -1 for thumbs-down.")

    model_config = {"json_schema_extra": {"example": {"interaction_id": 42, "rating": 1}}}


@router.post("/feedback", status_code=status.HTTP_204_NO_CONTENT)
async def submit_feedback(
    request: Request,
    body: FeedbackRequest,
    _token: dict = Depends(verify_jwt),
) -> None:
    """Record user feedback on a completed interaction. Idempotent — re-rating overwrites."""
    if body.rating not in (1, -1):
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="rating must be 1 (positive) or -1 (negative)",
        )

    db_path = request.app.state.settings.DB_PATH
    try:
        with sqlite3.connect(db_path) as conn:
            cursor = conn.execute(
                "UPDATE interactions SET feedback = ? WHERE id = ?",
                (body.rating, body.interaction_id),
            )
            if cursor.rowcount == 0:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Interaction {body.interaction_id} not found.",
                )
    except HTTPException:
        raise
    except Exception as exc:
        logger.error("Feedback write failed for interaction %d: %s", body.interaction_id, exc)
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Feedback store unavailable.",
        )