feat(avocet): POST /api/label endpoint
This commit is contained in:
parent
c1a6dd4fc5
commit
ff27053aa9
2 changed files with 45 additions and 1 deletions
25
app/api.py
25
app/api.py
|
|
@ -8,7 +8,10 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI, Query
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException, Query
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
_ROOT = Path(__file__).parent.parent
|
_ROOT = Path(__file__).parent.parent
|
||||||
_DATA_DIR: Path = _ROOT / "data" # overridable in tests via set_data_dir()
|
_DATA_DIR: Path = _ROOT / "data" # overridable in tests via set_data_dir()
|
||||||
|
|
@ -67,3 +70,23 @@ _last_action: dict | None = None
|
||||||
def get_queue(limit: int = Query(default=10, ge=1, le=50)):
|
def get_queue(limit: int = Query(default=10, ge=1, le=50)):
|
||||||
items = _read_jsonl(_queue_file())
|
items = _read_jsonl(_queue_file())
|
||||||
return {"items": items[:limit], "total": len(items)}
|
return {"items": items[:limit], "total": len(items)}
|
||||||
|
|
||||||
|
|
||||||
|
class LabelRequest(BaseModel):
|
||||||
|
id: str
|
||||||
|
label: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/label")
|
||||||
|
def post_label(req: LabelRequest):
|
||||||
|
global _last_action
|
||||||
|
items = _read_jsonl(_queue_file())
|
||||||
|
match = next((x for x in items if x["id"] == req.id), None)
|
||||||
|
if not match:
|
||||||
|
raise HTTPException(404, f"Item {req.id!r} not found in queue")
|
||||||
|
record = {**match, "label": req.label,
|
||||||
|
"labeled_at": datetime.now(timezone.utc).isoformat()}
|
||||||
|
_append_jsonl(_score_file(), record)
|
||||||
|
_write_jsonl(_queue_file(), [x for x in items if x["id"] != req.id])
|
||||||
|
_last_action = {"type": "label", "item": record}
|
||||||
|
return {"ok": True}
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,24 @@ def test_queue_empty_when_no_file(client):
|
||||||
r = client.get("/api/queue")
|
r = client.get("/api/queue")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json() == {"items": [], "total": 0}
|
assert r.json() == {"items": [], "total": 0}
|
||||||
|
|
||||||
|
|
||||||
|
def test_label_appends_to_score(client, queue_with_items):
|
||||||
|
from app import api as api_module
|
||||||
|
r = client.post("/api/label", json={"id": "id0", "label": "interview_scheduled"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
records = api_module._read_jsonl(api_module._score_file())
|
||||||
|
assert len(records) == 1
|
||||||
|
assert records[0]["id"] == "id0"
|
||||||
|
assert records[0]["label"] == "interview_scheduled"
|
||||||
|
assert "labeled_at" in records[0]
|
||||||
|
|
||||||
|
def test_label_removes_from_queue(client, queue_with_items):
|
||||||
|
from app import api as api_module
|
||||||
|
client.post("/api/label", json={"id": "id0", "label": "rejected"})
|
||||||
|
queue = api_module._read_jsonl(api_module._queue_file())
|
||||||
|
assert not any(x["id"] == "id0" for x in queue)
|
||||||
|
|
||||||
|
def test_label_unknown_id_returns_404(client, queue_with_items):
|
||||||
|
r = client.post("/api/label", json={"id": "unknown", "label": "neutral"})
|
||||||
|
assert r.status_code == 404
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue