131 lines
4.2 KiB
Python
131 lines
4.2 KiB
Python
"""Shared loader for richer analysis project snapshots."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from sqlalchemy import and_, select
|
|
|
|
from app.models.project import Project
|
|
from app.models.signal import Signal
|
|
from app.models.task import Task
|
|
|
|
def _normalize_text(value: Any, *, max_len: int = 240) -> str:
|
|
text = " ".join(str(value or "").split()).strip()
|
|
if not text:
|
|
return ""
|
|
return text[:max_len]
|
|
def _object_ref(*, kind: str, row: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
payload = dict(row or {})
|
|
title = _normalize_text(payload.get("title"), max_len=120)
|
|
if not title:
|
|
return None
|
|
ref: dict[str, Any] = {
|
|
"kind": kind,
|
|
"title": title,
|
|
}
|
|
try:
|
|
object_id = int(payload.get("id")) if payload.get("id") is not None else None
|
|
except Exception:
|
|
object_id = None
|
|
if object_id is not None:
|
|
ref["id"] = object_id
|
|
return ref
|
|
|
|
|
|
def _build_analysis_object_refs(
|
|
*,
|
|
tasks: list[dict[str, str]],
|
|
signals: list[dict[str, str]],
|
|
) -> dict[str, Any]:
|
|
_ = (tasks, signals)
|
|
# Fail closed: without explicit structural task-signal relations in the data model,
|
|
# do not synthesize business object refs from task/signal wording.
|
|
return {}
|
|
|
|
|
|
async def load_analysis_project_snapshot(
|
|
*,
|
|
db: Any,
|
|
tenant_id: int | None,
|
|
project_id: int | None,
|
|
task_limit: int = 4,
|
|
signal_limit: int = 6,
|
|
) -> dict[str, Any]:
|
|
if project_id is None or db is None or not hasattr(db, "execute"):
|
|
return {}
|
|
|
|
project_filters = [Project.id == int(project_id)]
|
|
task_filters = [Task.project_id == int(project_id)]
|
|
signal_filters = [Signal.matched_project_id == int(project_id)]
|
|
if tenant_id is not None:
|
|
project_filters.append(Project.tenant_id == int(tenant_id))
|
|
task_filters.append(Task.tenant_id == int(tenant_id))
|
|
signal_filters.append(Signal.tenant_id == int(tenant_id))
|
|
|
|
project_row = (
|
|
await db.execute(
|
|
select(Project).where(and_(*project_filters)).limit(1)
|
|
)
|
|
).scalar_one_or_none()
|
|
if project_row is None:
|
|
return {}
|
|
|
|
task_rows = (
|
|
await db.execute(
|
|
select(Task)
|
|
.where(and_(*task_filters))
|
|
.order_by(Task.id.desc())
|
|
.limit(int(task_limit))
|
|
)
|
|
).scalars().all()
|
|
signal_rows = (
|
|
await db.execute(
|
|
select(Signal)
|
|
.where(and_(*signal_filters))
|
|
.order_by(Signal.id.desc())
|
|
.limit(int(signal_limit))
|
|
)
|
|
).scalars().all()
|
|
|
|
tasks = [
|
|
{
|
|
"id": getattr(row, "id", None),
|
|
"title": _normalize_text(getattr(row, "title", ""), max_len=120),
|
|
"description": _normalize_text(getattr(row, "description", ""), max_len=220),
|
|
"status": _normalize_text(getattr(row, "status", ""), max_len=40),
|
|
}
|
|
for row in task_rows
|
|
if _normalize_text(getattr(row, "title", ""), max_len=120)
|
|
]
|
|
signals = [
|
|
{
|
|
"id": getattr(row, "id", None),
|
|
"title": _normalize_text(getattr(row, "title", ""), max_len=120),
|
|
"summary": _normalize_text(getattr(row, "summary", ""), max_len=220),
|
|
"reasoning": _normalize_text(getattr(row, "reasoning", ""), max_len=220),
|
|
"status": _normalize_text(getattr(row, "status", ""), max_len=40),
|
|
"source": _normalize_text(getattr(row, "source", ""), max_len=120),
|
|
}
|
|
for row in signal_rows
|
|
if _normalize_text(getattr(row, "title", ""), max_len=120)
|
|
]
|
|
|
|
analysis_object_refs = _build_analysis_object_refs(tasks=tasks, signals=signals)
|
|
|
|
return {
|
|
"project_name": _normalize_text(
|
|
getattr(project_row, "title", None) or getattr(project_row, "name", ""),
|
|
max_len=120,
|
|
),
|
|
"project_description": _normalize_text(getattr(project_row, "description", ""), max_len=220),
|
|
"task_titles": [item["title"] for item in tasks],
|
|
"signal_titles": [item["title"] for item in signals],
|
|
"tasks": tasks,
|
|
"signals": signals,
|
|
"analysis_object_refs": analysis_object_refs,
|
|
}
|
|
|
|
|
|
__all__ = ["load_analysis_project_snapshot"]
|