PMFlow/backend/app/services/sstc/charter_data.py

129 lines
4.5 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) -> dict[str, Any]:
return {
"user_id": str(record.user_id),
"score": record.alignment_score,
"confirmed": bool(record.is_confirmed),
"comments": record.comments,
}