265 lines
8.4 KiB
Python
265 lines
8.4 KiB
Python
"""
|
|
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
|