Docs

Vendor Grading Spec

Vendor grading and contract renewal intelligence build spec.

Vendor Grading & Contract Renewal Intelligence — Build Spec (2026-07-08)

Merged verdict from two DRs (ChatGPT: "Vendor Grading and Contract Renewal Intelligence System"; Gemini: "Vendor Grading Intelligence System" — extracts in session scratchpad) + governance repo map. This module ships INSIDE nomoi-governance: same org/RLS/auth/Stripe/import rails.

Verdict on the DRs (what we keep, what we kill for v1)

Both DRs converge on the same two-layer split, and that split IS the product: LLM extracts and cites (behind a human gate); a deterministic engine decides. Nothing scored is free text. Every score component traces to typed events and evidence quotes. This is exactly the governance house philosophy (import-sync engine, date-lock, audit spine) — which is why this is a module, not a new app.

KEEP (v1):

  • Canonical model: Vendor, Contract, Obligation, Event, Score, RenewalRecommendation, Document — with evidence spans on every extracted fact.
  • Extraction contract: every populated field carries a verbatim quote + location; quote is re-located in source text (exact indexOf first, sliding-window fuzzy ≥0.85 fallback; below threshold → verification failure, field goes to review as low-confidence).
  • Deterministic scoring, fixed default weights (org-editable, must sum to 1): Quality .30, Timeliness .25, Cost .15, Responsiveness .15, Compliance/Risk .15.
  • BenefitMetric = 100·clamp((x−L)/(U−L),0,1); CostMetric = 100·clamp((U−x)/(U−L),0,1)
  • Grade = Σ w_d · dim_d
  • Trend: EWMA over period grades, λ=0.3, stored separately from raw grade. DEGRADING = negative 2–3 period slope.
  • Decision engine emits exactly three actions with typed critical flags (never narrative):
  • RENEW: grade ≥ 85, no critical flag, trend not materially negative
  • RENEGOTIATE: 70 ≤ grade < 85; or ≥85 with one material risk flag; or sustained negative trend; or low grade but IMPROVING
  • TERMINATE: grade < 70 with STABLE/DEGRADING trend; or repeated uncured material breach; or any critical flag (security default, insolvency)
  • Flags enum: material_sla_breach, uncured_default_notice, security_nonconformance, financial_distress_signal, chronic_delivery_delay, audit_failure
  • Audit packet: recommendation → score lineage → event rows → evidence quotes. Test fixture: ChatGPT DR worked example (Q92/T68/C85/R74/K60 → 77.45 → RENEGOTIATE).

KILL for v1 (enterprise fantasy vs a Next.js module with 0 customers; revisit on demand):

  • Docling/GPU parse nodes → repo already has pdf-parse/mammoth/xlsx; scanned PDFs are flagged "needs OCR", out of scope.
  • Splink/Dedupe ER → org-scoped vendor lists are small; trade_names text[] + exact/alias match + manual merge action suffices.
  • AHP pairwise elicitation & TOPSIS → fixed editable weights + 3-action matrix cover it; consistency by construction.
  • pgvector/embeddings, BAML, Python microservices → all TS in-app, LLM via existing import-plan LiteLLM path.
  • SMTP ingress → Wave 6 (repo already has /api/email/inbound to extend); v1 events are manually logged or extracted from uploaded docs/pasted email text, always behind the review gate.

Schema (migration 0019, schema governance, all org-scoped, RLS per 0002 pattern)

  • vendors: id, org_id, canonical_name, trade_names text[], registration_id, category, contact_email, status(active|inactive), notes, created_at, updated_at
  • vendor_documents: id, org_id, vendor_id?, filename, mime, text_content, sha256, uploaded_by, created_at (immutable source-of-truth text; contracts/obligations point here)
  • vendor_contracts: id, org_id, vendor_id, title, contract_type(MSA|SOW|NDA|RFP|AMENDMENT|OTHER), effective_date, expiration_date, is_auto_renew, notice_period_days, renewal_term, value_amount, currency, status(active|expired|pending|terminated), document_id?, metadata jsonb
  • vendor_obligations: id, org_id, contract_id, clause_type(deliverable|milestone|sla|acceptance_criteria|reporting|penalty|termination|renewal|payment), description, due_rule, target_metric, penalty_rule, survives_termination, evidence jsonb {quote,page,char_start,char_end,confidence}, status
  • vendor_events (append-only; the performance log): id, org_id, vendor_id, contract_id?, obligation_id?, event_type(delivered_on_time|delivered_late|missed|sla_breach|dispute|escalation|resolved|quality_check|response_latency|audit_finding|default_notice|penalty_applied|note), event_date, quant jsonb {days_late,score,latency_hours,amount}, narrative, evidence jsonb, source(manual|document|email), logged_by, created_at
  • vendor_scores: id, org_id, vendor_id, contract_id?, period_start, period_end, quality, timeliness, cost, responsiveness, compliance (0–100 numerics), grade, ewma_grade, trend(IMPROVING|STABLE|DEGRADING), inputs jsonb (event-id lineage per dimension), computed_at
  • vendor_renewal_recommendations: id, org_id, contract_id, score_id, action(RENEW|RENEGOTIATE|TERMINATE), critical_flags text[], rationale, evidence jsonb, status(open|accepted|dismissed), decided_by?, created_at
  • vendor_weights: org_id pk, quality, timeliness, cost, responsiveness, compliance (defaults .30/.25/.15/.15/.15, CHECK sum ≈ 1), updated_by, updated_at

