- 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/
44 lines
1.9 KiB
Python
44 lines
1.9 KiB
Python
"""The sift — combine the two cheap rankers (white-box ‖ embedding vibe) by weighted-average
|
|
RANK fusion and return the **top-K candidate pool** for the curator (Opus).
|
|
|
|
This is NOT the final cut — it only decides which ~18 jobs the expensive brain reads (retrieve cheap,
|
|
rerank expensive). Each returned job already carries its white-box `match` block (the templated
|
|
fallback the curator's output later replaces).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from app.config import get_settings
|
|
from app.engine import embed as _embed
|
|
from app.engine import rank as _rank
|
|
|
|
W_WHITEBOX, W_VIBE = 0.55, 0.45 # white-box (checklist) leads slightly; vibe (semantics) sharpens
|
|
|
|
|
|
def _ranks(score_by_id: dict[str, float]) -> dict[str, float]:
|
|
"""id → normalized rank in [0,1] (1 = best, 0 = worst). Position-based, so scale-free."""
|
|
order = sorted(score_by_id, key=lambda i: -score_by_id[i])
|
|
n = len(order)
|
|
return {i: (1.0 - pos / (n - 1)) if n > 1 else 1.0 for pos, i in enumerate(order)}
|
|
|
|
|
|
def sift(prefs: dict, ctx: dict | None, jobs: list[dict], k: int | None = None):
|
|
"""Returns (top_k_jobs, debug). top_k_jobs are ranked best-first; each keeps its white-box match."""
|
|
s = get_settings()
|
|
k = k or s.SIFT_TOP_K
|
|
ranked = _rank.rank(prefs, ctx, jobs)["opportunities"] # white-box scores ALL (no early cut)
|
|
if not ranked:
|
|
return [], {"mode": "empty", "scored": 0, "k": 0}
|
|
|
|
wb = {j["id"]: j["matchScore"] / 100.0 for j in ranked}
|
|
vibe = _embed.vibe_scores(prefs, ctx, ranked) # id → cosine, or None
|
|
if vibe:
|
|
wr, vr = _ranks(wb), _ranks(vibe)
|
|
fused = {i: W_WHITEBOX * wr[i] + W_VIBE * vr.get(i, 0.0) for i in wb}
|
|
mode = "whitebox+vibe"
|
|
else:
|
|
fused = _ranks(wb)
|
|
mode = "whitebox-only"
|
|
|
|
top = sorted(ranked, key=lambda j: -fused[j["id"]])[:k]
|
|
return top, {"mode": mode, "scored": len(ranked), "k": len(top)}
|