"""Event Crawler — 爬取 hm.zhiweidata.ai/events 数据存入 events 表。 TODO: 替换为真实爬虫逻辑。当前返回占位数据。 """ import logging from datetime import datetime, date, timedelta, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.database import async_session from app.models.event import Event, EventStatus, EventLevel logger = logging.getLogger(__name__) PLACEHOLDER_EVENTS = [ { "title": "taiwan Gateway 频繁重启", "level": EventLevel.A, "domain": "gateway", "status": EventStatus.ACTIVE, "occurred_date": date.today() - timedelta(days=1), "last_reviewed": datetime.now(timezone.utc) - timedelta(hours=6), "no_new_info_count": 0, "next_review": datetime.now(timezone.utc) + timedelta(hours=6), }, { "title": "guangxi 爬虫数据延迟", "level": EventLevel.B, "domain": "crawler", "status": EventStatus.ACTIVE, "occurred_date": date.today() - timedelta(days=2), "last_reviewed": datetime.now(timezone.utc) - timedelta(days=1), "no_new_info_count": 1, "next_review": datetime.now(timezone.utc) + timedelta(hours=12), }, { "title": "飞书 Webhook 推送异常", "level": EventLevel.B, "domain": "feishu", "status": EventStatus.FADING, "occurred_date": date.today() - timedelta(days=5), "last_reviewed": datetime.now(timezone.utc) - timedelta(days=2), "no_new_info_count": 3, "next_review": datetime.now(timezone.utc) + timedelta(days=1), }, { "title": "ops-engineer 日报生成失败", "level": EventLevel.C, "domain": "reporter", "status": EventStatus.FADING, "occurred_date": date.today() - timedelta(days=7), "last_reviewed": datetime.now(timezone.utc) - timedelta(days=3), "no_new_info_count": 4, "next_review": datetime.now(timezone.utc) - timedelta(days=1), }, { "title": "hebo 数据库连接超时", "level": EventLevel.A, "domain": "database", "status": EventStatus.DISSIPATING, "occurred_date": date.today() - timedelta(days=14), "last_reviewed": datetime.now(timezone.utc) - timedelta(days=7), "no_new_info_count": 6, "next_review": datetime.now(timezone.utc) - timedelta(days=3), }, { "title": "密钥轮换提醒 (taiwan)", "level": EventLevel.C, "domain": "security", "status": EventStatus.DISSIPATING, "occurred_date": date.today() - timedelta(days=20), "last_reviewed": datetime.now(timezone.utc) - timedelta(days=10), "no_new_info_count": 5, "next_review": datetime.now(timezone.utc) - timedelta(days=5), }, { "title": "Gateway 配置同步冲突", "level": EventLevel.B, "domain": "gateway", "status": EventStatus.CLOSED, "occurred_date": date.today() - timedelta(days=30), "last_reviewed": datetime.now(timezone.utc) - timedelta(days=25), "no_new_info_count": 8, "next_review": None, }, { "title": "网络延迟告警 (overseas node)", "level": EventLevel.B, "domain": "network", "status": EventStatus.ACTIVE, "occurred_date": date.today() - timedelta(days=3), "last_reviewed": datetime.now(timezone.utc) - timedelta(hours=12), "no_new_info_count": 2, "next_review": datetime.now(timezone.utc) + timedelta(hours=18), }, { "title": "存储空间不足告警", "level": EventLevel.C, "domain": "storage", "status": EventStatus.ACTIVE, "occurred_date": date.today() - timedelta(days=4), "last_reviewed": None, "no_new_info_count": 0, "next_review": datetime.now(timezone.utc) + timedelta(days=1), }, { "title": "Monitor 心跳超时", "level": EventLevel.A, "domain": "monitor", "status": EventStatus.ACTIVE, "occurred_date": date.today() - timedelta(hours=8), "last_reviewed": datetime.now(timezone.utc) - timedelta(hours=4), "no_new_info_count": 0, "next_review": datetime.now(timezone.utc) + timedelta(hours=2), }, { "title": "Scheduler 任务积压", "level": EventLevel.B, "domain": "scheduler", "status": EventStatus.FADING, "occurred_date": date.today() - timedelta(days=6), "last_reviewed": datetime.now(timezone.utc) - timedelta(days=2), "no_new_info_count": 3, "next_review": datetime.now(timezone.utc) + timedelta(hours=10), }, { "title": "API 响应延迟 (p95 > 5s)", "level": EventLevel.A, "domain": "api", "status": EventStatus.ACTIVE, "occurred_date": date.today() - timedelta(hours=12), "last_reviewed": datetime.now(timezone.utc) - timedelta(hours=3), "no_new_info_count": 0, "next_review": datetime.now(timezone.utc) + timedelta(hours=4), }, ] async def seed_placeholder_events() -> list[Event]: """向数据库写入占位事件数据(幂等:清空后重写)。""" async with async_session() as db: existing = await db.execute(select(Event).limit(1)) if existing.scalar_one_or_none(): logger.info("events 表已有数据,跳过占位种子") return [] events = [] for data in PLACEHOLDER_EVENTS: event = Event(**data) db.add(event) events.append(event) await db.commit() logger.info("写入 %d 条占位事件", len(events)) return events async def run_crawl(): """定时爬取入口(TODO: 真实 HTTP 爬取 hm.zhiweidata.ai/events)。""" logger.info("Event crawler 执行开始") await seed_placeholder_events() logger.info("Event crawler 执行结束")