Numidium / app /api /routes /timeline.py
Madras1's picture
Upload 63 files
270c1c7 verified
"""
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" or "relationship"
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.
"""
# Calculate date range
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
events = []
# Get entities
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:
# Prefer event_date over created_at
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, "๐Ÿ“„")
))
# Get relationships
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:
# Prefer event_date over created_at
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="๐Ÿ”—"
))
# Sort by date
events.sort(key=lambda x: x.date, reverse=True)
# Group by date
groups_dict = defaultdict(list)
for event in events:
date_key = event.date[:10] # YYYY-MM-DD
groups_dict[date_key].append(event)
# Format groups
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"""
# Count entities by type
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
# Count relationships
relationship_count = db.query(Relationship).count()
# Recent activity (last 7 days)
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
}
}