mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
181 lines
5.7 KiB
Python
181 lines
5.7 KiB
Python
|
|
import json
|
||
|
|
from datetime import datetime, timedelta
|
||
|
|
from typing import Any, Dict, List, Optional
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
|
|
from pydantic import BaseModel
|
||
|
|
|
||
|
|
from api.db import db_client
|
||
|
|
from api.db.models import UserModel
|
||
|
|
from api.services.auth.depends import get_user
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/organizations")
|
||
|
|
|
||
|
|
|
||
|
|
class CurrentUsageResponse(BaseModel):
|
||
|
|
period_start: str
|
||
|
|
period_end: str
|
||
|
|
used_dograh_tokens: float
|
||
|
|
quota_dograh_tokens: int
|
||
|
|
percentage_used: float
|
||
|
|
next_refresh_date: str
|
||
|
|
quota_enabled: bool
|
||
|
|
total_duration_seconds: int
|
||
|
|
# New USD fields
|
||
|
|
used_amount_usd: Optional[float] = None
|
||
|
|
quota_amount_usd: Optional[float] = None
|
||
|
|
currency: Optional[str] = None
|
||
|
|
price_per_second_usd: Optional[float] = None
|
||
|
|
|
||
|
|
|
||
|
|
class WorkflowRunUsageResponse(BaseModel):
|
||
|
|
id: int
|
||
|
|
workflow_id: int
|
||
|
|
workflow_name: Optional[str]
|
||
|
|
name: str
|
||
|
|
created_at: str
|
||
|
|
dograh_token_usage: float
|
||
|
|
call_duration_seconds: int
|
||
|
|
recording_url: Optional[str] = None
|
||
|
|
transcript_url: Optional[str] = None
|
||
|
|
phone_number: Optional[str] = None
|
||
|
|
disposition: Optional[str] = None
|
||
|
|
initial_context: Optional[Dict[str, Any]] = None
|
||
|
|
gathered_context: Optional[Dict[str, Any]] = None
|
||
|
|
# New USD field
|
||
|
|
charge_usd: Optional[float] = None
|
||
|
|
|
||
|
|
|
||
|
|
class UsageHistoryResponse(BaseModel):
|
||
|
|
runs: List[WorkflowRunUsageResponse]
|
||
|
|
total_dograh_tokens: float
|
||
|
|
total_duration_seconds: int
|
||
|
|
total_count: int
|
||
|
|
page: int
|
||
|
|
limit: int
|
||
|
|
total_pages: int
|
||
|
|
|
||
|
|
|
||
|
|
class DailyUsageItem(BaseModel):
|
||
|
|
date: str
|
||
|
|
minutes: float
|
||
|
|
cost_usd: Optional[float] = None
|
||
|
|
dograh_tokens: float
|
||
|
|
call_count: int
|
||
|
|
|
||
|
|
|
||
|
|
class DailyUsageBreakdownResponse(BaseModel):
|
||
|
|
breakdown: List[DailyUsageItem]
|
||
|
|
total_minutes: float
|
||
|
|
total_cost_usd: Optional[float] = None
|
||
|
|
total_dograh_tokens: float
|
||
|
|
currency: Optional[str] = None
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/usage/current-period", response_model=CurrentUsageResponse)
|
||
|
|
async def get_current_period_usage(user: UserModel = Depends(get_user)):
|
||
|
|
"""Get current billing period usage for the user's organization."""
|
||
|
|
if not user.selected_organization_id:
|
||
|
|
raise HTTPException(status_code=400, detail="No organization selected")
|
||
|
|
|
||
|
|
try:
|
||
|
|
usage = await db_client.get_current_usage(user.selected_organization_id)
|
||
|
|
return usage
|
||
|
|
except Exception as e:
|
||
|
|
raise HTTPException(status_code=500, detail=str(e))
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/usage/runs", response_model=UsageHistoryResponse)
|
||
|
|
async def get_usage_history(
|
||
|
|
start_date: Optional[str] = Query(None, description="ISO format date string"),
|
||
|
|
end_date: Optional[str] = Query(None, description="ISO format date string"),
|
||
|
|
page: int = Query(1, ge=1),
|
||
|
|
limit: int = Query(50, ge=1, le=100),
|
||
|
|
filters: Optional[str] = Query(None, description="JSON string of filters"),
|
||
|
|
user: UserModel = Depends(get_user),
|
||
|
|
):
|
||
|
|
"""Get paginated workflow runs with usage for the organization."""
|
||
|
|
if not user.selected_organization_id:
|
||
|
|
raise HTTPException(status_code=400, detail="No organization selected")
|
||
|
|
|
||
|
|
# Parse dates if provided
|
||
|
|
start_dt = datetime.fromisoformat(start_date) if start_date else None
|
||
|
|
end_dt = datetime.fromisoformat(end_date) if end_date else None
|
||
|
|
|
||
|
|
# Parse filters if provided
|
||
|
|
parsed_filters = None
|
||
|
|
if filters:
|
||
|
|
try:
|
||
|
|
parsed_filters = json.loads(filters)
|
||
|
|
except json.JSONDecodeError:
|
||
|
|
raise HTTPException(status_code=400, detail="Invalid filters format")
|
||
|
|
|
||
|
|
try:
|
||
|
|
offset = (page - 1) * limit
|
||
|
|
(
|
||
|
|
runs,
|
||
|
|
total_count,
|
||
|
|
total_tokens,
|
||
|
|
total_duration,
|
||
|
|
) = await db_client.get_usage_history(
|
||
|
|
user.selected_organization_id,
|
||
|
|
start_date=start_dt,
|
||
|
|
end_date=end_dt,
|
||
|
|
limit=limit,
|
||
|
|
offset=offset,
|
||
|
|
filters=parsed_filters,
|
||
|
|
)
|
||
|
|
|
||
|
|
total_pages = (total_count + limit - 1) // limit
|
||
|
|
|
||
|
|
return {
|
||
|
|
"runs": runs,
|
||
|
|
"total_dograh_tokens": total_tokens,
|
||
|
|
"total_duration_seconds": total_duration,
|
||
|
|
"total_count": total_count,
|
||
|
|
"page": page,
|
||
|
|
"limit": limit,
|
||
|
|
"total_pages": total_pages,
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
raise HTTPException(status_code=500, detail=str(e))
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/usage/daily-breakdown", response_model=DailyUsageBreakdownResponse)
|
||
|
|
async def get_daily_usage_breakdown(
|
||
|
|
days: int = Query(7, ge=1, le=30, description="Number of days to include"),
|
||
|
|
user: UserModel = Depends(get_user),
|
||
|
|
):
|
||
|
|
"""Get daily usage breakdown for the last N days. Only available for organizations with pricing."""
|
||
|
|
if not user.selected_organization_id:
|
||
|
|
raise HTTPException(status_code=400, detail="No organization selected")
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Get organization to check if it has pricing
|
||
|
|
org = await db_client.get_organization_by_id(user.selected_organization_id)
|
||
|
|
if not org or org.price_per_second_usd is None:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400,
|
||
|
|
detail="Daily breakdown is only available for organizations with pricing configured",
|
||
|
|
)
|
||
|
|
|
||
|
|
# Calculate date range
|
||
|
|
end_date = datetime.now()
|
||
|
|
start_date = end_date - timedelta(days=days - 1)
|
||
|
|
start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||
|
|
|
||
|
|
# Get daily breakdown
|
||
|
|
breakdown = await db_client.get_daily_usage_breakdown(
|
||
|
|
user.selected_organization_id,
|
||
|
|
start_date,
|
||
|
|
end_date,
|
||
|
|
org.price_per_second_usd,
|
||
|
|
user_id=user.id,
|
||
|
|
)
|
||
|
|
|
||
|
|
return breakdown
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
raise HTTPException(status_code=500, detail=str(e))
|