feat: Add AI report generation with DIFY integration
- Add Users table for display name resolution from AD authentication - Integrate DIFY AI service for report content generation - Create docx assembly service with image embedding from MinIO - Add REST API endpoints for report generation and download - Add WebSocket notifications for generation progress - Add frontend UI with progress modal and download functionality - Add integration tests for report generation flow Report sections (Traditional Chinese): - 事件摘要 (Summary) - 時間軸 (Timeline) - 參與人員 (Participants) - 處理過程 (Resolution Process) - 目前狀態 (Current Status) - 最終處置結果 (Final Resolution) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
105
app/modules/report_generation/schemas.py
Normal file
105
app/modules/report_generation/schemas.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Pydantic schemas for report generation API
|
||||
|
||||
Request and response models for the report generation endpoints.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ReportStatus(str, Enum):
|
||||
"""Report generation status"""
|
||||
PENDING = "pending"
|
||||
COLLECTING_DATA = "collecting_data"
|
||||
GENERATING_CONTENT = "generating_content"
|
||||
ASSEMBLING_DOCUMENT = "assembling_document"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
# Request Schemas
|
||||
class ReportGenerateRequest(BaseModel):
|
||||
"""Request to generate a report (optional parameters)"""
|
||||
include_images: bool = Field(default=True, description="Whether to embed images in the report")
|
||||
include_file_list: bool = Field(default=True, description="Whether to include file attachment list")
|
||||
|
||||
|
||||
# Response Schemas
|
||||
class ReportGenerateResponse(BaseModel):
|
||||
"""Response after triggering report generation"""
|
||||
report_id: str = Field(..., description="Unique report identifier")
|
||||
status: ReportStatus = Field(..., description="Initial status (typically 'pending')")
|
||||
message: str = Field(default="Report generation started", description="Status message")
|
||||
|
||||
|
||||
class ReportStatusResponse(BaseModel):
|
||||
"""Full report metadata response"""
|
||||
report_id: str
|
||||
room_id: str
|
||||
generated_by: str
|
||||
generated_at: datetime
|
||||
status: ReportStatus
|
||||
error_message: Optional[str] = None
|
||||
report_title: Optional[str] = None
|
||||
prompt_tokens: Optional[int] = None
|
||||
completion_tokens: Optional[int] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ReportListItem(BaseModel):
|
||||
"""Report item in list response"""
|
||||
report_id: str
|
||||
generated_at: datetime
|
||||
generated_by: str
|
||||
status: ReportStatus
|
||||
report_title: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ReportListResponse(BaseModel):
|
||||
"""List of reports for a room"""
|
||||
reports: List[ReportListItem]
|
||||
total: int
|
||||
|
||||
|
||||
# AI Report Content Schemas (validated JSON from DIFY)
|
||||
class TimelineEvent(BaseModel):
|
||||
"""Single event in timeline"""
|
||||
time: str = Field(..., description="Time of event (HH:MM or YYYY-MM-DD HH:MM)")
|
||||
description: str = Field(..., description="Event description")
|
||||
|
||||
|
||||
class ParticipantInfo(BaseModel):
|
||||
"""Participant information"""
|
||||
name: str = Field(..., description="Participant name")
|
||||
role: str = Field(..., description="Role in incident (e.g., 事件發起人, 維修負責人)")
|
||||
|
||||
|
||||
class AIReportContent(BaseModel):
|
||||
"""Validated JSON schema from DIFY AI response"""
|
||||
summary: dict = Field(..., description="Event summary with 'content' field")
|
||||
timeline: dict = Field(..., description="Timeline with 'events' list")
|
||||
participants: dict = Field(..., description="Participants with 'members' list")
|
||||
resolution_process: dict = Field(..., description="Resolution process with 'content' field")
|
||||
current_status: dict = Field(..., description="Current status with 'status' and 'description' fields")
|
||||
final_resolution: dict = Field(..., description="Final resolution with 'has_resolution' and 'content' fields")
|
||||
|
||||
@classmethod
|
||||
def validate_structure(cls, data: dict) -> bool:
|
||||
"""Validate the basic structure of AI response"""
|
||||
required_keys = ["summary", "timeline", "participants", "resolution_process", "current_status", "final_resolution"]
|
||||
for key in required_keys:
|
||||
if key not in data:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# Error Response
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Error response"""
|
||||
detail: str
|
||||
Reference in New Issue
Block a user