| """
|
| Timeline API Routes - Temporal view of entities and relationships
|
| """
|
| from fastapi import APIRouter, Depends, Query |
| from pydantic import BaseModel |
| from typing import Optional, List, Dict, Any |
| from datetime import datetime, timedelta |
| from collections import defaultdict |
| from sqlalchemy.orm import Session |
|
|
| from app.api.deps import get_scoped_db |
| from app.models.entity import Entity, Relationship |
|
|
|
|
| router = APIRouter(prefix="/timeline", tags=["Timeline"])
|
|
|
|
|
| class TimelineEvent(BaseModel):
|
| id: str
|
| type: str
|
| entity_type: Optional[str] = None
|
| name: str
|
| description: Optional[str] = None
|
| date: str
|
| icon: str
|
|
|
|
|
| class TimelineGroup(BaseModel):
|
| date: str
|
| label: str
|
| events: List[TimelineEvent]
|
|
|
|
|
| class TimelineResponse(BaseModel):
|
| groups: List[TimelineGroup]
|
| total_events: int
|
|
|
|
|
| @router.get("", response_model=TimelineResponse) |
| async def get_timeline( |
| days: int = Query(default=30, ge=1, le=365), |
| entity_type: Optional[str] = None, |
| limit: int = Query(default=100, ge=1, le=500), |
| db: Session = Depends(get_scoped_db) |
| ): |
| """
|
| Get timeline of recent entities and relationships.
|
| Groups events by date.
|
| """
|
| |
| end_date = datetime.now()
|
| start_date = end_date - timedelta(days=days)
|
|
|
| events = []
|
|
|
|
|
| query = db.query(Entity).filter(
|
| Entity.created_at >= start_date
|
| )
|
|
|
| if entity_type:
|
| query = query.filter(Entity.type == entity_type)
|
|
|
| entities = query.order_by(Entity.created_at.desc()).limit(limit).all()
|
|
|
| icon_map = {
|
| "person": "๐ค",
|
| "organization": "๐ข",
|
| "location": "๐",
|
| "event": "๐
",
|
| "concept": "๐ก",
|
| "product": "๐ฆ"
|
| }
|
|
|
| for e in entities:
|
|
|
| date = e.event_date if e.event_date else e.created_at
|
| events.append(TimelineEvent(
|
| id=e.id,
|
| type="entity",
|
| entity_type=e.type,
|
| name=e.name,
|
| description=e.description[:100] if e.description else None,
|
| date=date.isoformat() if date else datetime.now().isoformat(),
|
| icon=icon_map.get(e.type, "๐")
|
| ))
|
|
|
|
|
| relationships = db.query(Relationship).filter(
|
| Relationship.created_at >= start_date
|
| ).order_by(Relationship.created_at.desc()).limit(limit // 2).all()
|
|
|
| for r in relationships:
|
| source = db.query(Entity).filter(Entity.id == r.source_id).first()
|
| target = db.query(Entity).filter(Entity.id == r.target_id).first()
|
|
|
| if source and target:
|
|
|
| date = r.event_date if r.event_date else r.created_at
|
| events.append(TimelineEvent(
|
| id=r.id,
|
| type="relationship",
|
| name=f"{source.name} โ {target.name}",
|
| description=r.type,
|
| date=date.isoformat() if date else datetime.now().isoformat(),
|
| icon="๐"
|
| ))
|
|
|
|
|
| events.sort(key=lambda x: x.date, reverse=True)
|
|
|
|
|
| groups_dict = defaultdict(list)
|
| for event in events:
|
| date_key = event.date[:10]
|
| groups_dict[date_key].append(event)
|
|
|
|
|
| groups = []
|
| for date_key in sorted(groups_dict.keys(), reverse=True):
|
| try:
|
| dt = datetime.fromisoformat(date_key)
|
| label = dt.strftime("%d %b %Y")
|
| except:
|
| label = date_key
|
|
|
| groups.append(TimelineGroup(
|
| date=date_key,
|
| label=label,
|
| events=groups_dict[date_key]
|
| ))
|
|
|
| return TimelineResponse(
|
| groups=groups,
|
| total_events=len(events)
|
| )
|
|
|
|
|
| @router.get("/stats") |
| async def get_timeline_stats(db: Session = Depends(get_scoped_db)): |
| """Get statistics for timeline visualization""" |
|
|
|
|
| entity_counts = {}
|
| for entity_type in ["person", "organization", "location", "event", "concept"]:
|
| count = db.query(Entity).filter(Entity.type == entity_type).count()
|
| entity_counts[entity_type] = count
|
|
|
|
|
| relationship_count = db.query(Relationship).count()
|
|
|
|
|
| week_ago = datetime.now() - timedelta(days=7)
|
| recent_entities = db.query(Entity).filter(Entity.created_at >= week_ago).count()
|
| recent_relationships = db.query(Relationship).filter(Relationship.created_at >= week_ago).count()
|
|
|
| return {
|
| "entity_counts": entity_counts,
|
| "relationship_count": relationship_count,
|
| "recent_activity": {
|
| "entities": recent_entities,
|
| "relationships": recent_relationships,
|
| "total": recent_entities + recent_relationships
|
| }
|
| }
|
|
|