Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/ai/graph_route.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 |
| Branch | master |
| Path or archive member | internal/modules/ai/graph_route.go |
| Original SHA-256 | 2a4fe1e2aed260cf93da918ba3a535b12b8b53d5314000d5304da0be2d3102ca |
| Git blob SHA | b864715b1de974d13e06129ad5201721923a0e6c |
| Reading-copy SHA-256 | 2a4fe1e2aed260cf93da918ba3a535b12b8b53d5314000d5304da0be2d3102ca |
Every recorded occurrence
These 5 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/ai/graph_route.go | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / internal/modules/ai/graph_route.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/modules/ai/graph_route.go | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / internal/modules/ai/graph_route.go | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / internal/modules/ai/graph_route.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
Recovered source transcript
Download complete source text ↓831 lines · 45.7 KB
package ai
// RouteGraph is the deterministic graph reasoning surface. It is the routing layer that crosses the brain
// from a single table lookup (TAG) to walking a cited evidence path over the relational graph already in
// Postgres: the G3 curated skill adjacency and the G1 market and relationship backbone. Like every other
// route it runs the EEO and protected attribute guards FIRST, executes a FIXED, named, parameterized
// QueryIntent from the closed Registry through ExecuteIntent (which re guards via GuardReadOnlyQuery), and
// narrates the result behind the existing per claim faithfulness gate. It NEVER scores: the frozen engine in
// internal/modules/matching/engine stays the only scorer. RouteGraph reads structure (the reachable skills,
// the recency weighted dollar shares, the incumbent of record) and narrates it; a dollar share or a
// competitor rank is a MARKET aggregate, never a match score or an employment decision number. The model
// authors no SQL and binds no params it can invent: the intent is one of a fixed set and its arg slots are
// curated, and a free form question whose required params cannot be extracted unambiguously falls back to the
// existing RAG path rather than guessing.
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// graphIntents is the closed set of Registry intents the dispatcher treats as GRAPH intents (the G3 skill
// adjacency traversals and the G1 market and relationship traversals). It is how Answer tells a graph intent
// from a TAG intent: a structured request naming one of these dispatches to AnswerGraph instead of
// AnswerTAG, so the graph specific evidence rendering and the graph route audit apply. Every name here is a
// FIXED entry in the closed Registry, asserted by ValidateRegistry at boot; IsGraphIntent over this set is
// the single source of truth for the classification.
var graphIntents = map[string]bool{
// G3 skill and role adjacency graph (graph_skill.go).
IntentSkillSubstitutionClosure: true,
IntentSkillGapBridge: true,
// G1 market and relationship graph (graph_market.go).
IntentSegmentCompetitorField: true,
IntentIncumbentOfRecord: true,
IntentRecompetePipeline: true,
IntentAgencyBuyingPattern: true,
// G9 recompete lifecycle and foresight graph (graph_lifecycle.go).
IntentProcurementSuccession: true,
IntentRecompeteClock: true,
IntentForecastRunway: true,
// G7 agency buying pattern graph (graph_agency.go).
IntentAgencyBuyingPatternDetail: true,
IntentAgencyVendorConcentration: true,
IntentAgencySpendTrend: true,
// G2 public vendor co occurrence and teaming field graph (graph_teaming.go).
IntentVendorCooccurrenceField: true,
IntentPartnerOrCompetitorRatio: true,
IntentRecompeteTeamingField: true,
// G5 clearance lattice (graph_clearance.go).
IntentClearanceReachability: true,
IntentCompartmentRequirement: true,
IntentBenchClearanceCoverage: true,
// G4 bench graph (graph_bench.go).
IntentBenchSkillCoverage: true,
IntentBenchReadinessDistribution: true,
IntentBenchGapForRequirement: true,
// G6 the loop (graph_loop.go), the composed capture picture, a person touching bench intent.
IntentCapturePictureForRequirement: true,
// M5 price to win (graph_wage.go), the advisory OEWS wage band, PUBLIC reference data, never suppressed.
IntentLaborRateBand: true,
// MAG opportunity win probability (mag.go), a model backed intent. It is registered here so the planner
// and Answer route it through the graph surface, but the AnswerGraph model fork dispatches it to
// answerModelIntent (NOT the raw ExecuteIntent path, which hard rejects a model intent), where it gathers
// its features through the guarded feature source intents and scores a calibrated probability in decimal.
IntentOpportunityWinProbability: true,
// Capture Readiness (capture_readiness.go), the bid pursue decision composition. Registered here so Answer
// and the planner route it through the graph surface; AnswerGraph intercepts it by name and dispatches to
// answerCaptureReadiness (NOT the raw ExecuteIntent path, whose registered SQL only resolves the segment).
IntentOpportunityCaptureReadiness: true,
// Phase 2 Vendor Intelligence (vendor.go), the vendor quality distribution: a person derived headcount
// partition, so it is ALSO a benchIntent below. It is a graph intent so Answer routes it to AnswerGraph,
// whose bench fork dispatches it to the suppressing VendorQualityDistribution executor, never the raw
// ExecuteIntent path. (vendor_performance_scorecard is a plain TAG aggregate, so it is deliberately NOT
// here and runs the standard AnswerTAG path.)
IntentVendorQualityDistribution: true,
}
// IsGraphIntent reports whether a registered intent name is a GRAPH intent (one of the G3 or G1 traversals),
// so the Answer dispatcher routes it to AnswerGraph rather than AnswerTAG. An unknown or empty name is not a
// graph intent.
func IsGraphIntent(name string) bool { return graphIntents[name] }
// benchIntents is the closed set of PERSON TOUCHING bench intents (the G4 bench plane plus the G5
// bench_clearance_coverage intersection): every one reads the RLS protected person tables and pipes its
// partition through small cell suppression. It is the set the D1 route gate uses to REQUIRE a resolved
// authenticated tenant context (an org_id and user_id from the session) before a person touching read runs;
// the G5 reference reads (clearance_reachability, compartment_requirement_closure) carry no person data and
// are NOT in this set. Building the gate itself is task D1; this set and IsBenchIntent are the registration
// the orchestrator's gate keys on.
var benchIntents = map[string]bool{
// G4 bench graph (graph_bench.go), all person touching and suppressed.
IntentBenchSkillCoverage: true,
IntentBenchReadinessDistribution: true,
IntentBenchGapForRequirement: true,
// G5 bench clearance coverage (graph_clearance.go), the one person touching read on the clearance plane.
IntentBenchClearanceCoverage: true,
// G6 the loop (graph_loop.go), the composed capture picture: it reads the bench, so it is person touching,
// auth gated, and rejected on the raw ExecuteIntent path; the bench fork dispatches it to its composition.
IntentCapturePictureForRequirement: true,
// Phase 2 Vendor Intelligence (vendor.go), the vendor quality distribution: it joins the vendor's
// submissions to the frozen candidate_evaluations band, a person derived per vendor per band headcount, so
// it is person touching, auth gated, rejected on the raw ExecuteIntent path, and runs only through the
// suppressing VendorQualityDistribution executor.
IntentVendorQualityDistribution: true,
}
// IsBenchIntent reports whether a registered intent name is a PERSON TOUCHING bench intent, so the D1 route
// gate requires an authenticated tenant context before it runs. An unknown or empty name is not a bench
// intent, and the G5 reference reads are deliberately excluded (they carry no person data).
func IsBenchIntent(name string) bool { return benchIntents[name] }
// AnswerGraph runs a registered GRAPH traversal and narrates its cited evidence path behind the faithfulness
// gate, the structured graph path. It mirrors AnswerTAG exactly: it runs ForbidEEO and
// MentionsProtectedAttribute on the question FIRST and refuses on either (the orchestrator precedent, so the
// graph path can never group, filter, or rank by a protected attribute), looks the intent up by name, runs
// it through ExecuteIntent (which re guards via GuardReadOnlyQuery and runs RLS scoped as the tenant), builds
// a DETERMINISTIC evidence rendering of the returned rows (the cited path), and narrates through o.generate
// and o.finalize so the model prose ships ONLY when the faithfulness gate confirms it, else the deterministic
// rendering (a 200, rationale_source deterministic). A plane outage degrades to the deterministic rendering,
// never a hang, because the data is the database's and generate returns a typed error that finalize turns
// into the deterministic fallback. The audit is aedt.ai.answered route graph, carrying route, rationale
// source, and the evidence COUNT only, never a UEI, a candidate id, a score, or answer text.
func (o *Orchestrator) AnswerGraph(ctx context.Context, question, intentName string, args []any) (*Response, error) {
if ForbidEEO(question) != nil || MentionsProtectedAttribute(question) {
return o.refuse(ctx, RouteGraph, "the question references prohibited demographic data"), nil
}
intent, ok := IntentByName(intentName)
if !ok {
return nil, fmt.Errorf("%w: %q", ErrUnknownIntent, intentName)
}
// A MAG (model backed) intent runs through answerModelIntent, NEVER the raw ExecuteIntent path (which hard
// rejects a model intent). It gathers its features through the existing guarded feature source intents,
// scores a calibrated probability in decimal, and narrates the contribution trace behind the faithfulness
// gate. It emits an opportunity level number, never a per candidate score.
if IsModelIntent(intentName) {
return o.answerModelIntent(ctx, question, intentName, args)
}
// Capture Readiness is a graph COMPOSITION intercepted by name: its registered SQL only resolves the
// opportunity's segment, and the composition (answerCaptureReadiness) gathers the factors and composes the
// decimal score. It never runs as the raw query and never computes a per candidate score.
if intentName == IntentOpportunityCaptureReadiness {
id, err := benchIDArg(args)
if err != nil {
return nil, err
}
return o.answerCaptureReadiness(ctx, id)
}
// A person touching bench intent runs through its suppressing executor, NEVER the raw ExecuteIntent path
// (which returns unsuppressed counts and which hard rejects bench intents). The bench path requires an
// authenticated tenant context, renders only suppressed safe counts, and emits a counts only audit. The G6
// capture picture is a person touching COMPOSITION over the bench and market planes, so the bench fork
// dispatches it to answerCapturePicture; the other four bench intents run their single suppressing executor
// through answerBench unchanged.
if IsBenchIntent(intentName) {
if intentName == IntentCapturePictureForRequirement {
id, err := benchIDArg(args)
if err != nil {
return nil, err
}
return o.answerCapturePicture(ctx, id)
}
return o.answerBench(ctx, intentName, args)
}
res, err := ExecuteIntent(ctx, o.db, intent, args)
if err != nil {
return nil, err
}
rendered, _ := json.Marshal(map[string]any{"columns": res.Columns, "rows": res.Rows})
evidence := []string{string(rendered)}
deterministic := renderGraphEvidence(intent.Description, res.Columns, res.Rows)
model, genErr := o.generate(ctx,
"State the result plainly from the graph evidence path. Cite the reached skills, the recipients, the agencies, and the awards exactly as the rows give them. Do not invent a UEI, a dollar figure, a share, or a skill the rows do not contain, and never compute a match score.",
"INTENT: "+intent.Description+"\n\nEVIDENCE PATH:\n"+string(rendered))
return o.finalize(ctx, RouteGraph, model, genErr, deterministic, evidence), nil
}
// renderGraphEvidence builds a deterministic, readable answer from a graph traversal result, the cited path
// the question is answered from even when the model prose is withheld. It reuses the TAG table rendering: a
// graph row is already the cited evidence (the reached skill and its evidence path for G3; the recipient UEI,
// the dollar share, the matched award for G1), so rendering the columns and rows verbatim, in the byte stable
// order the deterministic SQL returns, is the faithful deterministic answer. No number is recomputed and no
// score is produced; the rows are reported exactly as the database returned them.
func renderGraphEvidence(description string, columns []string, rows [][]any) string {
return renderTable(description, columns, rows)
}
// answerBench runs a person touching bench intent through its suppressing executor and narrates only the
// suppressed safe counts. It is the ONLY answer path for a bench intent: the raw ExecuteIntent path hard
// rejects bench intents, so an unsuppressed sub k headcount can never be served. The bench plane reads RLS
// protected person data, so it REQUIRES a resolved authenticated tenant context and refuses without one
// rather than relying only on the RLS empty result fail safe. Every count placed into the evidence, the
// model prompt, and the audit comes from RenderCount, so a suppressed cell is the sentinel and never a
// number. The frozen engine stays the only scorer; this path computes no match score.
func (o *Orchestrator) answerBench(ctx context.Context, intentName string, args []any) (*Response, error) {
if _, ok := tenant.OrgFromContext(ctx); !ok {
return o.refuse(ctx, RouteGraph, "a bench question requires an authenticated tenant context"), nil
}
k := ConfiguredSuppressionThreshold()
labels, cells, err := o.runBenchIntent(ctx, intentName, args, k)
if err != nil {
return nil, err
}
// Build the suppressed safe rendering: each cell renders through RenderCount, so a suppressed cell is the
// sentinel and no sub k number reaches the evidence, the model prompt, or the audit.
columns := []string{"bucket", "headcount"}
rows := make([][]any, len(cells))
suppressed := 0
for i, c := range cells {
if c.Suppressed {
suppressed++
}
rows[i] = []any{labels[i], RenderCount(c)}
}
deterministic := renderBenchEvidence(intentName, columns, rows, suppressed, k)
rendered, _ := json.Marshal(map[string]any{"columns": columns, "rows": rows})
evidence := []string{string(rendered)}
model, genErr := o.generate(ctx,
"State the bench counts plainly from the evidence. Report each bucket's headcount exactly as the rows give it. A headcount shown as "+SuppressionSentinel+" was withheld to protect individuals: say it was withheld and NEVER guess or state a number for it. Never compute a match score.",
"INTENT: "+intentName+"\n\nEVIDENCE:\n"+string(rendered))
resp := o.finalize(ctx, RouteGraph, model, genErr, deterministic, evidence)
// Counts only, suppression aware audit: the intent, the cell count, the suppressed count, and the
// configured k. It carries NO candidate id, NO raw sub k count, and NO answer text.
o.audit(ctx, "aedt.ai.bench_answered", map[string]any{"route": RouteGraph, "intent": intentName, "cells": len(cells), "suppressed": suppressed, "k": k})
return resp, nil
}
// runBenchIntent dispatches a bench intent name to its suppressing Go executor and returns the partition as
// parallel label and Cell slices (the Cells are already small cell suppressed by the executor). It is the
// single place that runs a bench intent; the generic ExecuteIntent rejects them so an unsuppressed raw count
// can never be served.
func (o *Orchestrator) runBenchIntent(ctx context.Context, intentName string, args []any, k int) ([]string, []Cell, error) {
switch intentName {
case IntentBenchSkillCoverage:
id, err := benchIDArg(args)
if err != nil {
return nil, nil, err
}
out, err := BenchSkillCoverage(ctx, o.db, id, k)
if err != nil {
return nil, nil, err
}
labels := make([]string, len(out))
cells := make([]Cell, len(out))
for i, r := range out {
labels[i], cells[i] = r.Bucket, r.Cell
}
return labels, cells, nil
case IntentBenchReadinessDistribution:
id, err := benchIDArg(args)
if err != nil {
return nil, nil, err
}
out, err := BenchReadinessDistribution(ctx, o.db, id, k)
if err != nil {
return nil, nil, err
}
labels := make([]string, len(out))
cells := make([]Cell, len(out))
for i, r := range out {
labels[i], cells[i] = r.Band, r.Cell
}
return labels, cells, nil
case IntentBenchGapForRequirement:
id, err := benchIDArg(args)
if err != nil {
return nil, nil, err
}
out, err := BenchGapForRequirement(ctx, o.db, id, k)
if err != nil {
return nil, nil, err
}
labels := make([]string, len(out))
cells := make([]Cell, len(out))
for i, r := range out {
labels[i], cells[i] = r.Bucket, r.Cell
}
return labels, cells, nil
case IntentBenchClearanceCoverage:
out, err := BenchClearanceCoverage(ctx, o.db, k)
if err != nil {
return nil, nil, err
}
labels := make([]string, len(out))
cells := make([]Cell, len(out))
for i, r := range out {
labels[i], cells[i] = r.LevelCode, r.Cell
}
return labels, cells, nil
case IntentVendorQualityDistribution:
// The vendor quality distribution takes no id (it reports every vendor the caller's org has recorded),
// like the bench clearance coverage. Each row's label is the vendor name and the frozen band so the
// suppressed rendering names which vendor and band a cell belongs to; the Cell is already small cell
// suppressed per vendor by the executor.
out, err := VendorQualityDistribution(ctx, o.db, k)
if err != nil {
return nil, nil, err
}
labels := make([]string, len(out))
cells := make([]Cell, len(out))
for i, r := range out {
labels[i], cells[i] = r.VendorName+" / "+r.Band, r.Cell
}
return labels, cells, nil
default:
return nil, nil, fmt.Errorf("ai: %q is not a bench intent", intentName)
}
}
// benchIDArg extracts the single id argument a bench intent that keys on a UUID expects (a skill id or a
// requirement id), accepting a string or any fmt.Stringer (so a uuid.UUID binds), and rejecting a missing,
// empty, or wrong shaped argument.
func benchIDArg(args []any) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("ai: a bench intent expects exactly one id argument, got %d", len(args))
}
switch v := args[0].(type) {
case string:
if v == "" {
return "", fmt.Errorf("ai: a bench intent id argument must be a non empty string")
}
return v, nil
case fmt.Stringer:
s := v.String()
if s == "" {
return "", fmt.Errorf("ai: a bench intent id argument must be a non empty string")
}
return s, nil
default:
return "", fmt.Errorf("ai: a bench intent id argument must be a string, got %T", args[0])
}
}
// renderBenchEvidence builds the deterministic, suppressed safe answer for a bench intent: each bucket with
// its headcount (the sentinel for a suppressed cell), plus a fixed line naming how many of the buckets were
// withheld to protect individuals. No raw sub k count appears, so the deterministic fallback is itself safe.
func renderBenchEvidence(intentName string, columns []string, rows [][]any, suppressed, k int) string {
base := renderTable(intentName, columns, rows)
if suppressed > 0 {
base += fmt.Sprintf(" %d of %d buckets withheld to protect individuals (fewer than %d).", suppressed, len(rows), k)
}
return base
}
// graphRoute is the chosen graph intent plus the bound params the conservative NL classifier extracted. A nil
// graphRoute means the question did not clearly name a graph traversal, or it named one but the required
// params could not be extracted unambiguously, so the caller falls back to the existing RAG path.
type graphRoute struct {
intent string
args []any
}
// naicsRe matches a bare six digit NAICS code as a standalone token, the only NAICS shape extracted
// unambiguously from free text. Anything else (a partial code, a code embedded in a larger number) is not
// extracted, so the classifier never guesses a segment key.
var naicsRe = regexp.MustCompile(`\b(\d{6})\b`)
// quotedAgencyRe matches a single or double quoted span, the only agency name extracted unambiguously from
// free text. A bare agency phrase is NOT extracted (the buyer name is ambiguous without quotes), so the
// classifier never guesses an agency and never authors a param the user did not delimit.
var quotedAgencyRe = regexp.MustCompile(`["']([^"']{2,})["']`)
// windowMonthsRe matches an explicit forward window phrased as a number of months, so a recompete question
// that names its own window binds it; an unspecified window falls to the intent's own default clamp.
var windowMonthsRe = regexp.MustCompile(`(\d{1,3})\s*month`)
// graphClassify is the conservative natural language router for the graph surface. It maps a free form
// question that CLEARLY names a capture or skill graph traversal (a curated keyword to intent table, never an
// LLM, mirroring how Classify routes count and sum to TAG) to a graphRoute, and returns nil when the question
// is not a graph question OR when it is but the required structured params cannot be extracted unambiguously.
// Because the graph intents need structured params (an agency, a NAICS, an opportunity id, a skill id) that
// are hard to extract deterministically from free text, extraction is deliberately MINIMAL: only a bare six
// digit NAICS and a quoted agency name are read, and when a matched intent's required params are missing the
// caller falls back to the existing RAG path rather than guessing. The model never authors the params; the
// intent and its arg slots are fixed here.
func graphClassify(question string) *graphRoute {
q := strings.ToLower(question)
// G6 the loop, the composed capture picture for a requirement. It reads the bench (person touching), so it
// keys on a requirement id (a UUID), which a user almost never types in free text; a capture picture
// question routes here only when it carries an explicit canonical UUID, the same conservative posture as
// the bench readiness and bench gap routes. It is checked FIRST so its distinct integrated phrasing wins
// over the narrower bench gap keywords. Absent an explicit requirement id it falls back to RAG rather than
// guessing a requirement.
captureKeywords := []string{"capture picture", "can we staff", "staffing and market", "staff it and the market", "bench and the market for the requirement"}
if containsAny(q, captureKeywords) {
if id, ok := quotedUUID(question); ok {
return &graphRoute{intent: IntentCapturePictureForRequirement, args: []any{id}}
}
return nil // a capture picture question with no explicit requirement id falls back to RAG
}
// MAG opportunity win probability (pwin) is the calibrated probability of winning an opportunity in a
// customer agency segment. It is a MODEL backed intent, an opportunity level number, NEVER a per candidate
// score. It is checked EARLY so its distinct win probability phrasing wins over the broader recompete and
// competitor keywords. It binds honestly only when the question carries a quoted agency (the only
// unambiguous segment key in free text); the naics is optional (the empty string means the overall segment,
// the model's own default). Absent a quoted agency it falls back to RAG rather than guessing a segment.
winProbabilityKeywords := []string{"win probability", "pwin", "p win", "probability of win", "probability of winning", "chance of winning"}
if containsAny(q, winProbabilityKeywords) {
agency, hasAgency := quotedAgency(question)
if hasAgency {
naics, _ := bareNAICS(question) // optional; the empty string is the model's overall segment default
return &graphRoute{intent: IntentOpportunityWinProbability, args: []any{agency, naics}}
}
return nil // a win probability question with no quoted agency falls back to RAG
}
// M5 labor rate band is the advisory OEWS prevailing wage for a role in a state, the price to win rate. It
// is PUBLIC reference data (bls_wages), never suppressed and never a candidate score. It binds honestly only
// when the question names BOTH a role we can resolve to a SOC through the curated crosswalk (from a quoted
// role span, the only unambiguous role shape in free text) AND a state we can resolve to a known OEWS area
// code (HI or NM), so an unresolvable role or a state with no wage data falls back to RAG rather than
// guessing a rate. It is checked here, before the market and skill routes, so its distinct wage phrasing
// wins over the broader keywords.
wageKeywords := []string{"labor rate", "prevailing wage", "oews wage", "price to win rate"}
if containsAny(q, wageKeywords) {
role, hasRole := quotedRole(question)
area, hasArea := quotedStateArea(question)
if hasRole && hasArea {
if occ, ok := resolveSOC(role); ok {
return &graphRoute{intent: IntentLaborRateBand, args: []any{occ.SOC, area}}
}
}
return nil // a wage question whose role or state we cannot resolve unambiguously falls back to RAG
}
// G5 bench clearance coverage is the suppressed structural headcount of the caller's bench per clearance
// level. It takes no SQL parameter (the suppression threshold is a Go argument, not a bound param), so a
// question that clearly asks for the clearance breakdown of the bench routes here directly. It is checked
// BEFORE the reachability route so the distinct bench coverage phrasing wins over a bare clearance keyword.
benchCoverageKeywords := []string{"clearance coverage", "clearance breakdown", "clearance distribution", "how many of our bench hold", "bench by clearance", "clearance mix of our bench"}
if containsAny(q, benchCoverageKeywords) {
return &graphRoute{intent: IntentBenchClearanceCoverage, args: nil}
}
// G4 bench skill coverage is the suppressed structural count of the caller's bench that holds a given
// skill versus does not. It keys on a skill id (a UUID), which a user almost never types in free text, so
// a bench skill coverage question routes only when it carries an explicit canonical UUID; absent one it
// falls back to RAG rather than guessing a skill. The same conservative UUID posture as the skill
// adjacency walks.
benchSkillKeywords := []string{"bench skill coverage", "how many of our bench hold the skill", "bench coverage for the skill", "who on the bench has the skill"}
if containsAny(q, benchSkillKeywords) {
if id, ok := quotedUUID(question); ok {
return &graphRoute{intent: IntentBenchSkillCoverage, args: []any{id}}
}
return nil // a bench skill coverage question with no explicit skill id falls back to RAG
}
// G4 bench readiness distribution is the suppressed distribution of bands the frozen engine already
// decided for one requirement, read from the audited candidate_evaluations. It keys on a requirement id
// (a UUID); absent an explicit canonical UUID it falls back to RAG rather than guessing a requirement.
benchReadinessKeywords := []string{"bench readiness", "readiness distribution", "band distribution", "how ready is our bench"}
if containsAny(q, benchReadinessKeywords) {
if id, ok := quotedUUID(question); ok {
return &graphRoute{intent: IntentBenchReadinessDistribution, args: []any{id}}
}
return nil // a bench readiness question with no explicit requirement id falls back to RAG
}
// G4 bench gap for a requirement is the suppressed structural count of the bench that meets a requirement
// versus does not, a count to size a gap, never a match score. It keys on a requirement id (a UUID);
// absent an explicit canonical UUID it falls back to RAG rather than guessing a requirement.
benchGapKeywords := []string{"bench gap", "structural gap", "how many of our bench meet", "who meets the requirement"}
if containsAny(q, benchGapKeywords) {
if id, ok := quotedUUID(question); ok {
return &graphRoute{intent: IntentBenchGapForRequirement, args: []any{id}}
}
return nil // a bench gap question with no explicit requirement id falls back to RAG
}
// G5 clearance reachability is the lattice fact: which required levels a held clearance satisfies. It binds
// the held clearance code from the six reference codes named in the question (the only unambiguous held key
// in free text); absent a named held clearance it falls back to RAG rather than guessing a level.
reachabilityKeywords := []string{"clearance satisfies", "what does a clearance satisfy", "which clearances satisfy", "clearance reachability", "satisfy the requirement", "clearance ladder"}
if containsAny(q, reachabilityKeywords) {
if code, ok := heldClearanceCode(q); ok {
return &graphRoute{intent: IntentClearanceReachability, args: []any{code}}
}
return nil // a reachability question with no named held clearance falls back to RAG
}
// G5 compartment requirement closure resolves a requirement's compartment codes to their kind. It binds the
// quoted comma separated compartment list (the only unambiguous compartment shape in free text); absent a
// quoted list it falls back to RAG rather than guessing the compartments.
compartmentKeywords := []string{"compartment required", "required compartments", "compartment closure", "which compartments", "sci compartment", "compartment gap"}
if containsAny(q, compartmentKeywords) {
if list, ok := quotedCompartmentList(question); ok {
return &graphRoute{intent: IntentCompartmentRequirement, args: []any{list}}
}
return nil // a compartment question with no quoted compartment list falls back to RAG
}
// G9 procurement succession is the lifecycle walk of one solicitation. It keys on an opportunity id (a
// UUID), which a user almost never types in free text, so a succession or lifecycle question routes only
// when it carries an explicit canonical UUID; absent one it falls back to RAG rather than guessing an
// opportunity. This is the same conservative posture as the skill adjacency UUID binding.
successionKeywords := []string{"succession", "lifecycle", "new or recompete", "is this a recompete", "predecessor award"}
if containsAny(q, successionKeywords) {
if id, ok := quotedUUID(question); ok {
return &graphRoute{intent: IntentProcurementSuccession, args: []any{id}}
}
return nil // a succession question with no explicit opportunity id falls back to RAG
}
// G9 recompete clock is the agency forward window read. The distinct phrase recompete clock routes here
// (checked BEFORE the existing recompete pipeline keyword so the more specific phrase wins) when a quoted
// agency is present, with the window defaulting to the intent clamp unless the question names a number of
// months. A bare recompete with no clock phrase still routes to the existing pipeline below, byte
// unchanged.
clockKeywords := []string{"recompete clock", "recompete window", "pop end window", "period of performance end"}
if containsAny(q, clockKeywords) {
agency, hasAgency := quotedAgency(question)
if hasAgency {
return &graphRoute{intent: IntentRecompeteClock, args: []any{agency, windowMonths(question)}}
}
return nil // a recompete clock question with no quoted agency falls back to RAG
}
// G9 forecast runway is the forward window read of agency forecasts by estimated award date. It routes
// only when the question names the forecast runway AND an explicit number of months (its only param), so
// the window is unambiguous; a forecast question with no stated window falls back to RAG rather than
// inventing a horizon.
runwayKeywords := []string{"forecast runway", "forecast pipeline", "upcoming forecasts", "forecast lead time"}
if containsAny(q, runwayKeywords) {
if months := windowMonths(question); months > 0 {
return &graphRoute{intent: IntentForecastRunway, args: []any{months}}
}
return nil // a forecast runway question with no explicit window falls back to RAG
}
// G7 agency vendor concentration is the recency weighted recipient dollar share for one (agency, naics) award
// segment. It needs BOTH a quoted agency and a bare six digit NAICS to bind honestly (the only keys
// usaspending_awards carries); it is checked BEFORE the competitor block so the distinct vendor concentration
// phrase wins over the broader competitor keywords. If either param is missing the question falls back to RAG.
vendorConcentrationKeywords := []string{"vendor concentration", "recipient concentration", "vendor share", "vendor concentration in", "concentration of vendors"}
if containsAny(q, vendorConcentrationKeywords) {
agency, hasAgency := quotedAgency(question)
naics, hasNAICS := bareNAICS(question)
if hasAgency && hasNAICS {
return &graphRoute{intent: IntentAgencyVendorConcentration, args: []any{agency, naics}}
}
return nil // a vendor concentration question whose segment we cannot bind unambiguously falls back to RAG
}
// G7 agency spend trend is the obligation trend by fiscal period for one (agency, naics) segment. It needs
// BOTH a quoted agency and a bare six digit NAICS to bind honestly; absent either it falls back to RAG.
spendTrendKeywords := []string{"spend trend", "obligation trend", "spending trend", "concentrating or fragmenting", "spend over time"}
if containsAny(q, spendTrendKeywords) {
agency, hasAgency := quotedAgency(question)
naics, hasNAICS := bareNAICS(question)
if hasAgency && hasNAICS {
return &graphRoute{intent: IntentAgencySpendTrend, args: []any{agency, naics}}
}
return nil // a spend trend question whose segment we cannot bind unambiguously falls back to RAG
}
// G7 agency buying pattern detail is the fuller where to position view (the vehicle mix, the set aside mix, the
// NAICS and PSC distribution, and the average and median value band). It routes on the distinct detail phrasing,
// checked BEFORE the existing G1 buying pattern keyword so the more specific phrase wins and the Phase 0 buying
// pattern route stays byte unchanged. It needs a quoted agency; the NAICS is optional (the empty string means
// every NAICS, the intent's own default).
buyingDetailKeywords := []string{"buying pattern detail", "vehicle mix", "value band", "average and median", "detailed buying pattern", "how does the agency buy"}
if containsAny(q, buyingDetailKeywords) {
agency, hasAgency := quotedAgency(question)
if hasAgency {
naics, _ := bareNAICS(question) // optional; the empty string is the intent's every NAICS default
return &graphRoute{intent: IntentAgencyBuyingPatternDetail, args: []any{agency, naics}}
}
return nil // a buying pattern detail question with no quoted agency falls back to RAG
}
// G2 partner or competitor ratio is the team versus compete read for one partner UEI in a core NAICS, from
// public co occurrence alone. It is checked FIRST among the G2 routes so the distinct partner versus
// competitor phrasing wins over the broader teaming keywords below. It needs BOTH a quoted UEI (a twelve
// character partner identifier the user delimits) and a bare six digit NAICS to bind honestly; absent
// either it falls back to RAG. It reads only the public is_galapagos self tag, never a tenant table.
partnerRatioKeywords := []string{"partner or competitor", "team or compete", "team versus compete", "do they team or compete", "partner versus competitor"}
if containsAny(q, partnerRatioKeywords) {
uei, hasUEI := quotedUEI(question)
naics, hasNAICS := bareNAICS(question)
if hasUEI && hasNAICS {
return &graphRoute{intent: IntentPartnerOrCompetitorRatio, args: []any{uei, naics}}
}
return nil // a partner or competitor question whose partner UEI and NAICS we cannot bind falls back to RAG
}
// G2 recompete teaming field is the forward window read of the incumbent's co occurring vendor
// neighborhood, the likely teaming field before the RFP drops. It is checked BEFORE the broader vendor co
// occurrence keywords so the distinct recompete teaming phrasing wins. It needs a quoted agency; the window
// defaults to the intent clamp unless the question names a number of months.
recompeteTeamingKeywords := []string{"recompete teaming", "teaming field before the rfp", "teaming field before the recompete", "who could team on the recompete"}
if containsAny(q, recompeteTeamingKeywords) {
agency, hasAgency := quotedAgency(question)
if hasAgency {
return &graphRoute{intent: IntentRecompeteTeamingField, args: []any{agency, windowMonths(question)}}
}
return nil // a recompete teaming question with no quoted agency falls back to RAG
}
// G2 vendor co occurrence field is the set of UEIs that co occur in one (agency, naics) segment, the likely
// teaming and competitor field. It is checked BEFORE the broader competitor block so the distinct co
// occurrence and teaming field phrasing wins. It needs BOTH a quoted agency and a bare six digit NAICS to
// bind honestly (the only keys usaspending_awards carries); absent either it falls back to RAG.
cooccurrenceKeywords := []string{"co occur", "cooccur", "co occurrence", "cooccurrence", "teaming field", "who teams up", "vendor field", "who appears together"}
if containsAny(q, cooccurrenceKeywords) {
agency, hasAgency := quotedAgency(question)
naics, hasNAICS := bareNAICS(question)
if hasAgency && hasNAICS {
return &graphRoute{intent: IntentVendorCooccurrenceField, args: []any{agency, naics}}
}
return nil // a co occurrence question whose segment we cannot bind unambiguously falls back to RAG
}
// The competitor field and the incumbent walk are agency plus NAICS segment questions. They need both a
// quoted agency and a bare six digit NAICS to bind honestly (the only keys usaspending_awards carries); if
// either is missing the question falls back to RAG.
competitorKeywords := []string{"competitor", "incumbent", "who teams", "teaming", "who else won", "market share", "dollar share", "who holds the work", "who has the work"}
if containsAny(q, competitorKeywords) {
agency, hasAgency := quotedAgency(question)
naics, hasNAICS := bareNAICS(question)
if hasAgency && hasNAICS {
// The recency lookback is left to the intent's own default clamp (a bound zero), so the NL path never
// invents a lookback the user did not state.
return &graphRoute{intent: IntentSegmentCompetitorField, args: []any{agency, naics, 0}}
}
return nil // a competitor or incumbent question whose segment we cannot bind unambiguously falls back to RAG
}
// The recompete pipeline is an agency plus a forward window question. It needs a quoted agency; the window
// is the intent's default clamp unless the question names an explicit number of months.
recompeteKeywords := []string{"recompete", "recompete pipeline", "expiring", "coming up for", "pipeline of", "what is coming up", "takeaway"}
if containsAny(q, recompeteKeywords) {
agency, hasAgency := quotedAgency(question)
if hasAgency {
return &graphRoute{intent: IntentRecompetePipeline, args: []any{agency, windowMonths(question)}}
}
return nil // a recompete question with no quoted agency falls back to RAG
}
// The agency buying pattern is a where to position question keyed on a quoted agency or department prefix,
// with an optional bare NAICS filter (the empty string means every NAICS, the intent's own default).
buyingKeywords := []string{"agency buys", "agency buying", "buying pattern", "what does the agency buy", "where to position", "where should we position", "set aside mix", "set-aside mix"}
if containsAny(q, buyingKeywords) {
agency, hasAgency := quotedAgency(question)
if hasAgency {
naics, _ := bareNAICS(question) // optional; the empty string is the intent's every NAICS default
return &graphRoute{intent: IntentAgencyBuyingPattern, args: []any{agency, naics}}
}
return nil // a buying pattern question with no quoted agency falls back to RAG
}
// The skill substitution and gap bridge walks key on a skill id (a UUID), which a user almost never types in
// free text. A skill adjacency question (what substitutes for, skill adjacency, what is close to) that names
// no skill id cannot be bound deterministically, so it falls back to RAG rather than guessing a skill. A
// quoted UUID is the only unambiguous skill key; absent one, RAG.
skillKeywords := []string{"what substitutes for", "substitutes for", "skill adjacency", "adjacent skill", "skill gap", "what is close to", "what bridges"}
if containsAny(q, skillKeywords) {
if id, ok := quotedUUID(question); ok {
return &graphRoute{intent: IntentSkillSubstitutionClosure, args: []any{id, 0}}
}
return nil // a skill question with no explicit skill id falls back to RAG
}
return nil // not a graph question
}
// containsAny reports whether the lowercased question contains any of the keywords.
func containsAny(loweredQuestion string, keywords []string) bool {
for _, kw := range keywords {
if strings.Contains(loweredQuestion, kw) {
return true
}
}
return false
}
// quotedAgency extracts a quoted agency name from the question, the only unambiguous agency shape. The match
// is from the ORIGINAL cased question (the agency name in the database is cased), so the bound param preserves
// the user's casing. A missing or too short quoted span is not an agency.
func quotedAgency(question string) (string, bool) {
m := quotedAgencyRe.FindStringSubmatch(question)
if len(m) < 2 {
return "", false
}
agency := strings.TrimSpace(m[1])
if agency == "" {
return "", false
}
return agency, true
}
// quotedRole extracts a quoted role span from the question, the only unambiguous role shape in free text, so
// the M5 wage route binds only an explicitly delimited role the caller means (for example "Program Manager").
// It reuses the quoted span matcher; a bare role phrase is NOT extracted (the role is ambiguous without
// quotes), so the classifier never guesses a role. The match is from the ORIGINAL cased question so
// resolveSOC sees the caller's casing. Absent a quoted role a wage question falls back to RAG.
func quotedRole(question string) (string, bool) {
m := quotedAgencyRe.FindStringSubmatch(question)
if len(m) < 2 {
return "", false
}
role := strings.TrimSpace(m[1])
if role == "" {
return "", false
}
return role, true
}
// quotedStateArea resolves a state named anywhere in the question to a known OEWS area code (HI or NM) through
// the same conservative matcher the G6 composition uses, so the M5 wage route binds only a state the wage
// baseline covers. A question naming no known state (or a state with no wage data) yields ok false and falls
// back to RAG rather than guessing a state.
func quotedStateArea(question string) (string, bool) {
return resolveArea(question)
}
// bareNAICS extracts a single bare six digit NAICS token from the question. Exactly one six digit token must
// be present for an unambiguous segment key; zero matches is no NAICS, and the first match is taken when one
// is present (a question rarely carries two distinct six digit codes, and the first is the segment of
// interest).
func bareNAICS(question string) (string, bool) {
m := naicsRe.FindStringSubmatch(question)
if len(m) < 2 {
return "", false
}
return m[1], true
}
// windowMonths extracts an explicit forward window in months when the question states one, else returns 0 so
// the recompete intent applies its own default clamp. A stated window is clamped by the intent, so an
// oversized number is bounded downstream.
func windowMonths(question string) int {
m := windowMonthsRe.FindStringSubmatch(strings.ToLower(question))
if len(m) < 2 {
return 0
}
n, err := strconv.Atoi(m[1])
if err != nil {
return 0
}
return n
}
// ueiRe matches a quoted twelve character SAM UEI (the alphanumeric unique entity identifier), the only
// unambiguous partner identifier a free form question can carry. The quotes delimit the partner the user
// means, so the classifier never guesses a UEI from a bare token; the twelve character alphanumeric shape is
// the SAM UEI format. A bare twelve character token in prose is NOT extracted (it is ambiguous), so the
// partner ratio route binds only an explicitly delimited UEI.
var ueiRe = regexp.MustCompile(`(?i)["']([0-9a-z]{12})["']`)
// quotedUEI extracts a quoted twelve character UEI from the question, the only unambiguous partner id shape.
// The match is from the ORIGINAL cased question so the bound param preserves the UEI's casing. Absent a quoted
// UEI a partner or competitor question has no bindable partner and falls back to RAG.
func quotedUEI(question string) (string, bool) {
m := ueiRe.FindStringSubmatch(question)
if len(m) < 2 {
return "", false
}
return m[1], true
}
// uuidRe matches a canonical UUID, the only unambiguous skill id a free form question can carry.
var uuidRe = regexp.MustCompile(`(?i)\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b`)
// quotedUUID extracts a canonical UUID from the question, the only unambiguous skill id shape. Absent one, a
// skill adjacency question has no bindable start skill and falls back to RAG.
func quotedUUID(question string) (string, bool) {
id := uuidRe.FindString(question)
if id == "" {
return "", false
}
return id, true
}
// heldClearanceCode extracts a held clearance code from a lowercased question by matching the natural
// language phrase for each of the six reference codes, the only unambiguous held key in free text. The
// longer phrases are checked first so ts sci wins over secret and top secret wins over secret. The matched
// code is one of the six ParamEnum allowed values, so ExecuteIntent's enum guard accepts it; absent a named
// held clearance the reachability question falls back to RAG rather than guessing a level.
func heldClearanceCode(loweredQuestion string) (string, bool) {
// Ordered longest phrase first so a more specific clearance wins over a shorter substring.
phrases := []struct {
phrase string
code string
}{
{"ts/sci", "ts_sci"},
{"ts sci", "ts_sci"},
{"ts_sci", "ts_sci"},
{"top secret sci", "ts_sci"},
{"sci", "ts_sci"},
{"top secret", "top_secret"},
{"top_secret", "top_secret"},
{"secret", "secret"},
{"confidential", "confidential"},
{"public trust", "none"},
{"unknown", "unknown"},
}
for _, p := range phrases {
if strings.Contains(loweredQuestion, p.phrase) {
return p.code, true
}
}
return "", false
}
// compartmentListRe matches a quoted comma separated compartment list, the only unambiguous compartment
// shape a free form question can carry. The quotes delimit the codes the user means, so the classifier never
// guesses a compartment from a bare token; the match is from the ORIGINAL cased question because compartment
// codes are upper cased in the reference table.
var compartmentListRe = regexp.MustCompile(`["']([A-Za-z0-9, ]{1,})["']`)
// quotedCompartmentList extracts a quoted comma separated compartment list from the question, the only
// unambiguous compartment shape. Absent a quoted list a compartment question has no bindable compartments and
// falls back to RAG.
func quotedCompartmentList(question string) (string, bool) {
m := compartmentListRe.FindStringSubmatch(question)
if len(m) < 2 {
return "", false
}
list := strings.TrimSpace(m[1])
if list == "" {
return "", false
}
return list, true
}