// Command demo-seed populates REAL engine backed screenMATCH evaluations for the demo org so the // Command Center WhoCanSupport panel and the Candidates Win Impact panel are not empty. For each // requirement in the org it screens a capped set of candidates through the existing // matching.Service.ScreenCandidate, which scores with the frozen engine.ScoreCandidate, writes a // served score audit event, and upserts a candidate_evaluations row. That is the ONLY correct way to // produce an evaluation: this tool never fabricates a score and never inserts a candidate_evaluations // row directly. // // It runs entirely under the NOBYPASSRLS application role, inside the tenant transaction that // ScreenCandidate opens (RLS scoped to the org set on the context via tenant.WithOrg). ScreenCandidate // upserts the latest qualification per candidate and requirement pair, so a rerun is safe and // idempotent. // // Env: MM_DATABASE_URL (the app DSN, required), MM_DEMO_ORG_ID (optional, overrides the default demo // org, itself overridable by the -org flag). Flags let you cap how many candidates are screened per // requirement, cap how many requirements are processed, and preview with -dry-run. package main import ( "context" "flag" "fmt" "log" "os" "sort" "strings" "time" "github.com/google/uuid" "github.com/missionworkforce/missionmatch/internal/modules/matching" "github.com/missionworkforce/missionmatch/internal/modules/matching/engine" "github.com/missionworkforce/missionmatch/internal/platform/audit" "github.com/missionworkforce/missionmatch/internal/platform/database" "github.com/missionworkforce/missionmatch/internal/platform/database/dbgen" "github.com/missionworkforce/missionmatch/internal/platform/tenant" ) // defaultOrg is the sales demo organization id. It is the same fixed id the other demo tooling uses. const defaultOrg = "11111111-1111-1111-1111-111111111111" // listPageLimit bounds a single ListRequirements or ListCandidates read. The demo org is small, so one // page covers it; the cap keeps the tool honest against a large org too. const listPageLimit = 500 // bandCounts is the green, yellow, and red tally for one requirement, read from the engine returned // MatchResult band. The engine is the only source of a band; this only counts what it returned. type bandCounts struct { green, yellow, red int } func (b bandCounts) total() int { return b.green + b.yellow + b.red } func main() { orgFlag := flag.String("org", firstNonEmpty(os.Getenv("MM_DEMO_ORG_ID"), defaultOrg), "organization id to seed (defaults to MM_DEMO_ORG_ID or the demo org)") perRequirement := flag.Int("per-requirement", 40, "cap on how many candidates to screen against each requirement") maxRequirements := flag.Int("max-requirements", 0, "cap on how many requirements to process (0 means all)") dryRun := flag.Bool("dry-run", false, "log what would be screened without writing any evaluation or audit row") flag.Parse() if *perRequirement <= 0 { log.Fatal("demo-seed: -per-requirement must be positive") } if *maxRequirements < 0 { log.Fatal("demo-seed: -max-requirements cannot be negative") } orgID, err := uuid.Parse(strings.TrimSpace(*orgFlag)) if err != nil { log.Fatalf("demo-seed: bad org id %q: %v", *orgFlag, err) } dsn := strings.TrimSpace(os.Getenv("MM_DATABASE_URL")) if dsn == "" { log.Fatal("demo-seed: MM_DATABASE_URL (the app DSN) is required") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) defer cancel() pool, err := database.NewPool(ctx, dsn) if err != nil { log.Fatalf("demo-seed: db pool: %v", err) } defer pool.Close() // Fail closed unless this is the NOBYPASSRLS app role, so the seed can never bypass tenant RLS and // can only write the demo org it is scoped to. ScreenCandidate itself runs under RLS. if err := database.AssertRole(ctx, pool, database.AppRoleName()); err != nil { log.Fatalf("demo-seed: app pool role check: %v", err) } appDB := tenant.New(pool) // The service is built with a nil matcher (exact match only, deterministic, no embedding provider // needed) and a real audit writer so every served score is recorded, the same served score audit the // live screenMATCH path writes. The nil matcher matches how the integration tests construct the // service, matching.NewService(appDB, nil, nil), and keeps the run reproducible with no plane calls. // The engine.ScoreCandidate reached through ScreenCandidate is the only scorer. var aud *audit.Writer if !*dryRun { aud = audit.New(appDB) } svc := matching.NewService(appDB, nil, aud) // Scope every read and write to the demo org. RunInTenantTx inside the service refuses to run without // this, and the RLS policy would match zero rows, so this is the correctness boundary for the seed. orgCtx := tenant.WithOrg(ctx, orgID.String()) reqs, err := svc.ListRequirements(orgCtx, listPageLimit, 0) if err != nil { log.Fatalf("demo-seed: list requirements for org %s: %v", orgID, err) } if len(reqs) == 0 { log.Fatalf("demo-seed: org %s has no requirements to seed", orgID) } if *maxRequirements > 0 && len(reqs) > *maxRequirements { reqs = reqs[:*maxRequirements] } // Load the candidate pool once: it is the same set for every requirement, so one read avoids a // per requirement round trip. ListCandidates already excludes soft deleted rows; anonymized rows are // filtered here so an erased candidate is never rescreened. cands, err := svc.ListCandidates(orgCtx, listPageLimit, 0) if err != nil { log.Fatalf("demo-seed: list candidates for org %s: %v", orgID, err) } cands = screenable(cands) if len(cands) == 0 { log.Fatalf("demo-seed: org %s has no screenable candidates", orgID) } if len(cands) > *perRequirement { cands = cands[:*perRequirement] } log.Printf("demo-seed: org=%s requirements=%d candidates_per_requirement=%d dry_run=%v", orgID, len(reqs), len(cands), *dryRun) perReq := make(map[uuid.UUID]bandCounts, len(reqs)) reqTitle := make(map[uuid.UUID]string, len(reqs)) var totalWritten, totalErrors int for _, r := range reqs { reqTitle[r.ID] = r.Title if *dryRun { log.Printf("demo-seed: [dry run] would screen %d candidates against requirement %s (%q)", len(cands), r.ID, r.Title) continue } var counts bandCounts for _, c := range cands { res, serr := svc.ScreenCandidate(orgCtx, c.ID, r.ID) if serr != nil { // A single candidate screen failure must not abort the whole run. Log it, wrapped with %w, and // keep going so a bad row does not deny the rest of the panel its evaluations. totalErrors++ log.Printf("demo-seed: %v", fmt.Errorf("screen candidate %s vs requirement %s: %w", c.ID, r.ID, serr)) continue } tallyBand(&counts, res.Band) totalWritten++ } perReq[r.ID] = counts log.Printf("demo-seed: requirement %s (%q): screened %d, green=%d yellow=%d red=%d", r.ID, r.Title, counts.total(), counts.green, counts.yellow, counts.red) } if *dryRun { log.Printf("demo-seed: [dry run] complete, would have screened %d candidates across %d requirements", len(cands)*len(reqs), len(reqs)) return } // Final summary: total evaluations written and the green, yellow, red counts per requirement, ordered // by requirement id so the report is stable across runs. log.Printf("demo-seed: DONE, wrote %d evaluations across %d requirements (%d screen errors)", totalWritten, len(reqs), totalErrors) ids := make([]uuid.UUID, 0, len(perReq)) for id := range perReq { ids = append(ids, id) } sort.Slice(ids, func(i, j int) bool { return ids[i].String() < ids[j].String() }) for _, id := range ids { c := perReq[id] log.Printf("demo-seed: summary requirement %s (%q): green=%d yellow=%d red=%d total=%d", id, reqTitle[id], c.green, c.yellow, c.red, c.total()) } } // screenable drops anonymized candidates from the pool. ListCandidates already excludes soft deleted // rows (deleted_at IS NULL). An anonymized row is an erased candidate that should never be rescreened, // so it is filtered here to keep the served scores clean. func screenable(in []dbgen.Candidate) []dbgen.Candidate { out := in[:0:0] for _, c := range in { if c.AnonymizedAt != nil { continue } out = append(out, c) } return out } // tallyBand increments the count for the band the engine returned. The band is authoritative from the // engine MatchResult; this only records it. func tallyBand(counts *bandCounts, b engine.Band) { switch b { case engine.BandGreen: counts.green++ case engine.BandYellow: counts.yellow++ case engine.BandRed: counts.red++ } } func firstNonEmpty(vals ...string) string { for _, v := range vals { if s := strings.TrimSpace(v); s != "" { return s } } return "" }