package ai import ( "context" "errors" "github.com/jackc/pgx/v5" "github.com/missionworkforce/missionmatch/internal/platform/tenant" ) // This file holds the augmented generation layers whose data structures are partly or fully scaffolded. // What is REAL today: KAG skill adjacency (the now live skills.embedding HNSW) and CAG public rules. // What is SCAFFOLDED (interface present, fuller build sequenced for more data or more clients): the // GraphRAG teaming graph, the capture and foresight models, and the federated learning loop, which is // designed in but DISABLED. // ---------- KAG: clearance and skills knowledge (skill adjacency is REAL) ---------- // AdjacentSkill is one semantically adjacent skill and its cosine distance. type AdjacentSkill struct { Name string `json:"name"` Distance float64 `json:"distance"` } // SkillAdjacency returns the k skills nearest to a named skill by embedding cosine, the curated skill // graph the audit found missing. It is LIVE: it uses the skills.embedding HNSW index populated by // cmd/embed-backfill. This is the substitution reasoner the engine's semantic skill match leans on (a // candidate who lacks the exact skill but holds an adjacent one). It reads only the global skills table, // never eeo. func SkillAdjacency(ctx context.Context, db *tenant.DB, skillName string, k int) ([]AdjacentSkill, error) { if db == nil { return nil, errors.New("ai: no database") } if err := ForbidEEO(skillName); err != nil { return nil, err } if k <= 0 || k > 25 { k = 8 } var out []AdjacentSkill err := db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { rows, err := tx.Query(ctx, ` SELECT s.name, (s.embedding <=> seed.embedding)::float8 AS dist FROM skills s, (SELECT embedding FROM skills WHERE name = $1 AND embedding IS NOT NULL LIMIT 1) seed WHERE s.embedding IS NOT NULL AND s.name <> $1 ORDER BY s.embedding <=> seed.embedding, s.name LIMIT $2`, skillName, k) if err != nil { return err } defer rows.Close() for rows.Next() { var a AdjacentSkill if err := rows.Scan(&a.Name, &a.Distance); err != nil { return err } out = append(out, a) } return rows.Err() }) return out, err } // ---------- CAG: stable public rules preloaded into model context (REAL) ---------- // PublicRules returns the stable, public, contents free rules and definitions the platform always knows: // the fixed scoring weights and bands, the clearance hierarchy and reciprocity, and the standing // disclaimers. Cache augmented generation preloads this once into model context (it fits easily), so the // model never has to retrieve it and never invents it. It contains NO client data and NO demographic // field, so it is safe to hold in any tenant's context. func PublicRules() string { return `MODEL (deterministic engine, v1.0.0; the floor, never overridden by the AI layer): Match Score = Clearance*0.25 + Skills*0.25 + Certifications*0.15 + CustomerExperience*0.15 + Experience*0.10 + LocationAvailability*0.10. Bands: Green (Proposal Ready) >= 90; Yellow (Potential) 75 to 89.99; Red (Recruiting Required) < 75. Clearance: exact or higher and active scores 100, else 0. Hierarchy: None < Confidential < Secret < Top Secret < TS/SCI. TS/SCI satisfies Secret; Secret does NOT satisfy TS/SCI. Polygraph: None < CI < Full Scope; Full Scope satisfies CI. Clearance is a self reported intelligence indicator, NEVER verified by the platform; the real check is the hiring client FSO through DISS at onboarding. Demographic and EEO data is NEVER an input to scoring or to any AI answer.` } // ---------- GraphRAG: teaming and incumbent graph (SCAFFOLD) ---------- // TeamingGraph is the relationship graph over awards and opportunity_records (incumbents, agencies, // NAICS, set asides). SCAFFOLD: the node and edge model and the community summarization pass are // designed but not built; the awards and opportunity_records tables hold the raw relationships today. // Building it is sequenced after the entity extraction pass. It will attach per tenant with its own // embeddings and will read only public market data, never eeo. type TeamingGraph struct { db *tenant.DB } // NewTeamingGraph returns the scaffold. BuildErr documents that the full graph is not yet constructed. func NewTeamingGraph(db *tenant.DB) *TeamingGraph { return &TeamingGraph{db: db} } // ErrScaffold marks a capability whose interface is present but whose full implementation is sequenced. var ErrScaffold = errors.New("ai: scaffolded capability, fuller build sequenced (see BUILD_REPORT.md)") // ---------- Capture and foresight (SCAFFOLD, needs more data) ---------- // Foresight groups the capture and foresight estimators: win probability, bid or no bid, price to win // from BLS and USASpending, clearance timing survival analysis, and demand forecasting. SCAFFOLD: these // need a labeled history (won and lost bids, time to clearance) the platform does not have enough of yet; // the interfaces are fixed so they can be filled as the data accrues. Marked needs more data. type Foresight struct{} // PriceToWinInputs are the public market signals a price to win estimate would draw on (BLS OEWS wages, // USASpending award amounts). The estimator is a scaffold. type PriceToWinInputs struct { NAICS string LaborCatSOC string AreaCode string } // EstimatePriceToWin is a SCAFFOLD: it returns ErrScaffold until a labeled bid history exists. func (Foresight) EstimatePriceToWin(_ context.Context, _ PriceToWinInputs) (float64, error) { return 0, ErrScaffold } // ---------- Federated learning with differential privacy (DESIGNED, DISABLED) ---------- // FederatedLearningEnabled is the master switch for cross tenant model improvement. It is DISABLED and // must stay disabled until multiple live clients exist. While disabled, no craft signal is transmitted // off a tenant (the scrub boundary already withholds transmission when teaching is off), and no cross // tenant aggregation runs. const FederatedLearningEnabled = false // FederatedConfig is the design surface for the disabled loop: the differential privacy budget and the // minimum number of distinct tenants required before any aggregate is computed (k anonymity over // tenants). These are recorded so the privacy accounting is fixed before the loop is ever activated. type FederatedConfig struct { Enabled bool // mirrors FederatedLearningEnabled; activation is a deliberate, reviewed change EpsilonPerWeek float64 // differential privacy budget per aggregation window Delta float64 // differential privacy delta MinTenants int // do not aggregate below this many distinct contributing tenants } // DefaultFederatedConfig is the disabled, conservative default. Activation requires multiple live // clients, a signed off privacy budget, and only contents scrubbed craft signals through the scrub // boundary, never raw data. func DefaultFederatedConfig() FederatedConfig { return FederatedConfig{Enabled: FederatedLearningEnabled, EpsilonPerWeek: 1.0, Delta: 1e-6, MinTenants: 5} }