first commit
This commit is contained in:
264
app/schemas/report.py
Normal file
264
app/schemas/report.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
Pydantic schemas for Report API endpoints.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Student Performance Report Schemas
|
||||
# =============================================================================
|
||||
|
||||
class StudentPerformanceRecordOutput(BaseModel):
|
||||
"""Individual student performance record output."""
|
||||
|
||||
session_id: str
|
||||
wp_user_id: str
|
||||
tryout_id: str
|
||||
NM: Optional[int] = None
|
||||
NN: Optional[int] = None
|
||||
theta: Optional[float] = None
|
||||
theta_se: Optional[float] = None
|
||||
total_benar: int
|
||||
time_spent: int # Total time in seconds
|
||||
start_time: Optional[datetime] = None
|
||||
end_time: Optional[datetime] = None
|
||||
scoring_mode_used: str
|
||||
rataan_used: Optional[float] = None
|
||||
sb_used: Optional[float] = None
|
||||
|
||||
|
||||
class AggregatePerformanceStatsOutput(BaseModel):
|
||||
"""Aggregate statistics for student performance output."""
|
||||
|
||||
tryout_id: str
|
||||
participant_count: int
|
||||
avg_nm: Optional[float] = None
|
||||
std_nm: Optional[float] = None
|
||||
min_nm: Optional[int] = None
|
||||
max_nm: Optional[int] = None
|
||||
median_nm: Optional[float] = None
|
||||
avg_nn: Optional[float] = None
|
||||
std_nn: Optional[float] = None
|
||||
avg_theta: Optional[float] = None
|
||||
pass_rate: float # Percentage with NN >= 500
|
||||
avg_time_spent: float # Average time in seconds
|
||||
|
||||
|
||||
class StudentPerformanceReportOutput(BaseModel):
|
||||
"""Complete student performance report output."""
|
||||
|
||||
generated_at: datetime
|
||||
tryout_id: str
|
||||
website_id: int
|
||||
date_range: Optional[Dict[str, str]] = None
|
||||
aggregate: AggregatePerformanceStatsOutput
|
||||
individual_records: List[StudentPerformanceRecordOutput] = []
|
||||
|
||||
|
||||
class StudentPerformanceReportRequest(BaseModel):
|
||||
"""Request schema for student performance report."""
|
||||
|
||||
tryout_id: str = Field(..., description="Tryout identifier")
|
||||
website_id: int = Field(..., description="Website identifier")
|
||||
date_start: Optional[datetime] = Field(None, description="Filter by start date")
|
||||
date_end: Optional[datetime] = Field(None, description="Filter by end date")
|
||||
format_type: Literal["individual", "aggregate", "both"] = Field(
|
||||
default="both", description="Report format"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Item Analysis Report Schemas
|
||||
# =============================================================================
|
||||
|
||||
class ItemAnalysisRecordOutput(BaseModel):
|
||||
"""Item analysis record output for a single item."""
|
||||
|
||||
item_id: int
|
||||
slot: int
|
||||
level: str
|
||||
ctt_p: Optional[float] = None
|
||||
ctt_bobot: Optional[float] = None
|
||||
ctt_category: Optional[str] = None
|
||||
irt_b: Optional[float] = None
|
||||
irt_se: Optional[float] = None
|
||||
calibrated: bool
|
||||
calibration_sample_size: int
|
||||
correctness_rate: float
|
||||
item_total_correlation: Optional[float] = None
|
||||
information_values: Dict[float, float] = Field(default_factory=dict)
|
||||
optimal_theta_range: str = "N/A"
|
||||
|
||||
|
||||
class ItemAnalysisReportOutput(BaseModel):
|
||||
"""Complete item analysis report output."""
|
||||
|
||||
generated_at: datetime
|
||||
tryout_id: str
|
||||
website_id: int
|
||||
total_items: int
|
||||
items: List[ItemAnalysisRecordOutput]
|
||||
summary: Dict[str, Any]
|
||||
|
||||
|
||||
class ItemAnalysisReportRequest(BaseModel):
|
||||
"""Request schema for item analysis report."""
|
||||
|
||||
tryout_id: str = Field(..., description="Tryout identifier")
|
||||
website_id: int = Field(..., description="Website identifier")
|
||||
filter_by: Optional[Literal["difficulty", "calibrated", "discrimination"]] = Field(
|
||||
None, description="Filter items by category"
|
||||
)
|
||||
difficulty_level: Optional[Literal["mudah", "sedang", "sulit"]] = Field(
|
||||
None, description="Filter by difficulty level (only when filter_by='difficulty')"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Calibration Status Report Schemas
|
||||
# =============================================================================
|
||||
|
||||
class CalibrationItemStatusOutput(BaseModel):
|
||||
"""Calibration status for a single item output."""
|
||||
|
||||
item_id: int
|
||||
slot: int
|
||||
level: str
|
||||
sample_size: int
|
||||
calibrated: bool
|
||||
irt_b: Optional[float] = None
|
||||
irt_se: Optional[float] = None
|
||||
ctt_p: Optional[float] = None
|
||||
|
||||
|
||||
class CalibrationStatusReportOutput(BaseModel):
|
||||
"""Complete calibration status report output."""
|
||||
|
||||
generated_at: datetime
|
||||
tryout_id: str
|
||||
website_id: int
|
||||
total_items: int
|
||||
calibrated_items: int
|
||||
calibration_percentage: float
|
||||
items_awaiting_calibration: List[CalibrationItemStatusOutput]
|
||||
avg_calibration_sample_size: float
|
||||
estimated_time_to_90_percent: Optional[str] = None
|
||||
ready_for_irt_rollout: bool
|
||||
items: List[CalibrationItemStatusOutput]
|
||||
|
||||
|
||||
class CalibrationStatusReportRequest(BaseModel):
|
||||
"""Request schema for calibration status report."""
|
||||
|
||||
tryout_id: str = Field(..., description="Tryout identifier")
|
||||
website_id: int = Field(..., description="Website identifier")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tryout Comparison Report Schemas
|
||||
# =============================================================================
|
||||
|
||||
class TryoutComparisonRecordOutput(BaseModel):
|
||||
"""Tryout comparison data point output."""
|
||||
|
||||
tryout_id: str
|
||||
date: Optional[str] = None
|
||||
subject: Optional[str] = None
|
||||
participant_count: int
|
||||
avg_nm: Optional[float] = None
|
||||
avg_nn: Optional[float] = None
|
||||
avg_theta: Optional[float] = None
|
||||
std_nm: Optional[float] = None
|
||||
calibration_percentage: float
|
||||
|
||||
|
||||
class TryoutComparisonReportOutput(BaseModel):
|
||||
"""Complete tryout comparison report output."""
|
||||
|
||||
generated_at: datetime
|
||||
comparison_type: Literal["date", "subject"]
|
||||
tryouts: List[TryoutComparisonRecordOutput]
|
||||
trends: Optional[Dict[str, Any]] = None
|
||||
normalization_impact: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class TryoutComparisonReportRequest(BaseModel):
|
||||
"""Request schema for tryout comparison report."""
|
||||
|
||||
tryout_ids: List[str] = Field(..., min_length=2, description="List of tryout IDs to compare")
|
||||
website_id: int = Field(..., description="Website identifier")
|
||||
group_by: Literal["date", "subject"] = Field(
|
||||
default="date", description="Group comparison by date or subject"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Report Scheduling Schemas
|
||||
# =============================================================================
|
||||
|
||||
class ReportScheduleRequest(BaseModel):
|
||||
"""Request schema for scheduling a report."""
|
||||
|
||||
report_type: Literal["student_performance", "item_analysis", "calibration_status", "tryout_comparison"] = Field(
|
||||
..., description="Type of report to generate"
|
||||
)
|
||||
schedule: Literal["daily", "weekly", "monthly"] = Field(
|
||||
..., description="Schedule frequency"
|
||||
)
|
||||
tryout_ids: List[str] = Field(..., description="List of tryout IDs for the report")
|
||||
website_id: int = Field(..., description="Website identifier")
|
||||
recipients: List[str] = Field(..., description="List of email addresses to send report to")
|
||||
export_format: Literal["csv", "xlsx", "pdf"] = Field(
|
||||
default="xlsx", description="Export format for the report"
|
||||
)
|
||||
|
||||
|
||||
class ReportScheduleOutput(BaseModel):
|
||||
"""Output schema for scheduled report."""
|
||||
|
||||
schedule_id: str
|
||||
report_type: str
|
||||
schedule: str
|
||||
tryout_ids: List[str]
|
||||
website_id: int
|
||||
recipients: List[str]
|
||||
format: str
|
||||
created_at: datetime
|
||||
last_run: Optional[datetime] = None
|
||||
next_run: Optional[datetime] = None
|
||||
is_active: bool
|
||||
|
||||
|
||||
class ReportScheduleResponse(BaseModel):
|
||||
"""Response schema for schedule creation."""
|
||||
|
||||
schedule_id: str
|
||||
message: str
|
||||
next_run: Optional[datetime] = None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Export Schemas
|
||||
# =============================================================================
|
||||
|
||||
class ExportRequest(BaseModel):
|
||||
"""Request schema for exporting a report."""
|
||||
|
||||
schedule_id: str = Field(..., description="Schedule ID to generate report for")
|
||||
export_format: Literal["csv", "xlsx", "pdf"] = Field(
|
||||
default="xlsx", description="Export format"
|
||||
)
|
||||
|
||||
|
||||
class ExportResponse(BaseModel):
|
||||
"""Response schema for export request."""
|
||||
|
||||
file_path: str
|
||||
file_name: str
|
||||
format: str
|
||||
generated_at: datetime
|
||||
download_url: Optional[str] = None
|
||||
Reference in New Issue
Block a user