agent_tracker/models.py
nickviljoen 6c231cb094 Add read-only admin role, client verification workflow, and email notifications
- Three-tier role system: user, admin, readonly_admin with dashboard gating
- Client field (Yes/No) on registration with conditional Client Name and Studio Name
- Auto-tag client agents as "needs_verification" with Verification tab on admin dashboard
- Client agent email notification via Mailgun to configured recipients
- Daily agent digest email scheduled via APScheduler (configurable hour)
- Manual digest trigger endpoint: POST /api/admin/digest/send
- Role dropdown replaces is_admin checkbox in user edit modal
- Registration form reordered: Name, Description, Purpose, Client, Client Name, Studio, Tool
- Stat card CSS fix for text truncation on admin dashboard
- Updated CLAUDE.md documentation and PLAN file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:47:02 +02:00

223 lines
9.3 KiB
Python

from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
class UsageTimelineEntry(BaseModel):
date: str = Field(..., description="Date in YYYY-MM-DD format")
message_count: int = Field(..., ge=0, description="Number of messages on this date")
token_count: int = Field(0, ge=0, description="Number of tokens consumed on this date")
class AiAgent(BaseModel):
agent_id: int
agent_name: str
agent_tool: str | None = Field(default=None, title="The tool or platform where the agent operates", max_length=100)
agent_description: str | None = Field(default=None, title="The description of the agent", max_length=300)
agent_purpose: str | None = Field(default=None, title="The purpose of the agent", max_length=200)
agent_version: str | None = Field(default=None, title="The version of the agent", max_length=100)
agent_status: str | None = Field(default=None, title="The status of the agent", max_length=100, enum=['Active', 'Inactive', 'Deprecated', 'Development'])
agent_location: str | None = Field(default=None, title="The location of the agent", max_length=100)
agent_department: str | None = Field(default=None, title="The department of the agent", max_length=100)
agent_contact_person: str | None = Field(default=None, title="The contact person for the agent", max_length=100)
agent_created_at: str | None = Field(default=None, title="The creation date of the agent", max_length=100)
agent_updated_at: str | None = Field(default=None, title="The last update date of the agent", max_length=100)
agent_tags: list[str] | None = Field(default=None, title="Tags associated with the agent", max_length=100)
agent_metadata: dict[str, str] | None = Field(default=None, title="Metadata associated with the agent")
agent_userbase: list[str] | None = Field(default=None, title="Userbase associated with the agent")
agent_capabilities: list[str] | None = Field(default=None, title="Capabilities of the agent")
url: str | None = Field(default=None, title="Direct link to create a conversation with this agent")
quality_audit_status: bool | None = Field(default=False, title="Quality audit status")
quality_audit_updated_by: str | None = Field(default=None, title="Admin user ID who updated quality audit")
quality_audit_updated_at: str | None = Field(default=None, title="Quality audit last update timestamp")
quality_audit_updated_by_name: str | None = Field(default=None, title="Admin user name who updated quality audit")
risk_factor: int | None = Field(default=None, title="Risk factor rating (1-5)", ge=1, le=5)
last_edited_by: str | None = Field(default=None, title="Email of user who last edited this agent")
discipline: str | None = Field(default=None, title="Business discipline/category", max_length=100)
rating: float | None = Field(default=None, title="Star rating (1-5)", ge=1, le=5)
# User Base Model
class UserCreate(BaseModel):
email: EmailStr
password: str
full_name: Optional[str] = None
class UserLogin(BaseModel):
email: EmailStr
password: str
class UserResponse(BaseModel):
email: EmailStr
full_name: Optional[str] = None
is_active: bool
is_admin: bool
role: Optional[str] = "user"
auth_provider: Optional[str] = "local"
class UserUpdate(BaseModel):
full_name: Optional[str] = None
is_active: Optional[bool] = None
is_admin: Optional[bool] = None
role: Optional[str] = Field(default=None, pattern="^(user|admin|readonly_admin)$")
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
# Admin user management models
class AdminUserCreate(BaseModel):
email: EmailStr
full_name: Optional[str] = None
password: str = Field(..., min_length=8)
is_admin: bool = False
class AdminPasswordReset(BaseModel):
new_password: str = Field(..., min_length=8)
class PasswordChange(BaseModel):
current_password: str
new_password: str = Field(..., min_length=8)
# Agent models for creation and response
class AiAgentCreate(BaseModel):
agent_name: str
agent_tool: str
agent_description: Optional[str] = None
agent_purpose: Optional[str] = None
agent_version: Optional[str] = None
agent_status: Optional[str] = "Development"
agent_location: Optional[str] = None
agent_department: Optional[str] = None
agent_contact_person: Optional[str] = None
agent_tags: Optional[list[str]] = None
agent_metadata: Optional[dict[str, str]] = None
agent_userbase: Optional[list[str]] = None
agent_capabilities: Optional[list[str]] = None
url: Optional[str] = None
quality_audit_status: Optional[bool] = False
quality_audit_updated_by: Optional[str] = None
quality_audit_updated_at: Optional[str] = None
quality_audit_updated_by_name: Optional[str] = None
risk_factor: Optional[int] = Field(default=None, ge=1, le=5)
last_edited_by: Optional[str] = None
discipline: Optional[str] = None
rating: Optional[float] = Field(default=None, ge=1, le=5)
client: Optional[str] = None
client_name: Optional[str] = None
studio_name: Optional[str] = None
class AiAgentResponse(BaseModel):
agent_id: str
agent_name: str
agent_tool: Optional[str] = None
agent_description: Optional[str] = None
agent_purpose: Optional[str] = None
agent_version: Optional[str] = None
agent_status: Optional[str] = None
agent_location: Optional[str] = None
agent_department: Optional[str] = None
agent_contact_person: Optional[str] = None
agent_created_at: Optional[str] = None
agent_updated_at: Optional[str] = None
agent_tags: Optional[list[str]] = None
agent_metadata: Optional[dict[str, str]] = None
agent_userbase: Optional[list[str]] = None
agent_capabilities: Optional[list[str]] = None
url: Optional[str] = None
quality_audit_status: Optional[bool] = None
quality_audit_updated_by: Optional[str] = None
quality_audit_updated_at: Optional[str] = None
quality_audit_updated_by_name: Optional[str] = None
risk_factor: Optional[int] = None
last_edited_by: Optional[str] = None
discipline: Optional[str] = None
rating: Optional[float] = None
rating_count: Optional[int] = None
client: Optional[str] = None
client_name: Optional[str] = None
studio_name: Optional[str] = None
verification_status: Optional[str] = None
verified_by: Optional[str] = None
verified_date: Optional[str] = None
created_by: str
# Usage tracking fields (new)
usage_timeline: Optional[List[dict]] = None
conversation_count: Optional[int] = None
unique_users: Optional[int] = None
total_messages: Optional[int] = None
first_used: Optional[str] = None
last_used: Optional[str] = None
total_tokens: Optional[int] = None
prompt_tokens: Optional[int] = None
completion_tokens: Optional[int] = None
# Agent Collector API Models (for compatibility with agent_collector app)
class AgentCollectorCreate(BaseModel):
name: str = Field(min_length=1)
description: str = Field(min_length=1)
purpose: str = Field(min_length=1)
tool: str = Field(min_length=1)
location: Optional[str] = None
userbase: Optional[list[str]] = None
version: Optional[str] = None
creation_date: Optional[str] = None # ISO 8601 datetime string
last_updated: Optional[str] = None # ISO 8601 datetime string
capabilities: Optional[list[str]] = None
status: Optional[str] = Field(default="development", pattern="^(?i)(active|inactive|deprecated|development)$")
department: Optional[str] = None
contact_person: Optional[str] = None
tags: Optional[list[str]] = None
metadata: Optional[dict] = None
url: Optional[str] = None
discipline: Optional[str] = None
client: Optional[str] = None
client_name: Optional[str] = None
studio_name: Optional[str] = None
# Usage tracking fields (new)
usage_timeline: Optional[List[UsageTimelineEntry]] = None
conversation_count: Optional[int] = Field(default=None, ge=0)
unique_users: Optional[int] = Field(default=None, ge=0)
total_messages: Optional[int] = Field(default=None, ge=0)
first_used: Optional[str] = None # ISO 8601 datetime string
last_used: Optional[str] = None # ISO 8601 datetime string
total_tokens: Optional[int] = Field(default=None, ge=0)
prompt_tokens: Optional[int] = Field(default=None, ge=0)
completion_tokens: Optional[int] = Field(default=None, ge=0)
class AgentCollectorResponse(BaseModel):
status: str = "success"
message: str = "Agent data collected successfully"
agent_id: str
class HealthCheckResponse(BaseModel):
status: str
message: str
timestamp: str
database: dict
class AgentUsageTrackingResponse(BaseModel):
status: str = "usage_logged"
message: str = "Agent already exists, usage tracked"
agent_name: str
class AgentUsageRecord(BaseModel):
agent_name: str
agent_data: dict
timestamp: str
usage_count: Optional[int] = None
class AgentUsageStatsResponse(BaseModel):
agent_name: str
total_usage_count: int
first_usage: Optional[str] = None
last_usage: Optional[str] = None
usage_by_period: dict
conversation_count: Optional[int] = None
unique_users: Optional[int] = None
total_tokens: Optional[int] = None
prompt_tokens: Optional[int] = None
completion_tokens: Optional[int] = None