compliance-matrix

Compliance Matrix Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "compliance-matrix" with this command: npx skills add atemndobs/nebula-rfp/atemndobs-nebula-rfp-compliance-matrix

Compliance Matrix Skill

Overview

This skill generates compliance matrices to track RFP requirements against proposal responses, ensuring complete coverage of all mandatory elements.

Data Structures

interface ComplianceMatrix { rfpId: string; rfpTitle: string; createdAt: Date; updatedAt: Date; status: "draft" | "in_review" | "complete"; sections: ComplianceSection[]; summary: MatrixSummary; }

interface ComplianceSection { id: string; name: string; requirements: ComplianceRequirement[]; }

interface ComplianceRequirement { id: string; reference: string; // RFP section reference requirement: string; // Original requirement text type: "mandatory" | "desirable" | "informational"; category: RequirementCategory; responseSection: string; // Proposal section responseText: string; // Draft response evidence: string; // Supporting evidence owner: string; // Team member responsible status: "pending" | "draft" | "review" | "complete" | "n/a"; notes: string; }

type RequirementCategory = | "technical" | "management" | "past_performance" | "pricing" | "certifications" | "staffing" | "security" | "compliance" | "other";

interface MatrixSummary { totalRequirements: number; mandatory: number; desirable: number; addressed: number; pending: number; notApplicable: number; }

Template Output

Compliance Matrix: {{RFP_TITLE}}

RFP ID: {{EXTERNAL_ID}} Agency: {{AGENCY}} Deadline: {{DEADLINE}} Last Updated: {{UPDATED_AT}}

Summary

StatusMandatoryDesirableTotal
Complete{{M_COMPLETE}}{{D_COMPLETE}}{{TOTAL_COMPLETE}}
Draft{{M_DRAFT}}{{D_DRAFT}}{{TOTAL_DRAFT}}
Pending{{M_PENDING}}{{D_PENDING}}{{TOTAL_PENDING}}
N/A{{M_NA}}{{D_NA}}{{TOTAL_NA}}

Section 1: Technical Requirements

RefRequirementTypeResponseStatusOwner
3.1.1{{REQ}}M{{RESPONSE_SECTION}}{{OWNER}}

Submission Checklist

  • All mandatory requirements addressed
  • Technical volume complete
  • Past performance complete
  • Pricing complete
  • All forms signed
  • Format requirements met
  • Red team review complete

Requirement Extraction

Convex Action

// convex/compliance.ts import { action, mutation, query } from "./_generated/server"; import { v } from "convex/values"; import { internal } from "./_generated/api";

export const generateMatrix = action({ args: { rfpId: v.id("rfps") }, handler: async (ctx, args) => { const rfp = await ctx.runQuery(internal.rfps.get, { id: args.rfpId }); if (!rfp) throw new Error("RFP not found");

// Extract requirements using AI
const requirements = await extractRequirements(rfp);

// Group into sections
const sections = groupRequirements(requirements);

// Create matrix
const matrix: ComplianceMatrix = {
  rfpId: args.rfpId,
  rfpTitle: rfp.title,
  createdAt: new Date(),
  updatedAt: new Date(),
  status: "draft",
  sections,
  summary: calculateSummary(sections),
};

// Save to pursuit
await ctx.runMutation(internal.pursuits.saveComplianceMatrix, {
  rfpId: args.rfpId,
  matrix: JSON.stringify(matrix),
});

return matrix;

}, });

AI Extraction

async function extractRequirements(rfp: RFP): Promise<ComplianceRequirement[]> { const prompt = ` Analyze this RFP and extract ALL requirements.

RFP Title: ${rfp.title} RFP Content: ${rfp.description}

For each requirement, identify:

  1. Section reference (use sequential numbering if not available)
  2. Exact requirement text
  3. Type: mandatory (must/shall/required), desirable (should/may), or informational
  4. Category: technical, management, past_performance, pricing, certifications, staffing, security, compliance, other

Respond with JSON array: [ { "reference": "3.1.1", "requirement": "System must support 1000 concurrent users", "type": "mandatory", "category": "technical" } ]`;

const response = await callAIProvider(prompt); const parsed = JSON.parse(response);

return parsed.map((req: any, index: number) => ({ id: req-${index + 1}, reference: req.reference || ${index + 1}, requirement: req.requirement, type: req.type, category: req.category, responseSection: "", responseText: "", evidence: "", owner: "", status: "pending" as const, notes: "", })); }

Grouping Logic

function groupRequirements( requirements: ComplianceRequirement[] ): ComplianceSection[] { const groups = new Map<string, ComplianceRequirement[]>();

for (const req of requirements) { const existing = groups.get(req.category) || []; existing.push(req); groups.set(req.category, existing); }

const categoryNames: Record<string, string> = { technical: "Technical Requirements", management: "Management Requirements", past_performance: "Past Performance", pricing: "Pricing Requirements", certifications: "Certifications & Compliance", staffing: "Staffing Requirements", security: "Security Requirements", compliance: "Regulatory Compliance", other: "Other Requirements", };

const order = [ "technical", "management", "staffing", "past_performance", "security", "compliance", "pricing", "certifications", "other", ];

return order .filter((cat) => groups.has(cat)) .map((category) => ({ id: category, name: categoryNames[category] || category, requirements: groups.get(category)!, })); }

Update Functions

// convex/compliance.ts export const updateRequirement = mutation({ args: { rfpId: v.id("rfps"), requirementId: v.string(), updates: v.object({ responseSection: v.optional(v.string()), responseText: v.optional(v.string()), evidence: v.optional(v.string()), owner: v.optional(v.string()), status: v.optional(v.string()), notes: v.optional(v.string()), }), }, handler: async (ctx, args) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) throw new Error("Not authenticated");

const pursuit = await ctx.db
  .query("pursuits")
  .withIndex("by_rfp", (q) => q.eq("rfpId", args.rfpId))
  .first();

if (!pursuit?.complianceMatrix) {
  throw new Error("Compliance matrix not found");
}

const matrix: ComplianceMatrix = JSON.parse(pursuit.complianceMatrix);

// Find and update requirement
for (const section of matrix.sections) {
  const req = section.requirements.find((r) => r.id === args.requirementId);
  if (req) {
    Object.assign(req, args.updates);
    break;
  }
}

// Recalculate summary
matrix.summary = calculateSummary(matrix.sections);
matrix.updatedAt = new Date();

await ctx.db.patch(pursuit._id, {
  complianceMatrix: JSON.stringify(matrix),
  updatedAt: Date.now(),
});

return matrix;

}, });

