"""产品推介材料接口请求结构。""" from datetime import date from typing import Any from pydantic import BaseModel, ConfigDict, Field, field_validator class StrictPayload(BaseModel): model_config = ConfigDict(extra="forbid") class PromotionTaskCreate(StrictPayload): product_name: str = Field(min_length=1, max_length=128) product_code: str | None = Field(default=None, max_length=32) material_title: str = Field(min_length=1, max_length=200) style_code: str = Field( default="balanced_allocation", pattern="^(steady_professional|balanced_allocation|growth_research)$", ) output_formats: list[str] = Field(default_factory=lambda: ["pptx"], min_length=1, max_length=2) @field_validator("output_formats") @classmethod def validate_output_formats(cls, value: list[str]) -> list[str]: invalid = set(value) - {"pptx", "pdf", "poster"} if invalid: raise ValueError("输出格式只支持 pptx、pdf 或 poster") if len(set(value)) != len(value): raise ValueError("输出格式不能重复") return value class ProductInfo(StrictPayload): fund_type: str = Field(min_length=1, max_length=64) operation_mode: str = Field(min_length=1, max_length=64) product_status: str = Field(default="new_product", max_length=64) investment_objective: str = Field(min_length=1, max_length=2000) benchmark: str | None = Field(default=None, max_length=500) risk_level: str | None = Field(default=None, max_length=32) class ManagerInfo(StrictPayload): manager_name: str = Field(min_length=1, max_length=64) management_company: str = Field(min_length=1, max_length=128) registration_code: str = Field(min_length=1, max_length=64) employment_years: str | None = Field(default=None, max_length=64) investment_management_experience: str | None = Field(default=None, max_length=1000) profile: str | None = Field(default=None, max_length=3000) class TeamInfo(StrictPayload): team_description: str = Field(min_length=1, max_length=3000) research_capability: str | None = Field(default=None, max_length=2000) class StrategyInfo(StrictPayload): investment_scope: str = Field(min_length=1, max_length=3000) strategy: str = Field(min_length=1, max_length=3000) restrictions: str = Field(min_length=1, max_length=3000) index_tool_attribute: str | None = Field(default=None, max_length=1000) class FeeStructure(StrictPayload): subscription_fee: str | None = Field(default=None, max_length=500) purchase_fee: str | None = Field(default=None, max_length=500) redemption_fee: str | None = Field(default=None, max_length=500) sales_service_fee: str | None = Field(default=None, max_length=500) management_fee: str | None = Field(default=None, max_length=500) custody_fee: str | None = Field(default=None, max_length=500) client_maintenance_fee: str | None = Field(default=None, max_length=500) class RankingInfo(StrictPayload): enabled: bool = False institution_name: str | None = Field(default=None, max_length=128) evaluation_period_years: str | None = Field(default=None, max_length=32) ranking_text: str | None = Field(default=None, max_length=1000) public_source: str | None = Field(default=None, max_length=1000) class PerformanceInfo(StrictPayload): as_of_date: date | None = None history_months: int | None = Field(default=None, ge=0, le=600) product_return: str | None = Field(default=None, max_length=64) max_drawdown: str | None = Field(default=None, max_length=64) volatility: str | None = Field(default=None, max_length=64) sharpe_ratio: str | None = Field(default=None, max_length=64) show_product_performance: bool = False show_manager_performance: bool = False ranking: RankingInfo = Field(default_factory=RankingInfo) class RiskDisclosure(StrictPayload): special_risks: list[str] = Field(default_factory=list, max_length=20) additional_notes: str | None = Field(default=None, max_length=2000) class PromotionInputsUpdate(StrictPayload): product_info: ProductInfo manager_info: ManagerInfo team_info: TeamInfo strategy_info: StrategyInfo fee_structure: FeeStructure performance_info: PerformanceInfo risk_disclosure: RiskDisclosure = Field(default_factory=RiskDisclosure) source_notes: dict[str, Any] = Field(default_factory=dict) class PromotionGenerationRequest(StrictPayload): output_formats: list[str] | None = Field(default=None, min_length=1, max_length=2) @field_validator("output_formats") @classmethod def validate_generation_formats(cls, value: list[str] | None) -> list[str] | None: if value is None: return value if set(value) - {"pptx", "pdf", "poster"} or len(set(value)) != len(value): raise ValueError("输出格式只支持不重复的 pptx、pdf 或 poster") return value class PromotionReviewRequest(StrictPayload): material_version_id: int = Field(gt=0) decision: str = Field(pattern="^(approved|rejected|revision_requested)$") comment: str | None = Field(default=None, max_length=1000) class PromotionDeliveryRequest(StrictPayload): material_version_id: int = Field(gt=0) advisor_ids: list[int] = Field(min_length=1, max_length=100) delivery_channel: str = Field(default="internal_record", max_length=32)