sage/wwwroot/hotspot/stats.dspy
yumoqing 64cc51cb6e feat(hotspot): comprehensive revamp — 6 tables, 3 fetch modes, 5-dim analysis
Database (3→6 tables):
- hotspot_source: support api/browser/crawler types with type-specific fields
- hotspot_schedule: cron/interval daemon config per source
- hotspot_fetch_log: per-run audit trail (status/duration/items/errors)
- hotspot_item: add heat_velocity, engagement/comment/share counts, sentiment
- hotspot_analysis: 5-dimension scoring (time/heat/content/propagation/audience)
- hotspot_alert: alert rules (heat threshold/velocity/sentiment triggers)

Fetch engine (3 modes):
- API: GET/POST + headers + Bearer auth + JSON extraction
- Browser: HTML title/link extraction + og:title fallback
- Crawler: recursive with depth/domain allowlist/link following

Analysis engine:
- Exponential decay model (7-day half-life)
- Auto-classify: emerging→rising→hot→cooling→expired
- 5-dimension scoring with detail JSON per dimension

Dashboard: 6 tabs + 7 stat cards + workflow guide
Permission paths: 42 paths registered (6 tables × 7 CRUD ops)
2026-08-01 11:14:58 +08:00

64 lines
2.2 KiB
Plaintext

# 热点统计:全维度
from datetime import datetime, timedelta
from sqlor.dbpools import DBPools
async def main(request):
db = DBPools()
now = datetime.now()
day_ago = (now - timedelta(hours=24)).strftime('%Y-%m-%d %H:%M:%S')
async with db.sqlorContext('sage') as sor:
items = await sor.R('hotspot_item', {'order': 'heat_score desc'})
sources = await sor.R('hotspot_source', {})
logs = await sor.sqlExe(
"select * from hotspot_fetch_log where start_time >= ${t}$",
{'t': day_ago}
)
status_count = {'emerging': 0, 'rising': 0, 'hot': 0, 'cooling': 0, 'expired': 0}
for item in items:
s = item.get('status', 'emerging')
status_count[s] = status_count.get(s, 0) + 1
sources_active = sum(1 for s in sources if s.get('enabled') == '1')
failures = sum(1 for log in logs if log.get('status') == 'failed')
# Top sources by item count
src_count = {}
for item in items:
sid = item.get('source_id', '')
src_count[sid] = src_count.get(sid, 0) + 1
top_src = sorted(src_count.items(), key=lambda x: x[1], reverse=True)[:5]
src_names = {s['id']: s.get('name', s['id']) for s in sources}
# Avg heat by category
cat_heat = {}
for item in items:
cat = item.get('category', '未分类') or '未分类'
h = float(item.get('heat_score', 0))
if cat not in cat_heat:
cat_heat[cat] = {'sum': 0, 'cnt': 0}
cat_heat[cat]['sum'] += h
cat_heat[cat]['cnt'] += 1
top_categories = sorted(
[{'name': k, 'avg': round(v['sum']/v['cnt'], 1), 'cnt': v['cnt']}
for k, v in cat_heat.items()],
key=lambda x: x['cnt'], reverse=True
)[:10]
return {
'total': len(items),
'emerging': status_count['emerging'],
'rising': status_count['rising'],
'hot': status_count['hot'],
'cooling': status_count['cooling'],
'expired': status_count['expired'],
'sources': sources_active,
'failures': failures,
'top_sources': [{'name': src_names.get(k, k), 'count': v} for k, v in top_src],
'top_categories': top_categories,
'log_count_24h': len(logs),
}