export const getProgress = query({ args: { rfpId: v.id("rfps") }, handler: async (ctx, args) => { const pursuit = await ctx.db .query("pursuits") .withIndex("by_rfp", (q) => q.eq("rfpId", args.rfpId)) .first();

if (!pursuit?.complianceMatrix) return null;

const matrix: ComplianceMatrix = JSON.parse(pursuit.complianceMatrix);

return {
  summary: matrix.summary,
  byCategory: matrix.sections.map((s) => ({
    category: s.name,
    total: s.requirements.length,
    complete: s.requirements.filter((r) => r.status === "complete").length,
    mandatory: s.requirements.filter((r) => r.type === "mandatory").length,
  })),
  lastUpdated: matrix.updatedAt,
};

}, });

UI Components

// components/ComplianceMatrixView.tsx export function ComplianceMatrixView({ rfpId }: { rfpId: Id<"rfps"> }) { const progress = useQuery(api.compliance.getProgress, { rfpId }); const generateMatrix = useMutation(api.compliance.generateMatrix);

if (progress === undefined) return <LoadingSpinner />;

if (!progress) { return ( <div className="p-6 text-center"> <p className="text-muted-foreground mb-4"> No compliance matrix generated yet. </p> <button onClick={() => generateMatrix({ rfpId })} className="px-4 py-2 bg-primary text-primary-foreground rounded" > Generate Compliance Matrix </button> </div> ); }

return ( <div className="p-6 space-y-6"> {/* Summary Cards */} <div className="grid grid-cols-4 gap-4"> <SummaryCard label="Total" value={progress.summary.totalRequirements} color="blue" /> <SummaryCard label="Complete" value={progress.summary.addressed} color="green" /> <SummaryCard label="Pending" value={progress.summary.pending} color="yellow" /> <SummaryCard label="Mandatory" value={progress.summary.mandatory} color="red" /> </div>

  {/* Progress by Category */}
  &#x3C;div className="space-y-2">
    {progress.byCategory.map((cat) => (
      &#x3C;ProgressBar
        key={cat.category}
        label={cat.category}
        current={cat.complete}
        total={cat.total}
      />
    ))}
  &#x3C;/div>
&#x3C;/div>

); }

Export Formats

export function exportToMarkdown(matrix: ComplianceMatrix): string { // See template above }

export function exportToCsv(matrix: ComplianceMatrix): string { const headers = [ "Section", "Reference", "Requirement", "Type", "Response Section", "Status", "Owner", "Notes", ];

const rows = matrix.sections.flatMap((section) => section.requirements.map((req) => [ section.name, req.reference, "${req.requirement.replace(/"/g, '""')}", req.type, req.responseSection, req.status, req.owner, "${req.notes.replace(/"/g, '""')}", ]) );

return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n"); }

Status Legend

Icon Status Meaning

✅ Complete Fully addressed with evidence

🔶 Draft Response written, needs review

⏳ Pending Not yet started

➖ N/A Not applicable to this proposal

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

rfp-evaluate

No summary provided by upstream source.

Repository SourceNeeds Review
General

csv-export

No summary provided by upstream source.

Repository SourceNeeds Review
General

pursuit-brief

No summary provided by upstream source.

Repository SourceNeeds Review
General

proposal-builder

No summary provided by upstream source.

Repository SourceNeeds Review