Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/federalsignal/tenant_tag.go |
| Original SHA-256 | ef485d9c5c8b5577bf2881deb9809975eaef32628d901d5fbaf81ba424da2f7f |
| Git blob SHA | 4f079f94436f888d3e0a0b439d06bdfe68c0f95c |
| Reading-copy SHA-256 | ef485d9c5c8b5577bf2881deb9809975eaef32628d901d5fbaf81ba424da2f7f |
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/tenant_tag.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
| Janady13/missionmatch / internal/modules/federalsignal/tenant_tag.go | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
Recovered source transcript
Download complete source text ↓175 lines · 7.9 KB
package federalsignal
// Phase 5a tenant mission tagging. This file is the TENANT scoped sibling of the global classification
// pass in service.go: it runs the same pure deterministic missionclass.Classify over a tenant's own
// candidates and requirements and upserts structured mission tags into candidate_mission_tags and
// requirement_mission_tags. Those tags are the structured signal the Phase 5 retrieval upgrade and the
// Phase 6 Command Center read, replacing the WS6 text only mission filter.
//
// Every tag WRITE runs inside RunInTenantTx as the application role, so Postgres Row Level Security is
// exercised on every insert: the org id comes from the validated GUC context, never a caller argument,
// and the composite FK guarantees the candidate or requirement belongs to that org. The classifier itself
// is pure (no DB, no clock, no network), so this service is unit testable with an injected fake repo and a
// fixed AliasIndex, with no database connection.
//
// Mission tags are reference data, NEVER a scoring input, and this path never reaches the eeo schema.
//
// SCOPE NOTE: write wiring into the candidate and requirement create paths (tag on create) is a documented
// follow up and is intentionally OUT of scope for Phase 5a. The tag-missions backfill command is the
// operator path for now: it is idempotent and safe to rerun, so an operator brings a tenant current by
// running it, and a future create path hook will keep it current incrementally.
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/missionworkforce/missionmatch/internal/platform/database/dbgen"
"github.com/missionworkforce/missionmatch/internal/platform/missionclass"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// TenantTagRepo is the narrow port the tenant tagging service depends on. The production implementation
// (NewTenantTagRepo) is dbgen backed and runs every method inside RunInTenantTx as the application role,
// so the reads and the writes are RLS scoped to the org in the context. The test injects an in memory fake
// so the whole service is exercised with no DB.
type TenantTagRepo interface {
// ListCandidatesForTagging returns the org's live candidates with the text the classifier reads, RLS
// scoped to the org in ctx. The rows are ordered by id in SQL so iteration is byte stable.
ListCandidatesForTagging(ctx context.Context) ([]dbgen.ListCandidatesForMissionTaggingRow, error)
// ListRequirementsForTagging returns the org's live requirements with the classifier text, RLS scoped.
ListRequirementsForTagging(ctx context.Context) ([]dbgen.ListRequirementsForMissionTaggingRow, error)
// UpsertCandidateMissionTags writes the given candidate mission tags idempotently, all inside ONE
// tenant transaction as the application role so RLS is exercised on every row. An empty slice is a no op.
UpsertCandidateMissionTags(ctx context.Context, tags []dbgen.UpsertCandidateMissionTagParams) error
// UpsertRequirementMissionTags writes the given requirement mission tags idempotently inside one tenant
// transaction as the application role. An empty slice is a no op.
UpsertRequirementMissionTags(ctx context.Context, tags []dbgen.UpsertRequirementMissionTagParams) error
}
// TenantTagService tags a tenant's candidates and requirements with structured mission areas. The repo and
// the prebuilt AliasIndex are injected so the service is pure logic over its inputs and needs no DB to test.
type TenantTagService struct {
repo TenantTagRepo
idx missionclass.AliasIndex
}
// NewTenantTagService builds the tenant tagging service from a repo and a prebuilt AliasIndex. The index is
// built ONCE by the caller from the global alias pairs (see BuildAliasIndex) and shared across orgs, so the
// expensive alias compile happens one time per backfill run, not once per org.
func NewTenantTagService(repo TenantTagRepo, idx missionclass.AliasIndex) *TenantTagService {
return &TenantTagService{repo: repo, idx: idx}
}
// TenantTagResult is the counted outcome of tagging one org. Every count is derived from rows actually
// classified and written, never fabricated.
type TenantTagResult struct {
CandidatesScanned int
CandidateTags int
RequirementsScanned int
RequirementTags int
}
// TagCandidatesForOrg classifies every live candidate in the org in ctx and upserts a candidate_mission_tag
// per Hit. The classify text is the headline followed by the candidate's skill names, so a tenant's SIGINT
// analyst gets a SIGINT tag from its headline and a COMSEC tag from a COMSEC skill. The org id stamped on
// each tag comes from the validated GUC context, never a caller argument. Idempotent and RLS scoped: the
// whole write runs inside RunInTenantTx as the application role.
func (s *TenantTagService) TagCandidatesForOrg(ctx context.Context) (TenantTagResult, error) {
var res TenantTagResult
orgID, err := orgUUIDFromContext(ctx)
if err != nil {
return res, err
}
rows, err := s.repo.ListCandidatesForTagging(ctx)
if err != nil {
return res, fmt.Errorf("federalsignal: list candidates for tagging: %w", err)
}
var tags []dbgen.UpsertCandidateMissionTagParams
for _, r := range rows {
res.CandidatesScanned++
text := joinText(r.Headline, r.SkillText)
for _, h := range missionclass.Classify(text, "", "", s.idx) {
tags = append(tags, dbgen.UpsertCandidateMissionTagParams{
OrganizationID: orgID,
CandidateID: r.CandidateID,
MissionCode: h.Code,
Mechanism: h.Mechanism,
Evidence: h.Evidence,
})
}
}
if err := s.repo.UpsertCandidateMissionTags(ctx, tags); err != nil {
return res, fmt.Errorf("federalsignal: upsert candidate mission tags: %w", err)
}
res.CandidateTags = len(tags)
return res, nil
}
// TagRequirementsForOrg classifies every live requirement in the org in ctx and upserts a
// requirement_mission_tag per Hit. The classify text is the title, the labor category, and the required
// skill names. Same isolation guarantees as the candidate path: the org id comes from the GUC context and
// the write runs inside RunInTenantTx as the application role. Idempotent.
func (s *TenantTagService) TagRequirementsForOrg(ctx context.Context) (TenantTagResult, error) {
var res TenantTagResult
orgID, err := orgUUIDFromContext(ctx)
if err != nil {
return res, err
}
rows, err := s.repo.ListRequirementsForTagging(ctx)
if err != nil {
return res, fmt.Errorf("federalsignal: list requirements for tagging: %w", err)
}
var tags []dbgen.UpsertRequirementMissionTagParams
for _, r := range rows {
res.RequirementsScanned++
text := joinText(r.Title, r.LaborCategory, r.SkillText)
for _, h := range missionclass.Classify(text, "", "", s.idx) {
tags = append(tags, dbgen.UpsertRequirementMissionTagParams{
OrganizationID: orgID,
RequirementID: r.RequirementID,
MissionCode: h.Code,
Mechanism: h.Mechanism,
Evidence: h.Evidence,
})
}
}
if err := s.repo.UpsertRequirementMissionTags(ctx, tags); err != nil {
return res, fmt.Errorf("federalsignal: upsert requirement mission tags: %w", err)
}
res.RequirementTags = len(tags)
return res, nil
}
// orgUUIDFromContext reads the validated org id from the tenant context and parses it. A missing or
// malformed org is a hard error: tagging refuses to run unscoped, mirroring RunInTenantTx fail closed.
func orgUUIDFromContext(ctx context.Context) (uuid.UUID, error) {
raw, ok := tenant.OrgFromContext(ctx)
if !ok {
return uuid.Nil, tenant.ErrNoTenant
}
id, err := uuid.Parse(raw)
if err != nil {
return uuid.Nil, fmt.Errorf("federalsignal: org id in context is not a uuid: %w", err)
}
return id, nil
}
// joinText joins non empty parts with single spaces into the one blob the classifier reads. Empty parts
// are dropped so a missing headline or labor category never injects stray whitespace that the word boundary
// matcher would have to absorb.
func joinText(parts ...string) string {
out := ""
for _, p := range parts {
if p == "" {
continue
}
if out != "" {
out += " "
}
out += p
}
return out
}