//go:build integration package integration import ( "strings" "testing" "github.com/google/uuid" "github.com/missionworkforce/missionmatch/internal/modules/ai" "github.com/missionworkforce/missionmatch/internal/platform/audit" ) // TestM5NationalBandServesARequirementInAnyState is the live proof of the M5 Phase 1 national expansion: a // requirement in Texas (a state with NO state level bls_wages data) whose title resolves to a national SOC // (through the broadened national OEWS occupation set) gets the US NATIONAL wage band, labeled United // States, instead of the honest floor. This is the whole point of the expansion: any requirement whose // title resolves to a SOC gets at least the national band even outside Hawaii and New Mexico. func TestM5NationalBandServesARequirementInAnyState(t *testing.T) { org := seedOrg(t) k := ai.ConfiguredSuppressionThreshold() run := uuid.NewString()[:8] // A run unique occupation title and SOC so the broadened national read (which sees every test's global // area='US' rows) binds THIS occupation unambiguously as the longest match, immune to what parallel tests // seed. The requirement title below contains this exact occupation title, so resolveSOCBroadened picks it. occTitle := "Mechanical Engineers " + run soc := "9" + run[:5] // a run unique six character code distinct from any curated Galapagos SOC // Seed the two national rows (area='US', role=''), both metrics, plus an UNRELATED Hawaii state row so the // store holds both scopes. The Texas requirement below has no Texas state row, so it must fall back to the // national US band. seedWageBand(t, "US-ME-MEAN-"+run, soc, occTitle, "", "US", "United States", "annual_mean", "102000.00") seedWageBand(t, "US-ME-MEDIAN-"+run, soc, occTitle, "", "US", "United States", "annual_median", "97000.00") seedWageBand(t, "HI-UNREL-MEAN-"+run, "113021", "Computer and Information Systems Managers", "Program Manager", "HI", "Hawaii", "annual_mean", "185000.00") // A requirement in San Antonio, Texas whose title CONTAINS the OEWS occupation title so the broadened // resolver binds the national SOC. Texas is neither HI nor NM, so the area does not resolve to a state and // the leg falls back to the national US band. req := seedRequirementForWage(t, org, "Senior "+occTitle+", Weapons Systems", "San Antonio, Texas", "secret") for i := 0; i < k; i++ { seedCandidate(t, org, "secret", "active", true) } orch := ai.NewOrchestrator(appDB, nil, audit.New(appDB)) resp, err := orch.Answer(ctxOrg(org), "", ai.IntentCapturePictureForRequirement, []any{req.String()}) if err != nil { t.Fatalf("capture picture answer: %v", err) } if resp.Refused || resp.Route != ai.RouteGraph { t.Fatalf("a bound tenant capture question must answer on the graph route, got %+v", resp) } if strings.Contains(resp.Answer, "labor rate data not yet ingested") { t.Errorf("a requirement in Texas with a resolvable national SOC must show the national band, not the floor, got %q", resp.Answer) } for _, want := range []string{"OEWS labor rate", occTitle, "United States", "102000.00", "97000.00", "BLS OEWS", "advisory"} { if !strings.Contains(resp.Answer, want) { t.Errorf("the national price to win leg must carry %q, got %q", want, resp.Answer) } } // The band must NOT name a state that has no data for this requirement. if strings.Contains(resp.Answer, "Hawaii") { t.Errorf("the Texas requirement must not borrow the unrelated Hawaii label, got %q", resp.Answer) } assertCaptureAudit(t, org, true, []string{"102000", "97000"}) } // TestM5StatePreferredOverNational proves the STATE band is preferred over the national band when the // requirement names a state that HAS state level data for the SOC. A Hawaii requirement matching a Galapagos // role, with both a Hawaii state row AND a national row present for the same SOC, must show the HAWAII state // band (185000 from HI), not the national band (999999 from US), because the state is preferred over // national. This proves the fallback only fires when state data is absent. func TestM5StatePreferredOverNational(t *testing.T) { org := seedOrg(t) k := ai.ConfiguredSuppressionThreshold() run := uuid.NewString()[:8] soc := "113021" // Program Manager (Computer and Information Systems Managers) // The Hawaii STATE band (the preferred scope), both metrics. seedWageBand(t, "STATE-HI-MEAN-"+run, soc, "Computer and Information Systems Managers", "Program Manager", "HI", "Hawaii", "annual_mean", "185000.00") seedWageBand(t, "STATE-HI-MEDIAN-"+run, soc, "Computer and Information Systems Managers", "Program Manager", "HI", "Hawaii", "annual_median", "172500.00") // A national US band for the SAME SOC with distinctly different dollars, so the test can tell which scope // the leg chose. State must win, so these national numbers must NOT surface. seedWageBand(t, "NAT-PM-MEAN-"+run, soc, "Computer and Information Systems Managers", "", "US", "United States", "annual_mean", "999111.00") seedWageBand(t, "NAT-PM-MEDIAN-"+run, soc, "Computer and Information Systems Managers", "", "US", "United States", "annual_median", "999222.00") req := seedRequirementForWage(t, org, "Senior Program Manager, Space Systems", "Honolulu, Hawaii", "secret") for i := 0; i < k; i++ { seedCandidate(t, org, "secret", "active", true) } orch := ai.NewOrchestrator(appDB, nil, audit.New(appDB)) resp, err := orch.Answer(ctxOrg(org), "", ai.IntentCapturePictureForRequirement, []any{req.String()}) if err != nil { t.Fatalf("capture picture answer: %v", err) } if resp.Refused || resp.Route != ai.RouteGraph { t.Fatalf("a bound tenant capture question must answer on the graph route, got %+v", resp) } // The HAWAII state band surfaces (state preferred over national). for _, want := range []string{"OEWS labor rate", "Program Manager", "Hawaii", "185000.00", "172500.00"} { if !strings.Contains(resp.Answer, want) { t.Errorf("the Hawaii requirement must show the HI state band %q, got %q", want, resp.Answer) } } // The national dollars must NOT surface: state was preferred. for _, forbidden := range []string{"999111", "999222", "United States"} { if strings.Contains(resp.Answer, forbidden) { t.Errorf("state must be preferred over national; the national scope must not surface %q, got %q", forbidden, resp.Answer) } } assertCaptureAudit(t, org, true, []string{"185000", "172500", "999111", "999222"}) }