from app import db from datetime import datetime import pytz import json def beijing_now(): """获取北京时间""" tz = pytz.timezone('Asia/Shanghai') return datetime.now(tz) class Post(db.Model): """帖子模型""" id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # 内容 title = db.Column(db.String(200)) # 标题(可选) content = db.Column(db.Text, nullable=False) # 内容 content_type = db.Column(db.String(20), default='text') # 内容类型:text/rich_text/link # 时间 created_at = db.Column(db.DateTime, default=beijing_now) updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now) # 统计 likes_count = db.Column(db.Integer, default=0) comments_count = db.Column(db.Integer, default=0) view_count = db.Column(db.Integer, default=0) # 状态 status = db.Column(db.String(20), default='active') # active/hidden/deleted is_top = db.Column(db.Boolean, default=False) # 是否置顶 # 关系 user = db.relationship('User', backref='posts') likes = db.relationship('PostLike', backref='post', lazy='dynamic') comments = db.relationship('Comment', backref='post', lazy='dynamic') class User(db.Model): """用户模型""" id = db.Column(db.Integer, primary_key=True) # 基础账号信息(注册时必填) username = db.Column(db.String(80), unique=True, nullable=False) # 用户名 email = db.Column(db.String(120), unique=True, nullable=False) # 邮箱 password_hash = db.Column(db.String(128), nullable=False) # 密码哈希 email_confirmed = db.Column(db.Boolean, default=False) # 邮箱是否验证 # 账号状态 created_at = db.Column(db.DateTime, default=beijing_now) # 注册时间 last_seen = db.Column(db.DateTime, default=beijing_now) # 最后活跃时间 status = db.Column(db.String(20), default='active') # 账号状态 active/banned/deleted # 个人资料(可选,后续在个人中心完善) nickname = db.Column(db.String(30)) # 社区昵称 avatar_url = db.Column(db.String(200)) # 头像URL banner_url = db.Column(db.String(200)) # 个人主页背景图 bio = db.Column(db.String(200)) # 个人简介 gender = db.Column(db.String(10)) # 性别 birth_date = db.Column(db.Date) # 生日 location = db.Column(db.String(100)) # 所在地 # 联系方式(可选) phone = db.Column(db.String(20)) # 手机号 wechat_id = db.Column(db.String(80)) # 微信号 # 实名认证信息(可选) real_name = db.Column(db.String(30)) # 真实姓名 id_number = db.Column(db.String(18)) # 身份证号(加密存储) is_verified = db.Column(db.Boolean, default=False) # 是否实名认证 verify_time = db.Column(db.DateTime) # 实名认证时间 # 投资相关信息(可选) trading_experience = db.Column(db.Integer) # 炒股年限 investment_style = db.Column(db.String(50)) # 投资风格 risk_preference = db.Column(db.String(20)) # 风险偏好 investment_amount = db.Column(db.String(20)) # 投资规模 preferred_markets = db.Column(db.String(200), default='[]') # 偏好市场 JSON # 社区信息(系统自动更新) user_level = db.Column(db.Integer, default=1) # 用户等级 reputation_score = db.Column(db.Integer, default=0) # 信用积分 contribution_point = db.Column(db.Integer, default=0) # 贡献点数 post_count = db.Column(db.Integer, default=0) # 发帖数 comment_count = db.Column(db.Integer, default=0) # 评论数 follower_count = db.Column(db.Integer, default=0) # 粉丝数 following_count = db.Column(db.Integer, default=0) # 关注数 # 创作者信息(可选) is_creator = db.Column(db.Boolean, default=False) # 是否创作者 creator_type = db.Column(db.String(20)) # 创作者类型 creator_tags = db.Column(db.String(200), default='[]') # 创作者标签 JSON # 系统设置 email_notifications = db.Column(db.Boolean, default=True) # 邮件通知 sms_notifications = db.Column(db.Boolean, default=False) # 短信通知 wechat_notifications = db.Column(db.Boolean, default=False) # 微信通知 notification_preferences = db.Column(db.String(500), default='{}') # 通知偏好 JSON privacy_level = db.Column(db.String(20), default='public') # 隐私级别 theme_preference = db.Column(db.String(20), default='light') # 主题偏好 blocked_keywords = db.Column(db.String(500), default='[]') # 屏蔽关键词 JSON # 手机号验证 phone_confirmed = db.Column(db.Boolean, default=False) # 手机是否验证 phone_confirm_time = db.Column(db.DateTime) # 手机验证时间 def __init__(self, username, email=None, password=None, phone=None): self.username = username if email: self.email = email if password: self.set_password(password) if phone: self.phone = phone def set_password(self, password): from werkzeug.security import generate_password_hash self.password_hash = generate_password_hash(password) def check_password(self, password): from werkzeug.security import check_password_hash return check_password_hash(self.password_hash, password) def update_last_seen(self): self.last_seen = beijing_now() db.session.commit() def get_preferred_markets(self): try: return json.loads(self.preferred_markets) except (json.JSONDecodeError, TypeError): return [] def get_blocked_keywords(self): try: return json.loads(self.blocked_keywords) except (json.JSONDecodeError, TypeError): return [] def get_notification_preferences(self): try: return json.loads(self.notification_preferences) except (json.JSONDecodeError, TypeError): return {} def get_creator_tags(self): try: return json.loads(self.creator_tags) except (json.JSONDecodeError, TypeError): return [] def set_preferred_markets(self, markets): self.preferred_markets = json.dumps(markets) def set_blocked_keywords(self, keywords): self.blocked_keywords = json.dumps(keywords) def set_notification_preferences(self, preferences): self.notification_preferences = json.dumps(preferences) def set_creator_tags(self, tags): self.creator_tags = json.dumps(tags) def to_dict(self): return { 'id': self.id, 'username': self.username, 'email': self.email, 'nickname': self.nickname, 'avatar_url': self.avatar_url, 'bio': self.bio, 'created_at': self.created_at.isoformat() if self.created_at else None, 'last_seen': self.last_seen.isoformat() if self.last_seen else None, 'status': self.status, 'user_level': self.user_level, 'reputation_score': self.reputation_score, 'post_count': self.post_count, 'follower_count': self.follower_count, 'following_count': self.following_count } def __repr__(self): return f'' class Comment(db.Model): """评论""" id = db.Column(db.Integer, primary_key=True) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) content = db.Column(db.Text, nullable=False) parent_id = db.Column(db.Integer, db.ForeignKey('comment.id')) # 父评论ID,用于回复 created_at = db.Column(db.DateTime, default=beijing_now) status = db.Column(db.String(20), default='active') user = db.relationship('User', backref='comments') replies = db.relationship('Comment', backref=db.backref('parent', remote_side=[id])) class CommentLike(db.Model): """评论点赞记录(基于session_id以兼容匿名点赞)""" __tablename__ = 'comment_like' id = db.Column(db.Integer, primary_key=True) comment_id = db.Column(db.Integer, db.ForeignKey('comment.id'), nullable=False) session_id = db.Column(db.String(100), nullable=False) created_at = db.Column(db.DateTime, default=beijing_now) __table_args__ = (db.UniqueConstraint('comment_id', 'session_id'),) class EventFollow(db.Model): """事件关注""" id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False) created_at = db.Column(db.DateTime, default=beijing_now) user = db.relationship('User', backref='event_follows') __table_args__ = (db.UniqueConstraint('user_id', 'event_id'),) class PostLike(db.Model): """帖子点赞""" id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False) created_at = db.Column(db.DateTime, default=beijing_now) user = db.relationship('User', backref='post_likes') __table_args__ = (db.UniqueConstraint('user_id', 'post_id'),) class Event(db.Model): """事件模型""" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text) # 事件类型与状态 event_type = db.Column(db.String(50)) status = db.Column(db.String(20), default='active') # 时间相关 start_time = db.Column(db.DateTime, default=beijing_now) end_time = db.Column(db.DateTime) created_at = db.Column(db.DateTime, default=beijing_now) updated_at = db.Column(db.DateTime, default=beijing_now) # 热度与统计 hot_score = db.Column(db.Float, default=0) view_count = db.Column(db.Integer, default=0) trending_score = db.Column(db.Float, default=0) post_count = db.Column(db.Integer, default=0) follower_count = db.Column(db.Integer, default=0) # 关联信息 related_industries = db.Column(db.JSON) keywords = db.Column(db.JSON) files = db.Column(db.JSON) importance = db.Column(db.String(20)) related_avg_chg = db.Column(db.Float, default=0) related_max_chg = db.Column(db.Float, default=0) related_week_chg = db.Column(db.Float, default=0) # 新增字段 invest_score = db.Column(db.Integer) # 超预期得分 expectation_surprise_score = db.Column(db.Integer) # 创建者信息 creator_id = db.Column(db.Integer, db.ForeignKey('user.id')) creator = db.relationship('User', backref='created_events') # 关系 posts = db.relationship('Post', backref='event', lazy='dynamic') followers = db.relationship('EventFollow', backref='event', lazy='dynamic') related_stocks = db.relationship('RelatedStock', backref='event', lazy='dynamic') historical_events = db.relationship('HistoricalEvent', backref='event', lazy='dynamic') related_data = db.relationship('RelatedData', backref='event', lazy='dynamic') related_concepts = db.relationship('RelatedConcepts', backref='event', lazy='dynamic') @property def keywords_list(self): if isinstance(self.keywords, list): return self.keywords elif isinstance(self.keywords, str): try: return json.loads(self.keywords) except (json.JSONDecodeError, TypeError): return [] return [] def set_keywords(self, keywords): if isinstance(keywords, list): self.keywords = keywords elif isinstance(keywords, str): try: self.keywords = json.loads(keywords) except json.JSONDecodeError: self.keywords = [keywords] else: self.keywords = [] class RelatedStock(db.Model): """相关标的模型""" id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id')) stock_code = db.Column(db.String(20)) # 股票代码 stock_name = db.Column(db.String(100)) # 股票名称 sector = db.Column(db.String(100)) # 关联类型 relation_desc = db.Column(db.String(1024)) # 关联原因描述 created_at = db.Column(db.DateTime, default=beijing_now) updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now) correlation = db.Column(db.Float()) momentum = db.Column(db.String(1024)) #动量 class RelatedData(db.Model): """关联数据模型""" id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id')) title = db.Column(db.String(200)) # 数据标题 data_type = db.Column(db.String(50)) # 数据类型 data_content = db.Column(db.JSON) # 数据内容(JSON格式) description = db.Column(db.Text) # 数据描述 created_at = db.Column(db.DateTime, default=beijing_now) class RelatedConcepts(db.Model): """关联数据模型""" id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id')) concept_code = db.Column(db.String(20)) # 数据标题 concept = db.Column(db.String(100)) # 数据类型 reason = db.Column(db.Text) # 数据描述 image_paths = db.Column(db.JSON) # 数据内容(JSON格式) created_at = db.Column(db.DateTime, default=beijing_now) @property def image_paths_list(self): if isinstance(self.image_paths, list): return self.image_paths elif isinstance(self.image_paths, str): try: return json.loads(self.image_paths) except (json.JSONDecodeError, TypeError): return [] return [] def set_image_paths(self, image_paths): if isinstance(image_paths, list): self.image_paths = image_paths elif isinstance(image_paths, str): try: self.image_paths = json.loads(image_paths) except json.JSONDecodeError: self.image_paths = [image_paths] else: self.image_paths = [] def get_first_image_path(self): paths = self.image_paths_list return paths[0] if paths else None class EventHotHistory(db.Model): """事件热度历史记录""" id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id')) score = db.Column(db.Float) # 总分 interaction_score = db.Column(db.Float) # 互动分数 follow_score = db.Column(db.Float) # 关注度分数 view_score = db.Column(db.Float) # 浏览量分数 recent_activity_score = db.Column(db.Float) # 最近活跃度分数 time_decay = db.Column(db.Float) # 时间衰减因子 created_at = db.Column(db.DateTime, default=beijing_now) event = db.relationship('Event', backref='hot_history') class EventTransmissionNode(db.Model): """事件传导节点模型""" __tablename__ = 'event_transmission_nodes' id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False) node_type = db.Column(db.Enum('company', 'industry', 'policy', 'technology', 'market', 'event', 'other'), nullable=False) node_name = db.Column(db.String(200), nullable=False) node_description = db.Column(db.Text) importance_score = db.Column(db.Integer, default=50) stock_code = db.Column(db.String(20)) is_main_event = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=beijing_now) updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now) # Relationships event = db.relationship('Event', backref='transmission_nodes') outgoing_edges = db.relationship('EventTransmissionEdge', foreign_keys='EventTransmissionEdge.from_node_id', backref='from_node', cascade='all, delete-orphan') incoming_edges = db.relationship('EventTransmissionEdge', foreign_keys='EventTransmissionEdge.to_node_id', backref='to_node', cascade='all, delete-orphan') __table_args__ = ( db.Index('idx_event_node_type', 'event_id', 'node_type'), db.Index('idx_node_name', 'node_name'), ) class EventTransmissionEdge(db.Model): """事件传导边模型""" __tablename__ = 'event_transmission_edges' id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False) from_node_id = db.Column(db.Integer, db.ForeignKey('event_transmission_nodes.id'), nullable=False) to_node_id = db.Column(db.Integer, db.ForeignKey('event_transmission_nodes.id'), nullable=False) transmission_type = db.Column(db.Enum('supply_chain', 'competition', 'policy', 'technology', 'capital_flow', 'expectation', 'cyclic_effect', 'other'), nullable=False) transmission_mechanism = db.Column(db.Text) direction = db.Column(db.Enum('positive', 'negative', 'neutral', 'mixed'), default='neutral') strength = db.Column(db.Integer, default=50) impact = db.Column(db.Text) is_circular = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=beijing_now) updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now) # Relationship event = db.relationship('Event', backref='transmission_edges') __table_args__ = ( db.Index('idx_event_edge_type', 'event_id', 'transmission_type'), db.Index('idx_from_to_nodes', 'from_node_id', 'to_node_id'), ) class EventSankeyFlow(db.Model): """事件桑基流模型""" __tablename__ = 'event_sankey_flows' id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False) # 流的基本信息 source_node = db.Column(db.String(200), nullable=False) source_type = db.Column(db.Enum('event', 'policy', 'technology', 'industry', 'company', 'product'), nullable=False) source_level = db.Column(db.Integer, nullable=False, default=0) target_node = db.Column(db.String(200), nullable=False) target_type = db.Column(db.Enum('policy', 'technology', 'industry', 'company', 'product'), nullable=False) target_level = db.Column(db.Integer, nullable=False, default=1) # 流量信息 flow_value = db.Column(db.Numeric(10, 2), nullable=False) flow_ratio = db.Column(db.Numeric(5, 4), nullable=False) # 传导机制 transmission_path = db.Column(db.String(500)) impact_description = db.Column(db.Text) evidence_strength = db.Column(db.Integer, default=50) # 时间戳 created_at = db.Column(db.DateTime, default=beijing_now) updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now) # 关系 event = db.relationship('Event', backref='sankey_flows') __table_args__ = ( db.Index('idx_event_flow', 'event_id'), db.Index('idx_source_target', 'source_node', 'target_node'), ) class HistoricalEvent(db.Model): """历史事件模型""" id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('event.id')) title = db.Column(db.String(200)) content = db.Column(db.Text) event_date = db.Column(db.DateTime) relevance = db.Column(db.Integer) # 相关性 importance = db.Column(db.Integer) # 重要程度 related_stock = db.Column(db.JSON) # 保留JSON字段 created_at = db.Column(db.DateTime, default=beijing_now) # 新增关系 stocks = db.relationship('HistoricalEventStock', backref='historical_event', lazy='dynamic', cascade='all, delete-orphan') class HistoricalEventStock(db.Model): """历史事件相关股票模型""" __tablename__ = 'historical_event_stocks' id = db.Column(db.Integer, primary_key=True) historical_event_id = db.Column(db.Integer, db.ForeignKey('historical_event.id'), nullable=False) stock_code = db.Column(db.String(20), nullable=False) stock_name = db.Column(db.String(50)) relation_desc = db.Column(db.Text) correlation = db.Column(db.Float, default=0.5) sector = db.Column(db.String(100)) created_at = db.Column(db.DateTime, default=beijing_now) __table_args__ = ( db.Index('idx_historical_event_stock', 'historical_event_id', 'stock_code'), )