RLS: reuse is_member/role_in_org. Read: all org members. Write: pm + exec (events also writable by owner role). vendor_events: INSERT-only for non-pm (no UPDATE/DELETE policies); corrections are new events. audit_log trigger pattern as elsewhere.

Dimension inputs (deterministic, from events)

  • Quality: mean quality_check score (0–100)
  • Timeliness: 100·on_time/(on_time+late+missed), penalized by mean days_late (cost-metric on days_late, U=14)
  • Cost: cost-metric on penalty/dispute amounts vs contract value (U=5% of value)
  • Responsiveness: cost-metric on mean latency_hours (L=4, U=72); manual rating events can override
  • Compliance/Risk: 100 − deductions per sla_breach/audit_finding/default_notice event (typed deduction table in engine)

Dimensions with zero events return null and re-normalize remaining weights (never fabricate a score).

Waves

  1. Migration 0019 (Codex) → dogfood Sonnet review (DDL = high-risk) → apply via Supabase MCP → verify information_schema.
  2. src/lib/vendor-grading.ts pure engine + vendor-grading.test.ts (Codex). Fixtures: both DR worked examples.
  3. Contract ingestion: extend import-plan pattern → extraction schema above → tri-state review (reuse sync review UI pattern) → apply. Evidence relocation util src/lib/evidence-locate.ts.
  4. UI: /vendors (list w/ grade chips), /vendors/[id] (scorecard, trend, contracts, obligations, event timeline, recommendation card w/ evidence), event logging form, /vendors/renewals queue (expiring ≤90d + notice deadlines). nomoi-taste before building.
  5. Full treatment: Stripe (decision pending steal-scan pricing signal — default: vendor module included in Growth $149 + new standalone "Vendor Intelligence" price), landing section on governance.nomoi.ai, nomoi.ai catalog, Bug Desk registration, audit-packet export (print view v1).
  6. (Later) email-in events via /api/email/inbound; OCR lane; cron: recompute scores + renewal-window alerts into notifications table (dedupe_key exists).

Renewal queue rule: surface contracts where expiration_date − notice_period_days ≤ today+90d; auto_renew contracts show "silent renewal" warning before notice deadline.

Steal-scan addendum (2026-07-08, see VENDOR_GRADING_STEALSCAN.md)

  • Standings gate (ERPNext pattern, adopt in Wave 4): map grade bands to named standings (e.g. Preferred ≥85 / Acceptable 70–85 / Probation 55–70 / Blocked <55) shown as the vendor's primary chip; standings are the human-legible layer over the numeric grade and later gate actions (v2: block new contracts for Blocked vendors). Store band → label mapping alongside vendor_weights.
  • Positioning (Wave 5): GCC compliance wedge — "ADHICS Domain 10 vendor oversight evidence pack" export (Abu Dhabi healthcare) and Abu Dhabi Procurement Standards supplier-performance-score output (AD government). The audit packet IS the evidence pack; brand it as such.
  • Pricing (Wave 5, confirmed by scan): market floor is Gatekeeper ~$15k/yr; OSS field is empty. Anchor $99–299/mo, priced per vendor count (Gatekeeper's axis), unlimited users. Sirion's "9% revenue drain from missed renewals" stat is usable in copy.
  • v2 extraction quorum: CUAD-fine-tuned RoBERTa checkpoints (renewal/expiration/notice-period labels) as a deterministic second engine; disagreement with LLM extraction → review flag.