NoteStack-Test / backend /app /routers /resurface.py
Arslan1997's picture
added blogs
34b0226
Raw History Blame Contribute Delete
2.29 kB
from datetime import UTC, datetime, timedelta
from fastapi import APIRouter, Depends
from sqlalchemy import select
from app.auth import Ctx, get_ctx
from app.models import Document, Source
from app.routers.sources import serialize_document
from app.services.jobs import create_job, serialize_job
router = APIRouter(prefix="/api/resurface", tags=["resurface"])
COOLDOWN = timedelta(days=90)
def _aware(dt: datetime | None) -> datetime | None:
return dt if dt is None or dt.tzinfo else dt.replace(tzinfo=UTC)
@router.get("")
def suggestions(ctx: Ctx = Depends(get_ctx), limit: int = 20):
now = datetime.now(UTC)
rows = ctx.db.execute(
select(Document, Source).outerjoin(Source, Source.id == Document.source_id)
.where(Document.workspace_id == ctx.workspace.id, Document.path.is_not(None))
).all()
evergreen, on_this_day = [], []
unscored = 0
for d, s in rows:
published = _aware(d.published_at)
resurfaced = _aware(d.last_resurfaced_at)
meta = d.metadata_json or {}
item = {**serialize_document(d, s), "reason": meta.get("evergreen_reason"), "angle": meta.get("reshare_angle"),
"last_resurfaced_at": resurfaced.isoformat() if resurfaced else None}
if published and published.month == now.month and published.day == now.day and published.year < now.year:
on_this_day.append({**item, "years_ago": now.year - published.year})
if d.evergreen_score is None:
unscored += 1
continue
if resurfaced and now - resurfaced < COOLDOWN:
continue
age_days = (now - published).days if published else 365
# Old, timeless and not recently reshared ranks highest; brand new posts do not need resurfacing.
age_factor = min(age_days / 365, 2.0) if age_days > 30 else 0.1
evergreen.append({**item, "rank": round(d.evergreen_score * (0.5 + age_factor / 2), 3)})
evergreen.sort(key=lambda x: -x["rank"])
return {"evergreen": evergreen[: min(limit, 100)], "on_this_day": on_this_day, "unscored": unscored,
"total_posts": len(rows)}
@router.post("/scan")
def scan(ctx: Ctx = Depends(get_ctx)):
return serialize_job(create_job(ctx.db, ctx.workspace.id, "resurface_scan", {}, max_attempts=2))