304 lines
12 KiB
Python
304 lines
12 KiB
Python
"""
|
|
数据库模型
|
|
"""
|
|
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,
|
|
}
|