# scheme/statistics.py # 统计分析模块的请求/响应模型(含 2.7 通用高级筛选器) from typing import Any, List, Literal, Union from pydantic import BaseModel, Field # ============================================================ # 2.7.1 通用高级筛选器:支持 AND/OR 逻辑组合与嵌套 sub_rules # ============================================================ class FilterRule(BaseModel): """单条筛选规则""" field: str = Field(..., description="字段名,如 age/gender/salary/class_name") operator: Literal[">", "<", "=", "!=", ">=", "<=", "like", "in"] = Field( ..., description="比较操作符" ) value: Any = Field(..., description="比较值;operator=in 时必须是列表") class FilterGroup(BaseModel): """逻辑组合规则:可嵌套 sub_rules 实现任意深度的 AND/OR 组合""" logic: Literal["AND", "OR"] = Field("AND", description="组合方式") sub_rules: List[Union["FilterGroup", "FilterRule"]] = Field( ..., min_length=1, description="子规则(可继续嵌套 FilterGroup)" ) # Pydantic v2 递归模型需要显式 rebuild FilterGroup.model_rebuild() class FilterRequest(BaseModel): """高级筛选请求体""" model: Literal["student"] = Field(..., description="目标模型(当前支持 student)") rules: List[Union[FilterGroup, FilterRule]] = Field( ..., min_length=1, description="筛选规则列表(顶层默认 AND 连接)" ) class FilterResponse(BaseModel): total: int items: List[dict] = Field(..., description="命中的学生记录") # ============================================================ # 2.6 统计分析响应模型 # ============================================================ class ClassGenderStat(BaseModel): """2.6.1 多维度班级统计:总人数 + 性别分布""" class_id: int class_name: str total: int male: int female: int class AllAboveStudent(BaseModel): """2.6.2 每次考试都在分数线以上的学生""" stu_id: int stu_name: str class_name: str exam_count: int = Field(..., description="参加考核次数") min_score: float = Field(..., description="最低分(>= 分数线)") scores: List[dict] = Field(..., description="各次成绩明细 [{exam_id, score}]") class FailStudent(BaseModel): """2.6.2 不及格次数 >= N 的学生""" stu_id: int stu_name: str class_name: str fail_count: int = Field(..., description="不及格次数") fail_details: List[dict] = Field(..., description="不及格成绩明细 [{exam_id, score}]") class ClassExamAvg(BaseModel): """2.6.2 每次考试每个班级的平均分(支持动态排序)""" exam_id: int class_id: int class_name: str avg_score: float class TopSalaryStudent(BaseModel): """2.6.3 薪资 Top N""" stu_id: int stu_name: str class_name: str job_time: Any = Field(None, description="offer 下发时间") company_name: str salary: float class EmploymentDuration(BaseModel): """2.6.3 单个学生就业时长(天)""" stu_id: int stu_name: str class_name: str employment_open_time: Any job_time: Any = Field(None, description="未拿到 offer 为 null") duration_days: int = Field(..., description="offer下发时间 - 就业开放时间;未拿到 offer 为 -1") class ClassAvgDuration(BaseModel): """2.6.3 每个班级平均就业时长""" class_id: int class_name: str opened_count: int = Field(..., description="进入就业阶段人数(有开放时间)") offered_count: int = Field(..., description="已拿到 offer 人数") avg_duration_days: float = Field(..., description="平均就业时长(仅统计已拿到 offer 的学生)") class ScoreVolatility(BaseModel): """2.7.2 成绩波动分析:最大分差 Top N""" stu_id: int stu_name: str class_name: str max_score: float min_score: float diff: float = Field(..., description="最大分差 = 最高分 - 最低分") class EmploymentFunnel(BaseModel): """2.7.2 班级就业漏斗:总人数 -> 已就业 -> 高薪(>10k) -> 就业率""" class_id: int class_name: str total: int = Field(..., description="班级总人数") employed: int = Field(..., description="已就业人数") high_salary: int = Field(..., description="高薪人数(薪资 > 10000)") employment_rate: float = Field(..., description="就业率(百分比,保留2位)")