generated from coulomb/repo-seed
Add persisted Suggestion entities with WSJF ranking, relevance bumps, REST/MCP write surfaces, /suggestions dashboard page, and daily triage digest integration. Document the cluster operating model, archive the completed migration workplan, and seed WARDEN-WP-0012 routing scenarios.
118 lines
3.3 KiB
Python
118 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from api.models.suggestion import (
|
|
OPEN_SUGGESTION_STAGES,
|
|
Suggestion,
|
|
SuggestionRelevanceBump,
|
|
SuggestionStage,
|
|
)
|
|
|
|
DEBOUNCE_SECONDS = 3600
|
|
|
|
|
|
async def bump_relevance(
|
|
session: AsyncSession,
|
|
suggestion: Suggestion,
|
|
*,
|
|
source: str,
|
|
source_key: str,
|
|
reason: str | None = None,
|
|
) -> bool:
|
|
"""Increment relevance when a suggestion is needed-but-unmet.
|
|
|
|
Returns True when the counter was incremented, False when debounced.
|
|
"""
|
|
if suggestion.stage not in OPEN_SUGGESTION_STAGES:
|
|
return False
|
|
|
|
cutoff = datetime.now(tz=timezone.utc) - timedelta(seconds=DEBOUNCE_SECONDS)
|
|
existing = (
|
|
await session.execute(
|
|
select(SuggestionRelevanceBump.id)
|
|
.where(SuggestionRelevanceBump.suggestion_id == suggestion.id)
|
|
.where(SuggestionRelevanceBump.source == source)
|
|
.where(SuggestionRelevanceBump.source_key == source_key)
|
|
.where(SuggestionRelevanceBump.created_at >= cutoff)
|
|
.limit(1)
|
|
)
|
|
).scalar_one_or_none()
|
|
if existing is not None:
|
|
return False
|
|
|
|
now = datetime.now(tz=timezone.utc)
|
|
suggestion.relevance += 1
|
|
suggestion.relevance_events += 1
|
|
suggestion.last_requested_at = now
|
|
session.add(
|
|
SuggestionRelevanceBump(
|
|
suggestion_id=suggestion.id,
|
|
source=source,
|
|
source_key=source_key,
|
|
reason=reason,
|
|
)
|
|
)
|
|
return True
|
|
|
|
|
|
async def bump_matching_for_capability_request(
|
|
session: AsyncSession,
|
|
*,
|
|
request_id: uuid.UUID,
|
|
title: str,
|
|
description: str,
|
|
capability_type: str,
|
|
catalog_entry_id: uuid.UUID | None = None,
|
|
) -> int:
|
|
"""Bump open suggestions that match an unfulfilled capability need."""
|
|
rows = (
|
|
await session.execute(
|
|
select(Suggestion).where(Suggestion.stage.in_(OPEN_SUGGESTION_STAGES))
|
|
)
|
|
).scalars().all()
|
|
|
|
haystack = f"{title} {description} {capability_type}".lower()
|
|
bumped = 0
|
|
for suggestion in rows:
|
|
matched = False
|
|
if suggestion.origin_ref:
|
|
ref = suggestion.origin_ref.lower()
|
|
if ref in haystack or any(token in haystack for token in ref.split("-") if len(token) > 4):
|
|
matched = True
|
|
if not matched and suggestion.title.lower() in haystack:
|
|
matched = True
|
|
if not matched:
|
|
continue
|
|
if await bump_relevance(
|
|
session,
|
|
suggestion,
|
|
source="capability_request",
|
|
source_key=str(request_id),
|
|
reason=f"Capability request matched: {title}",
|
|
):
|
|
bumped += 1
|
|
return bumped
|
|
|
|
|
|
async def bump_suggestions_for_next_steps(
|
|
session: AsyncSession,
|
|
suggestions: list[Suggestion],
|
|
) -> int:
|
|
"""Bump only suggestions surfaced in a next-steps response."""
|
|
bucket = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H")
|
|
bumped = 0
|
|
for suggestion in suggestions:
|
|
if await bump_relevance(
|
|
session,
|
|
suggestion,
|
|
source="next_steps",
|
|
source_key=bucket,
|
|
reason="Surfaced during get_next_steps lookup",
|
|
):
|
|
bumped += 1
|
|
return bumped |