Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/federalsignal/service.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/federalsignal/service.go |
| Original SHA-256 | b38a4495cda2a1b76a7dcc6f8d149ee14e616d30227241a915b2a9491d89399f |
| Git blob SHA | 30b10545d4d52b61e4648cfadbf5ff5da67be230 |
| Reading-copy SHA-256 | b38a4495cda2a1b76a7dcc6f8d149ee14e616d30227241a915b2a9491d89399f |
Every recorded occurrence
These 15 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/federalsignal/service.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/service.go | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
Recovered source transcript
Download complete source text ↓264 lines · 11.5 KB
// Package federalsignal mines public federal records for the deterministic mission and labor classifier.
// This file is the classification sync pass: it reads the global opportunity_records, usaspending_awards,
// and sam_opportunities catalogs, runs the pure missionclass classifier over the record text plus, for
// SAM notices, the fetched attachment text, and upserts mission tags and incumbent labor roles
// idempotently. The pass is pure of any scoring path and never reaches the eeo schema: mission tags are
// reference data, never a match input. It writes ONE append only audit event per run carrying counts
// only, never any PII or record text.
package federalsignal
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/shopspring/decimal"
"github.com/missionworkforce/missionmatch/internal/platform/audit"
"github.com/missionworkforce/missionmatch/internal/platform/database/dbgen"
"github.com/missionworkforce/missionmatch/internal/platform/missionclass"
)
// Record table identifiers, pinned to the CHECK constraint in migration 0026 so a typo cannot silently
// write a row the database would reject. sam_opportunities is mission tagged through its
// opportunity_records projection, so the SAM path writes only incumbent roles, never a federal_mission_tag.
const (
tableOpportunityRecords = "opportunity_records"
tableUsaspendingAwards = "usaspending_awards"
tableSamOpportunities = "sam_opportunities"
)
// Repo is the narrow data port the service depends on. The real implementation (NewRepo) is dbgen backed
// and reads the global catalogs as the migrator role; the test injects an in memory fake so the service
// is exercised with no DB. Every read is ordered by a unique key in the SQL so iteration is byte stable.
type Repo interface {
// AliasPairs returns the (phrase, code) rows that seed the missionclass AliasIndex from real alias and
// mission_area skill rows. An empty result is fine: the keyword classifier still tags records.
AliasPairs(ctx context.Context) (map[string]string, error)
// ListOpportunityRecordsForClassify returns the opportunity_records projection the classifier reads.
ListOpportunityRecordsForClassify(ctx context.Context) ([]dbgen.ListOpportunityRecordsForClassifyRow, error)
// ListAwardsForClassify returns the usaspending_awards projection the classifier reads.
ListAwardsForClassify(ctx context.Context) ([]dbgen.ListAwardsForClassifyRow, error)
// ListSamForClassify returns the sam_opportunities projection the attachment fetcher mines.
ListSamForClassify(ctx context.Context) ([]dbgen.ListSamForClassifyRow, error)
// UpsertFederalMissionTag writes one global mission tag idempotently.
UpsertFederalMissionTag(ctx context.Context, p dbgen.UpsertFederalMissionTagParams) error
// UpsertAwardIncumbentRole writes one incumbent labor role idempotently.
UpsertAwardIncumbentRole(ctx context.Context, p dbgen.UpsertAwardIncumbentRoleParams) error
}
// SolicitationFetcher fetches and extracts the plain text of a SAM notice's attachments. The real
// *federalsignal.Fetcher satisfies it; the test injects a fake that returns fixed text with no network.
type SolicitationFetcher interface {
FetchSolicitationText(ctx context.Context, descURL string, links []string) (string, error)
}
// Auditor records the one platform event for a run. The concrete audit.Writer satisfies it through
// System; the test uses a fake. Mirrors the oppintel ingest audit shape (a global, NULL org event).
type Auditor interface {
System(ctx context.Context, e audit.Event) error
}
// Service runs the classification pass. All dependencies are injected so the whole pass is unit testable
// with no DB and no network.
type Service struct {
repo Repo
fetcher SolicitationFetcher
auditor Auditor
// maxAttachments bounds how many SAM notices the run will fetch attachments for, so one run stays
// within the SAM API daily quota. Notices past the cap are counted as skipped, never silently dropped.
maxAttachments int
}
// NewService builds the pass. maxAttachments caps the SAM attachment fetches per run; a value below zero
// is treated as zero (fetch nothing), a value of zero disables SAM attachment mining for the run.
func NewService(repo Repo, fetcher SolicitationFetcher, auditor Auditor, maxAttachments int) *Service {
if maxAttachments < 0 {
maxAttachments = 0
}
return &Service{repo: repo, fetcher: fetcher, auditor: auditor, maxAttachments: maxAttachments}
}
// Result is the counted outcome of one run. Every count is derived from rows actually written, never a
// fabricated number. It is the audit body, so it carries no PII and no record text.
type Result struct {
RecordsScanned int `json:"records_scanned"`
MissionTagsWritten int `json:"mission_tags_written"`
IncumbentRoles int `json:"incumbent_roles_written"`
SamFetched int `json:"sam_attachments_fetched"`
SamSkippedCap int `json:"sam_attachments_skipped_cap"`
}
// confidenceFull is the fixed confidence the deterministic classifier records. The classifier is exact
// (keyword, curated alias, curated code), so every tag it writes is full confidence, decimal not float so
// the value is bit stable for the AEDT asset.
var confidenceFull = decimal.NewFromInt(1)
// ClassifyAll runs the whole pass: it builds the AliasIndex once from real rows, classifies the global
// records, and upserts mission tags and incumbent roles idempotently, then writes one audit event with
// counts only. A second run is a no op churn: every upsert is idempotent so no duplicate rows appear.
func (s *Service) ClassifyAll(ctx context.Context) (Result, error) {
var res Result
pairs, err := s.repo.AliasPairs(ctx)
if err != nil {
return res, fmt.Errorf("federalsignal: load alias pairs: %w", err)
}
idx := missionclass.NewAliasIndex(pairs)
if err := s.classifyOpportunityRecords(ctx, idx, &res); err != nil {
return res, err
}
if err := s.classifyAwards(ctx, idx, &res); err != nil {
return res, err
}
if err := s.classifySam(ctx, &res); err != nil {
return res, err
}
if err := s.audit(ctx, res); err != nil {
return res, err
}
return res, nil
}
// classifyOpportunityRecords tags each opportunity_records row from its title, psc, and naics. The record
// id (as text) is the record_id the tag table keys on.
func (s *Service) classifyOpportunityRecords(ctx context.Context, idx missionclass.AliasIndex, res *Result) error {
rows, err := s.repo.ListOpportunityRecordsForClassify(ctx)
if err != nil {
return fmt.Errorf("federalsignal: list opportunity records: %w", err)
}
for _, r := range rows {
res.RecordsScanned++
for _, h := range missionclass.Classify(r.Title, r.Psc, r.Naics, idx) {
if err := s.repo.UpsertFederalMissionTag(ctx, dbgen.UpsertFederalMissionTagParams{
RecordTable: tableOpportunityRecords,
RecordID: r.RecordID,
MissionCode: h.Code,
Mechanism: h.Mechanism,
Evidence: h.Evidence,
Confidence: confidenceFull,
}); err != nil {
return fmt.Errorf("federalsignal: upsert opportunity mission tag: %w", err)
}
res.MissionTagsWritten++
}
}
return nil
}
// classifyAwards tags each usaspending_awards row from its description plus naics_description, with the
// naics_code feeding the crosswalk, and mines the incumbent labor mix from the description. The
// generated_internal_id is the record_id both tables key on.
func (s *Service) classifyAwards(ctx context.Context, idx missionclass.AliasIndex, res *Result) error {
rows, err := s.repo.ListAwardsForClassify(ctx)
if err != nil {
return fmt.Errorf("federalsignal: list awards: %w", err)
}
for _, r := range rows {
res.RecordsScanned++
missionText := r.Description + " " + r.NaicsDescription
for _, h := range missionclass.Classify(missionText, "", r.NaicsCode, idx) {
if err := s.repo.UpsertFederalMissionTag(ctx, dbgen.UpsertFederalMissionTagParams{
RecordTable: tableUsaspendingAwards,
RecordID: r.RecordID,
MissionCode: h.Code,
Mechanism: h.Mechanism,
Evidence: h.Evidence,
Confidence: confidenceFull,
}); err != nil {
return fmt.Errorf("federalsignal: upsert award mission tag: %w", err)
}
res.MissionTagsWritten++
}
if err := s.writeIncumbentRoles(ctx, tableUsaspendingAwards, r.RecordID, r.Description, res); err != nil {
return err
}
}
return nil
}
// classifySam mines the incumbent labor mix from each SAM notice's attachment text. It does NOT write a
// federal_mission_tag for a SAM notice: the 0026 CHECK forbids it, and a SAM notice is mission tagged
// through its opportunity_records projection. Attachment fetches are bounded by maxAttachments so a run
// stays within the SAM API quota; notices past the cap are counted as skipped, never silently dropped.
func (s *Service) classifySam(ctx context.Context, res *Result) error {
rows, err := s.repo.ListSamForClassify(ctx)
if err != nil {
return fmt.Errorf("federalsignal: list sam opportunities: %w", err)
}
for _, r := range rows {
res.RecordsScanned++
if res.SamFetched >= s.maxAttachments {
res.SamSkippedCap++
continue
}
links := decodeResourceLinks(r.ResourceLinks)
text, ferr := s.fetcher.FetchSolicitationText(ctx, r.DescriptionUrl, links)
if ferr != nil {
// A cancelled context (or any fetch error) for one notice is best effort: count nothing for it
// and continue. The fetcher itself is best effort per attachment, so an error here is a whole
// notice failure (for example a cancelled run), never the URL or the api_key, which the fetcher
// keeps out of its error strings.
log.Printf("federalsignal: sam notice attachment fetch failed, skipping notice")
continue
}
res.SamFetched++
if err := s.writeIncumbentRoles(ctx, tableSamOpportunities, r.RecordID, text, res); err != nil {
return err
}
}
return nil
}
// writeIncumbentRoles classifies the labor mix from text and upserts one award_incumbent_roles row per
// labor category, with the deterministic count and the first matched phrase as evidence. Empty text or no
// match writes nothing.
func (s *Service) writeIncumbentRoles(ctx context.Context, table, recordID, text string, res *Result) error {
for _, lh := range missionclass.ClassifyLaborCounts(text) {
if err := s.repo.UpsertAwardIncumbentRole(ctx, dbgen.UpsertAwardIncumbentRoleParams{
RecordTable: table,
RecordID: recordID,
LaborCategory: lh.LCAT,
RoleCount: int32(lh.Count),
Evidence: lh.Evidence,
}); err != nil {
return fmt.Errorf("federalsignal: upsert incumbent role: %w", err)
}
res.IncumbentRoles++
}
return nil
}
// audit writes the single append only event for the run, counts only, no PII and no record text. It
// mirrors the oppintel ingest audit shape (a global System event with a NULL org). It is fail closed: a
// run the trail cannot record returns an error, and because every upsert is idempotent a rerun is safe.
func (s *Service) audit(ctx context.Context, res Result) error {
body, err := json.Marshal(res)
if err != nil {
return fmt.Errorf("federalsignal: marshal classify audit: %w", err)
}
return s.auditor.System(ctx, audit.Event{
Action: "aedt.federalsignal.classified",
EntityType: "federalsignal_sync",
EntityID: "classify_all",
NewValues: body,
})
}
// decodeResourceLinks parses the sam_opportunities.resource_links JSON array of strings into a slice. The
// column is a JSON array (never nil at write, "[]" for empty). A malformed value yields no links rather
// than a fatal error, so one bad row never aborts the pass.
func decodeResourceLinks(raw []byte) []string {
if len(raw) == 0 {
return nil
}
var links []string
if err := json.Unmarshal(raw, &links); err != nil {
return nil
}
return links
}