Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / cmd/opportunity-amendment-diff/main.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | cmd/opportunity-amendment-diff/main.go |
| Original SHA-256 | e6762fa9819e5ec8df3c992077174756decc34222d42fba25d4bf87b79180e0c |
| Git blob SHA | 18c1b721f89e137e14b06df54a229c25d5d58e1e |
| Reading-copy SHA-256 | e6762fa9819e5ec8df3c992077174756decc34222d42fba25d4bf87b79180e0c |
Recovered source transcript
Download complete source text ↓441 lines · 16.4 KB
// Command opportunity-amendment-diff compares an amendment against the version it supersedes and flags the
// APPROVED requirements it affects. Directive acceptance item 11, and the whole safety promise of Phase 2.
//
// THE RULE THIS ENFORCES IS A REFUSAL: an amendment NEVER rewrites an approved requirement. It flags it for
// a named human and stops. A pipeline that silently updated a requirement because the solicitation changed
// would be making a staffing decision on its own, which is exactly what an AEDT must not do. The synthetic
// proof already exists (an approved requirement survived twenty ingest rounds byte for byte); this makes the
// same guarantee on real amendment text and produces the human readable diff that a reviewer acts on.
//
// WHAT IT COMPARES. Deterministic signals extracted from each version's document chunks, never a model: the
// deadline, labor categories, quantities, clearance and polygraph, certifications, travel, and money. A
// model asked "what changed" produces a fluent summary that cannot be checked. A field by field comparison
// of literal spans produces a list a contracts person can verify against the page.
//
// Usage:
//
// opportunity-amendment-diff -dsn <dsn> -opportunity <uuid> [-apply] [-json]
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/missionworkforce/missionmatch/internal/modules/opportunities"
)
func main() {
dsn := flag.String("dsn", "", "migrator DSN, defaults to MM_MIGRATOR_DATABASE_URL")
oppID := flag.String("opportunity", "", "tracked opportunity id (required)")
apply := flag.Bool("apply", false, "raise impact flags on affected approved requirements")
asJSON := flag.Bool("json", false, "emit the diff as json")
flag.Parse()
if *dsn == "" {
*dsn = os.Getenv("MM_MIGRATOR_DATABASE_URL")
}
if *dsn == "" || strings.TrimSpace(*oppID) == "" {
log.Fatal("opportunity-amendment-diff: -dsn and -opportunity are required")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, *dsn)
if err != nil {
log.Fatalf("opportunity-amendment-diff: pool: %v", err)
}
defer pool.Close()
org, err := orgOf(ctx, pool, *oppID)
if err != nil {
log.Fatalf("opportunity-amendment-diff: %v", err)
}
docs, err := documentsByType(ctx, pool, org, *oppID)
if err != nil {
log.Fatalf("opportunity-amendment-diff: documents: %v", err)
}
// The pair is chosen WITHIN a document type where possible, earliest against latest, preferring the type
// that carries scope. Both sides come from the database rather than from arguments, so the comparison
// cannot be aimed at a convenient pair. Finding F6 corrected the old recency based choice, which compared
// a solicitation against an amended PWS on Package B.
base, amend, how := pickPair(docs)
if how == pairNone || base.ID == "" || amend.ID == "" {
log.Fatalf("opportunity-amendment-diff: need two comparable documents, found %d; "+
"nothing is inferred from a single version", len(docs))
}
log.Printf("baseline: %s (%s)", base.FileName, base.DocumentType)
log.Printf("amendment: %s (%s)", amend.FileName, amend.DocumentType)
log.Printf("pairing: %s", how)
if how == pairCrossType {
// Said out loud rather than buried, because every difference below is then partly a difference between
// two DOCUMENTS and not only between two VERSIONS.
log.Printf("NOTE: no document type had two versions, so this compares ACROSS types. Differences below " +
"include differences between the documents themselves, not only changes made by the amendment.")
}
baseText, err := chunkText(ctx, pool, org, base.ID)
if err != nil {
log.Fatalf("opportunity-amendment-diff: baseline chunks: %v", err)
}
amendText, err := chunkText(ctx, pool, org, amend.ID)
if err != nil {
log.Fatalf("opportunity-amendment-diff: amendment chunks: %v", err)
}
if strings.TrimSpace(baseText) == "" || strings.TrimSpace(amendText) == "" {
log.Fatal("opportunity-amendment-diff: one side has no extracted chunks, run opportunity-extract first. " +
"Diffing an empty side would report every field as removed, which is a lie about the amendment.")
}
changes := diffSignals(opportunities.ExtractSignals(baseText), opportunities.ExtractSignals(amendText))
if *asJSON {
b, _ := json.MarshalIndent(map[string]any{
"opportunity": *oppID, "baseline_document": base.ID, "amendment_document": amend.ID,
"changes": changes,
}, "", " ")
fmt.Println(string(b))
} else if len(changes) == 0 {
log.Print("NO MATERIAL CHANGE detected in the compared fields. That is a real answer, not a failure: " +
"an amendment often moves a date and nothing else this job measures.")
} else {
log.Printf("%d MATERIAL CHANGE(S):", len(changes))
for _, c := range changes {
log.Printf(" %-16s %q -> %q", c.Field, clip(c.Before), clip(c.After))
}
}
reqs, err := approvedRequirements(ctx, pool, org, *oppID)
if err != nil {
log.Fatalf("opportunity-amendment-diff: requirements: %v", err)
}
if len(reqs) == 0 {
log.Print("no APPROVED requirements derived from this opportunity, so nothing to flag")
return
}
log.Printf("%d approved requirement(s) derived from this opportunity", len(reqs))
if len(changes) == 0 {
log.Print("nothing changed, so nothing is flagged")
return
}
if !*apply {
log.Printf("DRY RUN. Pass -apply to raise impact flags. Nothing was written.")
for _, r := range reqs {
log.Printf(" would flag %s (%s)", r.ID, r.Title)
}
return
}
n, err := raiseFlags(ctx, pool, org, reqs, changes, amend.ID)
if err != nil {
log.Fatalf("opportunity-amendment-diff: raising flags: %v", err)
}
log.Printf("flagged %d requirement(s) for a named human. NONE was rewritten, which is the point.", n)
}
type change struct {
Field string `json:"field"`
Before string `json:"before"`
After string `json:"after"`
}
type document struct{ ID, FileName, DocumentType string }
type requirement struct{ ID, Title string }
func orgOf(ctx context.Context, pool *pgxpool.Pool, oppID string) (string, error) {
var org string
err := pool.QueryRow(ctx, `SELECT organization_id::text FROM tracked_opportunities WHERE id=$1`, oppID).Scan(&org)
if err != nil {
return "", fmt.Errorf("no tracked opportunity %s: %w", oppID, err)
}
return org, nil
}
func documentsByType(ctx context.Context, pool *pgxpool.Pool, org, oppID string) ([]document, error) {
rows, err := pool.Query(ctx, `
SELECT id::text, file_name, document_type FROM opportunity_documents
WHERE organization_id=$1 AND opportunity_id=$2 AND is_current AND extraction_status='done'
ORDER BY created_at`, org, oppID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []document
for rows.Next() {
var d document
if e := rows.Scan(&d.ID, &d.FileName, &d.DocumentType); e != nil {
return nil, e
}
out = append(out, d)
}
return out, rows.Err()
}
// How a pair was chosen. It is reported alongside the diff, because a cross type comparison is a weaker
// statement than a same type one and a reader deserves to know which they are looking at.
const (
pairSameType = "same_type" // earliest and latest of one document type, the strong case
pairCrossType = "cross_type" // no type had two documents, so a baseline was matched to an amendment
pairNone = "none" // fewer than two comparable documents
)
// scopeBearingOrder ranks document types by how much of the STAFFING scope they carry. When more than one type
// has two versions, the diff should compare the documents a requirement is actually derived from. A PWS states
// the work; a pricing template does not.
//
// attachment AND other ARE DELIBERATELY ABSENT. An earlier version listed them, which meant a package holding
// two attachments and one PWS would diff the two attachments against each other. Comparing an RFC log to a
// deliverables list produces confident nonsense, which is worse than the recency bug being fixed.
var scopeBearingOrder = []string{"pws", "sow", "soo", "solicitation", "customer_scope", "subcontract_scope",
"labor_categories", "evaluation_criteria", "security"}
// nameTokens reduces a file name to comparable words, so 04_PWS_original_10JUL26.pdf and
// 05_PWS_amended_23JUL26.pdf can be recognised as versions of the same document.
func nameTokens(name string) map[string]bool {
out := map[string]bool{}
cur := strings.Builder{}
flush := func() {
if cur.Len() >= 3 {
out[strings.ToLower(cur.String())] = true
}
cur.Reset()
}
for _, r := range name {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z':
cur.WriteRune(r)
default:
flush()
}
}
flush()
// Words that appear in nearly every solicitation file name carry no signal about WHICH document this is,
// and leaving them in makes everything look related to everything.
for _, noise := range []string{"pdf", "docx", "final", "draft", "copy", "conformed", "original", "amended",
"amendment", "rev"} {
delete(out, noise)
}
return out
}
func nameAffinity(a, b string) int {
ta, tb := nameTokens(a), nameTokens(b)
n := 0
for t := range ta {
if tb[t] {
n++
}
}
return n
}
// pickPair chooses the two documents to diff.
//
// FINDING F6. This used to take the first non amendment document as the baseline and the latest amendment as
// the other side, which on Package B compared the SOLICITATION against the AMENDED PWS. Two documents about
// different things produce differences that are artifacts of the pairing rather than of the amendment.
//
// It now pairs WITHIN a document type, earliest against latest, preferring the type that carries scope. Package
// B ships an original PWS and an amended PWS specifically so this comparison is available. The cross type
// behaviour is kept as a labelled FALLBACK, because a package with one solicitation and one SF30 has no same
// type pair and refusing to diff would be less useful than a comparison that says what it is.
func pickPair(docs []document) (document, document, string) {
if len(docs) < 2 {
return document{}, document{}, pairNone
}
// Group in created order, which is the order the caller queried.
byType := map[string][]document{}
for _, d := range docs {
byType[d.DocumentType] = append(byType[d.DocumentType], d)
}
// Prefer a same type pair, most scope bearing type first.
for _, want := range scopeBearingOrder {
if group := byType[want]; len(group) >= 2 {
return group[0], group[len(group)-1], pairSameType
}
}
// CROSS TYPE, AND CHOSEN BY FILE NAME AFFINITY RATHER THAN BY POSITION.
//
// This is the branch the real Package B actually takes, and getting it wrong is finding F6 itself. The
// intake classified 05_PWS_amended_23JUL26.pdf as an AMENDMENT, so the pws type holds only the original and
// no same type pair exists. Taking "the first non amendment document" then picks 01_Solicitation and
// compares it against the amended PWS, which is the defect.
//
// So the amendment is matched to the baseline whose FILE NAME it most resembles. 04_PWS_original and
// 05_PWS_amended share the token PWS; the solicitation shares nothing. Version words like original and
// amended are stripped first, because they are exactly the words that DIFFER between two versions of one
// document and would otherwise cancel the signal out.
var amend document
for _, d := range docs {
if d.DocumentType == "amendment" {
amend = d // latest wins, the slice is in created order
}
}
if amend.ID == "" {
return document{}, document{}, pairNone
}
var base document
bestAffinity := -1
for _, d := range docs {
if d.DocumentType == "amendment" || d.ID == amend.ID {
continue
}
// Only a scope bearing baseline is eligible. Diffing an amendment against an unrelated attachment is
// the same class of mistake as diffing two attachments against each other.
eligible := false
for _, t := range scopeBearingOrder {
if d.DocumentType == t {
eligible = true
break
}
}
if !eligible {
continue
}
a := nameAffinity(d.FileName, amend.FileName)
// Strictly greater keeps the EARLIEST document on a tie, which preserves the old behaviour when no file
// name tells us anything and keeps the choice deterministic.
if a > bestAffinity {
bestAffinity, base = a, d
}
}
if base.ID == "" {
return document{}, document{}, pairNone
}
return base, amend, pairCrossType
}
func chunkText(ctx context.Context, pool *pgxpool.Pool, org, docID string) (string, error) {
rows, err := pool.Query(ctx, `
SELECT text_content FROM opportunity_document_chunks
WHERE organization_id=$1 AND document_id=$2
ORDER BY page_number NULLS FIRST, created_at`, org, docID)
if err != nil {
return "", err
}
defer rows.Close()
var b strings.Builder
for rows.Next() {
var t string
if e := rows.Scan(&t); e != nil {
return "", e
}
b.WriteString(t)
b.WriteString("\n")
}
return b.String(), rows.Err()
}
// diffSignals compares the deterministic signals field by field. A field that is ZERO on one side is
// reported as stated or not stated rather than as a value change, because a solicitation that stops
// mentioning travel has not set travel to zero percent.
func diffSignals(a, b opportunities.Signals) []change {
var out []change
add := func(field, before, after string) {
if before != after {
out = append(out, change{Field: field, Before: before, After: after})
}
}
add("clearance", a.ClearanceLevel, b.ClearanceLevel)
add("polygraph", a.Polygraph, b.Polygraph)
add("labor_category", a.LaborCategory, b.LaborCategory)
add("quantity", intOrUnstated(a.Quantity), intOrUnstated(b.Quantity))
add("minimum_years", intOrUnstated(a.MinimumYears), intOrUnstated(b.MinimumYears))
add("travel_percent", intOrUnstated(a.TravelPercent), intOrUnstated(b.TravelPercent))
add("certifications", joinSorted(a.Certifications), joinSorted(b.Certifications))
add("dates", joinSorted(a.Dates), joinSorted(b.Dates))
add("money", joinSorted(a.Money), joinSorted(b.Money))
return out
}
func intOrUnstated(v int) string {
if v == 0 {
return "not stated"
}
return fmt.Sprintf("%d", v)
}
func joinSorted(in []string) string {
if len(in) == 0 {
return "not stated"
}
c := append([]string(nil), in...)
sort.Strings(c)
return strings.Join(c, ", ")
}
func approvedRequirements(ctx context.Context, pool *pgxpool.Pool, org, oppID string) ([]requirement, error) {
rows, err := pool.Query(ctx, `
SELECT r.id::text, COALESCE(r.title,'')
FROM requirement_drafts d
JOIN requirements r ON r.id = d.approved_requirement_id AND r.organization_id = d.organization_id
WHERE d.organization_id=$1 AND d.opportunity_id=$2 AND d.review_status='approved'`, org, oppID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []requirement
for rows.Next() {
var r requirement
if e := rows.Scan(&r.ID, &r.Title); e != nil {
return nil, e
}
out = append(out, r)
}
return out, rows.Err()
}
// raiseFlags writes an alert per affected requirement. It does NOT touch the requirement row, and that
// omission is the acceptance criterion: the guarantee is not that the rewrite is careful, it is that there
// is no rewrite.
func raiseFlags(ctx context.Context, pool *pgxpool.Pool, org string, reqs []requirement, changes []change, amendDocID string) (int, error) {
tx, err := pool.Begin(ctx)
if err != nil {
return 0, err
}
defer func() { _ = tx.Rollback(ctx) }()
if _, err := tx.Exec(ctx, `SELECT set_config('app.current_org_id', $1, true)`, org); err != nil {
return 0, err
}
summary := make([]string, 0, len(changes))
for _, c := range changes {
summary = append(summary, fmt.Sprintf("%s: %s -> %s", c.Field, clip(c.Before), clip(c.After)))
}
body := "An amendment changed the solicitation this requirement was derived from. It has NOT been " +
"changed automatically and needs a named human to decide. Changes: " + strings.Join(summary, "; ")
n := 0
for _, r := range reqs {
// dedupe_key ties the flag to THIS amendment and THIS requirement, so re running the job does not
// bury a reviewer under one alert per run. Re running an analysis is normal; being punished for it
// is how people stop re running it.
if _, err := tx.Exec(ctx, `
INSERT INTO alerts (organization_id, alert_type, severity, subject_type, subject_id,
title, detail, dedupe_key, status)
VALUES ($1,'opportunity_amendment','watch','requirement',$2,$3,$4,$5,'unread')
ON CONFLICT (organization_id, dedupe_key) DO NOTHING`,
org, r.ID,
"Amendment affects an approved requirement, human decision needed",
body, "amendment:"+amendDocID+":req:"+r.ID); err != nil {
return n, err
}
n++
}
return n, tx.Commit(ctx)
}
func clip(s string) string {
if len(s) <= 72 {
return s
}
return s[:69] + "..."
}