Spaces:
Running
Running
File size: 3,933 Bytes
79b0bef | 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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | """
src/api/routes/entities.py
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Endpoints for entity frequency and co-occurrence data.
Routes
ββββββ
GET /entities β list entities for a note
GET /entities/top β top-N most frequent entities
GET /entities/cooccurrence β co-occurrence pairs for the graph
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from src.api.schemas import EntitySummary, TopEntitiesResponse, TopEntityItem
from src.db.connection import get_db_session
from src.db.repository import EntityRepository
from src.utils.logger import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/entities", tags=["Entities"])
@router.get(
"",
response_model = list[EntitySummary],
summary = "List entities for a stored note",
)
def list_entities_for_note(
note_id: int = Query(..., description="Note ID to fetch entities for"),
db: Session = Depends(get_db_session),
) -> list[EntitySummary]:
"""Return all entities extracted from a specific stored note.
Entities are ordered by their position in the note text
(start character offset ascending).
Args:
note_id: Primary key of the parent note.
Returns:
List of :class:`EntitySummary` objects.
"""
entities = EntityRepository.list_by_note(db, note_id)
return [EntitySummary.model_validate(e) for e in entities]
@router.get(
"/top",
response_model = TopEntitiesResponse,
summary = "Most frequently extracted entities",
)
def top_entities(
label: str | None = Query(
None,
description = "Filter to this entity type (DISEASE, MEDICATION, etc.)",
),
limit: int = Query(20, ge=1, le=100),
db: Session = Depends(get_db_session),
) -> TopEntitiesResponse:
"""Return the top-N most frequently extracted entity texts.
Used by the dashboard frequency bar chart.
Args:
label: Optional entity type filter.
limit: Number of top entities to return.
Returns:
:class:`TopEntitiesResponse` with ranked entity texts and counts.
"""
rows = EntityRepository.top_entities(db, label=label, limit=limit)
return TopEntitiesResponse(
label = label,
limit = limit,
items = [TopEntityItem(text=t, count=c) for t, c in rows],
)
@router.get(
"/cooccurrence",
response_model = list[dict],
summary = "Entity co-occurrence pairs",
)
def cooccurrence_pairs(
label: str = Query("DISEASE", description="Entity type to analyse"),
min_count: int = Query(5, ge=1, description="Minimum co-occurrence count"),
limit: int = Query(100, ge=1, le=500),
db: Session = Depends(get_db_session),
) -> list[dict]:
"""Return entity pairs that frequently co-occur in the same note.
Used by the dashboard to build the co-occurrence network graph.
Each item in the response is an edge in the graph with source,
target, and weight (co-occurrence count).
Args:
label: Entity type β usually DISEASE for clinical insight.
min_count: Minimum co-occurrences for a pair to be included.
limit: Maximum number of pairs to return.
Returns:
List of dicts: ``{"source": str, "target": str, "weight": int}``.
"""
pairs = EntityRepository.cooccurrence_pairs(
db, label=label, min_count=min_count, limit=limit
)
return [
{"source": a, "target": b, "weight": w}
for a, b, w in pairs
]
|