64 lines
1.9 KiB
Python
64 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
def _utc_now_naive() -> datetime:
|
|
return now_cn_naive()
|
|
|
|
async def _ensure_project_signal_link(
|
|
db: AsyncSession,
|
|
*,
|
|
tenant_id: int,
|
|
project_id: int,
|
|
signal_id: int,
|
|
link_type: str = "related",
|
|
) -> None:
|
|
existing = (
|
|
await db.execute(
|
|
select(ProjectSignalLink.id).where(
|
|
and_(
|
|
ProjectSignalLink.tenant_id == int(tenant_id),
|
|
ProjectSignalLink.project_id == int(project_id),
|
|
ProjectSignalLink.signal_id == int(signal_id),
|
|
)
|
|
).limit(1)
|
|
)
|
|
).scalar_one_or_none()
|
|
if existing is not None:
|
|
return
|
|
db.add(
|
|
ProjectSignalLink(
|
|
tenant_id=int(tenant_id),
|
|
project_id=int(project_id),
|
|
signal_id=int(signal_id),
|
|
link_type=link_type,
|
|
relevance_score=1.0,
|
|
)
|
|
)
|
|
|
|
def _parse_related_users(raw_value: Any) -> List[str]:
|
|
"""兼容 involved_user_ids 的 list / json-string / 单值字符串。"""
|
|
if raw_value is None:
|
|
return []
|
|
if isinstance(raw_value, list):
|
|
return [str(item) for item in raw_value if item is not None]
|
|
if isinstance(raw_value, (bytes, bytearray)):
|
|
raw_value = raw_value.decode("utf-8", errors="ignore")
|
|
if isinstance(raw_value, str):
|
|
text = raw_value.strip()
|
|
if not text:
|
|
return []
|
|
try:
|
|
parsed = json.loads(text)
|
|
except (TypeError, ValueError, json.JSONDecodeError):
|
|
if "," in text:
|
|
return [v.strip() for v in text.split(",") if v.strip()]
|
|
return [text]
|
|
if isinstance(parsed, list):
|
|
return [str(item) for item in parsed if item is not None]
|
|
if parsed is None:
|
|
return []
|
|
return [str(parsed)]
|
|
return [str(raw_value)]
|
|
|