package ai // Capture Readiness (Phase 1 of the Mission Workforce Technologies module set): the bid pursue decision. For an // opportunity it composes opportunity level factors into a Capture Readiness Score and a deterministic GO / // REVIEW / NO_GO recommendation, so a capture team sees, explainably, whether to pursue and whether they can // win. It is a DETERMINISTIC WEIGHTED COMPOSITION over signals already grounded (the segment win rate, the // staffing readiness of the requirements, the agency incumbency structure, the org's engagement depth), NOT a // per candidate score and NOT an employment decision: the frozen v1.4.0 engine stays the ONLY per candidate // scorer, and this touches no person score, no band, and never the eeo schema. // // The weights live in the coefficient store (aedt_model_coefficients under the opportunity_capture_readiness // champion), so a governance owner tunes the capture logic without a code change, and a later phase can fit a // learned calibration on realized bid_outcomes on the SAME surface. The score is an EXACT decimal weighted // average (the four factors are already in [0, 1] and the weights sum to one); there is no logistic sigmoid, // which would distort a weighted average. The GO / REVIEW / NO_GO band is a deterministic threshold on the // score. Every factor's value, weight, contribution, and rationale is in the trace, the citations the // faithfulness gate grounds the narration against. A human reads the trace and records the actual bid decision. import ( "context" "errors" "fmt" "sort" "strings" "github.com/jackc/pgx/v5" "github.com/shopspring/decimal" ) // IntentOpportunityCaptureReadiness is the FIXED registered name of the capture readiness composition. It is a // graph composition (not a raw SQL intent and not a MAG model intent): AnswerGraph intercepts it and dispatches // to answerCaptureReadiness, which resolves the opportunity's segment under RLS, gathers the factors, reads the // stored weights, and composes the decimal score. const IntentOpportunityCaptureReadiness = "opportunity_capture_readiness" // CaptureReadinessModelKey is the model_key under which the tunable factor weights live in the coefficient store // (migration 0041 seeds the prior weights; a governance owner or a later learned fit updates them). const CaptureReadinessModelKey = "opportunity_capture_readiness" // The GO / REVIEW / NO_GO band thresholds on the readiness score (a governance decision, encoded here and // surfaced in the answer). GO at or above goThreshold, NO_GO below noGoThreshold, REVIEW in between. var ( captureGoThreshold = decimal.RequireFromString("0.65") captureNoGoThreshold = decimal.RequireFromString("0.45") ) // The four capture factor names, the exact keys the migration seeds as coefficients and the trace reports. const ( factorStaffingReadiness = "staffing_readiness" factorMarketWinLikelihood = "market_win_likelihood" factorIncumbencyPosition = "incumbency_position" factorPastPerformance = "past_performance" ) // captureContextSQL resolves the opportunity's customer agency segment under RLS from the opportunity id, the // same shape the G6 composition uses to resolve a requirement's segment. It is the intent's registered SQL so // ValidateRegistry can guard it; the composition runs it to derive the agency the market factors key on. const captureContextSQL = ` SELECT COALESCE(NULLIF(o.customer_agency, ''), '(unspecified)') AS customer_agency, o.name AS name FROM opportunities o WHERE o.id = $1` // staffingReadinessSQL is the STAFFING READINESS factor: over the opportunity's own requirements, the fraction // that already have a proposal ready (green) candidate evaluation. It is a STRUCTURAL coverage read, never a // match score, RLS scoped to the caller's org. A ratio guarded with NULLIF so an opportunity with no // requirements returns a null ratio (the composition reads it as zero, honestly not staffed). const staffingReadinessSQL = ` SELECT (count(*) FILTER (WHERE has_green))::numeric / NULLIF(count(*), 0) AS readiness FROM ( SELECT r.id, EXISTS (SELECT 1 FROM candidate_evaluations ce WHERE ce.organization_id = r.organization_id AND ce.requirement_id = r.id AND ce.band = 'green') AS has_green FROM requirements r WHERE r.opportunity_id = $1 AND r.deleted_at IS NULL ) x` // incumbencyPositionSQL is the INCUMBENCY POSITION factor: one minus the largest single recipient's dollar share // of the agency's awards, so a wide open agency scores near one (easy to break in) and an agency dominated by // one entrenched incumbent scores low (a headwind). It reads the GLOBAL PUBLIC usaspending_awards reference (no // org, no person), matched on the normalized agency name, never a candidate score. A null share (no awards for // the agency) returns null, read as one (an open field). The dollar figures are numeric scanned into decimal. const incumbencyPositionSQL = ` WITH agency_awards AS ( SELECT recipient_uei, sum(award_amount) AS dollars FROM usaspending_awards WHERE lower(btrim(awarding_agency)) = lower(btrim($1)) AND recipient_uei <> '' AND award_amount IS NOT NULL GROUP BY recipient_uei ) SELECT 1 - (max(dollars) / NULLIF(sum(dollars), 0)) AS position FROM agency_awards` // captureFactor is one gathered factor before weighting: its name, its value in [0, 1], and a human readable // rationale for the trace and the faithfulness citations. type captureFactor struct { name string value decimal.Decimal rationale string } // answerCaptureReadiness composes the bid pursue decision for an opportunity. It resolves the opportunity's // segment under RLS, gathers the four factors (each guarded and RLS scoped where it reads person or tenant // data, public where it reads the award reference), reads the tunable weights from the champion coefficients, // composes the EXACT decimal weighted average, maps it to a GO / REVIEW / NO_GO band, and narrates the ordered // factor contributions plus the band behind the faithfulness gate, else the deterministic rendering. It emits a // counts only audit (route, model_key, version, factor_count), NO score value, NO opportunity id, NO agency, // NO PII. It computes NO per candidate score. func (o *Orchestrator) answerCaptureReadiness(ctx context.Context, opportunityID string) (*Response, error) { if o.db == nil { return nil, errors.New("ai: no database") } var ( agency string factors []captureFactor weights map[string]decimal.Decimal version int ) err := o.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { // Resolve the opportunity's agency segment (RLS scoped; a foreign org's opportunity is invisible). if err := GuardReadOnlyQuery(captureContextSQL, ReadAllowlist); err != nil { return err } var name string if err := tx.QueryRow(ctx, captureContextSQL, opportunityID).Scan(&agency, &name); err != nil { if errors.Is(err, pgx.ErrNoRows) { return fmt.Errorf("ai: opportunity %q not found", opportunityID) } return err } var gerr error factors, gerr = gatherCaptureFactors(ctx, tx, opportunityID, agency) if gerr != nil { return gerr } m, lerr := loadChampionModel(ctx, tx, CaptureReadinessModelKey) if lerr != nil { return lerr } weights = m.Coefficients version = m.Version return nil }) if err != nil { return nil, err } score, trace := scoreCaptureReadiness(weights, factors) band := captureBand(score) citations := captureCitations(score, band, trace) deterministic := renderCaptureReadiness(score, band, trace) model, genErr := o.generate(ctx, "State the capture readiness plainly from the composition evidence: report the Capture Readiness Score exactly as given, then the GO / REVIEW / NO_GO recommendation exactly as given, then the ordered factor contributions exactly as the trace gives them, then the disclosure. This is an OPPORTUNITY level bid pursue advisory, NOT a per candidate match score and NOT an employment decision: never compute or state a candidate score, a band for a person, or a hiring decision. Do not invent a factor, a weight, or a number the trace does not contain.", "CAPTURE READINESS EVIDENCE:\n"+strings.Join(citations, "\n")) resp := o.finalize(ctx, RouteGraph, model, genErr, deterministic, citations) o.audit(ctx, "aedt.ai.capture_readiness", map[string]any{ "route": RouteGraph, "model_key": CaptureReadinessModelKey, "version": version, "factor_count": len(trace), }) return resp, nil } // gatherCaptureFactors gathers the four capture factors under the caller's tenant tx. The market win likelihood // and the past performance depth come from the caller's own bid_outcomes segment ledger (the same read the win // probability model uses); the staffing readiness from the opportunity's own requirements; the incumbency // position from the global public award reference. Each SQL is guarded read only against the allowlist before it // runs. Absent data resolves to a defensible neutral value with an honest rationale, so a thin segment is not // penalized into a false NO_GO. func gatherCaptureFactors(ctx context.Context, tx pgx.Tx, opportunityID, agency string) ([]captureFactor, error) { factors := make([]captureFactor, 0, 4) // Market win likelihood and past performance depth, from the caller's own segment ledger. winRate, decided, err := captureSegmentLedger(ctx, tx, agency) if err != nil { return nil, err } if decided == 0 { factors = append(factors, captureFactor{factorMarketWinLikelihood, decimal.RequireFromString("0.5"), "no decided bid history in this segment yet, so the win likelihood is a neutral even odds prior"}) factors = append(factors, captureFactor{factorPastPerformance, decimal.Zero, "no decided bids in this segment yet, so the engagement depth is zero"}) } else { factors = append(factors, captureFactor{factorMarketWinLikelihood, clamp01(winRate), fmt.Sprintf("the segment win rate over %d decided bids is %s", decided, winRate.StringFixed(4))}) // Engagement depth: decided bids normalized against a floor of ten, capped at one. depth := clamp01(decimal.NewFromInt(int64(decided)).Div(decimal.NewFromInt(10))) factors = append(factors, captureFactor{factorPastPerformance, depth, fmt.Sprintf("the org has %d decided bids in this segment, a normalized engagement depth of %s", decided, depth.StringFixed(4))}) } // Staffing readiness, from the opportunity's own requirements. readiness, hasReqs, err := captureStaffingReadiness(ctx, tx, opportunityID) if err != nil { return nil, err } if !hasReqs { factors = append(factors, captureFactor{factorStaffingReadiness, decimal.Zero, "the opportunity has no requirements yet, so staffing readiness is zero"}) } else { factors = append(factors, captureFactor{factorStaffingReadiness, clamp01(readiness), "the fraction of the opportunity's requirements with a proposal ready candidate is " + readiness.StringFixed(4)}) } // Incumbency position, from the global public award reference. position, hasAwards, err := captureIncumbencyPosition(ctx, tx, agency) if err != nil { return nil, err } if !hasAwards { factors = append(factors, captureFactor{factorIncumbencyPosition, decimal.NewFromInt(1), "no award history for this agency, so it reads as an open field with no entrenched incumbent"}) } else { factors = append(factors, captureFactor{factorIncumbencyPosition, clamp01(position), "one minus the largest recipient's dollar share of the agency's awards is " + position.StringFixed(4) + " (higher is a more open field)"}) } return factors, nil } // captureSegmentLedger reads the caller's own segment win rate and the decided bid count for the agency from // bid_outcomes joined to requirements (the same advisory ledger the win probability model reads). RLS scoped. func captureSegmentLedger(ctx context.Context, tx pgx.Tx, agency string) (winRate decimal.Decimal, decided int, err error) { if err = GuardReadOnlyQuery(bidWinRateBySegmentSQL, ReadAllowlist); err != nil { return decimal.Zero, 0, err } rows, err := tx.Query(ctx, bidWinRateBySegmentSQL) if err != nil { return decimal.Zero, 0, err } defer rows.Close() want := strings.ToLower(strings.TrimSpace(agency)) for rows.Next() { var seg string var winCount, totalDecided int var rate *decimal.Decimal if err := rows.Scan(&seg, &winCount, &totalDecided, &rate); err != nil { return decimal.Zero, 0, err } if strings.ToLower(strings.TrimSpace(seg)) == want { if rate != nil { winRate = *rate } decided = totalDecided return winRate, decided, rows.Err() } } return decimal.Zero, 0, rows.Err() } // captureStaffingReadiness reads the green coverage fraction of the opportunity's requirements. hasReqs is false // when the opportunity has no requirements (a null ratio), so the caller reports an honest zero. func captureStaffingReadiness(ctx context.Context, tx pgx.Tx, opportunityID string) (readiness decimal.Decimal, hasReqs bool, err error) { if err = GuardReadOnlyQuery(staffingReadinessSQL, ReadAllowlist); err != nil { return decimal.Zero, false, err } var ratio *decimal.Decimal if err := tx.QueryRow(ctx, staffingReadinessSQL, opportunityID).Scan(&ratio); err != nil { return decimal.Zero, false, err } if ratio == nil { return decimal.Zero, false, nil } return *ratio, true, nil } // captureIncumbencyPosition reads one minus the top recipient's dollar share of the agency's awards. hasAwards // is false when the agency has no award history (a null position), so the caller reports an open field. func captureIncumbencyPosition(ctx context.Context, tx pgx.Tx, agency string) (position decimal.Decimal, hasAwards bool, err error) { if err = GuardReadOnlyQuery(incumbencyPositionSQL, ReadAllowlist); err != nil { return decimal.Zero, false, err } var pos *decimal.Decimal if err := tx.QueryRow(ctx, incumbencyPositionSQL, agency).Scan(&pos); err != nil { return decimal.Zero, false, err } if pos == nil { return decimal.Zero, false, nil } return *pos, true, nil } // scoreCaptureReadiness composes the EXACT decimal weighted average of the factors under the stored weights and // returns the score plus the ordered contribution trace. A factor with no stored weight contributes zero (the // safe default). There is NO sigmoid: the factors are already in [0, 1] and the weights sum to one, so the // weighted average is the score directly. func scoreCaptureReadiness(weights map[string]decimal.Decimal, factors []captureFactor) (decimal.Decimal, []Contribution) { score := decimal.Zero trace := make([]Contribution, 0, len(factors)) for _, f := range factors { w := weights[f.name] // a missing weight is decimal zero contribution := w.Mul(f.value) score = score.Add(contribution) trace = append(trace, Contribution{ FeatureName: f.name, Value: f.value, Coefficient: w, Contribution: contribution, Rationale: f.rationale, }) } // Clamp to a valid [0, 1] score (weights that do not sum to one, a governance retune, cannot push it out). return clamp01(score), trace } // captureBand maps the readiness score to the deterministic GO / REVIEW / NO_GO recommendation. func captureBand(score decimal.Decimal) string { if score.GreaterThanOrEqual(captureGoThreshold) { return "GO" } if score.LessThan(captureNoGoThreshold) { return "NO_GO" } return "REVIEW" } // captureCitations builds the citation lines the faithfulness gate grounds the prose against: the score, the // band, one line per factor (name, value, weight, contribution, rationale), and the disclosure. No opportunity // id, no candidate, no person score appears; it is an opportunity level advisory. func captureCitations(score decimal.Decimal, band string, trace []Contribution) []string { cites := make([]string, 0, len(trace)+3) cites = append(cites, "Capture Readiness Score = "+score.StringFixed(4)) cites = append(cites, "Recommendation = "+band) for _, c := range trace { cites = append(cites, fmt.Sprintf("%s: value=%s weight=%s contribution=%s (%s)", c.FeatureName, c.Value.StringFixed(4), c.Coefficient.StringFixed(4), c.Contribution.StringFixed(4), c.Rationale)) } cites = append(cites, captureReadinessDisclosure) return cites } // captureReadinessDisclosure is the fixed reliability line the answer surfaces so a reader knows the score is an // advisory opportunity level bid decision aid a human resolves, never a per candidate match score. No dash. const captureReadinessDisclosure = "Reliability: this is an opportunity level bid pursue advisory composed from grounded factors, each weight shown and tunable. It is NOT a per candidate match score and NOT an employment decision; a human makes the bid decision. The frozen engine stays the only per candidate scorer." // renderCaptureReadiness builds the deterministic answer the composition ships when the model prose is withheld: // the score, the GO / REVIEW / NO_GO recommendation, the ordered factor contributions (by absolute contribution // descending with a name tiebreaker so the strongest driver reads first), and the disclosure. Every number is // the trace's exact decimal; it names no candidate and states no match score. func renderCaptureReadiness(score decimal.Decimal, band string, trace []Contribution) string { var b strings.Builder b.WriteString("Capture Readiness Score for the opportunity: ") b.WriteString(score.StringFixed(4)) b.WriteString(". Recommendation: ") b.WriteString(band) b.WriteString(". Factors (weighted contributions): ") ordered := make([]Contribution, len(trace)) copy(ordered, trace) sort.SliceStable(ordered, func(i, j int) bool { ai, aj := ordered[i].Contribution.Abs(), ordered[j].Contribution.Abs() if !ai.Equal(aj) { return ai.GreaterThan(aj) } return ordered[i].FeatureName < ordered[j].FeatureName }) parts := make([]string, 0, len(ordered)) for _, c := range ordered { parts = append(parts, fmt.Sprintf("%s contributes %s (weight %s times value %s)", c.FeatureName, c.Contribution.StringFixed(4), c.Coefficient.StringFixed(4), c.Value.StringFixed(4))) } b.WriteString(strings.Join(parts, "; ")) b.WriteString(". ") b.WriteString(captureReadinessDisclosure) return b.String() } // clamp01 clamps a decimal into the closed [0, 1] range so every factor and the composed score stays a valid // proportion regardless of the input data or a weight retune. func clamp01(d decimal.Decimal) decimal.Decimal { if d.LessThan(decimal.Zero) { return decimal.Zero } if d.GreaterThan(decimal.NewFromInt(1)) { return decimal.NewFromInt(1) } return d }