"""通用高级筛选器出入参(需求 2.7.1)。 请求体形如: { "model": "student", "rules": [ {"field": "age", "operator": ">", "value": 25}, {"field": "gender", "operator": "=", "value": "男"}, {"logic": "OR", "sub_rules": [ {"field": "salary", "operator": ">=", "value": 15000}, {"field": "class_name", "operator": "like", "value": "Java"} ]} ], "order_by": "age", "order": "desc", "page": 1, "page_size": 10 } """ from __future__ import annotations from typing import Any, Literal from pydantic import BaseModel, Field, field_validator, model_validator from app.schema.common import SortOrder class FilterRule(BaseModel): """一条规则 = 一个条件 或 一组子规则。""" field: str | None = Field(None, description="字段名,与 sub_rules 二选一") operator: str | None = Field( None, description="操作符 > < = != >= <= like in not_like not_in between is_null not_null", ) value: Any = Field(None, description="比较值;in/not_in 传数组,between 传 [a, b]") logic: Literal["AND", "OR", "and", "or"] | None = Field(None, description="子规则之间的逻辑,AND / OR") sub_rules: list["FilterRule"] | None = Field(None, description="嵌套子规则,可多层") @model_validator(mode="after") def _check(self): is_group = bool(self.sub_rules) if is_group: if self.logic is None: self.logic = "AND" return self if not self.field or not self.operator: raise ValueError("叶子规则必须同时提供 field 和 operator") return self @field_validator("operator") @classmethod def _lower(cls, v): # noqa: ANN001, ANN201 return v.strip().lower() if isinstance(v, str) else v FilterRule.model_rebuild() class QueryRequest(BaseModel): model: str = Field("student", description="目标模型:student / class / teacher / score / employment / advisor") rules: list[FilterRule] = Field(default_factory=list, description="筛选规则树") order_by: str | None = Field(None, description="排序字段(同样受白名单限制)") order: SortOrder = Field(SortOrder.ASC, description="排序方向 asc / desc") page: int = Field(1, ge=1) page_size: int = Field(10, ge=1, le=200) fields: list[str] | None = Field(None, description="指定返回字段,不传返回全部可读字段") with_explain: bool = Field(True, description="是否返回规则解析后的说明,方便调试规则") class MetricSpec(BaseModel): func: Literal["count", "count_distinct", "avg", "sum", "max", "min"] = Field(..., description="聚合函数") field: str | None = Field(None, description="聚合字段;count 时可省略") alias: str | None = Field(None, description="结果列名") class AggregationParams(BaseModel): model: str = Field(..., description="目标模型") group_by: list[str] = Field(default_factory=list, description="分组字段,可为空=整体聚合") metrics: list[MetricSpec] = Field(..., min_length=1, description="聚合指标") rules: list[FilterRule] = Field(default_factory=list, description="筛选规则树") having: list[FilterRule] = Field(default_factory=list, description="对聚合结果的过滤(HAVING)") order_by: str | None = Field(None, description="按结果列名排序") order: SortOrder = SortOrder.DESC limit: int = Field(50, ge=1, le=500) class AggregationResult(BaseModel): columns: list[str] = Field(default_factory=list) rows: list[dict[str, Any]] = Field(default_factory=list) total: int = 0 sql: str | None = Field(None, description="实际执行的 SQL(已参数化,便于讲解与排查)") class FieldMeta(BaseModel): field: str type: str desc: str sortable: bool = True filterable: bool = True class ModelMeta(BaseModel): model: str label: str fields: list[FieldMeta] = Field(default_factory=list) relations: list[str] = Field(default_factory=list, description="可通过 JOIN 使用的关联字段")