138 lines
4.8 KiB
Python
138 lines
4.8 KiB
Python
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,
|
|
user_name: Optional[str] = None,
|
|
) -> dict[str, Any]:
|
|
confirmed_at = None
|
|
if bool(record.is_confirmed):
|
|
ts = record.updated_at or record.created_at
|
|
confirmed_at = ts.isoformat() if ts else None
|
|
|
|
return {
|
|
"user_id": str(record.user_id),
|
|
"user_name": user_name or f"用户 {record.user_id}",
|
|
"score": record.alignment_score,
|
|
"confirmed": bool(record.is_confirmed),
|
|
"comments": record.comments,
|
|
"confirmed_at": confirmed_at,
|
|
}
|