"""E0-lite 数据健康——「数据截至」角标 + 实体新鲜度。 完整 E0(调度日志解析/成功率账本)二批;v1 用表新鲜度判定: msg_daily_stats 当日有行 = 4h 消息链路活着。 """ from __future__ import annotations import datetime as dt from typing import Any import sqlalchemy as sa from sqlalchemy.ext.asyncio import AsyncSession from app.db.models import ( ChatGroup, Customer, Media, Message, MomentsPost, MsgDailyStats, PhoneCall, ) async def data_as_of(session: AsyncSession) -> int | None: """数据截至(ms-epoch)= MAX(msg_daily_stats.last_ts)。 漏迁移导致表缺失时按 500 响亮报错是有意的——本模块是诊断端点, 要暴露部署问题而非掩盖(0ade58a 的空表静默守卫是业务端点的容错, 别好心搬到这里)。 """ val = ( await session.execute(sa.select(sa.func.max(MsgDailyStats.last_ts))) ).scalar_one_or_none() return int(val) if val is not None else None async def entity_freshness(session: AsyncSession) -> list[dict[str, Any]]: """各实体最后更新时间(UPDATED_AT 列 MAX;message 用 fetched_at)。 同 data_as_of:表缺失按 500 响亮报错是有意的(诊断端点不容错)。 """ async def _max(col) -> dt.datetime | None: return (await session.execute(sa.select(sa.func.max(col)))).scalar_one_or_none() out = [] for name, col in ( ("customer", Customer.updated_at), ("chat_group", ChatGroup.updated_at), ("phone_call", PhoneCall.updated_at), ("media", Media.updated_at), ("moments_post", MomentsPost.updated_at), ("message", Message.fetched_at), ): out.append({"table": name, "last_seen": await _max(col)}) return out