Files
matchmaking-v2/app/db/repo.py
raulgupta b1e9cdd182 Engine v2: embeddings sift + Opus curator + Postgres feed persistence
- A1: fire the hard filter (Apify is precise); embedding vibe-ranker (embed.py)
  blends with the floor-free white-box (rank.py) to sift ~90 -> top 18
- A2: Opus curator (curate.py) reads the 18 -> honest <=count + Gemini-voiced
  report cards; graceful fallback to the white-box + templated cards
- LLM via opencode.ai/zen gateway (llm.py): Opus chat + direct-OpenAI embeddings
- Run LLM work off the event loop (asyncio.to_thread) so the Redis response publishes
- C1: dedicated Postgres (app/db/) persists the per-user feed; get_scout_feed
  replays it so matches survive navigation/refresh
- match contract + report-card fields (schema.py); skills.py; tests/
2026-06-19 15:51:16 +05:30

79 lines
2.8 KiB
Python

"""Feed repository — upsert + read the per-user cached feed. All calls degrade to no-op/None
when the DB is off or unreachable (persistence is optional; the engine must never crash on it)."""
from __future__ import annotations
import logging
from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from app.db.models import Base, UserFeed
from app.db.session import engine, session_factory
logger = logging.getLogger(__name__)
async def init_db() -> None:
"""Create tables if the DB is configured (best-effort; logged, never fatal)."""
eng = engine()
if eng is None:
logger.info("DATABASE_URL unset — feed persistence disabled")
return
try:
async with eng.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("feed store ready")
except Exception as e: # noqa: BLE001
logger.warning("feed store init failed (persistence off): %s", e)
async def save_feed(user_id: str, prefs: dict, result: dict) -> None:
"""Upsert the user's last completed search. No-op if the DB is off."""
factory = session_factory()
if factory is None or not user_id:
return
row = {
"user_id": user_id,
"prefs": prefs or {},
"opportunities": result.get("opportunities") or [],
"sources": result.get("sources") or {},
"engine": result.get("engine") or "",
"scanned": int(result.get("scanned") or 0),
}
try:
async with factory() as s:
stmt = pg_insert(UserFeed).values(**row)
stmt = stmt.on_conflict_do_update(
index_elements=[UserFeed.user_id],
set_={k: row[k] for k in ("prefs", "opportunities", "sources", "engine", "scanned")},
)
await s.execute(stmt)
await s.commit()
except Exception as e: # noqa: BLE001
logger.warning("save_feed failed (non-fatal): %s", e)
async def get_feed(user_id: str) -> dict | None:
"""The user's last cached feed as a search_complete-shaped dict, or None."""
factory = session_factory()
if factory is None or not user_id:
return None
try:
async with factory() as s:
row = (await s.execute(select(UserFeed).where(UserFeed.user_id == user_id))).scalar_one_or_none()
except Exception as e: # noqa: BLE001
logger.warning("get_feed failed (non-fatal): %s", e)
return None
if not row:
return None
return {
"opportunities": row.opportunities or [],
"sources": row.sources or {},
"prefs": row.prefs or {},
"engine": row.engine,
"scanned": row.scanned,
"shortlisted": len(row.opportunities or []),
"cached": True,
"updated_at": row.updated_at.isoformat() if row.updated_at else None,
}