-- W3 AI Staffing Recommendations queries. Reads are RLS scoped by the app.current_org_id policy, so a -- bare SELECT only ever sees the caller's org; writes pass organization_id explicitly and the RLS WITH -- CHECK validates it matches the session org. No query here names the eeo schema, and none can: the -- generated code is built only from public schema queries. -- name: CreateRecommendationRun :one INSERT INTO recommendation_runs (organization_id, view, scope_ref, model_version, parameters, created_by) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; -- name: CreateStaffingRecommendation :one INSERT INTO staffing_recommendations ( organization_id, run_id, requirement_id, candidate_id, action, rank, confidence, value_score, rationale, rationale_source, evidence ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *; -- name: ListProactiveRecommendations :many -- The current bench watch: pending recommendations from proactive runs, capped and paginated. SELECT sr.* FROM staffing_recommendations sr JOIN recommendation_runs rr ON rr.id = sr.run_id AND rr.organization_id = sr.organization_id WHERE rr.view = 'proactive' AND sr.status = 'pending' ORDER BY sr.candidate_id, sr.rank, sr.id LIMIT $1 OFFSET $2; -- name: GetStaffingRecommendationForUpdate :one -- Locks the row for the decision path so two concurrent decisions on the same recommendation serialize; -- the service checks existence (else 404) and status (else 409) under this lock before deciding. SELECT * FROM staffing_recommendations WHERE id = $1 FOR UPDATE; -- name: MarkRecommendationDecision :one -- Status guarded: only a pending row can be decided, so a row that was concurrently superseded or -- decided returns no row and the service surfaces 409 rather than overwriting a dead or decided row. UPDATE staffing_recommendations SET status = $2, decided_by = $3, decided_at = now(), decision_note = $4 WHERE id = $1 AND status = 'pending' RETURNING *; -- name: SupersedePendingGap :exec -- A fresh gap run for a requirement supersedes ONLY the prior pending gap rows for that requirement. -- Decided rows (approved, rejected, dismissed) are immutable and are never touched. UPDATE staffing_recommendations sr SET status = 'superseded' FROM recommendation_runs rr WHERE sr.run_id = rr.id AND sr.organization_id = rr.organization_id AND rr.view = 'gap' AND sr.requirement_id = $1 AND sr.status = 'pending'; -- name: SupersedePendingPortfolio :exec -- A fresh portfolio run supersedes ONLY the prior pending portfolio rows for the org. Decided rows are -- immutable. UPDATE staffing_recommendations sr SET status = 'superseded' FROM recommendation_runs rr WHERE sr.run_id = rr.id AND sr.organization_id = rr.organization_id AND rr.view = 'portfolio' AND sr.status = 'pending'; -- name: SupersedePendingProactive :exec -- A fresh proactive recompute for one candidate supersedes ONLY that candidate's prior pending proactive -- rows. Decided rows are immutable. UPDATE staffing_recommendations sr SET status = 'superseded' FROM recommendation_runs rr WHERE sr.run_id = rr.id AND sr.organization_id = rr.organization_id AND rr.view = 'proactive' AND sr.candidate_id = $1 AND sr.status = 'pending'; -- name: DeleteRecommendationsForCandidate :exec -- Right to erasure: when a candidate is anonymized or sourced-erased, their recommendation rows are -- removed (anonymize in place does not delete the candidate row, so this is explicit, mirroring the -- ai_memory cleanup). DELETE FROM staffing_recommendations WHERE candidate_id = $1; -- name: ListCommitments :many -- A candidate is committed to a requirement when they are an approved slate member (the source of truth) -- or have an approved W3 ASSIGN that has not yet flowed to a slate (provisional commitment). The gap view -- reads this so a committed candidate is classified REASSIGN, never a duplicate ASSIGN. SELECT sm.candidate_id AS candidate_id, sl.requirement_id AS requirement_id, 'slate'::text AS source FROM slate_members sm JOIN slates sl ON sl.id = sm.slate_id AND sl.organization_id = sm.organization_id WHERE sm.decision = 'approved' UNION SELECT sr.candidate_id AS candidate_id, sr.requirement_id AS requirement_id, 'provisional'::text AS source FROM staffing_recommendations sr WHERE sr.action = 'assign' AND sr.status = 'approved' AND sr.candidate_id IS NOT NULL; -- name: ListOpenRequirementsWithValue :many -- Open requirements with their backing opportunity dollar value and its provenance, for the portfolio -- value function. A standalone requirement (no opportunity) has a null value and is handled by the -- documented value fallback in the optimizer. SELECT r.*, o.value AS opportunity_value, o.value_source AS opportunity_value_source FROM requirements r LEFT JOIN opportunities o ON o.id = r.opportunity_id AND o.organization_id = r.organization_id WHERE r.deleted_at IS NULL AND (r.opportunity_id IS NULL OR o.status = 'open') ORDER BY r.id LIMIT $1 OFFSET $2; -- name: GetRequirementWithValue :one -- One requirement with its backing opportunity value, provenance, and status, for the gap view. The -- service uses deleted_at and the opportunity status to 404 a requirement that is not a real open gap. SELECT r.*, o.value AS opportunity_value, o.value_source AS opportunity_value_source, o.status AS opportunity_status FROM requirements r LEFT JOIN opportunities o ON o.id = r.opportunity_id AND o.organization_id = r.organization_id WHERE r.id = $1;