Files
matchmaking-v2/tests/test_contract.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

51 lines
2.4 KiB
Python

"""Phase 0 — the `match` contract test.
The engine's `match` block must (a) validate against the Pydantic model and (b) serialize
to EXACTLY the keys the frontend `scoutJobToMatchRole` reads (scout/lib/jobMatching.ts →
`JobMatch`). If either drifts, backend ⇄ frontend have silently diverged — this test fails.
"""
import pytest
from pydantic import ValidationError
from app.engine.schema import Growth, MatchDim, MatchResult
# The exact field sets the frontend reads. Keep in lockstep with `JobMatch` / `MatchDim`.
FRONTEND_MATCH_KEYS = {"score", "fit", "archetype", "one_line", "reason", "breakdown", "growth",
"coach_note", "proofReady", "factors"}
FRONTEND_DIM_KEYS = {"name", "score", "level", "note"}
FRONTEND_GROWTH_KEYS = {"text", "from", "to"}
def _golden() -> MatchResult:
return MatchResult(
score=88, fit="stretch", reason="Matches 5/7 skills · Senior fit",
breakdown=[MatchDim(name="Product Management", score=92, level="Strong"),
MatchDim(name="SQL", score=48, level="Light")],
growth=Growth(text="Add an SQL proof point", **{"from": 88, "to": 95}),
proofReady=True, factors={"skill": 0.71, "experience": 0.9, "salary": None},
)
def test_match_block_keys_align_with_frontend():
d = _golden().as_dict()
assert set(d) == FRONTEND_MATCH_KEYS, f"match keys drifted: {set(d) ^ FRONTEND_MATCH_KEYS}"
assert set(d["breakdown"][0]) == FRONTEND_DIM_KEYS
assert set(d["growth"]) == FRONTEND_GROWTH_KEYS
# the `from` keyword must serialize under its alias, not `from_`
assert d["growth"]["from"] == 88 and d["growth"]["to"] == 95
def test_constrained_enums_reject_bad_values():
with pytest.raises(ValidationError):
MatchResult(score=1, fit="maybe", reason="x") # fit ∉ {fit, stretch}
with pytest.raises(ValidationError):
MatchDim(name="x", score=1, level="Great") # level ∉ {Strong, Solid, Light}
def test_minimal_match_is_valid_and_floor_free_shaped():
d = MatchResult(score=50, fit="fit", reason="ok").as_dict()
assert d["growth"] is None and d["breakdown"] == [] and d["factors"] == {}
# factors may carry None (an *absent* factor) — that's the coverage-aware contract, not a floor
d2 = MatchResult(score=50, fit="fit", reason="ok", factors={"salary": None}).as_dict()
assert d2["factors"]["salary"] is None