""" 数据库模型 """ from sqlalchemy import Column, Integer, String, DateTime, Text, JSON, Float, Boolean, ForeignKey, UniqueConstraint from sqlalchemy.sql import func from database import Base class User(Base): """管理员用户""" __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, index=True) hashed_password = Column(String) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) class Customer(Base): """本地客户""" __tablename__ = "customers" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True) # 公司名称 country = Column(String) # 国家 source_country = Column(String) # 数据来源国家 import_data = Column(JSON, default=dict) # 进口数据 export_data = Column(JSON, default=dict) # 出口数据 raw_data = Column(JSON, default=dict) # 原始数据 notes = Column(Text) # 备注 created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) class Country(Base): """国家信息表""" __tablename__ = "countries" id = Column(Integer, primary_key=True, index=True) country_id = Column(Integer, unique=True, index=True) # API中的countryid continent_id = Column(Integer, index=True) # 大洲ID sort_order = Column(Integer, default=0) # 排序 cn_name = Column(String(100), index=True) # 中文名 en_name = Column(String(100)) # 英文名 en_name_ali = Column(String(100)) # 英文别名 short_name = Column(String(10)) # 简称(如CN) short_name_ali = Column(String(10)) # 简称别名 pinyin = Column(String(10)) # 拼音首字母 del_state = Column(Integer, default=0) # 删除状态 0=正常 1=删除 def to_dict(self): return { "id": self.id, "country_id": self.country_id, "continent_id": self.continent_id, "cn_name": self.cn_name, "en_name": self.en_name, "short_name": self.short_name, "pinyin": self.pinyin, } class ImportTask(Base): """潜在客户导入任务""" __tablename__ = "import_tasks" id = Column(Integer, primary_key=True, index=True) keyword = Column(String(200), index=True) # 搜索关键词 status = Column(String(20), default="pending", index=True) # pending/running/completed/failed/cancelled # 分页信息 total_pages = Column(Integer, default=0) # 总页数 current_page = Column(Integer, default=0) # 当前处理页码 page_size = Column(Integer, default=10) # 每页数量 # 统计信息 total_count = Column(Integer, default=0) # 预计总企业数 processed_count = Column(Integer, default=0) # 已处理企业数 success_count = Column(Integer, default=0) # 成功导入企业数 failed_count = Column(Integer, default=0) # 失败企业数 duplicate_count = Column(Integer, default=0) # 重复跳过数 # 错误信息 error_msg = Column(Text) # 错误信息 # 时间 started_at = Column(DateTime(timezone=True)) # 开始时间 completed_at = Column(DateTime(timezone=True)) # 完成时间 created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) def to_dict(self): return { "id": self.id, "keyword": self.keyword, "status": self.status, "total_pages": self.total_pages, "current_page": self.current_page, "page_size": self.page_size, "total_count": self.total_count, "processed_count": self.processed_count, "success_count": self.success_count, "failed_count": self.failed_count, "duplicate_count": self.duplicate_count, "error_msg": self.error_msg, "started_at": self.started_at.isoformat() if self.started_at else None, "completed_at": self.completed_at.isoformat() if self.completed_at else None, "created_at": self.created_at.isoformat() if self.created_at else None, "progress_percent": round((self.processed_count / self.total_count * 100), 1) if self.total_count > 0 else 0, } class PotentialCustomer(Base): """潜在客户企业""" __tablename__ = "potential_customers" id = Column(Integer, primary_key=True, index=True) external_id = Column(String(64), index=True) # fumamx API 返回的 id domain = Column(String(200), index=True) # 域名 name = Column(String(500)) # 公司名称 country = Column(String(10)) # 国家代码 country_en = Column(String(100)) # 英文国家名 country_cn = Column(String(100)) # 中文国家名 # 企业信息 industry = Column(String(200)) # 行业 employees = Column(String(50)) # 员工数 founded = Column(String(20)) # 成立时间 address = Column(String(500)) # 地址 phone = Column(String(100)) # 主电话 description = Column(Text) # 描述 # 在线资料 website = Column(String(300)) # 网站 linkedin_url = Column(String(500)) # LinkedIn # 贸易数据 procurement_frequency = Column(Integer, default=0) # 采购频次 annual_purchase_amount = Column(Float, default=0) # 年采购额 hs_codes = Column(JSON, default=list) # HS编码列表 hs_entities = Column(JSON, default=list) # HS商品名列表 # 来源信息 keyword = Column(String(200), index=True) # 搜索关键词 source_score = Column(Float, default=0) # 匹配分数 source_db = Column(Integer) # 数据源 import_task_id = Column(Integer, ForeignKey("import_tasks.id")) # 导入任务ID # 关联的联系人数量(冗余统计) contact_count = Column(Integer, default=0) # 深度分析状态(冗余字段,避免级联查询) analysis_status = Column(String(20), default="none", index=True) # none/pending/running/completed/failed analysis_count = Column(Integer, default=0) # 分析次数 last_analyzed_at = Column(DateTime(timezone=True)) # 最后分析时间 created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # 联合唯一索引:domain + external_id __table_args__ = ( UniqueConstraint('domain', 'external_id', name='uix_customer_domain_external'), ) def to_dict(self): return { "id": self.id, "external_id": self.external_id, "domain": self.domain, "name": self.name, "country": self.country, "country_en": self.country_en, "country_cn": self.country_cn, "industry": self.industry, "employees": self.employees, "address": self.address, "phone": self.phone, "description": self.description, "website": self.website, "linkedin_url": self.linkedin_url, "procurement_frequency": self.procurement_frequency, "annual_purchase_amount": self.annual_purchase_amount, "hs_codes": self.hs_codes, "hs_entities": self.hs_entities, "keyword": self.keyword, "source_score": self.source_score, "contact_count": self.contact_count, "analysis_status": self.analysis_status or "none", # 深度分析状态(反范式设计) "analysis_count": self.analysis_count or 0, "last_analyzed_at": self.last_analyzed_at.isoformat() if self.last_analyzed_at else None, "created_at": self.created_at.isoformat() if self.created_at else None, } class PotentialContact(Base): """潜在客户联系人""" __tablename__ = "potential_contacts" id = Column(Integer, primary_key=True, index=True) customer_id = Column(Integer, ForeignKey("potential_customers.id"), index=True) # 联系人信息 name = Column(String(200)) # 姓名 position = Column(String(300)) # 职位 email = Column(String(300), index=True) # 邮箱 phone = Column(String(100)) # 电话 # 社交资料 linkedin = Column(String(500)) # LinkedIn URL facebook = Column(String(500)) # Facebook twitter = Column(String(500)) # Twitter whatsapp = Column(String(100)) # WhatsApp # 评分标签 score = Column(Integer, default=0) # 匹配分数 is_executive = Column(Boolean, default=False) # 是否高管 is_kp = Column(Boolean, default=False) # 是否关键人 is_purchase = Column(Boolean, default=False) # 是否采购负责人 # 验证状态 email_check_status = Column(String(50)) # 邮箱验证状态 whatsapp_status = Column(String(50)) # WhatsApp状态 created_at = Column(DateTime(timezone=True), server_default=func.now()) def to_dict(self): return { "id": self.id, "customer_id": self.customer_id, "name": self.name, "position": self.position, "email": self.email, "phone": self.phone, "linkedin": self.linkedin, "score": self.score, "is_executive": self.is_executive, "is_kp": self.is_kp, "created_at": self.created_at.isoformat() if self.created_at else None, } class DeepAnalysis(Base): """深度分析记录""" __tablename__ = "deep_analyses" id = Column(Integer, primary_key=True, index=True) customer_id = Column(Integer, ForeignKey("potential_customers.id"), index=True) company_name = Column(String(500)) # 企业名称(冗余) # 分析状态 status = Column(String(20), default="pending", index=True) # pending/processing/completed/failed # 报告文件 report_file_path = Column(String(500)) # 报告文件路径 # 使用的模型 model_used = Column(String(50)) # 商业画布9个维度的摘要(可选,用于快速预览) canvas_summary = Column(JSON, default=dict) # 错误信息 error_message = Column(Text) # 时间 created_at = Column(DateTime(timezone=True), server_default=func.now()) completed_at = Column(DateTime(timezone=True)) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) def to_dict(self): return { "id": self.id, "customer_id": self.customer_id, "company_name": self.company_name, "status": self.status, "report_file_path": self.report_file_path, "model_used": self.model_used, "canvas_summary": self.canvas_summary, "error_message": self.error_message, "created_at": self.created_at.isoformat() if self.created_at else None, "completed_at": self.completed_at.isoformat() if self.completed_at else None, } class SystemSetting(Base): """系统设置""" __tablename__ = "system_settings" id = Column(Integer, primary_key=True, index=True) key = Column(String(100), unique=True, index=True) # 设置项key value = Column(Text) # 设置项value(加密存储敏感信息) description = Column(String(500)) # 描述 updated_at = Column(DateTime(timezone=True), onupdate=func.now()) def to_dict(self): return { "id": self.id, "key": self.key, "value": self.value, "description": self.description, "updated_at": self.updated_at.isoformat() if self.updated_at else None, }