feat: filter external moderation queue
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s

This commit is contained in:
ik
2026-09-12 21:37:10 +07:00
parent 6e18e8e565
commit 8e419c1832
7 changed files with 93 additions and 10 deletions
+24 -3
View File
@@ -15,7 +15,7 @@ import httpx
from fastapi import Depends, FastAPI, File, Header, HTTPException, Query, Request, Response, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sqlalchemy import func, select
from sqlalchemy import func, or_, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, joinedload
@@ -178,7 +178,11 @@ def _external_out(item: ExternalObservation) -> ExternalObservationOut:
def admin_external_observations(
db: Db, _: Annotated[str, Depends(_admin)],
status: Literal["staged", "mapped", "ready", "published", "rejected", "review"] | None = None,
source_system: str | None = None, limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0),
source_system: str | None = None,
completeness: Literal["all", "complete", "incomplete"] = "all",
order: Literal["newest", "oldest"] = "newest",
q: str | None = Query(None, max_length=100),
limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0),
) -> list[ExternalObservationOut]:
query = select(ExternalObservation).options(
joinedload(ExternalObservation.fish), joinedload(ExternalObservation.waterbody),
@@ -189,7 +193,24 @@ def admin_external_observations(
query = query.where(ExternalObservation.status == status)
if source_system:
query = query.where(ExternalObservation.source_system == source_system)
items = db.scalars(query.order_by(ExternalObservation.last_seen_at.desc(), ExternalObservation.id.desc()).offset(offset).limit(limit))
if completeness == "complete":
query = query.where(
ExternalObservation.x.is_not(None), ExternalObservation.y.is_not(None),
ExternalObservation.weight_g.is_not(None),
)
elif completeness == "incomplete":
query = query.where(or_(
ExternalObservation.x.is_(None), ExternalObservation.y.is_(None),
ExternalObservation.weight_g.is_(None),
))
if q and q.strip():
term = q.strip()
query = query.where(or_(
ExternalObservation.fish_name.icontains(term, autoescape=True),
ExternalObservation.waterbody_name.icontains(term, autoescape=True),
))
direction = ExternalObservation.last_seen_at.asc() if order == "oldest" else ExternalObservation.last_seen_at.desc()
items = db.scalars(query.order_by(direction, ExternalObservation.id.desc()).offset(offset).limit(limit))
return [_external_out(item) for item in items]
+46
View File
@@ -1979,6 +1979,52 @@
"title": "Source System"
}
},
{
"in": "query",
"name": "completeness",
"required": false,
"schema": {
"default": "all",
"enum": [
"all",
"complete",
"incomplete"
],
"title": "Completeness",
"type": "string"
}
},
{
"in": "query",
"name": "order",
"required": false,
"schema": {
"default": "newest",
"enum": [
"newest",
"oldest"
],
"title": "Order",
"type": "string"
}
},
{
"in": "query",
"name": "q",
"required": false,
"schema": {
"anyOf": [
{
"maxLength": 100,
"type": "string"
},
{
"type": "null"
}
],
"title": "Q"
}
},
{
"in": "query",
"name": "limit",
+5 -1
View File
@@ -339,8 +339,12 @@ def test_incomplete_external_observation_is_publicly_labelled_but_not_counted()
assert signal["source_system"] == "rf4db"
assert signal["quality"] == "incomplete"
assert signal["missing_fields"] == ["вес"]
assert all(item["x"] != 32 or item["y"] != 42 for item in client.get("/api/v1/activity").json()["items"])
headers = {"Authorization": "Bearer change-me-in-production"}
incomplete = client.get("/api/v1/admin/external-observations?status=review&completeness=incomplete&q=Pike", headers=headers)
assert any(item["id"] == str(observation_id) for item in incomplete.json())
complete = client.get("/api/v1/admin/external-observations?status=review&completeness=complete&q=Pike", headers=headers)
assert all(item["id"] != str(observation_id) for item in complete.json())
assert all(item["x"] != 32 or item["y"] != 42 for item in client.get("/api/v1/activity").json()["items"])
mapped = client.patch(
f"/api/v1/admin/external-observations/{observation_id}/mapping", headers=headers,
json={"fish_slug": "pike", "waterbody_slug": "test-lake"},