skills/ppt-station-skill/ppt_station/models/job.py

177 lines
5.3 KiB
Python
Raw Permalink Normal View History

"""
任务配置数据模型
"""
from typing import Dict, List, Any, Optional, Literal
from pydantic import BaseModel, Field, model_validator
class DataSource(BaseModel):
"""数据源配置"""
type: Literal["csv", "xlsx", "sql", "http", "tushare"]
# CSV/Excel 配置
path: Optional[str] = None
encoding: Optional[str] = "utf-8"
sheet_name: Optional[str] = None
# SQL 配置
conn: Optional[str] = None
query: Optional[str] = None
params: Optional[Dict[str, Any]] = None
# HTTP 配置
url: Optional[str] = None
method: Optional[str] = "GET"
headers: Optional[Dict[str, str]] = None
# Tushare 配置
api_name: Optional[str] = None # pro_bar, index_daily, etc.
ts_code: Optional[str] = None # 股票/指数代码
index_code: Optional[str] = None # 指数代码
start_date: Optional[str] = None
end_date: Optional[str] = None
fields: Optional[List[str]] = None
class TransformOp(BaseModel):
"""数据转换操作"""
type: Literal["groupby", "pivot", "merge", "compute", "filter", "sort", "rename"]
# groupby 参数
by: Optional[List[str]] = None
agg: Optional[Dict[str, str]] = None
# pivot 参数
index: Optional[str] = None
columns: Optional[str] = None
values: Optional[str] = None
# merge 参数
on: Optional[str | List[str]] = None
how: Optional[str] = "inner"
# compute 参数
expr: Optional[str] = None
output_col: Optional[str] = None
# filter 参数
condition: Optional[str] = None
# sort 参数
sort_by: Optional[List[str]] = None
ascending: Optional[bool] = True
# rename 参数
map: Optional[Dict[str, str]] = None
class Transform(BaseModel):
"""数据转换配置"""
from_: str | List[str] = Field(..., alias="from")
ops: List[TransformOp]
class ChartSeriesSpec(BaseModel):
"""图表系列配置"""
key: str # DataFrame 列名
name: str # 显示名称
type: Optional[Literal["bar", "line", "area"]] = None # 图表类型composer 模式用)
axis: Optional[Literal["primary", "secondary"]] = None
slot: Optional[int] = None # 对齐模板系列顺序
class ChartSpec(BaseModel):
"""图表配置"""
mode: Literal["create", "update", "excel_embedded"] = "update"
target: Optional[str] = None # 模板中的图表名称update 模式)
target_placeholder: Optional[str] = None # 占位符名称create/excel_embedded 模式)
chart_type: Optional[str] = None # XL_CHART_TYPEcreate 模式)或 Excel 图表类型
source: str # 数据源名称
categories: str # 分类列名
series: List[ChartSeriesSpec]
respect_template_types: bool = True
series_order_strict: bool = True
def has_secondary_axis(self) -> bool:
"""检查是否有次坐标轴系列"""
return any(s.axis == "secondary" for s in self.series)
class TableSpec(BaseModel):
"""表格配置"""
target: str # 模板中的表格名称
source: str # 数据源名称
columns: List[str]
header: Optional[List[str]] = None
number_format: Optional[Dict[str, str]] = None
class TextSpec(BaseModel):
"""文本配置"""
target: str # 模板中的文本框名称
value: str # 支持 Jinja2 模板语法
class SlideSpec(BaseModel):
"""幻灯片配置"""
id: str
layout: Optional[str] = None # 版式名称
texts: Optional[List[TextSpec]] = None
tables: Optional[List[TableSpec]] = None
charts: Optional[List[ChartSpec]] = None
class TemplateSpec(BaseModel):
"""模板配置"""
path: str
master: Optional[str] = None
notes: Optional[str] = None
class OutputSpec(BaseModel):
"""输出配置"""
path: str
overwrite: bool = True
add_metadata: bool = True # 是否添加生成信息到元数据
class ComposerPageSpec(BaseModel):
"""Composer 模式的页面规格"""
layout: str # LAYOUT_REGISTRY 中的布局名称
data: Dict[str, Any] # 传给布局函数的 data dict
class Job(BaseModel):
"""完整的任务配置 — 支持 template 和 composer 两种模式"""
mode: Literal["template", "composer"] = "template"
# template 模式字段
template: Optional[TemplateSpec] = None
slides: Optional[List[SlideSpec]] = None
# composer 模式字段
theme: Optional[str] = None # THEMES 中的名称
aspect_ratio: Optional[str] = None # "4:3" 或 "16:9"(默认 16:9
pages: Optional[List[ComposerPageSpec]] = None
# 共用字段
datasources: Dict[str, DataSource] = Field(default_factory=dict)
transforms: Optional[Dict[str, Transform]] = None
params: Optional[Dict[str, Any]] = None
default_layout_config: Optional[Dict[str, Any]] = None # 图表布局默认配置
output: OutputSpec
class Config:
populate_by_name = True
@model_validator(mode="after")
def check_mode_fields(self):
if self.mode == "template":
if not self.template:
raise ValueError("template 模式必须提供 template 字段")
if not self.slides:
raise ValueError("template 模式必须提供 slides 字段")
elif self.mode == "composer":
if not self.pages:
raise ValueError("composer 模式必须提供 pages 字段")
return self