"""海报计划书上传模型。""" from sqlalchemy import Column, String, Text, BigInteger, Integer, TIMESTAMP, func from insurance.db.compat import db class PosterCaseUpload(db.Model): """海报计划书上传记录表。""" __tablename__ = "poster_case_uploads" id = Column(BigInteger, primary_key=True, autoincrement=True) user_id = Column(String(50), nullable=False, comment="用户 ID") product_id = Column(String(50), nullable=False, comment="产品 ID") product_source_type = Column(String(20), nullable=True, comment="library_product/user_material") product_source_id = Column(String(64), nullable=True, comment="产品来源 ID") product_snapshot_json = Column(Text, nullable=True, comment="产品信息快照 JSON") source_file_url = Column(String(500), nullable=False, comment="源文件地址") parse_status = Column( String(20), default="pending", comment="解析状态: pending/queued/parsing/parsed/partial/failed", ) parse_progress = Column(Integer, nullable=False, default=0, comment="解析进度 0-100") parse_message = Column(String(500), nullable=False, default="", comment="解析进度说明") parse_error = Column(Text, nullable=True, comment="解析失败原因") parse_task_id = Column(String(200), nullable=True, comment="Celery 任务 ID") parse_started_at = Column(TIMESTAMP, nullable=True) parse_heartbeat_at = Column(TIMESTAMP, nullable=True) parse_finished_at = Column(TIMESTAMP, nullable=True) file_hash = Column(String(64), nullable=True, index=True, comment="源文件 SHA-256") parsed_data = Column(Text, nullable=True, comment="系统解析结果 JSON") confirmed_data = Column(Text, nullable=True, comment="人工核对后最终结果 JSON") confirmed_by = Column(String(50), nullable=True, comment="核对人") confirmed_at = Column(TIMESTAMP, nullable=True, comment="核对时间") document_id = Column(BigInteger, nullable=True, index=True, comment="Document IR 文档 ID") snapshot_id = Column(BigInteger, nullable=True, index=True, comment="最新 confirmed PlanData 快照 ID") created_at = Column(TIMESTAMP, server_default=func.now()) def to_dict(self): import json def _safe_json(text): if not text: return None try: return json.loads(text) except (json.JSONDecodeError, TypeError): return None def _safe_error(value): if not value: return "" message = str(value).strip() unsafe_markers = ( "traceback", "file \"", "\\", "/app/", "/api/", "http://", "https://", "api_key", "token=", ) if "\n" in message or any(marker in message.lower() for marker in unsafe_markers): return "解析失败,请重试;如多次失败请联系管理员" return message[:300] return { "id": self.id, "userId": self.user_id, "productId": self.product_id, "productSource": { "type": self.product_source_type or "library_product", "id": self.product_source_id or self.product_id, }, "productSnapshot": _safe_json(self.product_snapshot_json), "sourceFileAvailable": bool(self.source_file_url), "fileHash": self.file_hash, "parseSnapshotHash": self._parse_snapshot_hash(), "parseStatus": self.parse_status, "parseProgress": self.parse_progress or 0, "parseMessage": self.parse_message or "", "parseError": _safe_error(self.parse_error), "parseTaskId": self.parse_task_id, "parseStartedAt": self.parse_started_at.isoformat() if self.parse_started_at else None, "parseHeartbeatAt": self.parse_heartbeat_at.isoformat() if self.parse_heartbeat_at else None, "parseFinishedAt": self.parse_finished_at.isoformat() if self.parse_finished_at else None, "parsedData": _safe_json(self.parsed_data), "confirmedData": _safe_json(self.confirmed_data), "confirmedBy": self.confirmed_by, "confirmedAt": self.confirmed_at.isoformat() if self.confirmed_at else None, "documentId": self.document_id, "snapshotId": self.snapshot_id, "createdAt": self.created_at.isoformat() if self.created_at else None, } def _parse_snapshot_hash(self): from insurance.plan_data.validators import parse_snapshot_hash return parse_snapshot_hash(self)