# 五维 + 状态自动分析 # emerging → rising → hot → cooling → expired import json from datetime import datetime, timedelta from sqlor.dbpools import DBPools from appPublic.uniqueID import getID def now_str(): return datetime.now().strftime('%Y-%m-%d %H:%M:%S') async def main(request): db = DBPools() now = datetime.now() results = {'analyzed': 0, 'status_changes': 0, 'dimensions': {}} async with db.sqlorContext('sage') as sor: items = await sor.sqlExe( "select * from hotspot_item order by first_seen desc limit 500", {} ) for item in items: try: first_seen = datetime.strptime(str(item.get('first_seen', '')), '%Y-%m-%d %H:%M:%S') except: first_seen = now hours_alive = max(0.1, (now - first_seen).total_seconds() / 3600) heat = float(item.get('heat_score', 0)) velocity = float(item.get('heat_velocity', 0)) engagement = int(item.get('engagement_count', 0)) comments = int(item.get('comment_count', 0)) shares = int(item.get('share_count', 0)) current_status = item.get('status', 'emerging') item_id = item['id'] # === 五维分析 === dimensions = {} # 1. 时效维度 (0-100) — 越新越高 if hours_alive < 1: dim_time = 95 elif hours_alive < 6: dim_time = 85 elif hours_alive < 24: dim_time = 70 elif hours_alive < 72: dim_time = 50 elif hours_alive < 168: dim_time = 30 else: dim_time = 10 dimensions['time'] = dim_time # 2. 热度维度 (0-100) if heat > 10000: dim_heat = 95 elif heat > 5000: dim_heat = 85 elif heat > 1000: dim_heat = 70 elif heat > 500: dim_heat = 55 elif heat > 100: dim_heat = 35 else: dim_heat = 15 dimensions['heat'] = dim_heat # 3. 内容维度 (0-100) — 基于标题长度+摘要丰富度 title_len = len(item.get('title', '')) summary_len = len(item.get('summary', '')) has_tags = bool(item.get('tags')) has_category = bool(item.get('category')) dim_content = min(100, (20 if title_len > 15 else 10) + (30 if summary_len > 100 else 15) + (25 if has_tags else 0) + (25 if has_category else 0) ) dimensions['content'] = dim_content # 4. 传播维度 (0-100) — 互动量 total_engagement = engagement + comments * 2 + shares * 3 if total_engagement > 10000: dim_propagation = 95 elif total_engagement > 5000: dim_propagation = 80 elif total_engagement > 1000: dim_propagation = 60 elif total_engagement > 100: dim_propagation = 35 else: dim_propagation = 10 dimensions['propagation'] = dim_propagation # 5. 受众维度 (0-100) — 基于互动率 if heat > 0: engagement_rate = total_engagement / heat else: engagement_rate = 0 if engagement_rate > 0.5: dim_audience = 90 elif engagement_rate > 0.2: dim_audience = 70 elif engagement_rate > 0.05: dim_audience = 45 elif total_engagement > 0: dim_audience = 25 else: dim_audience = 5 dimensions['audience'] = dim_audience # === 状态分类 (基于热度 + 时间) === # 热度衰减: heat * e^(-hours/168) ~ 7天半衰期 import math decay = math.exp(-hours_alive / 168) adjusted_heat = heat * decay # 热度加速度 (简化:基于当前热度/time) new_velocity = round(heat / max(hours_alive, 0.1), 2) if hours_alive > 336: # 超过14天 new_status = 'expired' elif hours_alive > 168: # 7-14天 new_status = 'cooling' elif adjusted_heat > 5000: new_status = 'hot' elif adjusted_heat > 500: new_status = 'rising' if new_velocity > 50 else 'emerging' elif adjusted_heat > 100: new_status = 'rising' if new_velocity > 100 else 'emerging' else: new_status = 'emerging' # 保存 async with db.sqlorContext('sage') as sor: # 更新条目 await sor.U('hotspot_item', { 'id': item_id, 'heat_score': round(adjusted_heat, 2), 'heat_velocity': new_velocity, 'status': new_status, 'last_updated': now_str(), }) results['analyzed'] += 1 if new_status != current_status: results['status_changes'] += 1 # 保存五维分析 for dim, score in dimensions.items(): dim_names = { 'time': '时效维度', 'heat': '热度指标', 'content': '内容属性', 'propagation': '传播路径', 'audience': '受众画像', } analysis_data = json.dumps({ 'dimension': dim, 'dimension_cn': dim_names.get(dim, dim), 'score': score, 'detail': { 'hours_alive': round(hours_alive, 1), 'adjusted_heat': round(adjusted_heat, 2), 'heat_velocity': new_velocity, 'total_engagement': total_engagement, 'decay_factor': round(decay, 4), } }, ensure_ascii=False) # Upsert: delete old analysis for this item+dimension, insert new old = await sor.sqlExe( "select id from hotspot_analysis where item_id=${iid}$ and dimension=${dim}$", {'iid': item_id, 'dim': dim} ) if old: await sor.U('hotspot_analysis', { 'id': old[0]['id'], 'score': score, 'analysis_data': analysis_data, 'analyzed_at': now_str(), }) else: await sor.C('hotspot_analysis', { 'id': getID(), 'item_id': item_id, 'dimension': dim, 'score': score, 'analysis_data': analysis_data, 'analyzed_at': now_str(), }) results['dimensions'] = { 'time': dim_names['time'], 'heat': dim_names['heat'], 'content': dim_names['content'], 'propagation': dim_names['propagation'], 'audience': dim_names['audience'], } return results