"""对外(同层以上)提供「最近一份报告的画像摘要」,供 Agent 上下文注入。 只取结论摘要而非报告全文:素材走 ``copy.compose_prompt_material``——与 REPORT_COPY 场景同一套裁剪纪律(skipped 维度不进素材防幻觉、超长按优先级 裁、硬结论永不裁),只是预算从报告的 6000 字符收紧到调用方给的额度。 归属纪律不变:rp_report / rp_input 只有 report 域内部可触(repo.py 头部 约定),外部要画像只能走这里。 """ from __future__ import annotations from typing import Any from sqlalchemy.ext.asyncio import AsyncSession from packages.report import repo from packages.report.copy import compose_prompt_material from packages.shared.logging import get_logger logger = get_logger(__name__) #: 往前翻几份报告找可用的诊断产物。正常用户只有几份;留余量是为了容错 #: 「最新一份 failed/processing 而上一份 succeeded」的场景。 _SCAN_LIMIT = 5 async def latest_profile_summary( session: AsyncSession, *, user_id: int, max_chars: int ) -> str | None: """该用户最近一份带诊断结论的 succeeded 报告 → 紧凑 JSON 素材;无则 None。 NFR-SEC-03:摘要内容不进日志,这里只记「命中第几份报告」。 """ reports = await repo.list_reports(session, user_id=user_id, limit=_SCAN_LIMIT) for index, report in enumerate(reports): if report.status != repo.STATUS_SUCCEEDED: continue diagnosis: Any = (report.result or {}).get("diagnosis") if not isinstance(diagnosis, dict) or not diagnosis.get("dimensions"): continue material, dropped = compose_prompt_material(diagnosis, max_chars=max_chars) logger.info( "report_profile_summary_built", report_id=report.report_id, scan_depth=index, dropped_keys=len(dropped), ) return material return None