from __future__ import annotations from .shared import * from .signal_core import _as_dict, _extract_signal_attachments, _normalize_planner_status, _signal_display_type, _signal_project_scope, _signal_source_context from .signal_filters import _signal_proposer_name, _signal_title_text async def _fetch_valid_project_signal_ids( db: AsyncSession, *, tenant_id: int, project_id: int, signal_ids: list[int], ) -> set[int]: link_result = await db.execute( select(ProjectSignalLink.signal_id).where( and_( ProjectSignalLink.project_id == project_id, ProjectSignalLink.signal_id.in_(signal_ids), ProjectSignalLink.tenant_id == tenant_id, ) ) ) return {int(v) for v in link_result.scalars().all()} async def _load_signal_rows( db: AsyncSession, *, tenant_id: int, signal_ids: list[int], ) -> list[Signal]: if not signal_ids: return [] return ( await db.execute( select(Signal).where( and_( Signal.tenant_id == tenant_id, Signal.id.in_(signal_ids), ) ) ) ).scalars().all() def _build_charter_summary_payload(charter: TruthNode) -> dict[str, Any]: content = _as_dict(charter.content) return { "id": str(charter.id), "title": charter.title, "content": content, "signal_ids": content.get("signal_ids", []), "status": charter.status, "version": charter.version, } def _serialize_charter_signal(signal: Signal) -> dict[str, Any]: return { "id": str(signal.id), "source": signal.source, "signal_type": _signal_display_type(signal), "project_scope": _signal_project_scope(signal), "proposer_name": _signal_proposer_name(signal), "title": _signal_title_text(signal)[:80] or "未命名信号", "content": signal.content, "priority": signal.priority, "impact_score": signal.impact_score, "attachments": _extract_signal_attachments(signal), "strategic_analysis": _as_dict(signal.strategic_analysis), "planner_status": _normalize_planner_status(getattr(signal, "planner_status", None)), "planner_analyzed_at": ( signal.planner_analyzed_at.isoformat() if getattr(signal, "planner_analyzed_at", None) else None ), "planner_assignee_user_id": ( str(signal.planner_assignee_user_id) if getattr(signal, "planner_assignee_user_id", None) is not None else None ), } def _serialize_source_signal(signal: Signal) -> dict[str, Any]: return { "id": str(signal.id), "title": _signal_title_text(signal)[:80] or "未命名信号", "signal_type": _signal_display_type(signal), "priority": signal.priority, "content": signal.content, "attachments": _extract_signal_attachments(signal), "strategic_analysis": _as_dict(signal.strategic_analysis), "planner_status": _normalize_planner_status(getattr(signal, "planner_status", None)), "planner_analyzed_at": ( signal.planner_analyzed_at.isoformat() if getattr(signal, "planner_analyzed_at", None) else None ), "planner_assignee_user_id": ( str(signal.planner_assignee_user_id) if getattr(signal, "planner_assignee_user_id", None) is not None else None ), } def _select_source_signal(charter: TruthNode, signal_rows: list[Signal]) -> Optional[Signal]: if charter.signal_id: for signal in signal_rows: if int(signal.id) == int(charter.signal_id): return signal if signal_rows: return signal_rows[0] return None def _serialize_charter_task(task: Task) -> dict[str, Any]: return { "id": str(task.id), "title": task.title, "description": task.description, "status": task.status, "priority": task.priority, "estimated_hours": float(task.estimated_hours) if task.estimated_hours else None, "assignee_id": str(task.assignee_id) if task.assignee_id else None, "due_date": task.due_date.isoformat() if task.due_date else None, } def _serialize_alignment_record(record: AlignmentRecord) -> dict[str, Any]: return { "user_id": str(record.user_id), "score": record.alignment_score, "confirmed": bool(record.is_confirmed), "comments": record.comments, }