AiPmTool / app /models /schemas.py
devarshia5's picture
Upload 43 files
1def50b verified
Raw
History Blame Contribute Delete
15.4 kB
# ============================================================================
# PYDANTIC MODELS
# ============================================================================
from pydantic import BaseModel, Field, validator, root_validator
from typing import List, Optional, Dict, Any, Literal, Union
from datetime import datetime
from enum import Enum
from .enums import TaskStatus, TaskType, TestCasePriority, TestCaseType, TestCaseCategory, OutputFormat
from app.config import Config
# ============================================================================
# FRONTEND-COMPATIBLE ENUMS (Match PM Tool Frontend)
# ============================================================================
class TaskStatusFrontend(str, Enum):
"""Status values matching frontend test-case-form.tsx"""
DRAFT = "Draft"
READY = "Ready"
IN_PROGRESS = "In Progress"
BLOCKED = "Blocked"
COMPLETED = "Completed"
PASSED = "Passed"
FAILED = "Failed"
SKIPPED = "Skipped"
class TaskPriorityFrontend(str, Enum):
"""Priority values matching frontend test-case-form.tsx"""
CRITICAL = "Critical"
HIGH = "High"
MEDIUM = "Medium"
LOW = "Low"
class TaskSeverityFrontend(str, Enum):
"""Severity values matching frontend test-case-form.tsx"""
BLOCKER = "Blocker"
CRITICAL = "Critical"
MAJOR = "Major"
MINOR = "Minor"
TRIVIAL = "Trivial"
class TaskEnvironmentFrontend(str, Enum):
"""Environment values matching frontend test-case-form.tsx"""
DEVELOPMENT = "Development"
TESTING = "Testing"
STAGING = "Staging"
PRODUCTION = "Production"
class Credentials(BaseModel):
email: str = Field(..., description="PM Tool email")
password: str = Field(..., description="PM Tool password")
@validator('email')
def validate_email(cls, v):
if '@' not in v:
raise ValueError('Invalid email format')
return v.lower()
class DateRange(BaseModel):
from_date: Optional[str] = Field(None, alias="from", description="Start date (YYYY-MM-DD)")
to_date: Optional[str] = Field(None, alias="to", description="End date (YYYY-MM-DD)")
@validator('from_date', 'to_date')
def validate_date(cls, v):
if v:
try:
datetime.strptime(v, '%Y-%m-%d')
except ValueError:
raise ValueError('Date must be in YYYY-MM-DD format')
return v
# ============================================================================
# DOCUMENT PROCESSING MODELS
# ============================================================================
class AttachmentConfig(BaseModel):
"""Configuration for individual attachment processing"""
attachment_id: Optional[str] = Field(None, description="Attachment ID from PM Tool")
filename: str = Field(..., description="Filename")
skip: bool = Field(False, description="Skip processing this attachment")
skip_reason: Optional[str] = Field(None, description="Reason for skipping")
priority: int = Field(0, ge=0, le=10, description="Processing priority (higher = first)")
max_tokens: Optional[int] = Field(None, description="Max tokens to use from this document")
class DocumentProcessingConfig(BaseModel):
"""Global document processing configuration"""
enabled: bool = Field(True, description="Enable document processing")
max_total_tokens: int = Field(6000, ge=500, le=32000, description="Max total tokens for document context")
max_chunk_tokens: int = Field(1500, ge=200, le=4000, description="Max tokens per chunk")
overlap_tokens: int = Field(100, ge=0, le=500, description="Overlap between chunks")
include_metadata: bool = Field(True, description="Include document metadata in context")
auto_download: bool = Field(True, description="Auto-download attachments from PM Tool")
download_dir: Optional[str] = Field(None, description="Directory to save downloaded files")
class ProjectSelection(BaseModel):
"""Project selection options"""
project_id: Optional[int] = Field(None, description="Project ID")
project_name: Optional[str] = Field(None, description="Project name (auto-resolved)")
include_all_tasks: bool = Field(False, description="Include all tasks from project")
class GenerateRequest(BaseModel):
# Authentication
credentials: Credentials
# Task Selection (provide at least one)
task_id: Optional[int] = Field(None, description="Single task ID")
task_ids: Optional[List[int]] = Field(None, description="Multiple task IDs")
created_by_name: Optional[str] = Field(None, description="Filter by task creator")
project_selection: Optional[ProjectSelection] = Field(None, description="Project selection options")
# Legacy project_id for backward compatibility
project_id: Optional[int] = Field(None, description="All tasks in project (legacy)")
# Filters
date_range: Optional[DateRange] = None
status: Optional[List[TaskStatus]] = Field(None, description="Filter by task status")
priority: Optional[List[TestCasePriority]] = None
task_type: Optional[TaskType] = Field(TaskType.TASK, description="Task type filter")
# Generation Settings
test_case_count: int = Field(8, ge=1, le=15, description="Test cases per task")
include_attachments: bool = Field(True, description="Fetch and analyze attachments")
include_related_tasks: bool = Field(False, description="Cross-reference similar tasks")
# Document Processing Settings
document_processing: Optional[DocumentProcessingConfig] = Field(
None, description="Document processing configuration"
)
attachment_configs: Optional[List[AttachmentConfig]] = Field(
None, description="Per-attachment processing config (skip, priority, etc.)"
)
# AI Settings
groq_model: str = Field(Config.GROQ_MODEL, description="Groq model to use")
temperature: float = Field(Config.GROQ_TEMPERATURE, ge=0, le=1)
# Output
output_format: OutputFormat = Field(OutputFormat.JSON, description="Output format")
include_metadata: bool = Field(True, description="Include generation metadata")
@root_validator(skip_on_failure=True)
def validate_task_selection(cls, values):
# At least one selection method must be provided
selections = [
values.get('task_id'),
values.get('task_ids'),
values.get('created_by_name'),
values.get('project_id'),
values.get('project_selection'),
]
if not any(selections):
raise ValueError('Provide at least one task selection method (task_id, task_ids, created_by_name, project_id, or project_selection)')
return values
class DocumentProcessingResult(BaseModel):
"""Result of document processing"""
document_id: str
filename: str
processed: bool
skipped: bool = False
skip_reason: Optional[str] = None
chunks_count: int = 0
tokens_used: int = 0
error: Optional[str] = None
class TestStep(BaseModel):
step_number: int
action: str
expected_result: str
test_data: Optional[str] = None
class TestCase(BaseModel):
"""Legacy test case format (kept for backward compatibility)"""
test_case_id: str
task_id: int
task_code: str
project_name: str
issue_name: str
feature_name: str
task_name: str
task_description: str
created_by: str
title: str
objective: str
preconditions: List[str]
test_steps: List[TestStep]
expected_outcome: str
test_type: TestCaseType
priority: TestCasePriority
category: TestCaseCategory
metadata: Optional[Dict[str, Any]] = None
# ============================================================================
# FRONTEND-COMPATIBLE TEST CASE (Matches Task Interface)
# ============================================================================
class FrontendTestCase(BaseModel):
"""
Test case format that matches the frontend Task interface exactly.
This is what the frontend expects to save directly via tasksApi.create()
Required fields (marked with * in frontend):
- title: Test Case Title
- description: Test Case Description
All other fields are optional with sensible defaults.
"""
# Core Identity
type: str = Field(default="Test Case", description="Always 'Test Case' for test cases")
id: Optional[int] = Field(default=None, description="Auto-generated by PM Tool DB")
# Required Fields
title: str = Field(..., description="Test case title (REQUIRED)")
description: str = Field(..., description="Test case description (REQUIRED)")
# Linking
issuesId: Optional[int] = Field(default=None, description="Feature/Issue ID this test case belongs to")
projectId: Optional[int] = Field(default=None, description="Project ID")
# Status & Priority
status: str = Field(default="Draft", description="Test case status")
priority: Optional[str] = Field(default="Medium", description="Priority: Critical, High, Medium, Low")
severity: Optional[str] = Field(default="Minor", description="Severity: Blocker, Critical, Major, Minor, Trivial")
# Test Details (Tab 2: Test Steps)
stepsToReproduce: Optional[str] = Field(default=None, description="Numbered test steps as text")
inputs: Optional[str] = Field(default=None, description="Test data/inputs")
expectedBehavior: Optional[str] = Field(default=None, description="Expected result")
actualBehavior: Optional[str] = Field(default="", description="Actual result (filled during testing)")
# Technical Details (Tab 3)
rootCause: Optional[str] = Field(default="", description="Root cause (filled after bug analysis)")
resolution: Optional[str] = Field(default="", description="Resolution (filled after fix)")
# Additional Fields
environment: Optional[str] = Field(default="Staging", description="Test environment")
version: Optional[str] = Field(default="", description="Version being tested")
url: Optional[str] = Field(default="", description="Related URL")
# Assignment & Dates
assignedTo: Optional[int] = Field(default=None, description="Assigned employee ID")
stateDate: Optional[str] = Field(default=None, description="Start date (ISO format)")
endDate: Optional[str] = Field(default=None, description="End date (ISO format)")
# Auto-generated fields
taskCode: Optional[str] = Field(default=None, description="Auto-generated task code")
createdAt: Optional[str] = Field(default=None, description="Creation timestamp")
createdBy: Optional[int] = Field(default=None, description="Creator employee ID")
createdByName: Optional[str] = Field(default=None, description="Creator name")
# Related names (for display)
projectName: Optional[str] = Field(default=None, description="Project name")
issueName: Optional[str] = Field(default=None, description="Issue/Feature name")
deliverableName: Optional[str] = Field(default=None, description="Deliverable/Feature name")
# Sprint info
sprintName: Optional[str] = Field(default=None, description="Sprint name")
estimates: Optional[str] = Field(default="", description="Time estimate")
esUnit: Optional[int] = Field(default=0, description="Estimate unit")
remainingHr: Optional[int] = Field(default=0, description="Remaining hours")
# Link back to original task (for reference)
related_task_id: Optional[int] = Field(default=None, description="Original task ID this was generated from")
related_task_code: Optional[str] = Field(default=None, description="Original task code")
class Config:
extra = "allow" # Allow additional fields from PM Tool
class GenerateResponse(BaseModel):
"""Legacy response format"""
success: bool
message: str
total_tasks_processed: int
total_test_cases_generated: int
failed_tasks: List[Dict[str, Any]] = []
test_cases: List[TestCase]
metadata: Optional[Dict[str, Any]] = None
download_url: Optional[str] = None
class FrontendGenerateResponse(BaseModel):
"""
Response format for frontend integration.
Returns test cases in Task format ready for tasksApi.create()
"""
success: bool
message: str
feature_id: Optional[int] = None
feature_name: Optional[str] = None
generated_at: Optional[str] = None
test_cases: List[FrontendTestCase]
metadata: Optional[Dict[str, Any]] = None
# ============================================================================
# FRONTEND REQUEST SCHEMA
# ============================================================================
class TaskInfo(BaseModel):
"""Task information from frontend"""
id: int
taskCode: Optional[str] = None
title: str
description: Optional[str] = None
status: Optional[str] = None
assignedTo: Optional[int] = None
class FrontendGenerateRequest(BaseModel):
"""
Request format from frontend for feature-based test case generation.
All dynamic data comes from the frontend's current state (issue-detail page).
"""
# Authentication (optional - can use session token)
credentials: Optional[Credentials] = None
session_token: Optional[str] = Field(None, description="PM Tool session token")
# Feature Context (from issue state)
feature_id: int = Field(..., description="Feature/Issue ID (required)")
feature_title: Optional[str] = None
feature_description: Optional[str] = None
# Project Context
project_id: Optional[int] = None
project_name: Optional[str] = None
# Deliverable Context
deliverable_id: Optional[int] = None
deliverable_name: Optional[str] = None
# Tasks to generate test cases for
tasks: List[TaskInfo] = Field(default=[], description="Tasks inside this feature")
# Existing test cases to avoid duplicates
existing_test_cases: Optional[List[Dict[str, str]]] = Field(
default=None,
description="Existing test cases (title, description) to avoid generating duplicates"
)
# Additional context from user (text or extracted PDF content)
additional_context: Optional[str] = Field(
default=None,
description="Additional context provided by user (text or PDF content) to enhance test case generation"
)
# Generation Options
test_case_count: int = Field(default=3, ge=1, le=10, description="Test cases per task")
environment: str = Field(default="Staging", description="Test environment")
include_attachments: bool = Field(default=True, description="Include document context")
# AI Settings
groq_model: Optional[str] = Field(None, description="Override default model")
temperature: Optional[float] = Field(None, ge=0, le=1, description="Override temperature")
@validator('environment')
def validate_environment(cls, v):
valid = ['Development', 'Testing', 'Staging', 'Production']
if v not in valid:
return 'Staging'
return v
class HealthResponse(BaseModel):
status: str
timestamp: str
version: str
groq_status: str
pm_tool_status: str
class ErrorResponse(BaseModel):
error: str
message: str
details: Optional[Dict[str, Any]] = None
timestamp: str