Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/opportunities/draftgen.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/opportunities/draftgen.go |
| Original SHA-256 | 15893cf3f185562375a5237ef0066fcd2602a43d6a5f1c28095b73dd97584d53 |
| Git blob SHA | a980124b1de685b1d244078f24f39054f76020df |
| Reading-copy SHA-256 | 15893cf3f185562375a5237ef0066fcd2602a43d6a5f1c28095b73dd97584d53 |
Recovered source transcript
Download complete source text ↓338 lines · 13.0 KB
package opportunities
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/missionworkforce/missionmatch/internal/platform/audit"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// DRAFT GENERATION, directive Section 6 screen 5 and Phase 2 item 5: generate drafts from accepted staffing
// scope, show missing required fields, allow manual completion, require named approval, promote, preserve
// lineage. Promotion and approval existed. Generation did not, so nothing ever produced a draft from reviewed
// scope, and the review to requirement path had a hole in the middle of it.
//
// IT LEAVES GAPS AS GAPS. A field the scope text does not state is left empty and REPORTED as missing rather
// than filled with a plausible default. A guessed clearance level is a fabricated requirement, and putting one
// in front of a reviewer with the box already ticked is worse than an empty box, because a human gate only
// works if the human can see what is not known.
//
// IT IS IDEMPOTENT. Generation runs whenever a reviewer finishes accepting scope, and it must not produce a
// second draft for a scope item that already has one. Lineage through source_scope_item_id is what makes that
// checkable, and it is the same column that lets a promoted requirement be traced back to its cited span.
// staffingScopeTypes are the scope types that describe PEOPLE. Generation runs over these only. A pricing
// instruction or an evaluation factor is real scope and belongs in the review queue, but it does not staff a
// role, and turning one into a requirement draft would put a non staffing row on the path to the scoring engine.
var staffingScopeTypes = map[string]bool{
"labor_category": true,
"staffing_quantity": true,
"key_personnel": true,
"skill": true,
"certification": true,
"clearance": true,
"customer_experience": true,
}
// DraftDerivation is what deterministic reading of one accepted scope item yields. It is a value, not a row:
// pure in, pure out, so the same accepted scope always produces the same draft.
type DraftDerivation struct {
SourceScopeItemID string
LaborCategory string
Quantity int
ClearanceLevel string
Polygraph string
MinimumYears int
TravelPercent int
WorkLocation string
RemoteAllowed bool
Certifications []string
DerivationMethod string
}
// DeriveDraft reads one accepted scope item into a draft shape using the same signal extractor the rest of
// Section 5.1 uses, so nothing here is a second opinion about what a document says.
func DeriveDraft(item ScopeItem) DraftDerivation {
text := strings.TrimSpace(item.Title + ". " + item.Description)
sig := ExtractSignals(text)
d := DraftDerivation{
SourceScopeItemID: item.ID,
LaborCategory: strings.TrimSpace(item.Title),
ClearanceLevel: "unknown",
Polygraph: "none",
DerivationMethod: "deterministic_scope",
// A NAMED LABOR CATEGORY IS AT LEAST ONE PERSON. That floor is implied by the document naming the role
// at all, which is different from inventing a headcount the document never states.
Quantity: 1,
}
if sig.ClearanceLevel != "" {
d.ClearanceLevel = sig.ClearanceLevel
}
if sig.Polygraph != "" {
d.Polygraph = sig.Polygraph
}
if sig.Quantity > 0 {
d.Quantity = sig.Quantity
}
d.MinimumYears = sig.MinimumYears
d.TravelPercent = sig.TravelPercent
d.RemoteAllowed = sig.RemoteAllowed
// CERTIFICATIONS IS ALWAYS NON NIL, and that is a database requirement rather than a style choice.
// requirement_drafts.required_certifications is text[] NOT NULL DEFAULT '{}', and a nil Go slice is sent
// as NULL, not as an empty array, so the insert failed with a not null violation on every document that
// named no certifications. No pure test could see that: the struct field looked correct either way.
d.Certifications = []string{}
if len(sig.Certifications) > 0 {
d.Certifications = append(d.Certifications, sig.Certifications...)
}
return d
}
// MissingRequiredDraftFields names every field a draft must carry before a human can responsibly promote it.
// This is what screen 5 renders, and it is deliberately a LIST OF NAMES rather than a boolean, because
// "incomplete" tells a reviewer nothing about what to do next.
func MissingRequiredDraftFields(d DraftDerivation) []string {
var missing []string
if strings.TrimSpace(d.LaborCategory) == "" {
missing = append(missing, "labor_category")
}
// unknown is the column default and therefore what an ungrounded draft carries. It is a GAP, not a
// decision: promoting it writes a requirement that no candidate can fail to satisfy on clearance, which
// silently removes the single heaviest component of the score.
if d.ClearanceLevel == "" || d.ClearanceLevel == "unknown" {
missing = append(missing, "clearance_level")
}
if d.Quantity < 1 {
missing = append(missing, "quantity")
}
return missing
}
// GenerateDrafts derives one requirement draft per accepted staffing scope item that does not already have
// one, in a single transaction, and returns how many it created and how many it skipped as already present.
func (s *Service) GenerateDrafts(ctx context.Context, opportunityID string) (created, skipped int, err error) {
if !s.db.Ready() {
return 0, 0, ErrNoDatabase
}
oid, pErr := uuid.Parse(strings.TrimSpace(opportunityID))
if pErr != nil {
return 0, 0, fmt.Errorf("%w: opportunity id must be a uuid", ErrInvalidInput)
}
actor := actorPtr(ctx)
if actor == nil {
return 0, 0, tenant.ErrNoTenant
}
types := make([]string, 0, len(staffingScopeTypes))
for t := range staffingScopeTypes {
types = append(types, t)
}
err = s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
// ONLY ACCEPTED OR EDITED SCOPE. 'extracted' has not been reviewed, and generating from it would let
// the machine's own output walk one step closer to the engine with no human involved. 'rejected',
// 'split' and 'merged' are retired.
rows, qErr := tx.Query(ctx, `
SELECT `+scopeCols+`
FROM opportunity_scope_items
WHERE opportunity_id = $1
AND review_status IN ('accepted','edited','manual')
AND scope_type = ANY($2)
ORDER BY display_order, created_at
FOR UPDATE`, oid, types)
if qErr != nil {
return qErr
}
items := make([]ScopeItem, 0)
for rows.Next() {
it, sErr := scanScope(rows)
if sErr != nil {
rows.Close()
return sErr
}
items = append(items, it)
}
rows.Close()
if rErr := rows.Err(); rErr != nil {
return rErr
}
for _, it := range items {
d := DeriveDraft(it)
// IDEMPOTENCE, checked inside the transaction that holds the scope rows. Two reviewers finishing
// at once would otherwise each generate a full set.
var exists bool
if sErr := tx.QueryRow(ctx,
`SELECT EXISTS (SELECT 1 FROM requirement_drafts WHERE source_scope_item_id = $1)`,
it.ID).Scan(&exists); sErr != nil {
return sErr
}
if exists {
skipped++
continue
}
var newID string
if sErr := tx.QueryRow(ctx, `
INSERT INTO requirement_drafts (
organization_id, opportunity_id, source_scope_item_id, labor_category, quantity,
clearance_level, polygraph, minimum_years_experience, travel_percent, work_location,
remote_allowed, required_certifications, derivation_method, derivation_confidence,
review_status)
VALUES (NULLIF(current_setting('app.current_org_id', true), '')::uuid,
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, 'draft')
RETURNING id::text`,
oid, it.ID, d.LaborCategory, d.Quantity, d.ClearanceLevel, d.Polygraph,
d.MinimumYears, d.TravelPercent, d.WorkLocation, d.RemoteAllowed,
d.Certifications, d.DerivationMethod, it.Confidence).Scan(&newID); sErr != nil {
return sErr
}
created++
nv, _ := json.Marshal(map[string]any{
"draft_id": newID, "source_scope_item_id": it.ID, "labor_category": d.LaborCategory,
"clearance_level": d.ClearanceLevel, "quantity": d.Quantity,
"missing_required_fields": MissingRequiredDraftFields(d),
})
if aErr := s.aud.OnTx(ctx, tx, audit.Event{
Action: "opportunity.requirement_draft.generated", ActorUserID: actor,
EntityType: "requirement_draft", EntityID: newID, NewValues: nv,
}); aErr != nil {
return aErr
}
}
return nil
})
if err != nil {
return 0, 0, err
}
return created, skipped, nil
}
// DraftPatch is a reviewer completing a draft by hand, which Section 6 screen 5 requires. Every field is a
// pointer so "not supplied" is distinguishable from "set to empty", and only fields a human is allowed to
// complete appear here: review_status, the approval stamps and the promoted requirement id are not among them,
// because those belong to the promotion transaction and to it alone.
type DraftPatch struct {
LaborCategory *string `json:"labor_category"`
Quantity *int `json:"quantity"`
ClearanceLevel *string `json:"clearance_level"`
Polygraph *string `json:"polygraph"`
MinimumYears *int `json:"minimum_years_experience"`
TravelPercent *int `json:"travel_percent"`
WorkLocation *string `json:"work_location"`
RemoteAllowed *bool `json:"remote_allowed"`
RequiredSkills []string `json:"required_skills"`
}
var draftClearances = map[string]bool{
"unknown": true, "none": true, "confidential": true, "secret": true, "top_secret": true, "ts_sci": true,
}
var draftPolygraphs = map[string]bool{"none": true, "ci": true, "full_scope": true}
// PatchDraft applies a reviewer's manual completion. It refuses to touch a draft that is already decided,
// because editing an approved draft would change the record of what a named human actually approved.
func (s *Service) PatchDraft(ctx context.Context, draftID string, p DraftPatch) (map[string]any, error) {
if !s.db.Ready() {
return nil, ErrNoDatabase
}
did, err := uuid.Parse(strings.TrimSpace(draftID))
if err != nil {
return nil, fmt.Errorf("%w: draft id must be a uuid", ErrInvalidInput)
}
if p.ClearanceLevel != nil && !draftClearances[strings.TrimSpace(*p.ClearanceLevel)] {
return nil, fmt.Errorf("%w: clearance_level %q is not valid", ErrInvalidInput, *p.ClearanceLevel)
}
if p.Polygraph != nil && !draftPolygraphs[strings.TrimSpace(*p.Polygraph)] {
return nil, fmt.Errorf("%w: polygraph %q is not valid", ErrInvalidInput, *p.Polygraph)
}
if p.Quantity != nil && *p.Quantity < 0 {
return nil, fmt.Errorf("%w: quantity cannot be negative", ErrInvalidInput)
}
if p.TravelPercent != nil && (*p.TravelPercent < 0 || *p.TravelPercent > 100) {
return nil, fmt.Errorf("%w: travel_percent must be between 0 and 100", ErrInvalidInput)
}
actor := actorPtr(ctx)
if actor == nil {
return nil, tenant.ErrNoTenant
}
sets := []string{"updated_at = now()"}
var args []any
add := func(col string, v any) {
args = append(args, v)
sets = append(sets, fmt.Sprintf("%s = $%d", col, len(args)))
}
if p.LaborCategory != nil {
add("labor_category", strings.TrimSpace(*p.LaborCategory))
}
if p.Quantity != nil {
add("quantity", *p.Quantity)
}
if p.ClearanceLevel != nil {
add("clearance_level", strings.TrimSpace(*p.ClearanceLevel))
}
if p.Polygraph != nil {
add("polygraph", strings.TrimSpace(*p.Polygraph))
}
if p.MinimumYears != nil {
add("minimum_years_experience", *p.MinimumYears)
}
if p.TravelPercent != nil {
add("travel_percent", *p.TravelPercent)
}
if p.WorkLocation != nil {
add("work_location", strings.TrimSpace(*p.WorkLocation))
}
if p.RemoteAllowed != nil {
add("remote_allowed", *p.RemoteAllowed)
}
if p.RequiredSkills != nil {
add("required_skills", p.RequiredSkills)
}
if len(args) == 0 {
return nil, fmt.Errorf("%w: no fields to update", ErrInvalidInput)
}
args = append(args, did)
out := map[string]any{}
err = s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
var status string
if sErr := tx.QueryRow(ctx,
`SELECT review_status FROM requirement_drafts WHERE id = $1 FOR UPDATE`, did).Scan(&status); sErr != nil {
return sErr
}
if status == "approved" || status == "rejected" {
return fmt.Errorf("%w: this draft is already %s and cannot be edited", ErrInvalidInput, status)
}
var laborCat, clearance, poly string
var qty, years int
if sErr := tx.QueryRow(ctx, `UPDATE requirement_drafts SET `+strings.Join(sets, ", ")+
fmt.Sprintf(" WHERE id = $%d RETURNING labor_category, quantity, clearance_level, polygraph, minimum_years_experience", len(args)),
args...).Scan(&laborCat, &qty, &clearance, &poly, &years); sErr != nil {
return sErr
}
missing := MissingRequiredDraftFields(DraftDerivation{
LaborCategory: laborCat, ClearanceLevel: clearance, Quantity: qty,
})
out = map[string]any{"id": did.String(), "labor_category": laborCat, "quantity": qty,
"clearance_level": clearance, "polygraph": poly, "minimum_years_experience": years,
"missing_required_fields": missing}
nv, _ := json.Marshal(out)
return s.aud.OnTx(ctx, tx, audit.Event{
Action: "opportunity.requirement_draft.edited", ActorUserID: actor,
EntityType: "requirement_draft", EntityID: did.String(), NewValues: nv,
})
})
if err != nil {
return nil, err
}
return out, nil
}