Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/matching/convert.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 09015496d565a172bcaa15cfe9a8e4b072b13df0 |
| Branch | workstream-b-screenmatch |
| Path or archive member | internal/modules/matching/convert.go |
| Original SHA-256 | 7dc6cafe1cd9e9d2290bf5bc244f0013282e0cdac5c559142a44f44465b9ccee |
| Git blob SHA | 8ecf9bd680da5fe0ee465f7e5be19eccaac2e2e5 |
| Reading-copy SHA-256 | 7dc6cafe1cd9e9d2290bf5bc244f0013282e0cdac5c559142a44f44465b9ccee |
Every recorded occurrence
These 21 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/matching/convert.go | workstream-b-screenmatch | 09015496d565a172bcaa15cfe9a8e4b072b13df0 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | workstream-d-missionmatch | 3be46ba89edb737182075a2b320509b42dc916f8 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | workstream-e-dataflow-deploy | 433815a042f9741411e60fa553fe72143e9b10db | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | w3-ai-staffing-recommendations | 43d56387270ef4da4cabd794d97ca6a49909bc7d | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | workstream-c-slatematch | 5cd60c918301b46ecd6ed3e03e20c395da0062e2 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | ai-intelligence-layer | 5d5ddff70393a197c5f54a45280bb5dd42b24755 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | workstream-c-slatematch | 5cd60c918301b46ecd6ed3e03e20c395da0062e2 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | w3-ai-staffing-recommendations | 43d56387270ef4da4cabd794d97ca6a49909bc7d | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | workstream-e-dataflow-deploy | 433815a042f9741411e60fa553fe72143e9b10db | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | ai-intelligence-layer | 5d5ddff70393a197c5f54a45280bb5dd42b24755 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | workstream-b-screenmatch | 09015496d565a172bcaa15cfe9a8e4b072b13df0 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / internal/modules/matching/convert.go | workstream-d-missionmatch | 3be46ba89edb737182075a2b320509b42dc916f8 | Open original |
Recovered source transcript
Download complete source text ↓229 lines · 9.4 KB
// Package matching is the SHARED SCORING CORE: the single source of truth for every Match and Mission
// Readiness number in the product. It loads candidates and requirements from the DB, runs the pure
// engine (internal/modules/matching/engine, the frozen weights and bands), and persists the
// explainable result. The engine stays pure; THIS package owns the DB<->engine boundary, including
// enum encode/decode (engine.Code/Parse) and the clearance expiry as of date rule.
//
// Today every scoring surface is served by the one flat handler in this package (handler.go). The
// three tool separation splits that handler into screenMATCH, slateMATCH, and missionMATCH routers,
// and each tool MUST keep calling these same service methods rather than re implement scoring:
// - screenMATCH: Screen, ScreenCandidate, RankCandidates, and the Mission Check flow (single
// candidate and pool qualification).
// - slateMATCH: GenerateSlate, DecideSlateMember, SlateMemberTrace (proposal slates).
// - missionMATCH: CandidateIntelligence here, plus the commandcenter rollups that read the persisted
// slates and evaluations rather than recompute them.
//
// Because there is exactly one scoring path, the same candidate shows the same numbers in every tool.
// The frozen weights and bands are guarded by engine.TestFrozenScoringConstants, the values are locked
// by engine.TestGoldenScoringSnapshot, and the cross tool equality is proven by the integration test
// TestSharedCoreScoresIdenticalAcrossTools. Do not add a second scoring path to any tool.
package matching
import (
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/missionworkforce/missionmatch/internal/modules/matching/engine"
"github.com/missionworkforce/missionmatch/internal/modules/matching/intelligence"
"github.com/missionworkforce/missionmatch/internal/platform/database/dbgen"
)
// ClearanceFactsFrom builds the intelligence ClearanceFacts (with the clearance dates the engine
// omits) from a DB candidate row plus its loaded customer agencies. Codes are CHECK constrained at
// the DB so parsing always succeeds for a stored row.
func ClearanceFactsFrom(c dbgen.Candidate, customerAgencies []string) intelligence.ClearanceFacts {
level, _ := engine.ParseClearanceLevel(c.ClearanceLevel)
poly, _ := engine.ParsePolygraph(c.ClearancePoly)
status, _ := engine.ParseClearanceStatus(c.ClearanceStatus)
return intelligence.ClearanceFacts{
Level: level,
Poly: poly,
Status: status,
GrantedAt: datePtr(c.ClearanceGrantedAt),
ExpiresAt: datePtr(c.ClearanceExpiresAt),
ReinvestigationDueAt: datePtr(c.ReinvestigationDueAt),
CustomerAgencies: customerAgencies,
Provenance: c.ClearanceProvenance,
VerifiedAt: c.ClearanceVerifiedAt,
}
}
func datePtr(d pgtype.Date) *time.Time {
if !d.Valid {
return nil
}
t := d.Time
return &t
}
// ToEngineCandidate maps a DB candidate (+ its loaded skills/certs/customer experience) into the
// pure engine input. asOf is the evaluation date: clearance_expires_at is the source of truth, so an
// Active flag with a past expiry is treated as Expired (review COMPL-1). It NEVER reads a demographic
// field, there is none on the candidate row by design (AEDT).
func ToEngineCandidate(c dbgen.Candidate, skills []dbgen.ListCandidateSkillsRow, certs, custExp []string, asOf time.Time) (engine.Candidate, error) {
level, err := engine.ParseClearanceLevel(c.ClearanceLevel)
if err != nil {
return engine.Candidate{}, fmt.Errorf("candidate %s: %w", c.ID, err)
}
poly, err := engine.ParsePolygraph(c.ClearancePoly)
if err != nil {
return engine.Candidate{}, fmt.Errorf("candidate %s: %w", c.ID, err)
}
status, err := engine.ParseClearanceStatus(c.ClearanceStatus)
if err != nil {
return engine.Candidate{}, fmt.Errorf("candidate %s: %w", c.ID, err)
}
status = effectiveStatus(status, c.ClearanceExpiresAt, asOf)
es := make([]engine.CandidateSkill, len(skills))
for i, s := range skills {
es[i] = engine.CandidateSkill{SkillID: s.ID.String(), Name: s.Name}
}
return engine.Candidate{
ID: c.ID.String(),
Clearance: engine.Clearance{Level: level, Poly: poly, Status: status},
Skills: es,
Certifications: certs,
CustomerExperience: custExp,
YearsExperience: int(c.YearsExperience),
Location: deref(c.Location),
Remote: c.Remote,
Relocatable: c.Relocatable,
Available: c.Available,
}, nil
}
// effectiveStatus downgrades an Active clearance to Expired when its expiry date has passed as of the
// evaluation date. The date column is authoritative over the stored flag. The comparison is on the
// CIVIL DATE (asOf truncated to midnight UTC of its Y/M/D) so a clearance expiring exactly on the
// as of day is still effective, matching the Command Center's `clearance_expires_at >= as_of::date`
// byte for byte regardless of the wall clock time carried by asOf [review ENGINE-1/EN-1].
func effectiveStatus(stored engine.ClearanceStatus, expires pgtype.Date, asOf time.Time) engine.ClearanceStatus {
asOfDate := time.Date(asOf.Year(), asOf.Month(), asOf.Day(), 0, 0, 0, 0, time.UTC)
if stored == engine.StatusActive && expires.Valid && expires.Time.Before(asOfDate) {
return engine.StatusExpired
}
return stored
}
// ToEngineRequirement maps a DB requirement (+ its required skills/certs) into the pure engine input.
func ToEngineRequirement(r dbgen.Requirement, reqSkills []dbgen.ListRequiredSkillsRow, reqCerts []string) (engine.Requirement, error) {
level, err := engine.ParseClearanceLevel(r.RequiredLevel)
if err != nil {
return engine.Requirement{}, fmt.Errorf("requirement %s: %w", r.ID, err)
}
poly, err := engine.ParsePolygraph(r.RequiredPoly)
if err != nil {
return engine.Requirement{}, fmt.Errorf("requirement %s: %w", r.ID, err)
}
rs := make([]engine.RequiredSkill, len(reqSkills))
for i, s := range reqSkills {
rs[i] = engine.RequiredSkill{SkillID: s.ID.String(), Name: s.Name, Mandatory: s.Mandatory}
}
return engine.Requirement{
ID: r.ID.String(),
RequiredLevel: level,
RequiredPoly: poly,
RequiredSkills: rs,
RequiredCerts: reqCerts,
CustomerAgency: deref(r.CustomerAgency),
RequiredYears: int(r.RequiredYears),
WorkLocation: deref(r.WorkLocation),
RemoteAllowed: r.RemoteAllowed,
KeyPersonnel: r.KeyPersonnel,
}, nil
}
// ToSlateMemberParams freezes an engine result into the AEDT decision record. Band is the DB Code()
// (never String()), and the full trace + gap summary are persisted as jsonb so the decision is
// reproducible and explainable.
func ToSlateMemberParams(orgID, slateID, candidateID uuid.UUID, res engine.MatchResult, rank *int32) (dbgen.AddSlateMemberParams, error) {
trace, err := json.Marshal(res.Trace)
if err != nil {
return dbgen.AddSlateMemberParams{}, fmt.Errorf("marshal decision trace: %w", err)
}
gaps := []byte("[]")
if len(res.GapSummary) > 0 {
if gaps, err = json.Marshal(res.GapSummary); err != nil {
return dbgen.AddSlateMemberParams{}, fmt.Errorf("marshal gap summary: %w", err)
}
}
return dbgen.AddSlateMemberParams{
OrganizationID: orgID,
SlateID: slateID,
CandidateID: candidateID,
TotalScore: res.Total,
Band: res.Band.Code(),
ClearanceScore: res.Clearance,
SkillsScore: res.Skills,
CertsScore: res.Certifications,
CustomerScore: res.CustomerExperience,
ExperienceScore: res.Experience,
LocationScore: res.Location,
Explanation: res.Explanation,
Trace: trace,
GapSummary: gaps,
Rank: rank,
ModelVersion: strPtr(engine.ModelVersion),
}, nil
}
func strPtr(s string) *string { return &s }
// ToCandidateEvaluationParams builds the upsert params for the persisted screenMATCH qualification of
// one candidate against one requirement. It is the same frozen MatchResult the slate stores, shaped
// for the candidate_evaluations table (the stored artifact missionMATCH reads). evaluatedBy is the
// actor or nil. Like the slate member, the band is the stored CODE (never String()) and the matched
// skills and gap summary are persisted as jsonb so the stored evaluation stays explainable.
func ToCandidateEvaluationParams(orgID, candidateID, requirementID uuid.UUID, res engine.MatchResult, evaluatedBy *uuid.UUID) (dbgen.UpsertCandidateEvaluationParams, error) {
matched := []byte("[]")
if len(res.MatchedSkills) > 0 {
m, err := json.Marshal(res.MatchedSkills)
if err != nil {
return dbgen.UpsertCandidateEvaluationParams{}, fmt.Errorf("marshal matched skills: %w", err)
}
matched = m
}
gaps := []byte("[]")
if len(res.GapSummary) > 0 {
g, err := json.Marshal(res.GapSummary)
if err != nil {
return dbgen.UpsertCandidateEvaluationParams{}, fmt.Errorf("marshal gap summary: %w", err)
}
gaps = g
}
return dbgen.UpsertCandidateEvaluationParams{
OrganizationID: orgID,
CandidateID: candidateID,
RequirementID: requirementID,
TotalScore: res.Total,
Band: res.Band.Code(),
ClearanceScore: res.Clearance,
SkillsScore: res.Skills,
CertsScore: res.Certifications,
CustomerScore: res.CustomerExperience,
ExperienceScore: res.Experience,
LocationScore: res.Location,
Explanation: res.Explanation,
MatchedSkills: matched,
GapSummary: gaps,
ModelVersion: engine.ModelVersion,
EvaluatedBy: evaluatedBy,
}, nil
}
func deref(s *string) string {
if s == nil {
return ""
}
return *s
}