Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/alerts/dto.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/alerts/dto.go |
| Original SHA-256 | 89c76d3549fd94f7c47557d04d37c958d20b90a2ab2564970792e18bc2da7b9b |
| Git blob SHA | bc4f8e76f3f7aa7fbd05e92acf785340b6dbe28f |
| Reading-copy SHA-256 | 89c76d3549fd94f7c47557d04d37c958d20b90a2ab2564970792e18bc2da7b9b |
Recovered source transcript
Download complete source text ↓144 lines · 7.2 KB
// Package alerts is the proactive alert engine, the watch loop. A best in world capture intelligence does
// not wait to be asked: it WATCHES the account and tells the analyst, unprompted, when something actionable
// happens. This module adds the MARKET EVENT arm to the existing scheduled bench watch
// (staffing.Service.RecomputeProactive) plus a typed, persisted, UI surfaceable alert.
//
// Four triggers fire per sweep, all RLS scoped to the caller's org:
//
// - clearance_expiring: a candidate the owner holds whose clearance lapses within the window, a staffing
// risk to flag before the reinvestigation lapses.
// - recompete_winnable: a recently posted federal opportunity in an agency the owner already shows interest
// in (a requirement for that agency, or a recorded bid) whose period of performance ends inside the
// forward recompete window. This is the market event arm: GLOBAL PUBLIC opportunity_records joined to the
// owner's own interest, RLS scoping only the interest read.
// - gap_fillable: an open requirement with no green evaluation that the bench NOW structurally covers (at
// least one candidate holds at least the required clearance level AND at least four fifths of the required
// skills, the SAME structural test as ai.BenchGapForRequirement, integer arithmetic, no float). This is a
// STRUCTURAL coverage test, never a match score: the frozen engine stays the only scorer.
// - collision: a candidate (most sharply a scarce cleared one) proposed or approved on the slates of two or
// more DISTINCT competing OPEN opportunities at once, the double commit of a scarce cleared person across
// competing bids. It is an ADVISORY finding with a deterministic best fit suggested primary a human
// resolves; it never auto rejects a slate member.
//
// Each alert UPSERTs ON CONFLICT (organization_id, dedupe_key), so a second sweep of the same event changes
// nothing new (idempotent). The sweep writes ONE counts only audit event (aedt.alert.swept) with the per type
// counts and NO PII: no candidate id, no agency value, just counts.
package alerts
// The four alert types, mirroring the alerts.alert_type CHECK constraint. Each names a distinct actionable
// event the watch loop detects. Collision is the cross opportunity deconfliction finding: a candidate proposed
// or approved on two or more distinct competing open opportunities at once.
const (
TypeRecompeteWinnable = "recompete_winnable"
TypeClearanceExpiring = "clearance_expiring"
TypeGapFillable = "gap_fillable"
TypeCollision = "collision"
// TypeRedeploymentDue is the firm's own contractor rolling off a live assignment with nothing next lined
// up. It is the only trigger about an event entirely inside the firm's control, and it went unwatched for
// the whole life of the alert engine while the data sat in placements.
TypeRedeploymentDue = "redeployment_due"
// The two opportunity kinds migration 0075 added. They are raised by the opportunities module rather than
// by this sweep, and they were MISSING from the vocabulary below until 0099: the map claimed to mirror the
// database CHECK and had not been updated when the CHECK was widened. Nothing broke, because the only
// consumer of the map is IsValidType and IsValidType has no caller anywhere in the tree, so the read path
// returns whatever the table holds. That is a latent trap rather than a live defect: the next person to
// wire the validator into a filter would silently drop every opportunity alert.
TypeOpportunityAmendment = "opportunity_amendment"
TypeOpportunityDeadline = "opportunity_deadline"
)
// The three severities, mirroring the alerts.severity CHECK constraint, ordered from quiet to loud.
const (
SeverityInfo = "info"
SeverityWatch = "watch"
SeverityUrgent = "urgent"
)
// The three statuses the UI moves an alert through, mirroring the alerts.status CHECK constraint.
const (
StatusUnread = "unread"
StatusRead = "read"
StatusDismissed = "dismissed"
)
// The three subject kinds an alert points at, the owner's own record. An empty subject is allowed (the
// column defaults to the empty string) but the three triggers always set one.
const (
SubjectOpportunity = "opportunity"
SubjectCandidate = "candidate"
SubjectRequirement = "requirement"
)
// validTypes, validSeverities, and validStatuses are the closed sets the DB CHECK constraints enforce. The
// service and handler validate against them before a write so a bad value is a clean rejection rather than a
// raw constraint violation.
var (
validTypes = map[string]bool{
TypeRecompeteWinnable: true,
TypeClearanceExpiring: true,
TypeGapFillable: true,
TypeCollision: true,
TypeRedeploymentDue: true,
TypeOpportunityAmendment: true,
TypeOpportunityDeadline: true,
}
validSeverities = map[string]bool{
SeverityInfo: true,
SeverityWatch: true,
SeverityUrgent: true,
}
validStatuses = map[string]bool{
StatusUnread: true,
StatusRead: true,
StatusDismissed: true,
}
)
// IsValidType reports whether t is one of the four allowed alert types.
func IsValidType(t string) bool { return validTypes[t] }
// IsValidSeverity reports whether s is one of the three allowed severities.
func IsValidSeverity(s string) bool { return validSeverities[s] }
// IsValidStatus reports whether s is one of the three allowed statuses.
func IsValidStatus(s string) bool { return validStatuses[s] }
// Alert is one persisted alert row returned to the UI. subject_id is omitted when the alert carries no
// subject; created_at and updated_at are RFC3339.
type Alert struct {
ID string `json:"id"`
AlertType string `json:"alert_type"`
Severity string `json:"severity"`
SubjectType string `json:"subject_type,omitempty"`
SubjectID string `json:"subject_id,omitempty"`
Title string `json:"title"`
Detail string `json:"detail,omitempty"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// SweepSummary is the per type count of alerts the sweep upserted, the only thing the sweep returns to the
// caller and the only thing the counts only audit event records. It carries NO PII: no candidate id, no
// agency value, no requirement title, just counts.
type SweepSummary struct {
ClearanceExpiring int `json:"clearance_expiring"`
RecompeteWinnable int `json:"recompete_winnable"`
GapFillable int `json:"gap_fillable"`
Collision int `json:"collision"`
RedeploymentDue int `json:"redeployment_due"`
}
// Total is the sum of the per type counts, the total number of alerts the sweep upserted.
func (s SweepSummary) Total() int {
return s.ClearanceExpiring + s.RecompeteWinnable + s.GapFillable + s.Collision + s.RedeploymentDue
}
// dedupeKey builds the stable per (org, the thing alerted) dedupe key the UPSERT keys on. The org scoping is
// the UNIQUE (organization_id, dedupe_key) constraint, so the key itself carries only the type prefix and the
// subject id; a re sweep of the same event yields the same key and the UPSERT refreshes the one row. Keeping
// this pure and named lets the unit test pin the construction.
func dedupeKey(alertType, subjectID string) string {
return alertType + ":" + subjectID
}