package matching import ( "context" "fmt" "sort" "strings" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/shopspring/decimal" "github.com/missionworkforce/missionmatch/internal/modules/commandcenter" "github.com/missionworkforce/missionmatch/internal/modules/matching/engine" "github.com/missionworkforce/missionmatch/internal/modules/matching/intelligence" "github.com/missionworkforce/missionmatch/internal/platform/database/dbgen" ) // CandidateIntelligenceView is the workforce intelligence projection of one candidate for the // Candidates page. Every value is computed from the same scoring engine the dashboard, Top Matches, // and the evaluation view use (Mission Readiness ties out), plus the Command Center coverage logic // (Win Impact). Nothing here is hardcoded. type CandidateIntelligenceView struct { ID uuid.UUID `json:"id"` FullName string `json:"full_name"` Source string `json:"source"` // seed or external Headline string `json:"headline,omitempty"` // self reported title and skills (External leads) YearsExperience int32 `json:"years_experience"` Location string `json:"location,omitempty"` Available bool `json:"available"` Clearance ClearanceCell `json:"clearance"` MissionReadiness ReadinessCell `json:"mission_readiness"` ProposalReady ProposalCell `json:"proposal_ready"` WinImpact WinImpactCell `json:"win_impact"` BestFit BestFitCell `json:"best_fit"` LaborCategoryCoverage []string `json:"labor_category_coverage"` } // ClearanceCell is the clearance intelligence indicator (self reported, never verified). When the // level is unknown it is shown plainly as Unknown so the layout stays balanced. type ClearanceCell struct { Level string `json:"level"` // TS/SCI, Secret, Unknown, ... Status string `json:"status"` // Active, Unknown, ... SciEligible bool `json:"sci_eligible"` SciKnown bool `json:"sci_known"` // whether SCI eligibility is determinable InvestigationYear string `json:"investigation_year,omitempty"` Provenance string `json:"provenance"` // honest label, "Self reported, not verified" by default Verified bool `json:"verified"` // true only for a sponsor FSO verification Available bool `json:"available"` } // ReadinessCell is the candidate's Mission Readiness against their best fit requirement. type ReadinessCell struct { Score string `json:"score"` Band string `json:"band"` Status string `json:"status"` } // ProposalCell is the derived Proposal Ready gate. Value is YES or REVIEW, never a bare NO; REVIEW // always carries a reason. type ProposalCell struct { Value string `json:"value"` Reason string `json:"reason"` } // WinImpactCell is HIGH, MEDIUM, or LOW, derived from the candidate's best gap filling value across // every open requirement they are eligible for (the Command Center slate coverage logic): filling an // under covered slot is HIGH, backfilling a thin qualified pool is MEDIUM, redundant depth is LOW. type WinImpactCell struct { Level string `json:"level"` Basis string `json:"basis"` } // BestFitCell is the candidate's top matching open requirement from the shared matching engine. type BestFitCell struct { RequirementID uuid.UUID `json:"requirement_id"` Name string `json:"name"` CustomerAgency string `json:"customer_agency,omitempty"` Score string `json:"score"` Band string `json:"band"` } // reqEntry is one requirement prepared for scoring plus its metadata. type reqEntry struct { id uuid.UUID er engine.Requirement lcat string title string customer string kp bool } // laborKeywords maps profile text (a candidate's headline and skill names) to Galapagos labor // categories, used so External leads with no structured skills still show the categories they fit. var laborKeywords = []struct { lcat string words []string }{ {"ISSO", []string{"isso", "information system security officer", "information systems security", "security officer", "issm"}}, {"Cyber Architect", []string{"security architect", "cyber architect", "cybersecurity architect", "soc architect"}}, {"Cloud Architect", []string{"cloud architect", "solutions architect", "aws architect", "azure architect"}}, {"Systems Engineer", []string{"systems engineer", "system engineer", "software engineer", "software developer", "devops"}}, {"Cyber Engineer", []string{"cyber engineer", "cybersecurity engineer", "security engineer", "soc analyst", "network security", "penetration"}}, {"Program Manager", []string{"program manager", "project manager", "pmp", "capture manager"}}, {"Data Scientist", []string{"data scientist", "machine learning", "data engineer"}}, {"Intelligence Analyst", []string{"intelligence analyst", "all source", "sigint", "geoint", "intel analyst"}}, } // CandidateIntelligence scores up to limit candidates against every requirement using the SHARED // engine (so Mission Readiness equals the value shown everywhere else), picks each candidate's best // fit open requirement, and derives Proposal Ready, Win Impact (from the Command Center coverage), and // the labor categories they qualify for. Read only. func (s *Service) CandidateIntelligence(ctx context.Context, limit int) ([]CandidateIntelligenceView, error) { if s.db == nil { return nil, ErrNoDatabase } if limit <= 0 || limit > 400 { limit = 120 } asOf := s.clock() var out []CandidateIntelligenceView err := s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { q := dbgen.New(tx) // Reuse the Command Center slate coverage logic (greedy assignment) for Win Impact thinness, plus // its hard eligibility edges so Win Impact gates on the same eligibility the assignment uses. coverage, eligible, err := commandcenter.Coverage(ctx, tx, asOf) if err != nil { return fmt.Errorf("coverage: %w", err) } reqRows, err := q.ListRequirements(ctx, dbgen.ListRequirementsParams{Limit: 500, Offset: 0}) if err != nil { return fmt.Errorf("list requirements: %w", err) } reqs := make([]reqEntry, 0, len(reqRows)) validLcat := map[string]bool{} for _, rr := range reqRows { er, err := reqFromRow(ctx, q, rr) if err != nil { return err } lc := strings.TrimSpace(deref(rr.LaborCategory)) reqs = append(reqs, reqEntry{id: rr.ID, er: er, lcat: lc, title: rr.Title, customer: deref(rr.CustomerAgency), kp: rr.KeyPersonnel}) if lc != "" { validLcat[lc] = true } } cands, err := q.ListCandidatesForIntel(ctx, int32(limit)) if err != nil { return fmt.Errorf("list candidates: %w", err) } out = make([]CandidateIntelligenceView, 0, len(cands)) for _, c := range cands { ec, err := loadCandidate(ctx, q, c, asOf) if err != nil { return err } view, bestRes := buildCandidateIntel(ctx, c, ec, asOf, reqs, validLcat, coverage, eligible, s.matcher) // The Candidates page scores every candidate against the open requirement set, the highest // volume screening stage AEDT output in the product. Record each candidate's best fit served // score so the screening stage adverse impact denominator is complete, not biased toward the // focused single candidate screens, and fail closed if it cannot be written. Skip when the org // has no requirements: nothing was scored, so there is no served score to record. if len(reqs) > 0 { if e := s.auditOnTx(ctx, tx, servedScoreEvent("screenMATCH.intelligence", c.ID, bestRes)); e != nil { return e } } out = append(out, view) } // Strongest first so the page opens on proposal ready talent; name breaks ties deterministically. sort.SliceStable(out, func(i, j int) bool { // Compare the scores as decimals, never float64, to keep the order byte stable for the AEDT asset. si, _ := decimal.NewFromString(out[i].MissionReadiness.Score) sj, _ := decimal.NewFromString(out[j].MissionReadiness.Score) if c := si.Cmp(sj); c != 0 { return c > 0 } return out[i].FullName < out[j].FullName }) return nil }) return out, err } // buildCandidateIntel scores one candidate against every requirement and assembles the intelligence. It // also returns the best fit MatchResult so the caller can record it as a served score (the AEDT audit); // the result is the zero value when the org has no requirements (nothing was scored). func buildCandidateIntel(ctx context.Context, c dbgen.Candidate, ec engine.Candidate, asOf time.Time, reqs []reqEntry, validLcat map[string]bool, coverage map[string]commandcenter.ReqCoverage, eligible map[string]map[string]bool, matcher engine.SkillMatcher) (CandidateIntelligenceView, engine.MatchResult) { facts := ClearanceFactsFrom(c, ec.CustomerExperience) if len(reqs) == 0 { // org has no requirements yet; render a complete, requirement free view return CandidateIntelligenceView{ ID: c.ID, FullName: c.FullName, Source: c.Source, Headline: deref(c.Headline), YearsExperience: c.YearsExperience, Location: deref(c.Location), Available: c.Available, Clearance: clearanceCell(c, facts, asOf), MissionReadiness: ReadinessCell{Score: "0.00", Band: engine.BandRed.Code(), Status: "No open requirements"}, ProposalReady: ProposalCell{Value: "REVIEW", Reason: "No open requirements to evaluate against yet"}, WinImpact: WinImpactCell{Level: "LOW", Basis: "Not yet eligible for an open requirement"}, BestFit: BestFitCell{}, LaborCategoryCoverage: []string{}, }, engine.MatchResult{} } candID := c.ID.String() bandLcat := map[string]bool{} // Best fit and Mission Readiness: the candidate's strongest OPEN match (ties out via the engine). bestIdx, anyIdx, bestOpen := -1, -1, false var bestRes, anyRes engine.MatchResult // Win Impact: their best gap filling value across every open requirement they are eligible for. winTier, winUnmet, winQty := 0, 0, 0 winLcat := "" for i, re := range reqs { res := engine.ScoreCandidate(ctx, ec, re.er, matcher) if re.lcat != "" && res.Band >= engine.BandYellow { bandLcat[re.lcat] = true } // Break score ties on requirement id so the Best Fit pick is stable regardless of row order. if anyIdx == -1 || res.Total.GreaterThan(anyRes.Total) || (res.Total.Equal(anyRes.Total) && re.id.String() < reqs[anyIdx].id.String()) { anyRes, anyIdx = res, i } cvg, open := coverage[re.id.String()] if !open { continue } if !bestOpen || res.Total.GreaterThan(bestRes.Total) || (res.Total.Equal(bestRes.Total) && re.id.String() < reqs[bestIdx].id.String()) { bestRes, bestIdx, bestOpen = res, i, true } if eligible[re.id.String()][candID] { // hard eligible per the assignment (clearance, years, mandatory skills) unmet := cvg.Quantity - cvg.Assigned if unmet < 0 { unmet = 0 } t := 1 // eligible but the role is fully staffed with a deep pool (redundant depth) if unmet > 0 { t = 3 // fills a current open gap } else if cvg.Qualified <= cvg.Quantity*2 { t = 2 // thin qualified pool, valuable backfill depth } // Highest tier wins; within a tier the larger open gap wins so the basis cites the biggest gap. if t > winTier || (t == winTier && unmet > winUnmet) { winTier, winUnmet, winQty, winLcat = t, unmet, cvg.Quantity, re.lcat } } } if bestIdx == -1 { // no open requirement; fall back to the strongest overall bestIdx, bestRes = anyIdx, anyRes } best := reqs[bestIdx] // Labor category coverage: union of band based qualification and profile text keywords, filtered // to the labor categories the org actually has open. cov := map[string]bool{} for l := range bandLcat { cov[l] = true } text := strings.ToLower(deref(c.Headline) + " " + skillText(ec.Skills)) for _, lk := range laborKeywords { if validLcat[lk.lcat] && containsAny(text, lk.words) { cov[lk.lcat] = true } } labor := make([]string, 0, len(cov)) for l := range cov { labor = append(labor, l) } sort.Strings(labor) return CandidateIntelligenceView{ ID: c.ID, FullName: c.FullName, Source: c.Source, Headline: deref(c.Headline), YearsExperience: c.YearsExperience, Location: deref(c.Location), Available: c.Available, Clearance: clearanceCell(c, facts, asOf), MissionReadiness: ReadinessCell{Score: bestRes.Total.StringFixed(2), Band: bestRes.Band.Code(), Status: intelligence.MissionStatusLabel(bestRes.Band)}, ProposalReady: proposalReady(bestRes, best.kp), WinImpact: winImpactCell(winTier, winUnmet, winQty, winLcat), BestFit: BestFitCell{RequirementID: best.id, Name: best.title, CustomerAgency: best.customer, Score: bestRes.Total.StringFixed(2), Band: bestRes.Band.Code()}, LaborCategoryCoverage: labor, }, bestRes } func clearanceCell(c dbgen.Candidate, facts intelligence.ClearanceFacts, asOf time.Time) ClearanceCell { ci := intelligence.Clearance(facts, asOf) known := c.ClearanceStatus != "unknown" && c.ClearanceLevel != "unknown" status := ci.Status if ci.Expired { // a lapsed active clearance is not active; show it as Expired, never confidently active status = "Expired" } cell := ClearanceCell{ Level: ci.Current, Status: status, SciEligible: ci.SCIEligible, SciKnown: known, Provenance: ci.ProvenanceLabel, Verified: ci.Verified, Available: c.Available, } if c.ClearanceGrantedAt.Valid { cell.InvestigationYear = fmt.Sprintf("%d", c.ClearanceGrantedAt.Time.Year()) } return cell } func proposalReady(res engine.MatchResult, kp bool) ProposalCell { green := res.Band == engine.BandGreen certsMet := res.Certifications.GreaterThanOrEqual(decimal.NewFromInt(100)) if green && certsMet { note := "Green band, certifications complete" if kp { note = "Green band, certifications complete, key personnel eligible" } return ProposalCell{Value: "YES", Reason: note} } if !green { return ProposalCell{Value: "REVIEW", Reason: fmt.Sprintf("Mission Readiness %s, below the green threshold of 90", res.Total.StringFixed(0))} } if g := firstCertGap(res.GapSummary); g != "" { return ProposalCell{Value: "REVIEW", Reason: g} } return ProposalCell{Value: "REVIEW", Reason: "Certifications incomplete for this labor category"} } // winImpactCell maps the gap filling tier to a label and a basis. Tier 3 fills a current open gap, // tier 2 backfills a thin qualified pool, tier 1 is redundant depth on a staffed role. func winImpactCell(tier, unmet, quantity int, lcat string) WinImpactCell { label := lcat if label == "" { label = "an open role" } switch tier { case 3: return WinImpactCell{Level: "HIGH", Basis: fmt.Sprintf("Fills an open %s slot, %d of %d still unfilled", label, unmet, quantity)} case 2: return WinImpactCell{Level: "MEDIUM", Basis: "Thin qualified pool for " + label + ", valuable backfill depth"} case 1: return WinImpactCell{Level: "LOW", Basis: label + " is fully staffed, this adds redundant depth"} default: return WinImpactCell{Level: "LOW", Basis: "Not yet eligible for an open requirement"} } } func firstCertGap(gaps []string) string { for _, g := range gaps { if strings.Contains(strings.ToLower(g), "cert") { return g } } return "" } func skillText(skills []engine.CandidateSkill) string { if len(skills) == 0 { return "" } names := make([]string, 0, len(skills)) for _, s := range skills { names = append(names, s.Name) } return strings.Join(names, " ") } func containsAny(haystack string, words []string) bool { for _, w := range words { if strings.Contains(haystack, w) { return true } } return false }