95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
"""STEP 05 — Vocabulary Terms (权威词表) CRUD + lookup + merge."""
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from app.auth import CurrentUser
|
|
from app.config import settings
|
|
from app.contracts import VocabularyTermCreate, VocabularyTermUpdate
|
|
from app.db import (
|
|
list_vocabulary_terms,
|
|
create_vocabulary_term,
|
|
update_vocabulary_term,
|
|
lookup_vocabulary,
|
|
get_conn,
|
|
)
|
|
from app.project_context import ProjectContext, get_project_context
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/vocabulary")
|
|
async def _list(
|
|
entity_type: str | None = None,
|
|
search: str | None = None,
|
|
context: ProjectContext = Depends(get_project_context),
|
|
_user: CurrentUser = None,
|
|
):
|
|
return await list_vocabulary_terms(
|
|
context.tenant_id, context.project_id, entity_type, search
|
|
)
|
|
|
|
|
|
@router.post("/vocabulary")
|
|
async def _create(
|
|
body: VocabularyTermCreate,
|
|
user: CurrentUser,
|
|
context: ProjectContext = Depends(get_project_context),
|
|
):
|
|
data = body.model_dump()
|
|
data["tenant_id"] = context.tenant_id
|
|
data["project_id"] = context.project_id
|
|
data["created_by"] = user["username"]
|
|
return await create_vocabulary_term(data)
|
|
|
|
|
|
@router.patch("/vocabulary/{term_id}")
|
|
async def _update(term_id: int, body: VocabularyTermUpdate, _user: CurrentUser):
|
|
row = await update_vocabulary_term(term_id, body.model_dump(exclude_none=True))
|
|
if not row:
|
|
raise HTTPException(404, "Term not found")
|
|
return row
|
|
|
|
|
|
@router.get("/vocabulary/lookup")
|
|
async def _lookup(
|
|
name: str,
|
|
context: ProjectContext = Depends(get_project_context),
|
|
_user: CurrentUser = None,
|
|
):
|
|
"""Normalize a name against the vocabulary."""
|
|
result = await lookup_vocabulary(context.tenant_id, context.project_id, name)
|
|
if not result:
|
|
return {"found": False, "name": name}
|
|
return {"found": True, "canonical_name": result["canonical_name"], "term": result}
|
|
|
|
|
|
@router.post("/vocabulary/{term_id}/merge-into/{target_id}")
|
|
async def _merge(term_id: int, target_id: int, _user: CurrentUser):
|
|
"""Merge aliases from term_id into target_id, then delete term_id."""
|
|
import json
|
|
s = settings.db_schema
|
|
|
|
async with get_conn() as conn:
|
|
async with conn.cursor() as cur:
|
|
await cur.execute(f"SELECT * FROM {s}.vocabulary_terms WHERE id=%s", (term_id,))
|
|
source = await cur.fetchone()
|
|
await cur.execute(f"SELECT * FROM {s}.vocabulary_terms WHERE id=%s", (target_id,))
|
|
target = await cur.fetchone()
|
|
|
|
if not source or not target:
|
|
raise HTTPException(404, "Term not found")
|
|
|
|
source_aliases = source["aliases"] if isinstance(source["aliases"], list) else json.loads(source["aliases"] or "[]")
|
|
target_aliases = target["aliases"] if isinstance(target["aliases"], list) else json.loads(target["aliases"] or "[]")
|
|
|
|
merged = list(set(target_aliases + [source["canonical_name"]] + source_aliases))
|
|
if target["canonical_name"] in merged:
|
|
merged.remove(target["canonical_name"])
|
|
|
|
await update_vocabulary_term(target_id, {"aliases": json.dumps(merged)})
|
|
|
|
async with conn.cursor() as cur:
|
|
await cur.execute(f"DELETE FROM {s}.vocabulary_terms WHERE id=%s", (term_id,))
|
|
await conn.commit()
|
|
|
|
return {"merged": term_id, "into": target_id, "aliases": merged}
|