""" gap_engine.models — 数据模型(照 GapEngine 原文,含创意卡扩展) 原文 7 个类:ParamValue / Param / Layer / ParamLibrary / Selection / GapResult / CreativeConstraints 扩展:ComboCandidate(AI 生成的候选组合)、CreativeCard(入库的创意卡) """ from __future__ import annotations from datetime import datetime, timezone from typing import Any, Optional from pydantic import BaseModel, Field # ════════════════════════════════════════════════════════════ # 参数库(照原文) # ════════════════════════════════════════════════════════════ class ParamValue(BaseModel): """单个参数值:值名 + 预期强度 + 画面联想描述""" value: str intensity: float = 0.5 cue: str = "" class Param(BaseModel): id: str name: str values: list[ParamValue] = Field(default_factory=list) class Layer(BaseModel): id: str name: str params: list[Param] = Field(default_factory=list) class ParamLibrary(BaseModel): layers: list[Layer] = Field(default_factory=list) def find_value(self, layer_id: str, param_id: str, value: str) -> Optional[ParamValue]: for L in self.layers: if L.id != layer_id: continue for p in L.params: if p.id != param_id: continue for v in p.values: if v.value == value: return v return None def param_name(self, layer_id: str, param_id: str) -> str: for L in self.layers: if L.id == layer_id: for p in L.params: if p.id == param_id: return p.name return param_id # ════════════════════════════════════════════════════════════ # 选择与缺口(照原文) # ════════════════════════════════════════════════════════════ class Selection(BaseModel): """一次选择:某个参数取某个值""" layer_id: str param_id: str param_name: str = "" value: str intensity: float cue: str = "" class GapResult(BaseModel): """缺口计算结果(字段口径照原文)""" selections: list[Selection] = Field(default_factory=list) variance: float = 0.0 gap_score: float = 0.0 # 0-100(方差 × 400 归一,原文口径:落差型反差) extreme_score: float = 0.0 # 0-100(平均偏离中性 0.5,补足"整体异质"型反差) high_count: int = 0 # intensity >= 0.6 low_count: int = 0 # intensity <= 0.3 scene_card: str = "" # 画面联想卡 level: str = "" # 显著 / 中等 / 微弱 class CreativeConstraints(BaseModel): """矩阵配置(负面/正向/道具场景)—— 本文档不含生成参数""" negative_hard: list[dict] = Field(default_factory=list) negative_soft: list[dict] = Field(default_factory=list) positive: list[dict] = Field(default_factory=list) props: list[dict] = Field(default_factory=list) scenes: list[dict] = Field(default_factory=list) def relaxed_soft_ids(self) -> list[str]: return [s["id"] for s in self.negative_soft if s.get("relaxed")] def locked_hard_ids(self) -> list[str]: return [s["id"] for s in self.negative_hard if s.get("locked")] # ════════════════════════════════════════════════════════════ # 候选组合(AI 生成,扩展) # ════════════════════════════════════════════════════════════ class ComboCandidate(BaseModel): """一条候选创意(= 一次完整选择 + 缺口分 + 一句话联想 + 风险标记)""" cid: str = "" # 候选 id(批次内唯一) one_liner: str = "" # 核心产物:一句话联想(导演据此脑补) selections: list[Selection] = Field(default_factory=list) key_points: list[str] = Field(default_factory=list) # 关键联想点(不展开就能判断) gap_score: float = 0.0 extreme_score: float = 0.0 # 整体异质度(全高强度也可能是强反差) variance: float = 0.0 level: str = "" scene_card: str = "" risks: list[dict] = Field(default_factory=list) # [{kind:'soft'|'hard', id, name, reason}] needs_soft: list[str] = Field(default_factory=list) # LLM 标注:本条需要放开哪些 🟡 项(id) needs_hard: list[str] = Field(default_factory=list) # LLM 标注:本条涉及哪些 🔴 项(id)→ 命中即淘汰 positive_hits: list[str] = Field(default_factory=list) # 命中的正向项 id bonus: float = 0.0 # 正向加权分 final_score: float = 0.0 # gap_score + bonus props: list[str] = Field(default_factory=list) # 道具(名称) scenes: list[str] = Field(default_factory=list) # 场景变量(名称) filtered: bool = False # 是否被硬过滤淘汰 filter_reason: str = "" # ════════════════════════════════════════════════════════════ # 创意卡(入库,扩展) # ════════════════════════════════════════════════════════════ INJECT_MODES = ("strict", "reference", "idea_only") class CreativeCard(BaseModel): """创意卡 = 入创意库的一条创意(可被分镜/口播/脚本流程引用)""" id: str = "" title: str = "" # 主题(一句话) one_liner: str = "" # 一句话联想 brief: str = "" # 原始输入的 1-2 句话 selections: list[Selection] = Field(default_factory=list) props: list[str] = Field(default_factory=list) scenes: list[str] = Field(default_factory=list) gap_score: float = 0.0 variance: float = 0.0 level: str = "" scene_card: str = "" positive_hits: list[str] = Field(default_factory=list) matrix_snapshot: dict = Field(default_factory=dict) # 本次矩阵(含放开了哪些🟡) inject_mode: str = "reference" # strict 严格约束 / reference 参考 / idea_only 仅创意 status: str = "draft" # draft 草稿 / selected 已选 / used 已用于 used_by: list[dict] = Field(default_factory=list) # [{flow, run_id, at}] created_at: str = "" updated_at: str = "" def utcnow() -> str: return datetime.now(timezone.utc).isoformat(timespec="seconds")