package matching import ( "context" "encoding/json" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/missionworkforce/missionmatch/internal/platform/audit" "github.com/missionworkforce/missionmatch/internal/platform/database/dbgen" ) // SupportView is one candidate who can support a mission today, read from the persisted screenMATCH // qualification (the stored artifact), never a rescore. It carries the frozen total and band so the // number matches what screenMATCH and slateMATCH show for the same candidate. type SupportView struct { CandidateID string `json:"candidate_id"` FullName string `json:"full_name"` Total string `json:"total_score"` Band string `json:"band"` BandLabel string `json:"band_label"` Available bool `json:"available"` EvaluatedAt string `json:"evaluated_at"` } // WhoCanSupport returns the candidates with a persisted screenMATCH qualification for one requirement, // best first (the missionMATCH "who can support this mission today" rollup). It READS the stored // candidate_evaluations rows; it never rescores the pool, so the rollup agrees with the screen and the // slate to the cent. An empty result means no one has been screened against the requirement yet. func (s *Service) WhoCanSupport(ctx context.Context, requirementID uuid.UUID) ([]SupportView, error) { if s.db == nil { return nil, ErrNoDatabase } var out []SupportView err := s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { rows, e := dbgen.New(tx).ListEvaluationsForRequirement(ctx, requirementID) if e != nil { return e } out = make([]SupportView, len(rows)) for i, r := range rows { out[i] = SupportView{ CandidateID: r.CandidateID.String(), FullName: r.FullName, Total: r.TotalScore.StringFixed(2), Band: r.Band, BandLabel: bandLabel(r.Band), Available: r.Available, EvaluatedAt: r.EvaluatedAt.Format("2006-01-02"), } } // Cross tool data access: missionMATCH is reading screenMATCH's persisted evaluations. Record it // for traceability when there is data to read (the requirement and a count only, never candidate // PII or a score). Fail closed if the trace cannot be written. if len(out) > 0 { payload, _ := json.Marshal(map[string]any{ "reader": "missionMATCH", "source": "screenMATCH.evaluations", "requirement_id": requirementID.String(), "count": len(out), }) if e := s.auditOnTx(ctx, tx, audit.Event{ Action: "aedt.cross_tool.read", EntityType: "requirement", EntityID: requirementID.String(), NewValues: payload, }); e != nil { return e } } return nil }) return out, err }